用到2个库:JQ用来操作DOM,awesome用一些图标,都是在HTML里通过URL引用
得分原则:
- 单次消除N行比分多次消除N 行得分高
- 消除后,上面的自动落下又导致有行被消除,有比较多的额外奖励,因为创造条件有一定风险
游戏有4个状态:
- 终止:GAMEOVER的时候,这个状态只能refresh 别的操作都无效
- 正常游戏:自动下落,可以暂停、旋转、左右移动、加速下落
- 消除或自动下落:播放动画,自动消除,下落,再消除。不响应操作
- 暂停:不响应操作,不会自动往下落
附上代码,只有一个HTML 直接打开就能玩
1 <html> 2 <head> 3 <title>tetris</title> 4 <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script> 5 <style type="text/css"> 6 .data { 7 width: 35px; 8 height: 35px; 9 display: inline-block; 10 margin: 1px; 11 background-color: #999999; 12 color: #333333; 13 text-align: center; 14 vertical-align: middle; 15 } 16 .small-data { 17 width: 20px; 18 height: 20px; 19 } 20 .fill { 21 background-color: #333333; 22 color: #999999; 23 } 24 .clear { 25 animation-name: clearLine; 26 animation-duration: 200ms; 27 } 28 .inline { 29 display: inline-block; 30 } 31 .top { 32 width: 200px; 33 vertical-align: top; 34 } 35 .left { 36 text-align: right; 37 display: inline-grid; 38 grid-template-columns: 30px 30px; 39 grid-template-rows: repeat(6,30px); 40 align-items: center; 41 justify-content: center; 42 } 43 .right { 44 text-align: left; 45 } 46 .label { 47 float: left; 48 width: 100px; 49 text-align: left; 50 } 51 body { 52 background-color: #666666; 53 color: #999999; 54 font-size: 24px; 55 font-weight: bold; 56 } 57 @keyframes clearLine { 58 from{background-color: #333333;} 59 to{background-color: #999999;} 60 } 61 </style> 62 <link href="https://cdn.staticfile.org/font-awesome/4.7.0/css/font-awesome.css" rel="stylesheet" type="text/css"> 63 <meta http-equiv="content-type" content="text/html;charset=utf-8"> 64 </head> 65 <body> 66 <div style="text-align: center;" > 67 <div class="inline top left"> 68 <span>W:</span> 69 <i class="fa fa-rotate-left" title="rotate"></i> 70 <span>A:</span> 71 <i class="fa fa-arrow-left" title="move left"></i> 72 <span>S:</span> 73 <i class="fa fa-arrow-right" title="move right"></i> 74 <span>D:</span> 75 <i class="fa fa-arrow-down" title="drop fast"></i> 76 <span>Q:</span> 77 <i class="fa fa-pause pause" title="pause/play"></i> 78 <span>F:</span> 79 <i class="fa fa-refresh" title="refresh"></i> 80 </div> 81 <div class="inline" id="main"></div> 82 <div class="inline top right"> 83 <div> 84 <span class="label">score</span> 85 <span id="score">0</span> 86 </div> 87 <div> 88 <span class="label">next</span> 89 <div style="display: inline-block;vertical-align: middle;" id="next"></div> 90 </div> 91 </div> 92 </div> 93 </body> 94 <script> 95 var width=10; 96 var height=20; 97 var data=new Array(width * height).fill(false); 98 var rowIndexList=indexArray(height); 99 var columnIndexList=indexArray(width); 100 var shape=[ 101 [1*width+1,1*width+2,2*width+1,2*width+2],//O 102 [1,width+1,2*width+1,3*width+1],//I 103 [1*width+1,1*width+2,2*width,2*width+1],//S 104 [1*width,1*width+1,2*width+1,2*width+2],//Z 105 [width+1,2*width+1,3*width+1,3*width+2],//L 106 [width+2,2*width+2,3*width+1,3*width+2],//J 107 [1*width,1*width+1,1*width+2,2*width+1]//T 108 ]; 109 var next; 110 var current; 111 var gameState=0;//0:game over, 1:normal, 2:clear or drop, 3:pause 112 function indexArray(length){ 113 return Array.from(new Array(length)).map((v,k)=>k); 114 } 115 function newBlock(){ 116 current=next.slice(); 117 next=shape[parseInt(Math.random()*shape.length)]; 118 $('#next').html(indexArray(16).map(k=>(k%4==0?'<div>':'')+'<div class="data small-data'+(next.includes(k+parseInt(k/4)*(width-4))?' fill':'')+'"></div>'+(k%4==3?'</div>':'')).join('')); 119 while(current.every(u=>u>=width)) 120 current=current.map(u=>u-width); 121 current=current.map(u=>u+parseInt(width/2-1)); 122 if(current.some(u=>data[u])){ 123 while(current.some(u=>data[u])) 124 current=current.map(u=>u-width).filter(u=>u>=0); 125 gameOver(); 126 } 127 setData([],current); 128 } 129 function init(){ 130 next=shape[parseInt(Math.random()*shape.length)]; 131 setData(Array.from(data.keys()),[]); 132 data.fill(false); 133 $('.data').text(''); 134 newBlock(); 135 gameState=1; 136 } 137 function pause(){ 138 if(gameState==3){ 139 gameState=1; 140 $('.pause').removeClass('fa-Play'); 141 $('.pause').addClass('fa-pause'); 142 } 143 else if(gameState==1){ 144 gameState=3; 145 $('.pause').removeClass('fa-pause'); 146 $('.pause').addClass('fa-Play'); 147 } 148 } 149 function setData(setFalse,setTrue){ 150 setFalse.forEach(i=>data[i]=false); 151 setTrue.forEach(i=>data[i]=true); 152 new Set(setFalse.concat(setTrue)).forEach(i=>$('.data[data-index='+i+']').toggleClass('fill', data[i])); 153 } 154 function canMoveDown(){ 155 return current.every(u=>current.includes(u+width)||data[u+width]===false); 156 } 157 function moveDown(){ 158 setData(current,current.map(i=>i+width)); 159 current=current.map(u=>u+width); 160 } 161 function moveLeft(){ 162 setData(current,current.map(i=>i-1)); 163 current=current.map(u=>u-1); 164 } 165 function moveRight(){ 166 setData(current,current.map(i=>i+1)); 167 current=current.map(u=>u+1); 168 } 169 function rotate(){ 170 let target=current.map(u=>parseInt(u/width)-u%width*height); 171 while(target.every(u=>u<parseInt(Math.max(...current)/width)*height)) 172 target=target.map(u=>u+height); 173 while(target.some(u=>u%height>=width)||Math.min(...target.map(u=>u%height))>Math.min(...current.map(u=>u%width))) 174 target=target.map(u=>u-1); 175 while(target.every(u=>u%height<width-1)&&Math.min(...target.map(u=>u%height))<Math.min(...current.map(u=>u%width))) 176 target=target.map(u=>u+1); 177 target=target.map(u=>u-parseInt(u/height)*(height-width)); 178 if(target.every(u=>current.includes(u)||data[u]===false)){ 179 setData(current,target); 180 current=target; 181 } 182 } 183 function clearLine(){ 184 let lines=rowIndexList.filter(i=>columnIndexList.every(j=>data[i*width+j])); 185 if(lines.length==0) 186 return false; 187 let score=lines.length*20-10+(gameState-1)*100; 188 $('#score').text(parseInt($('#score').text())+score); 189 let cells=lines.flatMap(i=>columnIndexList.map(j=>i*width+j)); 190 cells.forEach(i=>$('.data[data-index='+i+']').toggleClass('clear', true)); 191 setTimeout(()=>cells.forEach(i=>$('.data[data-index='+i+']').toggleClass('clear', false)),200); 192 setData(cells,[]); 193 return true; 194 } 195 function dropDown(){ 196 let lines=rowIndexList.filter(i=>i>0&&columnIndexList.every(j=>!data[i*width+j])&&columnIndexList.some(j=>data[i*width+j-width])); 197 if(lines.length==0) 198 return false; 199 for(let i=lines.length-1;i>=0;i--){ 200 let startIndex=i==0?0:lines[i-1]*width+width; 201 var tmpData=data.slice(startIndex,lines[i]*width); 202 var blockList=[]; 203 var tmpSet=[]; 204 function getBlock(i){ 205 tmpSet.push(i); 206 tmpData[i]=false; 207 if(i%width>0&&tmpData[i-1]) 208 getBlock(i-1); 209 if(i%width<width-1&&tmpData[i+1]) 210 getBlock(i+1); 211 if(i>=width&&tmpData[i-width]) 212 getBlock(i-width); 213 if(i+width<tmpData.length&&tmpData[i+width]) 214 getBlock(i+width); 215 } 216 while(tmpData.some(u=>u)){ 217 getBlock(tmpData.findIndex(u=>u)); 218 blockList.push(tmpSet.map(u=>u+startIndex)); 219 tmpSet=[]; 220 } 221 while(blockList.some(u=>u.length>0)){ 222 for(let j=0;j<blockList.length;j++){ 223 if(blockList[j].every(u=>blockList[j].includes(u+width)||data[u+width]===false)){ 224 setData(blockList[j],blockList[j].map(i=>i+width)); 225 blockList[j]=blockList[j].map(i=>i+width); 226 } 227 else 228 blockList[j]=[]; 229 } 230 } 231 } 232 return true; 233 } 234 function gameOver(){ 235 gameState=0; 236 'GAMEOVER'.split('').forEach((v,i)=>$('.data[data-index='+((parseInt(height/2)+parseInt(i/4)-2)*width+parseInt(width/2)-2+i%4)+']').text(v)); 237 } 238 const interval=500; 239 setInterval(()=>{ 240 if(gameState==0||gameState==3) 241 return; 242 if(gameState==1&&canMoveDown()) 243 moveDown(); 244 else{ 245 if(clearLine()||dropDown()){ 246 gameState=2; 247 return; 248 } 249 gameState=1; 250 if(current.some(u=>u<width)) 251 gameOver(); 252 else 253 newBlock(); 254 } 255 },interval); 256 $('body').keydown(e=>{ 257 switch(e.which){ 258 case 70: 259 init(); 260 break; 261 case 81: 262 pause(); 263 break; 264 case 65: 265 if(gameState==1&¤t.every(u=>current.includes(u-1)||(u%width>0&&data[u-1]===false))) 266 moveLeft(); 267 break; 268 case 87: 269 if(gameState==1) 270 rotate(); 271 break; 272 case 68: 273 if(gameState==1&¤t.every(u=>current.includes(u+1)||(u%width<width-1&&data[u+1]===false))) 274 moveRight(); 275 break; 276 case 83: 277 if(gameState==1&&canMoveDown()) 278 moveDown(); 279 break; 280 } 281 }); 282 $('#main').html(data.map((v,k)=>(k%width==0?'<div>':'')+'<div class="data" data-index='+k+'></div>'+(k%width==width-1?'</div>':'')).join('')); 283 init(); 284 </script> 285 </html>