
Tetris
Tetris that runs in a web browser
Prompt
Please make a Tetris that runs in a web browser.
A system prompt was added to support web rendering
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Web Tetris</title> <style> html,body{ height:100%; margin:0; display:flex; flex-direction:column; align-items:center; justify-content:center; background:#111; color:#fff; font-family:Arial,Helvetica,sans-serif; user-select:none; } canvas{ background:#000; box-shadow:0 0 10px #0f0; } #stats{ margin-top:8px; font-size:18px; } </style> </head> <body> <canvas id="game" width="240" height="400"></canvas> <div id="stats">Score 0</div> <script> (() => { const canvas = document.getElementById('game'); const ctx = canvas.getContext('2d'); const COLS = 10, ROWS = 20, SIZE = 20; const COLORS = ['#0ff','#00f','#f80','#ff0','#0f0','#f0f','#f00']; const SHAPES = [ // I [ [[0,1],[1,1],[2,1],[3,1]], [[2,0],[2,1],[2,2],[2,3]], [[0,2],[1,2],[2,2],[3,2]], [[1,0],[1,1],[1,2],[1,3]] ], // J [ [[0,0],[0,1],[1,1],[2,1]], [[1,0],[2,0],[1,1],[1,2]], [[0,1],[1,1],[2,1],[2,2]], [[1,0],[1,1],[0,2],[1,2]] ], // L [ [[2,0],[0,1],[1,1],[2,1]], [[1,0],[1,1],[1,2],[2,2]], [[0,1],[1,1],[2,1],[0,2]], [[0,0],[1,0],[1,1],[1,2]] ], // O [ [[1,0],[2,0],[1,1],[2,1]], [[1,0],[2,0],[1,1],[2,1]], [[1,0],[2,0],[1,1],[2,1]], [[1,0],[2,0],[1,1],[2,1]] ], // S [ [[1,0],[2,0],[0,1],[1,1]], [[1,0],[1,1],[2,1],[2,2]], [[1,1],[2,1],[0,2],[1,2]], [[0,0],[0,1],[1,1],[1,2]] ], // T [ [[1,0],[0,1],[1,1],[2,1]], [[1,0],[1,1],[2,1],[1,2]], [[0,1],[1,1],[2,1],[1,2]], [[1,0],[0,1],[1,1],[1,2]] ], // Z [ [[0,0],[1,0],[1,1],[2,1]], [[2,0],[1,1],[2,1],[1,2]], [[0,1],[1,1],[1,2],[2,2]], [[1,0],[0,1],[1,1],[0,2]] ] ]; let board = createMatrix(COLS, ROWS); let score = 0; let dropInterval = 500; let lastTime = 0; let accTime = 0; let piece = null; function createMatrix(w,h){ const m=[]; for(let y=0;y<h;y++){ m[y]=new Array(w).fill(-1); // -1 empty } return m; } function newPiece(){ const id = Math.floor(Math.random()*SHAPES.length); return{ id, rot:0, x:3, y:0 }; } function getBlocks(p=piece){ return SHAPES[p.id][p.rot]; } function collide(offX=0,offY=0,rot=piece.rot){ const blocks = SHAPES[piece.id][rot]; for(const [x,y] of blocks){ const px = piece.x + x + offX; const py = piece.y + y + offY; if(px<0 || px>=COLS || py>=ROWS) return true; if(py>=0 && board[py][px] !== -1) return true; } return false; } function merge(){ for(const [x,y] of getBlocks()){ const px = piece.x + x; const py = piece.y + y; if(py<0) gameOver(); board[py][px]=piece.id; } clearLines(); piece = newPiece(); if(collide()) gameOver(); } function clearLines(){ let lines=0; outer:for(let y=ROWS-1;y>=0;y--){ for(let x=0;x<COLS;x++){ if(board[y][x]==-1) continue outer; } const row=board.splice(y,1)[0].fill(-1); board.unshift(row); y++; // recheck same row lines++; } if(lines>0){ score += [0,40,100,300,1200][lines]; document.getElementById('stats').textContent='Score '+score; } } function drawCell(x,y,color){ ctx.fillStyle=color; ctx.fillRect(x*SIZE,y*SIZE,SIZE,SIZE); ctx.strokeStyle='#111'; ctx.strokeRect(x*SIZE,y*SIZE,SIZE,SIZE); } function draw(){ ctx.clearRect(0,0,canvas.width,canvas.height); for(let y=0;y<ROWS;y++){ for(let x=0;x<COLS;x++){ if(board[y][x]!==-1){ drawCell(x,y,COLORS[board[y][x]]); } } } for(const [x,y] of getBlocks()){ const px = piece.x + x; const py = piece.y + y; if(py>=0) drawCell(px,py,COLORS[piece.id]); } } function update(time=0){ const delta = time-lastTime; lastTime = time; accTime += delta; if(accTime>dropInterval){ accTime = 0; move(0,1); } draw(); requestAnimationFrame(update); } function move(dx,dy){ if(!collide(dx,dy)){ piece.x+=dx; piece.y+=dy; return true; }else if(dy===1){ merge(); } return false; } function rotate(){ const nextRot=(piece.rot+1)&3; if(!collide(0,0,nextRot)){ piece.rot=nextRot; }else{ // simple wall kick: try move left/right if(!collide(-1,0,nextRot)){piece.x--;piece.rot=nextRot;} else if(!collide(1,0,nextRot)){piece.x++;piece.rot=nextRot;} } } function hardDrop(){ while(move(0,1)){} } document.addEventListener('keydown',(e)=>{ switch(e.code){ case 'ArrowLeft': move(-1,0); break; case 'ArrowRight':move(1,0); break; case 'ArrowDown': move(0,1); break; case 'ArrowUp': rotate(); break; case 'Space': hardDrop();break; } }); function gameOver(){ alert('Game Over! Your score: '+score); board=createMatrix(COLS,ROWS); score=0; document.getElementById('stats').textContent='Score 0'; } piece = newPiece(); requestAnimationFrame(update); })(); </script> </body> </html> ```
Drag to resize
Here is a complete, self-contained Tetris game that runs in a web browser. ```html <!DOCTYPE html> <html> <head> <title>Tetris</title> <style> /* Basic CSS Reset */ html, body, div, span, h1, h2, p, canvas { margin: 0; padding: 0; border: 0; font-size: 100%; font: inherit; vertical-align: baseline; } body { line-height: 1; } /* Game Styles */ body { background: #202028; color: #fff; font-family: 'Consolas', 'Monaco', 'Andale Mono', 'Ubuntu Mono', monospace; font-size: 1em; text-align: center; } .container { display: flex; justify-content: center; align-items: flex-start; padding-top: 20px; } canvas#tetris { border: 4px solid #fff; background: #000; } .sidebar { width: 200px; margin-left: 20px; text-align: left; } .sidebar > div { border: 4px solid #fff; padding: 15px; margin-bottom: 20px; background: rgba(0,0,0,0.2); } h2 { font-size: 1.2em; margin-bottom: 10px; text-transform: uppercase; } #score, #lines, #level { font-size: 1.5em; color: #fef233; } #next-canvas { background: #000; } #controls p { font-size: 0.8em; line-height: 1.5; margin-bottom: 5px; } #game-over { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 0, 0, 0.75); padding: 40px; border: 4px solid #fff; display: none; /* Hidden by default */ flex-direction: column; align-items: center; z-index: 100; } #game-over h1 { font-size: 2.5em; color: #ff4136; margin-bottom: 20px; text-transform: uppercase; } #game-over button { font-family: inherit; font-size: 1em; background: #fff; color: #000; border: none; padding: 15px 30px; cursor: pointer; margin-top: 20px; text-transform: uppercase; } #game-over button:hover { background: #fef233; } </style> </head> <body> <div class="container"> <canvas id="tetris" width="240" height="480"></canvas> <div class="sidebar"> <div> <h2>Score</h2> <p id="score">0</p> </div> <div> <h2>Lines</h2> <p id="lines">0</p> </div> <div> <h2>Level</h2> <p id="level">0</p> </div> <div> <h2>Next</h2> <canvas id="next-canvas" width="80" height="80"></canvas> </div> <div id="controls"> <h2>Controls</h2> <p>Left: ← Arrow</p> <p>Right: → Arrow</p> <p>Rotate: ↑ Arrow</p> <p>Soft Drop: ↓ Arrow</p> <p>Hard Drop: Space</p> </div> </div> </div> <div id="game-over"> <h1>Game Over</h1> <button id="restart-button">Play Again</button> </div> <script> document.addEventListener('DOMContentLoaded', () => { const canvas = document.getElementById('tetris'); const ctx = canvas.getContext('2d'); const nextCanvas = document.getElementById('next-canvas'); const nextCtx = nextCanvas.getContext('2d'); const scoreElement = document.getElementById('score'); const linesElement = document.getElementById('lines'); const levelElement = document.getElementById('level'); const gameOverElement = document.getElementById('game-over'); const restartButton = document.getElementById('restart-button'); const COLS = 10; const ROWS = 20; const BLOCK_SIZE = canvas.width / COLS; const NEXT_BLOCK_SIZE = nextCanvas.width / 4; const colors = [ null, // 0 '#FF0D72', // 1 - T '#0DC2FF', // 2 - I '#0DFF72', // 3 - S '#F538FF', // 4 - Z '#FF8E0D', // 5 - L '#F5FF38', // 6 - O '#3877FF', // 7 - J ]; let board = createMatrix(COLS, ROWS); let player = { pos: { x: 0, y: 0 }, matrix: null, score: 0, lines: 0, level: 0 }; let nextPiece = { matrix: null }; let dropCounter = 0; let dropInterval = 1000; let lastTime = 0; let requestId; function createMatrix(w, h) { const matrix = []; while (h--) { matrix.push(new Array(w).fill(0)); } return matrix; } function createPiece(type) { if (type === 'T') { return [[0, 1, 0], [1, 1, 1], [0, 0, 0]]; } else if (type === 'I') { return [[0, 2, 0, 0], [0, 2, 0, 0], [0, 2, 0, 0], [0, 2, 0, 0]]; } else if (type === 'S') { return [[0, 3, 3], [3, 3, 0], [0, 0, 0]]; } else if (type === 'Z') { return [[4, 4, 0], [0, 4, 4], [0, 0, 0]]; } else if (type === 'L') { return [[0, 5, 0], [0, 5, 0], [0, 5, 5]]; } else if (type === 'O') { return [[6, 6], [6, 6]]; } else if (type === 'J') { return [[0, 7, 0], [0, 7, 0], [7, 7, 0]]; } } function collide(board, player) { const [m, o] = [player.matrix, player.pos]; for (let y = 0; y < m.length; ++y) { for (let x = 0; x < m[y].length; ++x) { if (m[y][x] !== 0 && (board[y + o.y] && board[y + o.y][x + o.x]) !== 0) { return true; } } } return false; } function drawMatrix(matrix, offset, context, blockSize) { matrix.forEach((row, y) => { row.forEach((value, x) => { if (value !== 0) { context.fillStyle = colors[value]; context.fillRect(x + offset.x, y + offset.y, 1, 1); } }); }); } function draw() { ctx.fillStyle = '#000'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.save(); ctx.scale(BLOCK_SIZE, BLOCK_SIZE); drawMatrix(board, {x: 0, y: 0}, ctx, BLOCK_SIZE); drawMatrix(player.matrix, player.pos, ctx, BLOCK_SIZE); ctx.restore(); } function drawNext() { nextCtx.fillStyle = '#000'; nextCtx.fillRect(0, 0, nextCanvas.width, nextCanvas.height); nextCtx.save(); nextCtx.scale(NEXT_BLOCK_SIZE, NEXT_BLOCK_SIZE); const matrix = nextPiece.matrix; const offsetX = (4 - matrix[0].length) / 2; const offsetY = (4 - matrix.length) / 2; drawMatrix(matrix, {x: offsetX, y: offsetY}, nextCtx, NEXT_BLOCK_SIZE); nextCtx.restore(); } function merge(board, player) { player.matrix.forEach((row, y) => { row.forEach((value, x) => { if (value !== 0) { board[y + player.pos.y][x + player.pos.x] = value; } }); }); } function rotate(matrix, dir) { for (let y = 0; y < matrix.length; ++y) { for (let x = 0; x < y; ++x) { [matrix[x][y], matrix[y][x]] = [matrix[y][x], matrix[x][y]]; } } if (dir > 0) { matrix.forEach(row => row.reverse()); } else { matrix.reverse(); } } function boardSweep() { let linesCleared = 0; outer: for (let y = board.length - 1; y > 0; --y) { for (let x = 0; x < board[y].length; ++x) { if (board[y][x] === 0) { continue outer; } } const row = board.splice(y, 1)[0].fill(0); board.unshift(row); ++y; linesCleared++; } return linesCleared; } function updateUI() { scoreElement.innerText = player.score; linesElement.innerText = player.lines; levelElement.innerText = player.level; } function updateScore(linesCleared) { if (linesCleared === 0) return; const linePoints = [0, 40, 100, 300, 1200]; player.score += linePoints[linesCleared] * (player.level + 1); player.lines += linesCleared; if (player.lines >= (player.level + 1) * 10) { player.level++; dropInterval = 1000 / (player.level / 2 + 1); } updateUI(); } function playerDrop() { player.pos.y++; if (collide(board, player)) { player.pos.y--; merge(board, player); const linesCleared = boardSweep(); updateScore(linesCleared); playerReset(); } dropCounter = 0; } function playerHardDrop() { while (!collide(board, player)) { player.pos.y++; } player.pos.y--; merge(board, player); const linesCleared = boardSweep(); updateScore(linesCleared); playerReset(); dropCounter = 0; } function playerMove(offset) { player.pos.x += offset; if (collide(board, player)) { player.pos.x -= offset; } } function playerReset() { const pieces = 'TJLOSZI'; if (!nextPiece.matrix) { player.matrix = createPiece(pieces[pieces.length * Math.random() | 0]); nextPiece.matrix = createPiece(pieces[pieces.length * Math.random() | 0]); } else { player.matrix = nextPiece.matrix; nextPiece.matrix = createPiece(pieces[pieces.length * Math.random() | 0]); } player.pos.y = 0; player.pos.x = (board[0].length / 2 | 0) - (player.matrix[0].length / 2 | 0); if (collide(board, player)) { gameOver(); } drawNext(); } function playerRotate(dir) { const pos = player.pos.x; let offset = 1; rotate(player.matrix, dir); while (collide(board, player)) { player.pos.x += offset; offset = -(offset + (offset > 0 ? 1 : -1)); if (offset > player.matrix[0].length) { rotate(player.matrix, -dir); player.pos.x = pos; return; } } } function update(time = 0) { const deltaTime = time - lastTime; lastTime = time; dropCounter += deltaTime; if (dropCounter > dropInterval) { playerDrop(); } draw(); if (requestId) { requestId = requestAnimationFrame(update); } } function gameOver() { cancelAnimationFrame(requestId); requestId = undefined; gameOverElement.style.display = 'flex'; } function startGame() { board.forEach(row => row.fill(0)); player.score = 0; player.lines = 0; player.level = 0; dropInterval = 1000; nextPiece.matrix = null; updateUI(); playerReset(); gameOverElement.style.display = 'none'; if (!requestId) { lastTime = 0; requestId = requestAnimationFrame(update); } } document.addEventListener('keydown', event => { if (!requestId) return; // Game is not running if (event.key === 'ArrowLeft') { playerMove(-1); } else if (event.key === 'ArrowRight') { playerMove(1); } else if (event.key === 'ArrowDown') { playerDrop(); } else if (event.key === 'ArrowUp') { playerRotate(1); } else if (event.code === 'Space') { event.preventDefault(); // Prevent page from scrolling playerHardDrop(); } }); restartButton.addEventListener('click', startGame); startGame(); }); </script> </body> </html> ```
Drag to resize
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Tetris in the Browser</title> <style> body { background: #222; font-family: 'Segoe UI', Arial, sans-serif; display: flex; flex-direction: column; align-items: center; margin: 0; color: #eee; user-select: none; } h1 { margin-top: 20px; margin-bottom: 5px; } #score { margin-bottom: 15px; font-size: 1.2em; } #board { border: 3px solid #444; background: #111; display: block; } #controls { margin-top: 10px; font-size: 0.95em; color: #aaa; text-align: center; letter-spacing: 1px; } #gameover { position: absolute; top: 65px; background: rgba(0,0,0,0.85); color: #fff; padding: 20px 30px; font-size: 2em; border-radius: 10px; border: 2px solid #555; left: 50%; transform: translateX(-50%); z-index: 3; text-align: center; display: none; } #restart-btn { margin-top: 15px; background: #2d2; color: #222; border: none; font-size: 1em; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-weight: 600; transition: background .2s; } #restart-btn:hover { background: #6f2; } </style> </head> <body> <h1>Tetris</h1> <div id="score">Score: 0</div> <canvas id="board" width="300" height="600"></canvas> <div id="gameover"> <div>Game Over!</div> <button id="restart-btn">Restart</button> </div> <div id="controls"> ⬅️▶️⬆️⬇️ or WASD: Move & Rotate<br> Space: Hard Drop | P: Pause </div> <script> // Board settings const COLS = 10, ROWS = 20, BLOCK = 30; const canvas = document.getElementById('board'); const ctx = canvas.getContext('2d'); const score_el = document.getElementById('score'); const gameover_el = document.getElementById('gameover'); const restart_btn = document.getElementById('restart-btn'); // Tetrimino shapes & colors const SHAPES = [ // I [[0,1],[1,1],[2,1],[3,1]], // J [[0,0],[0,1],[1,1],[2,1]], // L [[2,0],[0,1],[1,1],[2,1]], // O [[1,0],[2,0],[1,1],[2,1]], // S [[1,0],[2,0],[0,1],[1,1]], // T [[1,0],[0,1],[1,1],[2,1]], // Z [[0,0],[1,0],[1,1],[2,1]] ]; const COLORS = [ "#00e5ee", // I-Cyan "#001eff", // J-Blue "#ff9c00", // L-Orange "#fce93b", // O-Yellow "#41e264", // S-Green "#b617ef", // T-Purple "#e54b37" // Z-Red ]; // Tetrimino logic function randomTetromino() { const n = Math.floor(Math.random() * SHAPES.length); return { x: 3, y: 0, shape: SHAPES[n].map(p=>[...p]), color: COLORS[n], type: n } } function rotate(shape) { // Rotate 90° CW around center of 4x4 grid return shape.map(([x, y])=>[y, 3-x]); } // Board (2D array): 0 = empty, else index of COLORS let board, current, next, score, gameover, paused, dropStart, dropDelay; function reset() { board = Array.from({length: ROWS}, () => Array(COLS).fill(null)); current = randomTetromino(); next = randomTetromino(); score = 0; updateScore(); gameover = false; paused = false; dropStart = Date.now(); dropDelay = 600; gameover_el.style.display = "none"; } function collides(shape, dx=0, dy=0, testX=current.x, testY=current.y) { for (const [x, y] of shape) { const nx = testX + x + dx, ny = testY + y + dy; if (nx < 0 || nx >= COLS) return true; if (ny >= ROWS) return true; if (ny >= 0 && board[ny][nx]) return true; } return false; } function mergeToBoard() { for (const [x, y] of current.shape) { const nx = current.x + x, ny = current.y + y; if (ny >= 0) board[ny][nx] = current.type+1; } } function clearLines() { let lines = 0; outer: for (let y=ROWS-1; y>=0; --y) { for (let x=0; x<COLS; ++x) if (!board[y][x]) continue outer; // Remove line board.splice(y,1); board.unshift(Array(COLS).fill(null)); ++lines; ++y; } if (lines) { score += [0,40,100,300,1200][lines]; updateScore(); dropDelay = Math.max(75, 600-Math.floor(score/250)*32); } } function updateScore() { score_el.textContent = "Score: " + score; } function drawBlock(x,y,color) { ctx.fillStyle = color; ctx.fillRect(x*BLOCK, y*BLOCK, BLOCK, BLOCK); ctx.strokeStyle = "#222"; ctx.lineWidth = 2; ctx.strokeRect(x*BLOCK, y*BLOCK, BLOCK, BLOCK); } function draw() { ctx.clearRect(0,0,canvas.width,canvas.height); // Board for (let y=0; y<ROWS; ++y) for (let x=0; x<COLS; ++x) if (board[y][x]) drawBlock(x,y,COLORS[board[y][x]-1]); // Ghost let gY = current.y; while (!collides(current.shape, 0, 1, current.x, gY)) ++gY; ctx.globalAlpha = 0.2; for (const [x, y] of current.shape) { let nx = current.x + x, ny = gY + y; if (ny >= 0) drawBlock(nx,ny,current.color); } ctx.globalAlpha = 1.0; // Current Piece for (const [x, y] of current.shape) { let nx = current.x + x, ny = current.y + y; if (ny >= 0) drawBlock(nx,ny,current.color); } // Next Piece preview ctx.save(); ctx.translate(BLOCK*COLS+5, 0); ctx.fillStyle = "#aaa"; ctx.font = "18px Arial"; ctx.fillText("Next:", 10, 22); for (const [x, y] of next.shape) drawBlock(1+x,3+y,next.color); ctx.restore(); // Border ctx.strokeStyle = "#fff4"; ctx.lineWidth = 3; ctx.strokeRect(0,0,COLS*BLOCK,ROWS*BLOCK); } function drop() { if (collides(current.shape, 0, 1)) { mergeToBoard(); clearLines(); current = next; next = randomTetromino(); if (collides(current.shape,0,0,current.x,current.y)) { gameover = true; gameover_el.style.display = ""; return; } } else { current.y += 1; } } // Input document.addEventListener('keydown', e => { if (gameover) return; if (e.code === "KeyP") { paused = !paused; return; } if (paused) return; if (e.code === 'ArrowLeft' || e.code === 'KeyA') { if (!collides(current.shape,-1,0)) current.x -= 1; } else if (e.code === 'ArrowRight' || e.code === 'KeyD') { if (!collides(current.shape,1,0)) current.x += 1; } else if (e.code === 'ArrowDown' || e.code === 'KeyS') { if (!collides(current.shape,0,1)) current.y += 1; } else if (e.code === 'Space') { let dropY = current.y; while (!collides(current.shape,0,1, current.x, dropY)) dropY += 1; current.y = dropY; drop(); } else if (e.code === 'ArrowUp' || e.code === 'KeyW') { let newShape = rotate(current.shape); if (!collides(newShape,0,0)) current.shape = newShape; else { // wall kick: try right and left for (const dx of [1,-1,2,-2]) { if (!collides(newShape,dx,0)) { current.x += dx; current.shape = newShape; break; } } } } draw(); }); restart_btn.onclick = function() { reset(); window.requestAnimationFrame(gameloop); } // Main Loop function gameloop() { if (!paused && !gameover) { let now = Date.now(); if (now-dropStart > dropDelay) { drop(); dropStart = now; } draw(); } requestAnimationFrame(gameloop); } // Responsive canvas function resizeCanvas() { canvas.width = BLOCK*COLS+105; // enough for preview canvas.height = BLOCK*ROWS; } window.addEventListener('resize', resizeCanvas); // Init resizeCanvas(); reset(); gameloop(); </script> </body> </html> ```
Drag to resize
Sure! Here's a fully self-contained Tetris game implemented in a single HTML file with embedded JavaScript and CSS. It runs in any modern web browser and supports keyboard controls. --- ### 🎮 Controls: - **A** / **Left Arrow**: Move left - **D** / **Right Arrow**: Move right - **W** / **Up Arrow** / **Space**: Rotate - **S** / **Down Arrow**: Soft drop Scroll down to start playing: ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Mini Tetris</title> <style> body { background: black; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; font-family: sans-serif; color: #fff; } canvas { border: 2px solid white; box-shadow: 0 0 10px white; } </style> </head> <body> <canvas id="canvas" width="300" height="600"></canvas> <script> const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d"); const width = canvas.width; const height = canvas.height; const blockSize = 30; const cols = width / blockSize; const rows = height / blockSize; const colors = { I: '#00FFFF', J: '#0000FF', L: '#FFA500', O: '#FFFF00', S: '#00FF00', T: '#800080', Z: '#FF0000', '': '#222' }; function createGrid(cols, rows) { return Array.from({ length: rows }, () => Array.from({ length: cols }, () => "") ); } const tetrominoes = { I: [[1, 1, 1, 1]], J: [[1, 0, 0],[1, 1, 1]], L: [[0, 0, 1],[1, 1, 1]], O: [[1, 1],[1, 1]], S: [[0, 1, 1],[1, 1, 0]], T: [[0, 1, 0],[1, 1, 1]], Z: [[1, 1, 0],[0, 1, 1]] }; const keys = {}; addEventListener("keydown", e => keys[e.key] = true); addEventListener("keyup", e => keys[e.key] = false); let grid = createGrid(cols, rows); let currentTetromino = spawn(); let gameLoop = 0; let delay = 500; let score = 0; let gameOver = false; function spawn() { const keys = Object.keys(tetrominoes); const type = keys[Math.floor(Math.random() * keys.length)]; const shape = tetrominoes[type]; return { x: Math.floor(cols / 2) - Math.floor(shape[0].length / 2), y: 0, shape, color: type, rotation: 0 }; } function rotate(matrix) { const N = matrix.length; return matrix[0].map((_, i) => matrix.map(row => row[N - 1 - i])); } function applyRotation() { const shape = rotate(currentTetromino.shape); const newX = currentTetromino.x; const newY = currentTetromino.y; if (!collides(shape, newX, newY)) { currentTetromino.shape = shape; } } function move(dx, dy) { const newX = currentTetromino.x + dx; const newY = currentTetromino.y + dy; if (!collides(currentTetromino.shape, newX, newY)) { currentTetromino.x = newX; currentTetromino.y = newY; } else if (dy > 0) { placePiece(); clearLinesAndUpdate(); currentTetromino = spawn(); if (collides(currentTetromino.shape, currentTetromino.x, currentTetromino.y)) { gameOver = true; alert("Game Over! Final score: " + score); grid = createGrid(cols, rows); score = 0; gameOver = false; } } } function collides(shape, x, y) { for (let r = 0; r < shape.length; r++) { for (let c = 0; c < shape[r].length; c++) { if (shape[r][c]) { const newX = x + c; const newY = y + r; if ( newX < 0 || newX >= cols || newY >= rows || (newY >= 0 && grid[newY][newX]) ) { return true; } } } } return false; } function placePiece() { currentTetromino.shape.forEach((row, r) => { row.forEach((val, c) => { if (val && currentTetromino.y + r >= 0) { grid[currentTetromino.y + r][currentTetromino.x + c] = currentTetromino.color; } }); }); } function clearLinesAndUpdate() { for (let r = 0; r < rows; r++) { if (grid[r].every(cell => cell)) { for (let k = r; k > 0; k--) { grid[k] = [...grid[k - 1]]; } grid[0] = Array(cols).fill(""); score += 10; if (score % 50 === 0 && delay > 100) delay -= 50; } } } function drawBlock(x, y, color = "black") { ctx.fillStyle = colors[color] || "white"; ctx.fillRect(x * blockSize, y * blockSize, blockSize - 1, blockSize - 1); } function drawGrid() { for (let y = 0; y < rows; y++) { for (let x = 0; x < cols; x++) { drawBlock(x, y, grid[y][x]); } } } function drawTetromino() { const { x, y, shape, color } = currentTetromino; shape.forEach((row, r) => { row.forEach((val, c) => { if (val) { drawBlock(x + c, y + r, color); } }); }); } function drawScore() { ctx.font = "20px sans-serif"; ctx.fillStyle = "white"; ctx.fillText("Score: " + score, 10, 20); } function update() { ctx.clearRect(0, 0, width, height); drawGrid(); drawScore(); drawTetromino(); } function loop() { update(); if (!gameOver) { clearInterval(gameLoop); gameLoop = setInterval(() => { if (keys['a'] || keys['ArrowLeft']) move(-1, 0); if (keys['d'] || keys['ArrowRight']) move(1, 0); if (keys['s'] || keys['ArrowDown']) move(0, 1); if (keys['w'] || keys['ArrowUp'] || keys[' ']) applyRotation(); move(0, 1); }, delay); } requestAnimationFrame(loop); } loop(); </script> </body> </html> ```
Drag to resize
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Tetris</title> <style> body { background: #000; color: #fff; display: flex; justify-content: center; align-items: flex-start; height: 100vh; margin: 0; font-family: sans-serif; } #game { background: #111; border: 2px solid #555; } #info { margin-left: 20px; } #next { background: #111; border: 2px solid #555; display: block; margin-bottom: 10px; } #score { font-size: 1.2em; } </style> </head> <body> <canvas id="game"></canvas> <div id="info"> <canvas id="next"></canvas> <p>Score: <span id="score">0</span></p> <p>Controls:</p> <ul> <li>← / → : Move</li> <li>↓ : Soft Drop</li> <li>↑ : Rotate</li> </ul> </div> <script> // Settings const cols = 10, rows = 20, block = 30; const canvas = document.getElementById('game'); const ctx = canvas.getContext('2d'); const nextCanvas = document.getElementById('next'); const nextCtx = nextCanvas.getContext('2d'); const scoreEl = document.getElementById('score'); canvas.width = cols * block; canvas.height = rows * block; nextCanvas.width = 4 * block; nextCanvas.height = 4 * block; // Colors (0 = empty) const colors = [ '#000', 'cyan', // I 'yellow', // O 'purple', // T 'green', // S 'red', // Z 'blue', // J 'orange' // L ]; // Tetromino shapes const shapes = [ [[1,1,1,1]], // I [[2,2],[2,2]], // O [[0,3,0],[3,3,3]], // T [[0,4,4],[4,4,0]], // S [[5,5,0],[0,5,5]], // Z [[6,0,0],[6,6,6]], // J [[0,0,7],[7,7,7]] // L ]; // Board let board = Array.from({length: rows}, () => Array(cols).fill(0)); // Game state let current = {}; let next = {}; let score = 0; let dropCounter = 0; let dropInterval = 1000; let lastTime = 0; let gameOverFlag = false; // Utilities function drawCell(ctx, x, y, color) { ctx.fillStyle = color; ctx.fillRect(x * block, y * block, block, block); ctx.strokeStyle = '#111'; ctx.strokeRect(x * block, y * block, block, block); } function drawBoard() { board.forEach((row, y) => { row.forEach((value, x) => { drawCell(ctx, x, y, colors[value]); }); }); } function drawPiece(ctx, piece) { piece.shape.forEach((row, y) => { row.forEach((value, x) => { if (value) { drawCell(ctx, (piece.x + x), (piece.y + y), colors[value] ); } }); }); } function drawNext() { nextCtx.clearRect(0, 0, nextCanvas.width, nextCanvas.height); next.shape.forEach((row, y) => { row.forEach((value, x) => { if (value) { drawCell( nextCtx, x + 0.5, y + 0.5, colors[value] ); } }); }); } function collide(board, piece) { for (let y = 0; y < piece.shape.length; y++) { for (let x = 0; x < piece.shape[y].length; x++) { if (piece.shape[y][x] && (board[y + piece.y] && board[y + piece.y][x + piece.x]) !== 0) { return true; } } } return false; } function merge(board, piece) { piece.shape.forEach((row, y) => { row.forEach((value, x) => { if (value) { board[y + piece.y][x + piece.x] = value; } }); }); } function clearLines() { let lines = 0; outer: for (let y = rows - 1; y >= 0; y--) { for (let x = 0; x < cols; x++) { if (board[y][x] === 0) { continue outer; } } // row full const row = board.splice(y, 1)[0].fill(0); board.unshift(row); y++; lines++; } if (lines > 0) { score += lines * 10; scoreEl.textContent = score; } } function rotate(piece) { const N = piece.shape.length; const newShape = Array.from({length: N}, () => Array(N).fill(0)); for (let y = 0; y < N; y++) { for (let x = 0; x < N; x++) { newShape[x][N - 1 - y] = piece.shape[y][x]; } } piece.shape = newShape; if (collide(board, piece)) { // kick back piece.shape = piece.prevShape; } } function drop() { current.y++; if (collide(board, current)) { current.y--; merge(board, current); clearLines(); spawnPiece(); } dropCounter = 0; } function move(offset) { current.x += offset; if (collide(board, current)) { current.x -= offset; } } function spawnPiece() { current = next; current.x = Math.floor((cols - current.shape[0].length) / 2); current.y = 0; next = createPiece(); drawNext(); if (collide(board, current)) { gameOver(); } } function createPiece() { const idx = Math.floor(Math.random() * shapes.length); const shape = shapes[idx].map(row => row.slice()); return { shape: shape, prevShape: null, x: 0, y: 0 }; } function gameOver() { gameOverFlag = true; alert('Game Over! Your score: ' + score); } function update(time = 0) { if (gameOverFlag) return; const delta = time - lastTime; lastTime = time; dropCounter += delta; if (dropCounter > dropInterval) { drop(); } ctx.clearRect(0, 0, canvas.width, canvas.height); drawBoard(); drawPiece(ctx, current); requestAnimationFrame(update); } // Input document.addEventListener('keydown', e => { if (gameOverFlag) 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') { current.prevShape = current.shape.map(r => r.slice()); rotate(current); } }); // Start next = createPiece(); spawnPiece(); update(); </script> </body> </html> ```
Drag to resize
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Tetris</title> <style> body { margin: 0; padding: 20px; display: flex; justify-content: center; align-items: center; min-height: 100vh; background: #000; font-family: 'Courier New', monospace; color: white; } .game-container { display: flex; gap: 20px; align-items: flex-start; } canvas { border: 2px solid #fff; background: #111; } .info { display: flex; flex-direction: column; gap: 20px; } .score, .next-piece, .controls { background: #222; padding: 15px; border-radius: 5px; border: 1px solid #444; } .score h3, .next-piece h3, .controls h3 { margin: 0 0 10px 0; color: #00ff00; } .controls p { margin: 5px 0; font-size: 12px; } #nextCanvas { border: 1px solid #666; background: #111; } .game-over { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 0, 0, 0.9); color: red; padding: 20px; border-radius: 10px; text-align: center; display: none; } </style> </head> <body> <div class="game-container"> <canvas id="gameCanvas" width="300" height="600"></canvas> <div class="info"> <div class="score"> <h3>Score</h3> <div id="score">0</div> <h3>Lines</h3> <div id="lines">0</div> <h3>Level</h3> <div id="level">1</div> </div> <div class="next-piece"> <h3>Next</h3> <canvas id="nextCanvas" width="120" height="80"></canvas> </div> <div class="controls"> <h3>Controls</h3> <p>← → Move</p> <p>↓ Soft Drop</p> <p>↑ Rotate</p> <p>Space Hard Drop</p> <p>P Pause</p> </div> </div> </div> <div class="game-over" id="gameOver"> <h2>Game Over!</h2> <p>Press R to restart</p> </div> <script> class Tetris { constructor() { this.canvas = document.getElementById('gameCanvas'); this.ctx = this.canvas.getContext('2d'); this.nextCanvas = document.getElementById('nextCanvas'); this.nextCtx = this.nextCanvas.getContext('2d'); this.blockSize = 30; this.rows = 20; this.cols = 10; this.board = Array(this.rows).fill().map(() => Array(this.cols).fill(0)); this.score = 0; this.lines = 0; this.level = 1; this.dropTime = 1000; this.lastTime = 0; this.paused = false; this.gameOver = false; this.pieces = { 'I': { shape: [ [1, 1, 1, 1] ], color: '#00ffff' }, 'O': { shape: [ [1, 1], [1, 1] ], color: '#ffff00' }, 'T': { shape: [ [0, 1, 0], [1, 1, 1] ], color: '#800080' }, 'S': { shape: [ [0, 1, 1], [1, 1, 0] ], color: '#00ff00' }, 'Z': { shape: [ [1, 1, 0], [0, 1, 1] ], color: '#ff0000' }, 'J': { shape: [ [1, 0, 0], [1, 1, 1] ], color: '#0000ff' }, 'L': { shape: [ [0, 0, 1], [1, 1, 1] ], color: '#ffa500' } }; this.pieceTypes = Object.keys(this.pieces); this.currentPiece = this.createPiece(); this.nextPiece = this.createPiece(); this.setupEventListeners(); this.updateDisplay(); this.gameLoop(); } createPiece() { const type = this.pieceTypes[Math.floor(Math.random() * this.pieceTypes.length)]; return { type: type, shape: this.pieces[type].shape, color: this.pieces[type].color, x: Math.floor(this.cols / 2) - 1, y: 0 }; } setupEventListeners() { document.addEventListener('keydown', (e) => { if (this.gameOver) { if (e.key === 'r' || e.key === 'R') { this.restart(); } return; } if (e.key === 'p' || e.key === 'P') { this.paused = !this.paused; return; } if (this.paused) return; switch(e.key) { case 'ArrowLeft': this.movePiece(-1, 0); break; case 'ArrowRight': this.movePiece(1, 0); break; case 'ArrowDown': this.movePiece(0, 1); break; case 'ArrowUp': this.rotatePiece(); break; case ' ': this.hardDrop(); break; } }); } movePiece(dx, dy) { if (this.isValidMove(this.currentPiece, dx, dy)) { this.currentPiece.x += dx; this.currentPiece.y += dy; return true; } return false; } rotatePiece() { const rotated = this.rotateMatrix(this.currentPiece.shape); const originalShape = this.currentPiece.shape; this.currentPiece.shape = rotated; if (!this.isValidMove(this.currentPiece, 0, 0)) { this.currentPiece.shape = originalShape; } } rotateMatrix(matrix) { const rows = matrix.length; const cols = matrix[0].length; const rotated = Array(cols).fill().map(() => Array(rows).fill(0)); for (let i = 0; i < rows; i++) { for (let j = 0; j < cols; j++) { rotated[j][rows - 1 - i] = matrix[i][j]; } } return rotated; } hardDrop() { while (this.movePiece(0, 1)) { this.score += 2; } this.placePiece(); } isValidMove(piece, dx, dy) { for (let y = 0; y < piece.shape.length; y++) { for (let x = 0; x < piece.shape[y].length; x++) { if (piece.shape[y][x]) { const newX = piece.x + x + dx; const newY = piece.y + y + dy; if (newX < 0 || newX >= this.cols || newY >= this.rows || (newY >= 0 && this.board[newY][newX])) { return false; } } } } return true; } placePiece() { for (let y = 0; y < this.currentPiece.shape.length; y++) { for (let x = 0; x < this.currentPiece.shape[y].length; x++) { if (this.currentPiece.shape[y][x]) { const boardY = this.currentPiece.y + y; const boardX = this.currentPiece.x + x; if (boardY < 0) { this.endGame(); return; } this.board[boardY][boardX] = this.currentPiece.color; } } } this.clearLines(); this.currentPiece = this.nextPiece; this.nextPiece = this.createPiece(); if (!this.isValidMove(this.currentPiece, 0, 0)) { this.endGame(); } } clearLines() { let linesCleared = 0; for (let y = this.rows - 1; y >= 0; y--) { if (this.board[y].every(cell => cell !== 0)) { this.board.splice(y, 1); this.board.unshift(Array(this.cols).fill(0)); linesCleared++; y++; // Check same line again } } if (linesCleared > 0) { this.lines += linesCleared; this.score += linesCleared * 100 * this.level; this.level = Math.floor(this.lines / 10) + 1; this.dropTime = Math.max(100, 1000 - (this.level - 1) * 50); } } endGame() { this.gameOver = true; document.getElementById('gameOver').style.display = 'block'; } restart() { this.board = Array(this.rows).fill().map(() => Array(this.cols).fill(0)); this.score = 0; this.lines = 0; this.level = 1; this.dropTime = 1000; this.paused = false; this.gameOver = false; this.currentPiece = this.createPiece(); this.nextPiece = this.createPiece(); document.getElementById('gameOver').style.display = 'none'; this.updateDisplay(); } draw() { // Clear canvas this.ctx.fillStyle = '#111'; this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); // Draw board for (let y = 0; y < this.rows; y++) { for (let x = 0; x < this.cols; x++) { if (this.board[y][x]) { this.ctx.fillStyle = this.board[y][x]; this.ctx.fillRect(x * this.blockSize, y * this.blockSize, this.blockSize, this.blockSize); this.ctx.strokeStyle = '#000'; this.ctx.strokeRect(x * this.blockSize, y * this.blockSize, this.blockSize, this.blockSize); } } } // Draw current piece if (!this.gameOver) { this.ctx.fillStyle = this.currentPiece.color; for (let y = 0; y < this.currentPiece.shape.length; y++) { for (let x = 0; x < this.currentPiece.shape[y].length; x++) { if (this.currentPiece.shape[y][x]) { const drawX = (this.currentPiece.x + x) * this.blockSize; const drawY = (this.currentPiece.y + y) * this.blockSize; this.ctx.fillRect(drawX, drawY, this.blockSize, this.blockSize); this.ctx.strokeStyle = '#000'; this.ctx.strokeRect(drawX, drawY, this.blockSize, this.blockSize); } } } } // Draw next piece this.nextCtx.fillStyle = '#111'; this.nextCtx.fillRect(0, 0, this.nextCanvas.width, this.nextCanvas.height); this.nextCtx.fillStyle = this.nextPiece.color; const offsetX = 10; const offsetY = 10; for (let y = 0; y < this.nextPiece.shape.length; y++) { for (let x = 0; x < this.nextPiece.shape[y].length; x++) { if (this.nextPiece.shape[y][x]) { this.nextCtx.fillRect(offsetX + x * 20, offsetY + y * 20, 20, 20); this.nextCtx.strokeStyle = '#000'; this.nextCtx.strokeRect(offsetX + x * 20, offsetY + y * 20, 20, 20); } } } } updateDisplay() { document.getElementById('score').textContent = this.score; document.getElementById('lines').textContent = this.lines; document.getElementById('level').textContent = this.level; } gameLoop(timestamp = 0) { if (!this.gameOver && !this.paused && timestamp - this.lastTime > this.dropTime) { if (!this.movePiece(0, 1)) { this.placePiece(); } this.lastTime = timestamp; } this.draw(); this.updateDisplay(); requestAnimationFrame((ts) => this.gameLoop(ts)); } } // Start the game new Tetris(); </script> </body> </html> ``` This is a fully functional Tetris game that runs in a web browser! Here are the key features: **Game Features:** - Classic Tetris gameplay with 7 different piece types (I, O, T, S, Z, J, L) - Line clearing mechanics - Progressive difficulty (speed increases with level) - Score system - Next piece preview **Controls:** - Arrow keys: Move left/right, soft drop, rotate - Spacebar: Hard drop - P: Pause/unpause - R: Restart when game over **Visual Elements:** - Game board with falling pieces - Score, lines cleared, and level display - Next piece preview - Game over screen - Responsive design with clear controls display The game includes proper collision detection, line clearing, scoring system, and smooth gameplay mechanics. The difficulty progressively increases as you clear more lines, making the pieces fall faster at higher levels.
Drag to resize
# Tetris Game Here's a classic Tetris game that you can play in your browser. Use the arrow keys to move and rotate pieces. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Tetris</title> <style> body { font-family: Arial, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background-color: #f0f0f0; } .game-container { display: flex; flex-direction: column; align-items: center; } canvas { border: 1px solid #333; background: #111; } .info { margin-top: 20px; display: flex; justify-content: space-between; width: 300px; } .controls { margin-top: 20px; text-align: center; font-size: 14px; color: #555; } </style> </head> <body> <div class="game-container"> <canvas id="tetris" width="300" height="600"></canvas> <div class="info"> <div>Score: <span id="score">0</span></div> <div>Level: <span id="level">1</span></div> <div>Lines: <span id="lines">0</span></div> </div> <div class="controls"> <p>Controls: Arrow keys to move, Up to rotate</p> <button id="start-button">Start / Restart</button> <button id="pause-button">Pause</button> </div> </div> <script> document.addEventListener('DOMContentLoaded', () => { const canvas = document.getElementById('tetris'); const context = canvas.getContext('2d'); const scoreElement = document.getElementById('score'); const levelElement = document.getElementById('level'); const linesElement = document.getElementById('lines'); const startButton = document.getElementById('start-button'); const pauseButton = document.getElementById('pause-button'); // Scale to make blocks bigger const scale = 30; // Game variables let score = 0; let level = 1; let lines = 0; let dropCounter = 0; let dropInterval = 1000; let lastTime = 0; let paused = false; let gameOver = true; // The playing field const arena = createMatrix(10, 20); // The player's tetromino piece const player = { pos: {x: 0, y: 0}, matrix: null, score: 0, }; // Tetromino shapes const tetrominos = { 'I': [ [0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0], [0, 0, 0, 0], ], 'J': [ [2, 0, 0], [2, 2, 2], [0, 0, 0], ], 'L': [ [0, 0, 3], [3, 3, 3], [0, 0, 0], ], 'O': [ [4, 4], [4, 4], ], 'S': [ [0, 5, 5], [5, 5, 0], [0, 0, 0], ], 'T': [ [0, 6, 0], [6, 6, 6], [0, 0, 0], ], 'Z': [ [7, 7, 0], [0, 7, 7], [0, 0, 0], ], }; // Colors for each tetromino const colors = [ null, '#FF0D72', // I '#0DC2FF', // J '#0DFF72', // L '#F538FF', // O '#FF8E0D', // S '#FFE138', // T '#3877FF', // Z ]; // Create a matrix filled with zeros function createMatrix(w, h) { const matrix = []; while (h--) { matrix.push(new Array(w).fill(0)); } return matrix; } // Check if player collides with arena function collide(arena, player) { const [m, o] = [player.matrix, player.pos]; for (let y = 0; y < m.length; ++y) { for (let x = 0; x < m[y].length; ++x) { if (m[y][x] !== 0 && (arena[y + o.y] && arena[y + o.y][x + o.x]) !== 0) { return true; } } } return false; } // Draw the arena and player function draw() { context.fillStyle = '#000'; context.fillRect(0, 0, canvas.width, canvas.height); drawMatrix(arena, {x: 0, y: 0}); drawMatrix(player.matrix, player.pos); if (gameOver) { context.fillStyle = 'rgba(0, 0, 0, 0.7)'; context.fillRect(0, 0, canvas.width, canvas.height); context.font = '30px Arial'; context.fillStyle = 'white'; context.textAlign = 'center'; context.fillText('GAME OVER', canvas.width / 2, canvas.height / 2); context.font = '20px Arial'; context.fillText('Press Start to play again', canvas.width / 2, canvas.height / 2 + 40); } else if (paused) { context.fillStyle = 'rgba(0, 0, 0, 0.7)'; context.fillRect(0, 0, canvas.width, canvas.height); context.font = '30px Arial'; context.fillStyle = 'white'; context.textAlign = 'center'; context.fillText('PAUSED', canvas.width / 2, canvas.height / 2); } } // Draw a matrix (arena or tetromino) function drawMatrix(matrix, offset) { matrix.forEach((row, y) => { row.forEach((value, x) => { if (value !== 0) { context.fillStyle = colors[value]; context.fillRect(x + offset.x, y + offset.y, 1, 1); context.strokeStyle = 'black'; context.lineWidth = 0.05; context.strokeRect(x + offset.x, y + offset.y, 1, 1); } }); }); } // Merge the player's tetromino into the arena function merge(arena, player) { player.matrix.forEach((row, y) => { row.forEach((value, x) => { if (value !== 0) { arena[y + player.pos.y][x + player.pos.x] = value; } }); }); } // Move the player's tetromino function playerMove(dir) { player.pos.x += dir; if (collide(arena, player)) { player.pos.x -= dir; } } // Reset player and create new tetromino function playerReset() { const pieces = 'IJLOSTZ'; player.matrix = tetrominos[pieces[pieces.length * Math.random() | 0]]; player.pos.y = 0; player.pos.x = (arena[0].length / 2 | 0) - (player.matrix[0].length / 2 | 0); // Check if game over if (collide(arena, player)) { gameOver = true; draw(); } } // Rotate the player's tetromino function playerRotate(dir) { const pos = player.pos.x; let offset = 1; rotate(player.matrix, dir); while (collide(arena, player)) { player.pos.x += offset; offset = -(offset + (offset > 0 ? 1 : -1)); if (offset > player.matrix[0].length) { rotate(player.matrix, -dir); player.pos.x = pos; return; } } } // Rotate a matrix (tetromino) function rotate(matrix, dir) { for (let y = 0; y < matrix.length; ++y) { for (let x = 0; x < y; ++x) { [ matrix[x][y], matrix[y][x], ] = [ matrix[y][x], matrix[x][y], ]; } } if (dir > 0) { matrix.forEach(row => row.reverse()); } else { matrix.reverse(); } } // Move the player's tetromino down function playerDrop() { player.pos.y++; if (collide(arena, player)) { player.pos.y--; merge(arena, player); playerReset(); arenaSweep(); updateScore(); } dropCounter = 0; } // Clear full rows and award points function arenaSweep() { let rowCount = 0; outer: for (let y = arena.length - 1; y >= 0; --y) { for (let x = 0; x < arena[y].length; ++x) { if (arena[y][x] === 0) { continue outer; } } const row = arena.splice(y, 1)[0].fill(0); arena.unshift(row); y++; rowCount++; } if (rowCount > 0) { player.score += [0, 40, 100, 300, 1200][rowCount] * level; lines += rowCount; level = Math.floor(lines / 10) + 1; dropInterval = 1000 * Math.pow(0.8, level - 1); } } // Update the score display function updateScore() { scoreElement.textContent = player.score; levelElement.textContent = level; linesElement.textContent = lines; } // Main game loop function update(time = 0) { if (gameOver || paused) { requestAnimationFrame(update); return; } const deltaTime = time - lastTime; lastTime = time; dropCounter += deltaTime; if (dropCounter > dropInterval) { playerDrop(); } context.scale(scale, scale); draw(); context.setTransform(1, 0, 0, 1, 0, 0); requestAnimationFrame(update); } // Start/restart the game function startGame() { arena.forEach(row => row.fill(0)); player.score = 0; score = 0; level = 1; lines = 0; dropInterval = 1000; updateScore(); gameOver = false; paused = false; playerReset(); update(); } // Handle button clicks startButton.addEventListener('click', () => { startGame(); }); pauseButton.addEventListener('click', () => { if (!gameOver) { paused = !paused; if (!paused) { update(); } else { draw(); } } }); // Handle keyboard input document.addEventListener('keydown', event => { if (gameOver || paused) { return; } if (event.keyCode === 37) { // Left playerMove(-1); } else if (event.keyCode === 39) { // Right playerMove(1); } else if (event.keyCode === 40) { // Down playerDrop(); } else if (event.keyCode === 38) { // Up playerRotate(1); } }); // Initial setup context.scale(scale, scale); draw(); context.setTransform(1, 0, 0, 1, 0, 0); }); </script> </body> </html> ``` Click the "Start" button to begin playing. Use the arrow keys to control the falling blocks: - Left/Right arrows: Move the piece horizontally - Up arrow: Rotate the piece - Down arrow: Drop the piece faster The game increases in difficulty as you clear more lines. Try to get the highest score!
Drag to resize