Files
tetris-web/public/app.js

36 lines
3.2 KiB
JavaScript
Raw Normal View History

const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const COLS = 10, ROWS = 20, BLOCK = 30;
const COLORS = ['#000','#00f0f0','#0000f0','#f0a000','#f0f000','#00f000','#a000f0','#f00000'];
const SHAPES = [
[],
[[1,1,1,1]],
[[2,0,0],[2,2,2]],
[[0,0,3],[3,3,3]],
[[4,4],[4,4]],
[[0,5,5],[5,5,0]],
[[0,6,0],[6,6,6]],
[[7,7,0],[0,7,7]]
];
let board, piece, score, lines, level, dropCounter, dropInterval, last, paused, over;
function newBoard(){return Array.from({length:ROWS},()=>Array(COLS).fill(0));}
function randPiece(){const id = 1 + Math.floor(Math.random()*7);return {x:3,y:0,id,shape:SHAPES[id].map(r=>[...r])};}
function collide(p){for(let y=0;y<p.shape.length;y++)for(let x=0;x<p.shape[y].length;x++)if(p.shape[y][x]){const nx=p.x+x, ny=p.y+y; if(nx<0||nx>=COLS||ny>=ROWS|| (ny>=0 && board[ny][nx])) return true;} return false;}
function merge(){piece.shape.forEach((r,y)=>r.forEach((v,x)=>{if(v&&piece.y+y>=0)board[piece.y+y][piece.x+x]=v;}));}
function rotate(s){return s[0].map((_,i)=>s.map(r=>r[i]).reverse());}
function clearLines(){let c=0; outer: for(let y=ROWS-1;y>=0;y--){for(let x=0;x<COLS;x++)if(!board[y][x])continue outer; board.splice(y,1); board.unshift(Array(COLS).fill(0)); c++; y++;}
if(c){lines+=c; score += [0,40,100,300,1200][c]*level; level = 1 + Math.floor(lines/10); dropInterval = Math.max(80, 700 - (level-1)*60);}}
function spawn(){piece=randPiece(); if(collide(piece)){over=true;}}
function move(dx){piece.x+=dx; if(collide(piece))piece.x-=dx;}
function drop(){piece.y++; if(collide(piece)){piece.y--; merge(); clearLines(); spawn();} dropCounter=0;}
function hardDrop(){while(!collide(piece))piece.y++; piece.y--; drop();}
function tryRotate(){const old=piece.shape; piece.shape=rotate(piece.shape); if(collide(piece)){piece.x++; if(collide(piece)){piece.x-=2; if(collide(piece)){piece.x++; piece.shape=old;}}}}
function drawCell(x,y,v){ctx.fillStyle=COLORS[v];ctx.fillRect(x*BLOCK,y*BLOCK,BLOCK-1,BLOCK-1);}
function draw(){ctx.fillStyle='#10152b';ctx.fillRect(0,0,canvas.width,canvas.height); board.forEach((r,y)=>r.forEach((v,x)=>v&&drawCell(x,y,v))); piece.shape.forEach((r,y)=>r.forEach((v,x)=>v&&drawCell(piece.x+x,piece.y+y,v))); if(paused||over){ctx.fillStyle='rgba(0,0,0,.55)';ctx.fillRect(0,0,300,600);ctx.fillStyle='#fff';ctx.font='bold 28px Arial';ctx.fillText(over?'Game Over':'Pause',80,300);} }
function ui(){scoreEl.textContent=score; levelEl.textContent=level; linesEl.textContent=lines;}
const scoreEl=document.getElementById('score'), levelEl=document.getElementById('level'), linesEl=document.getElementById('lines');
function loop(t=0){const dt=t-last; last=t; if(!paused&&!over){dropCounter+=dt; if(dropCounter>dropInterval)drop();} draw(); ui(); requestAnimationFrame(loop);}
function reset(){board=newBoard(); score=0; lines=0; level=1; dropCounter=0; dropInterval=700; last=0; paused=false; over=false; spawn();}
document.addEventListener('keydown',e=>{if(e.key==='p'||e.key==='P'){paused=!paused;return;} if(paused||over)return; if(e.key==='ArrowLeft')move(-1); else if(e.key==='ArrowRight')move(1); else if(e.key==='ArrowDown')drop(); else if(e.key==='ArrowUp')tryRotate(); else if(e.code==='Space'){e.preventDefault(); hardDrop();}});
document.getElementById('restart').onclick=reset;
reset(); loop();