Tetris en JavaScript (HTML5 Canvas)

Tetris jugable en el navegador como fichero HTML autocontenido. Incluye las siete piezas clásicas con rotación, eliminación de líneas, niveles de velocidad progresivos, puntuación, panel con la siguiente pieza y efecto de pieza fantasma que muestra dónde caerá. Funciona sin servidor ni dependencias; solo hay que abrir el HTML descargado.
				<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Tetris JS — David Carrero / carrero.es</title>
<style>
  *{box-sizing:border-box;margin:0;padding:0}
  body{background:#000;display:flex;flex-direction:column;align-items:center;
       justify-content:center;min-height:100vh;font-family:monospace;color:#0f0;gap:8px}
  h1{font-size:16px;letter-spacing:1px}
  #wrap{display:flex;gap:12px;align-items:flex-start}
  canvas{border:2px solid #222}
  #panel{width:120px;padding:8px;background:#0a0a0a;border:1px solid #222;font-size:12px}
  #panel h2{font-size:11px;color:#555;text-transform:uppercase;margin:0 0 2px}
  #panel .val{color:#fff;font-size:18px;font-weight:bold;margin-bottom:8px;display:block}
  #panel canvas{border:1px solid #222;display:block;margin:4px 0 10px}
  #panel .hint{color:#333;font-size:10px;line-height:1.8}
  footer{font-size:11px;color:#444;text-align:center;line-height:1.6}
  footer a{color:#555;text-decoration:none}
</style>
</head>
<body>
<h1>&#9632; TETRIS JS &mdash; <a href="https://carrero.es" style="color:#0a0;text-decoration:none">carrero.es</a></h1>
<div id="wrap">
  <canvas id="board"></canvas>
  <div id="panel">
    <h2>Siguiente</h2>
    <canvas id="next" width="80" height="64"></canvas>
    <h2>Puntos</h2><span class="val" id="pts">0</span>
    <h2>Nivel</h2><span class="val" id="lvl">1</span>
    <h2>L&iacute;neas</h2><span class="val" id="lin">0</span>
    <div class="hint">
      &larr; &rarr; mover<br>
      &uarr; rotar<br>
      &darr; bajar<br>
      Esp. ca&iacute;da<br>
      R reiniciar
    </div>
  </div>
</div>
<footer>
  Versiones: <a href="https://programacion.net/codigo/tetris_63">Pascal</a> &middot;
  <a href="https://programacion.net/codigo/tetris-en-php-cli_1895">PHP</a> &middot;
  <a href="https://programacion.net/codigo/tetris-en-python-con-curses_1896">Python</a>
</footer>
<script>
'use strict';
const ROWS=20,COLS=10,CS=28;
const bc=document.getElementById('board');
const bx=bc.getContext('2d');
bc.width=COLS*CS; bc.height=ROWS*CS;
const nc=document.getElementById('next');
const nx=nc.getContext('2d');

const SHAPES=[
  [[0,0],[0,1],[0,2],[0,3]], // I
  [[0,0],[0,1],[1,0],[1,1]], // O
  [[0,0],[0,1],[0,2],[1,1]], // T
  [[0,1],[0,2],[1,0],[1,1]], // S
  [[0,0],[0,1],[1,1],[1,2]], // Z
  [[0,0],[1,0],[1,1],[1,2]], // J
  [[0,2],[1,0],[1,1],[1,2]], // L
];
const COLORS=['','#0ef','#fe0','#b0f','#0d0','#e00','#06f','#f80'];
const SCORE=[0,100,300,500,800];

// --- Estado ---
let board,piece,pClr,next,nClr,pr,pc,pts,lvl,lin,state,timer;

function newBoard(){return Array.from({length:ROWS},()=>Array(COLS).fill(0));}

function randPiece(){
  const i=Math.floor(Math.random()*SHAPES.length);
  return{shape:SHAPES[i].map(s=>[...s]),clr:COLORS[i+1]};
}

function rotate(shape){
  const r=shape.map(([dr,dc])=>[dc,-dr]);
  const mr=Math.min(...r.map(([x])=>x));
  const mc=Math.min(...r.map(([,y])=>y));
  return r.map(([x,y])=>[x-mr,y-mc]);
}

function valid(shape,row,col){
  return shape.every(([dr,dc])=>{
    const r=row+dr,c=col+dc;
    return r>=0&&r<ROWS&&c>=0&&c<COLS&&!board[r][c];
  });
}

function place(){
  piece.forEach(([dr,dc])=>{board[pr+dr][pc+dc]=pClr;});
}

function clearLines(){
  let n=0;
  for(let r=ROWS-1;r>=0;r--){
    if(board[r].every(c=>c)){
      board.splice(r,1);
      board.unshift(Array(COLS).fill(0));
      n++;r++;
    }
  }
  return n;
}

function lock(){
  place();
  const n=clearLines();
  lin+=n;pts+=SCORE[n]*lvl;
  lvl=Math.floor(lin/10)+1;
  const np=randPiece();piece=next;pClr=nClr;next=np.shape;nClr=np.clr;
  pr=0;pc=Math.floor(COLS/2)-2;
  updatePanel();
  if(!valid(piece,pr,pc)){state='over';clearInterval(timer);draw();return;}
  resetTimer();
}

function drop(){
  if(state!=='play')return;
  if(valid(piece,pr+1,pc))pr++;
  else lock();
  draw();
}

function hardDrop(){
  while(valid(piece,pr+1,pc))pr++;
  lock();draw();
}

function resetTimer(){
  clearInterval(timer);
  const ms=Math.max(80,500-(lvl-1)*40);
  timer=setInterval(drop,ms);
}

function updatePanel(){
  document.getElementById('pts').textContent=pts;
  document.getElementById('lvl').textContent=lvl;
  document.getElementById('lin').textContent=lin;
}

function init(){
  board=newBoard();
  const p=randPiece();piece=p.shape;pClr=p.clr;
  const n=randPiece();next=n.shape;nClr=n.clr;
  pr=0;pc=Math.floor(COLS/2)-2;
  pts=0;lvl=1;lin=0;state='play';
  updatePanel();resetTimer();
}

// --- Dibujo ---
function block(ctx,x,y,color,sz){
  ctx.fillStyle=color;
  ctx.fillRect(x+1,y+1,sz-2,sz-2);
  // highlight top
  ctx.fillStyle='rgba(255,255,255,.28)';
  ctx.fillRect(x+1,y+1,sz-2,sz*.32);
  // shadow bottom
  ctx.fillStyle='rgba(0,0,0,.35)';
  ctx.fillRect(x+1,y+sz*.7,sz-2,sz*.28);
}

function ghostRow(){
  let gr=pr;
  while(valid(piece,gr+1,pc))gr++;
  return gr;
}

function draw(){
  // board bg
  bx.fillStyle='#111';bx.fillRect(0,0,bc.width,bc.height);
  // grid lines
  bx.strokeStyle='#1a1a1a';bx.lineWidth=1;
  for(let r=0;r<=ROWS;r++){bx.beginPath();bx.moveTo(0,r*CS);bx.lineTo(bc.width,r*CS);bx.stroke();}
  for(let c=0;c<=COLS;c++){bx.beginPath();bx.moveTo(c*CS,0);bx.lineTo(c*CS,bc.height);bx.stroke();}
  // placed blocks
  for(let r=0;r<ROWS;r++)for(let c=0;c<COLS;c++){
    if(board[r][c])block(bx,c*CS,r*CS,board[r][c],CS);
  }
  if(state==='play'){
    // ghost
    const gr=ghostRow();
    piece.forEach(([dr,dc])=>{
      const r=gr+dr,c=pc+dc;
      if(r>=0){
        bx.fillStyle='rgba(255,255,255,.08)';
        bx.fillRect(c*CS+1,r*CS+1,CS-2,CS-2);
        bx.strokeStyle='rgba(255,255,255,.15)';
        bx.strokeRect(c*CS+1,r*CS+1,CS-2,CS-2);
      }
    });
    // active piece
    piece.forEach(([dr,dc])=>{
      const r=pr+dr,c=pc+dc;
      if(r>=0)block(bx,c*CS,r*CS,pClr,CS);
    });
  }
  // overlay game over
  if(state==='over'){
    bx.fillStyle='rgba(0,0,0,.82)';bx.fillRect(0,0,bc.width,bc.height);
    bx.textAlign='center';
    bx.fillStyle='#f44';bx.font='bold 24px monospace';
    bx.fillText('GAME OVER',bc.width/2,bc.height/2-22);
    bx.fillStyle='#fff';bx.font='16px monospace';
    bx.fillText('Puntos: '+pts,bc.width/2,bc.height/2+6);
    bx.fillStyle='#555';bx.font='12px monospace';
    bx.fillText('Pulsa R para reiniciar',bc.width/2,bc.height/2+30);
    bx.textAlign='left';
  }
  // next piece panel
  nx.fillStyle='#0a0a0a';nx.fillRect(0,0,nc.width,nc.height);
  const nsz=14;
  next.forEach(([dr,dc])=>block(nx,dc*nsz+8,dr*nsz+8,nClr,nsz));
}

// --- Input ---
document.addEventListener('keydown',e=>{
  if(state==='over'){
    if(e.key==='r'||e.key==='R'){init();draw();}
    return;
  }
  if(state!=='play')return;
  switch(e.key){
    case 'ArrowLeft':  e.preventDefault();if(valid(piece,pr,pc-1))pc--;draw();break;
    case 'ArrowRight': e.preventDefault();if(valid(piece,pr,pc+1))pc++;draw();break;
    case 'ArrowUp':    e.preventDefault();
      {const rot=rotate(piece);if(valid(rot,pr,pc))piece=rot;}draw();break;
    case 'ArrowDown':  e.preventDefault();if(valid(piece,pr+1,pc)){pr++;draw();}break;
    case ' ':          e.preventDefault();hardDrop();break;
    case 'r':case 'R': clearInterval(timer);init();draw();break;
  }
});

init();draw();
</script>
</body>
</html>

			
Descargar adjuntos
COMPARTE ESTE TUTORIAL

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP