js实现2048小游戏
这是学完javascript基础,编写的入门级web小游戏
游戏规则:在玩法规则也非常的简单,一开始方格内会出现2或者4等这两个小数字,玩家只需要上下左右其中一个方向来移动出现的数字,所有的数字就会想滑动的方向靠拢,而滑出的空白方块就会随机出现一个数字,相同的数字相撞时会叠加靠拢,然后一直这样,不断的叠加最终拼凑出2048这个数字就算成功。但是我们的程序没有终点。
一、HTML部分
1 <body> 2 <!-- 分数行 --> 3 <p id="scorePanel">Score:<span id="score"></span></p> 4 <div id="gridPanel"> 5 <!-- 背景格 --> 6 <div id="g00" class="grid"></div> 7 <div id="g01" class="grid"></div> 8 <div id="g02" class="grid"></div> 9 <div id="g03" class="grid"></div> 10 11 <div id="g10" class="grid"></div> 12 <div id="g11" class="grid"></div> 13 <div id="g12" class="grid"></div> 14 <div id="g13" class="grid"></div> 15 16 <div id="g20" class="grid"></div> 17 <div id="g21" class="grid"></div> 18 <div id="g22" class="grid"></div> 19 <div id="g23" class="grid"></div> 20 21 <div id="g30" class="grid"></div> 22 <div id="g31" class="grid"></div> 23 <div id="g32" class="grid"></div> 24 <div id="g33" class="grid"></div> 25 26 <!-- 前景格 --> 27 <div id="c00" class="cell"></div> 28 <div id="c01" class="cell"></div> 29 <div id="c02" class="cell"></div> 30 <div id="c03" class="cell"></div> 31 32 <div id="c10" class="cell"></div> 33 <div id="c11" class="cell"></div> 34 <div id="c12" class="cell"></div> 35 <div id="c13" class="cell"></div> 36 37 <div id="c20" class="cell"></div> 38 <div id="c21" class="cell"></div> 39 <div id="c22" class="cell"></div> 40 <div id="c23" class="cell"></div> 41 42 <div id="c30" class="cell"></div> 43 <div id="c31" class="cell"></div> 44 <div id="c32" class="cell"></div> 45 <div id="c33" class="cell"></div> 46 47 </div> 48 <!-- 结束容器 --> 49 <div id="gameOver"> 50 <!-- 半透明灰色背景 --> 51 <div> 52 </div> 53 <!--居中小窗口--> 54 <p>Game Over!<br />Score:<span id="finalScore"></span><br/><a class="button" onclick="game.start()">Try again!</a></p> 55 </div> 56 </body>
二、CSS代码
1 #gridPanel{ 2 width:480px; 3 height:480px; 4 margin:0 auto; 5 background-color:#bbada0; 6 border-radius:10px; 7 position:relative; 8 } 9 /* 单元格 */ 10 .grid,.cell{ 11 width:100px; 12 height:100px; 13 border-radius:6px; 14 } 15 /* 背景单元格 */ 16 .grid { 17 margin:16px 0 0 16px; 18 float:left; 19 background-color:#ccc0b3; 20 } 21 /* 前景单元格 */ 22 .cell{ 23 /* top:-464px;*/ 24 position:absolute; 25 background-color:transparent; 26 text-align:center; 27 line-height:100px; 28 font-size:60px; 29 } 30 /*为后续做数字移动动画 而准备*/ 31 /* 行top值 */ 32 #c00,#c01,#c02,#c03{top:16px;} 33 #c10,#c11,#c12,#c13{top:132px;} 34 #c20,#c21,#c22,#c23{top:248px;} 35 #c30,#c31,#c32,#c33{top:364px;} 36 /* 列left值 */ 37 #c00,#c10,#c20,#c30{left:16px;} 38 #c01,#c11,#c21,#c31{left:132px;} 39 #c02,#c12,#c22,#c32{left:248px;} 40 #c03,#c13,#c23,#c33{left:364px;} 41 /* 数字单元格分数背景颜色 */ 42 .n2{background-color:#eee3da} 43 .n4{background-color:#ede0c8} 44 .n8{background-color:#f2b179} 45 .n16{background-color:#f59563} 46 .n32{background-color:#f67c5f} 47 .n64{background-color:#f65e3b} 48 .n128{background-color:#edcf72} 49 .n256{background-color:#edcc61} 50 .n512{background-color:#9c0} 51 .n1024{background-color:#33b5e5} 52 .n2048{background-color:#09c} 53 .n4096{background-color:#a6c} 54 .n8192{background-color:#93c} 55 .n8,.n16,.n32,.n64,.n128,.n256,.n512,.n1024,.n2048,.n4096,.n8192{color:#fff} 56 .n1024,.n2048,.n4096,.n8192{font-size:40px} 57 /**/ 58 59 /*分数显示*/ 60 p{ 61 width:470px; 62 margin:15px auto 5px; 63 font-family:Arial; 64 font-weight:bold; 65 font-size:40px; 66 67 padding:5px; 68 background-color:#ccc; 69 border-radius:10px; 70 color:#e4393c; 71 } 72 #score{ 73 color:#000; 74 } 75 76 /*Game Over*/ 77 #gameOver{ 78 width:100%; 79 height:100%; 80 position:absolute; 81 top:0; 82 left:0; 83 display:none; 84 } 85 #gameOver>div{ 86 width:100%; 87 height:100%; 88 background-color:#555; 89 opacity:.5; 90 } 91 #gameOver>p{ 92 width:300px; 93 height:200px; 94 border:1px solid #edcf72; 95 line-height:1.6em; 96 text-align:center; 97 background-color:#fff; 98 position:absolute; 99 border-radius:10px; 100 top:50%; 101 left:50%; 102 margin-top:-135px; 103 margin-left:-150px; 104 } 105 /*再来一次按钮*/ 106 .button{ 107 padding:10px; 108 border-radius:6px; 109 background-color:#9f8b77; 110 color:#fff; 111 cursor:pointer; 112 }
游戏开始样式
三、Javascript代码
1.定义二位数组初始化(保存游戏网格的数据状态)
1 data:[],//保存所有数字的二位数组 2 rn:4,//定义行数 3 cn:4,//定义列数 4 5 for (var r=0; r<this.rn; r++) 6 { 7 this.data[r]=[];//向空数组中添加行 8 for (var c=0; c<this.cn; c++) 9 { 10 this.data[r][c]=0;//向当前空数组中加0 11 } 12 }
2.随机的向界面添加2或4中的一个数
1 randomNum:function(){//在随机空位置生成一个数 2 //0-3随机生成一个行号row 3 //0-3随机生成一个列号col 4 //如果data[row][col]==0 5 // Math.random(): 6 // 随机生成一个数>=0.5?4:2 7 // 放入data[row][col] 8 if(this.isFull()) return -1;//判断二位数组中是否已满,满 返回-1 9 while(true){ 10 var row=parseInt(Math.random()*this.rn); 11 var col=parseInt(Math.random()*this.cn); 12 if(!this.data[row][col]){ 13 this.data[row][col]=Math.random()>=0.5?4:2; 14 break; 15 } 16 } 17 },
3.更新界面,根据数组中的状态更新界面
1 updataView:function(){//将二位数组中每个格的数字放入前景格中 2 //遍历二位素组中每个元素, 3 for (var row=0; row<this.rn; row++) 4 { 5 for (var col=0; col<this.cn; col++) 6 { 7 //获取要div标签 8 var div=document.getElementById("c"+row+col); 9 //console.log("c"+row+col); 10 var curr=this.data[row][col];//当前元素 11 //修改grid的数字内容 12 div.innerHTML=curr!=0?curr:""; 13 //修改div的class属性 14 div.className=curr!=0?"cell n"+curr:"cell"; 15 } 16 } 17 document.getElementById("score").innerHTML=this.score;
4.实现左移动
第一步:为了实现数据整体做移动,就要先实现一行可以向左移动,例如:如果我们现在移动0行,那么就要先查找当前元素右侧第一个不为0的数字,如果存在则返回不为0元素的下表,否则返回-1;
1 //找当前位置右侧,下一个不为0的数 2 getRightNext:function(row,col){ 3 //从col+1 遍历row行中剩余元素,<this.cn 4 // 遍历到不为0的元素返回nextc-->下一个元素的列下标 5 // 循环退出返回-1 6 for ( col+=1; col<this.cn; col++) 7 { 8 if (this.data[row][col]) 9 { 10 return col; 11 } 12 } 13 return -1; 14 },
第二步:实现一行的做移动。建立循环从左到右判断当前行的每一个元素。如果当前元素值为0,且查找右侧元素的返回值为-1,则判断为这一整行为空;如果当前元素值为0,返回值不为空,则将返回下标元素的值赋给当前元素,并将返回下标元素的值设置为0,继续判断本元素;如果当前元素值==返回下表元素的值,则将当前元素的值*=2,并将返回元素的值设置为0;如果返回值为-1,则跳出循环。
1 /*判断并向左移动指定行中的每个元素*/ 2 moveLeftInRow:function(row){ 3 //0开始,遍历row行中每一个元素,<cn-1结束 4 //获得当前元素下一个不为0的元素的下标nextc 5 //如果nextc==-1,说明右侧么有元素了,退出循环 6 //否则 7 // 如果自己==0 则将下一个位置放入当前位置,下一个位置设置为零,col--重新检查 8 // 如果当前位置==nextc的位置,将当前位置*=2;下一个位置设置为0; 9 for (var col=0; col<this.cn-1; col++) 10 { 11 var nextc=this.getRightNext(row,col); 12 if (nextc==-1){return nextc;} 13 else if (this.data[row][col]==0) 14 { 15 this.data[row][col]=this.data[row][nextc]; 16 this.data[row][nextc]=0; 17 col--; 18 } 19 else if (this.data[row][col]==this.data[row][nextc]) 20 { 21 this.data[row][col]*=2; 22 this.data[row][nextc]=0; 23 this.score+=this.data[row][col];//将当前值累加到score属性上 24 } 25 } 26 },
第三步:实现所有行的左移动。依次循环所有的行,实现元素左移动。
1 /*向左移动所有行*/ 2 moveLeft(){ 3 var oldStr=this.data.toString();//判断字符串是否移动 4 //循环遍历每一行 5 // 调用moveLeftInRow 6 //调用 randomNum(),随机生成一个数 7 //调用 updataView(),更新页面 8 for (var row=0; row<this.rn; row++) 9 { 10 this.moveLeftInRow(row); 11 }
//判断是否有元素左移动,如果没有则不生成数字和更新界面 12 if(oldStr!=this.data.toString()){ 13 this.randomNum(); 14 this.updataView(); 15 } 16 },
以相应的方式实现右移动和上下移动
5.判断数组元素是否都不为零(即判断页面元素已满)
1 isFull:function(){//判断当前数组是否已满 2 for (var row=0; row<this.rn; row++) 3 { 4 for (var col=0; col<this.cn; col++) 5 { 6 if (!this.data[row][col]) 7 { 8 return false; 9 } 10 } 11 } 12 return true; 13 },
6.判断游戏是否结束
//判断游戏状态是否结束 isGameOver:function(){ //如果数组没有满,则返回false if (!this.isFull()) { return false; }else{ //从左上角第一个元素开始,遍历二维数组,如果有相等的二个元素则游戏还可以继续,返回false for (var row=0; row<this.rn; row++) { for (var col=0; col<this.cn; col++) { //如果当前元素不是最右侧元素 if(col<this.cn-1){ if(this.data[row][col]==this.data[row][col+1]){return false;} } if(row<this.rn-1){ if(this.data[row][col]==this.data[row+1][col]){return false;} } } } return true; } },
7.定义键盘接受事件(响应上下左右按键事件)
1 //当按键盘按键时,触发移动 2 document.onkeydown=function(){ 3 if(game.state==game.RUNNING){ 4 //获得事件对象 5 var e=window.event||documents[0]; 6 //获得键盘号:e.keyCode 左37上38右39下40 7 if (e.keyCode==37)//左键 8 { 9 game.moveLeft(); 10 }else if (e.keyCode==38)//上键 11 { 12 game.moveUp(); 13 }else if (e.keyCode==39)//右键 14 { 15 game.moveRight(); 16 }else if (e.keyCode==40)//下键 17 { 18 game.moveDown(); 19 } 20 } 21 }
后记,这个游戏是在学习完js基础 老师的带领下完成的,不足之处请谅解,欢迎提出更好的建议和方案,希望可以多多交流。
附带完整js代码:
1 var game={ 2 data:[],//保存所有数字的二位数组 3 rn:4,//定义行数 4 cn:4,//定义列数 5 state:0,//游戏当前状态:running|GameOver 6 RUNNING:1, 7 GAMEOVER:0, 8 score:0,//定义总分数 9 //判断游戏状态是否结束 10 isGameOver:function(){ 11 //如果没有满,则返回false 12 if (!this.isFull()) 13 { 14 return false; 15 }else{ 16 //从左上角第一个元素开始,遍历二维数组 17 for (var row=0; row<this.rn; row++) 18 { 19 for (var col=0; col<this.cn; col++) 20 { 21 //如果当前元素不是最右侧元素 22 if(col<this.cn-1){ 23 if(this.data[row][col]==this.data[row][col+1]){return false;} 24 } 25 if(row<this.rn-1){ 26 if(this.data[row][col]==this.data[row+1][col]){return false;} 27 } 28 } 29 } 30 return true; 31 } 32 }, 33 start:function(){//游戏开始方法 34 this.state=this.RUNNING; 35 //隐藏游戏结束界面 36 document.getElementById("gameOver").style.display="none"; 37 this.data=[//初始化二位数组 38 [0,2,2,2], 39 [0,0,2,2], 40 [4,2,0,0], 41 [2,0,0,2] 42 ]; 43 this.score=0;//重置分数为0 44 45 for (var r=0; r<this.rn; r++) 46 { 47 this.data[r]=[];//向空数组中添加行 48 for (var c=0; c<this.cn; c++) 49 { 50 this.data[r][c]=0;//向当前空数组中加0 51 } 52 } 53 54 //在两个随机位置生成2或4 55 this.randomNum(); 56 this.randomNum(); 57 this.updataView();//将数字写入前景grid中 58 }, 59 randomNum:function(){//在随机空位置生成一个数 60 //0-3随机生成一个行号row 61 //0-3随机生成一个列号col 62 //如果data[row][col]==0 63 // Math.random(): 64 // 随机生成一个数>=0.5?4:2 65 // 放入data[row][col] 66 if(this.isFull()) return -1;//判断二位数组中是否已满,满 返回-1 67 while(true){ 68 var row=parseInt(Math.random()*this.rn); 69 var col=parseInt(Math.random()*this.cn); 70 if(!this.data[row][col]){ 71 this.data[row][col]=Math.random()>=0.5?4:2; 72 break; 73 } 74 } 75 }, 76 isFull:function(){//判断当前数组是否已满 77 for (var row=0; row<this.rn; row++) 78 { 79 for (var col=0; col<this.cn; col++) 80 { 81 if (!this.data[row][col]) 82 { 83 return false; 84 } 85 } 86 } 87 return true; 88 }, 89 updataView:function(){//将二位数组中每个格的数字放入前景格中 90 //遍历二位素组中每个元素, 91 for (var row=0; row<this.rn; row++) 92 { 93 for (var col=0; col<this.cn; col++) 94 { 95 //获取要div标签 96 var div=document.getElementById("c"+row+col); 97 //console.log("c"+row+col); 98 var curr=this.data[row][col];//当前元素 99 //修改grid的数字内容 100 div.innerHTML=curr!=0?curr:""; 101 //修改div的class属性 102 div.className=curr!=0?"cell n"+curr:"cell"; 103 } 104 } 105 document.getElementById("score").innerHTML=this.score; 106 //判断并修改游戏状态 107 if (this.isGameOver()) 108 { 109 this.state=this.GAMEOVER; 110 //修改节点style属性下的display的属性为"block" 111 document.getElementById("finalScore").innerHTML=this.score; 112 document.getElementById("gameOver").style.display="block"; 113 } 114 }, 115 116 //实现左移动 117 //找当前位置右侧,下一个不为0的数 118 getRightNext:function(row,col){ 119 //从col+1 遍历row行中剩余元素,<this.cn 120 // 遍历到不为0的元素返回nextc-->下一个元素的列下标 121 // 循环退出返回-1 122 for ( col+=1; col<this.cn; col++) 123 { 124 if (this.data[row][col]) 125 { 126 return col; 127 } 128 } 129 return -1; 130 }, 131 /*判断并向左移动指定行中的每个元素*/ 132 moveLeftInRow:function(row){ 133 //0开始,遍历row行中每一个元素,<cn-1结束 134 //获得当前元素下一个不为0的元素的下标nextc 135 //如果nextc==-1,说明右侧么有元素了,退出循环 136 //否则 137 // 如果自己==0 则将下一个位置放入当前位置,下一个位置设置为零,col--重新检查 138 // 如果当前位置==nextc的位置,将当前位置*=2;下一个位置设置为0; 139 for (var col=0; col<this.cn-1; col++) 140 { 141 var nextc=this.getRightNext(row,col); 142 if (nextc==-1){return nextc;} 143 else if (this.data[row][col]==0) 144 { 145 this.data[row][col]=this.data[row][nextc]; 146 this.data[row][nextc]=0; 147 col--; 148 } 149 else if (this.data[row][col]==this.data[row][nextc]) 150 { 151 this.data[row][col]*=2; 152 this.data[row][nextc]=0; 153 this.score+=this.data[row][col];//将当前值累加到score属性上 154 } 155 } 156 }, 157 /*向左移动所有行*/ 158 moveLeft(){ 159 var oldStr=this.data.toString();//判断字符串是否移动 160 //循环遍历每一行 161 // 调用moveLeftInRow 162 //调用 randomNum(),随机生成一个数 163 //调用 updataView(),更新页面 164 for (var row=0; row<this.rn; row++) 165 { 166 this.moveLeftInRow(row); 167 } 168 if(oldStr!=this.data.toString()){ 169 this.randomNum(); 170 this.updataView(); 171 } 172 }, 173 //实现右移动 174 //找当前位置左侧,下一个不为0的数 175 getLeftNext:function(row,col){ 176 //从col-1开始,遍历row行中剩余元素, >=0结束 177 for ( col-=1; col>=0; col--) 178 { 179 if (this.data[row][col]) 180 { 181 return col; 182 } 183 } 184 return -1; 185 }, 186 /*判断并向右移动指定行中的每个元素*/ 187 moveRightInRow:function(row){ 188 for (var col=this.cn; col>0; col--) 189 { 190 var nextc=this.getLeftNext(row,col); 191 if (nextc==-1){return nextc;} 192 else if (this.data[row][col]==0) 193 { 194 this.data[row][col]=this.data[row][nextc]; 195 this.data[row][nextc]=0; 196 col++; 197 } 198 else if (this.data[row][col]==this.data[row][nextc]) 199 { 200 this.data[row][col]*=2; 201 this.data[row][nextc]=0; 202 this.score+=this.data[row][col];//将当前值累加到score属性上 203 } 204 } 205 }, 206 /*向右移动所有行*/ 207 moveRight(){ 208 var oldStr=this.data.toString(); 209 //循环遍历每一行 210 // 调用moveRightInRow() 211 //调用 randomNum(),随机生成一个数 212 //调用 updataView(),更新页面 213 for (var row=0; row<this.rn; row++) 214 { 215 this.moveRightInRow(row); 216 } 217 if(oldStr!=this.data.toString()){ 218 this.randomNum(); 219 this.updataView(); 220 } 221 }, 222 //实现向上移动 223 /*找到当前位置下侧,不为0的数*/ 224 getDownNext:function(row,col){ 225 //nextr从row+1开始,到<this.rn结束 226 for ( row+=1; row<this.rn; row++) 227 { 228 if (this.data[row][col]) 229 { 230 return row; 231 } 232 } 233 return -1; 234 }, 235 /*判断并向上移动指定行中的每个元素*/ 236 moveUpInCol:function(col){ 237 //row从0开始,到 <rn-1,遍历每行元素 238 for (var row=0; row<this.rn-1; row++) 239 { 240 //先获得当前位置下方不为0的行nextr 241 var nextr=this.getDownNext(row,col); 242 if (nextr==-1){return nextr;}//如果next==-1 243 //否则 244 else if (this.data[row][col]==0)//如果当前位置等于0 245 { 246 //当前位置替换为nextr位置的元素 247 this.data[row][col]=this.data[nextr][col]; 248 //将nextr位置设置为0 249 this.data[nextr][col]=0; 250 row--; 251 } 252 //如果当前位置等于nextr位置 253 else if (this.data[row][col]==this.data[nextr][col]) 254 { 255 //将当前位置值*=2 256 this.data[row][col]*=2; 257 //将nextr位置设置为0 258 this.data[nextr][col]=0; 259 this.score+=this.data[row][col];//将当前值累加到score属性上 260 } 261 } 262 }, 263 /*向上移动所有行*/ 264 moveUp(){ 265 var oldStr=this.data.toString();//判断字符串是否移动 266 //循环遍历每一行 267 // 调用moveUpInCol() 268 //调用 randomNum(),随机生成一个数 269 //调用 updataView(),更新页面 270 for (var col=0; col<this.cn; col++) 271 { 272 this.moveUpInCol(col); 273 } 274 if(oldStr!=this.data.toString()){ 275 this.randomNum(); 276 this.updataView(); 277 } 278 }, 279 //实现向下移动 280 /*找到当前位置上侧,不为0的数*/ 281 getUpNext:function(row,col){ 282 //nextr从row-1开始,到<this.rn结束 283 for ( row-=1; row>=0; row--) 284 { 285 if (this.data[row][col]) 286 { 287 return row; 288 } 289 } 290 return -1; 291 }, 292 /*判断并向下移动指定行中的每个元素*/ 293 moveDownInCol:function(col){ 294 //row从rn-1开始 295 for (var row=this.rn-1; row>=0; row--) 296 { 297 var nextr=this.getUpNext(row,col); 298 //console.log((row+1)+"行"+(col+1)+"列:"+nextr); 299 if (nextr==-1){return nextr;} 300 else if (this.data[row][col]==0) 301 { 302 this.data[row][col]=this.data[nextr][col]; 303 this.data[nextr][col]=0; 304 row++; 305 } 306 else if (this.data[row][col]==this.data[nextr][col]) 307 { 308 this.data[row][col]*=2; 309 this.data[nextr][col]=0; 310 this.score+=this.data[row][col];//将当前值累加到score属性上 311 } 312 } 313 }, 314 /*向下移动所有行*/ 315 moveDown(){ 316 var oldStr=this.data.toString();//判断字符串是否移动 317 //循环遍历每一行 318 // 调用moveDownInCol() 319 //调用 randomNum(),随机生成一个数 320 //调用 updataView(),更新页面 321 for (var col=0; col<this.cn; col++) 322 { 323 this.moveDownInCol(col); 324 } 325 if(oldStr!=this.data.toString()){ 326 this.randomNum(); 327 this.updataView(); 328 } 329 } 330 } 331 //onload事件:当页面加载后自动执行 332 window.onload=function(){ 333 game.start();//页面加载后,自动启动游戏 334 //console.log(game.getDownNext(0,0)); 335 //console.log(game.getUpNext(3,1)); 336 //当按键盘按键时,触发移动 337 document.onkeydown=function(){ 338 if(game.state==game.RUNNING){ 339 //获得事件对象 340 var e=window.event||documents[0]; 341 //获得键盘号:e.keyCode 左37上38右39下40 342 if (e.keyCode==37)//左键 343 { 344 game.moveLeft(); 345 }else if (e.keyCode==38)//上键 346 { 347 game.moveUp(); 348 }else if (e.keyCode==39)//右键 349 { 350 game.moveRight(); 351 }else if (e.keyCode==40)//下键 352 { 353 game.moveDown(); 354 } 355 } 356 } 357 }