Tetris · ling-2.6-flash
Build a playable Tetris on a canvas with all seven tetrominoes, rotation, line clears, increasing speed, a next-piece preview, and score.
This one-shot run produced no runnable artifact.
agent produced no index.html
The transcript below shows what the model did — read it to see why the one shot fell short.
transcript · 2 messages
Build a playable Tetris on a canvas with all seven tetrominoes, rotation, line clears, increasing speed, a next-piece preview, and score.
I'll create a complete, playable Tetris game in a single HTML file with all the requested features. ```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> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: #1a1a2e; color: #eee; font-family: 'Courier New', monospace; display: flex; justify-content: center; align-items: center; min-height: 100vh; user-select: none; } #game-container { display: flex; gap: 20px; align-items: flex-start; } #game-board { border: 2px solid #444; background: #0f0f1a; display: block; } #ui-panel { display: flex; flex-direction: column; gap: 15px; } .panel-box { background: #16213e; border: 1px solid #444; border-radius: 8px; padding: 15px; text-align: center; } .panel-box h2 { font-size: 14px; color: #aaa; text-transform: uppercase; letter-spacing: 2px; margin-bottom: 8px; } .score-val, .level-val, .lines-val { font-size: 28px; color: #00d4ff; } .next-preview { width: 120px; height: 120px; background: #0f0f1a; border: 1px solid #444; border-radius: 4px; margin: 0 auto; } </style> </head> <body> <div id="game-container"> <canvas id="game-board" width="320" height="640"></canvas> <div id="ui-panel"> <div class="panel-box"> <h2>Score</h2> <div class="score-val" id="score">0</div> </div> <div class="panel-box"> <h2>Level</h2> <div class="level-val" id="level">1</div> </div> <div class="panel-box"> <h2>Lines</h2> <div class="lines-val" id="lines">0</div> </div> <div class="panel-box"> <h2>Next</h2> <canvas class="next-preview" id="next-preview" width="120" height="120"></canvas> </div> <div class="panel-box" style="flex-grow:1;"> <h2>Controls</h2> <div style="font-size:12px;color:#888;line-height:1.6;"> ← → : Move<br> ↑ : Rotate<br> ↓ : Soft Drop<br> Space : Hard Drop<br> P : Pause </div> </div> </div> </div> <script> const canvas = document.getElementById('game-board'); const ctx = canvas.getContext('2d'); const scoreEl = document.getElementById('score'); const levelEl = document.getElementById('level'); const linesEl = document.getElementById('lines'); const nextCanvas = document.getElementById('next-preview'); const nextCtx = nextCanvas.getContext('2d'); const COLS = 10; const ROWS = 20; const BLOCK = 32; canvas.width = COLS * BLOCK; canvas.height = ROWS * BLOCK; // Cell size and offset for centering const MARGIN_TOP = 4; const MARGIN_LEFT = 4; // Colors for each tetromino const COLORS = { I: '#00d4ff', O: '#ffd700', T: '#b44dff', S: '#00e676', Z: '#ff3d00', J: '#448aff', L: '#ff6e40' }; const SHAPES = { I: [[1,1,1,1]], O: [[1,1],[1,1]], T: [[0,1,0],[1,1,1]], S: [[0,1,1],[1,1,0]], Z: [[1,1,0],[0,1,1]], J: [[1,0,0],[1,1,1]], L: [[0,0,1],[1,1,1]] }; const TETROMINOES = Object.keys(SHAPES); // Build complete 20x10 grid const grid = Array.from({ length: ROWS }, () => Array(COLS).fill(0)); let score = 0; let level = 1; let lines = 0; let paused = false; let gameOver = false; let dropCooldown = 0; let dropInterval = 800; let dropTimer = 0; let currentPiece = null; let nextPiece = null; function getRandomPiece() { const name = TETROMINOES[Math.floor(Math.random() * TETROMINOES.length)]; return { name, shape: SHAPES[name].map(row => [...row]), row: 0, col: Math.floor(COLS / 2) - Math.floor(SHAPES[name][0].length / 2) }; } function createGhostPiece(piece) { const ghost = { ...piece, row: piece.row }; while (!collides(ghost)) ghost.row++; ghost.row--; return ghost; } function collides(piece) { const shape = piece.shape; for (let r = 0; r < shape.length; r++) { for (let c = 0; c < shape[r].length; c++) { if (shape[r][c]) { const row = piece.row + r; const col = piece.col + c; if (row >= ROWS || col < 0 || col >= COLS) return true; if (row >= 0 && grid[row][col]) return true; } } } return false; } function rotatePiece(piece) { const shape = piece.shape; const rows = shape.length; const cols = shape[0].length; const rotated = Array.from({ length: cols }, () => Array(rows).fill(0)); for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { rotated[c][rows - 1 - r] = shape[r][c]; } } const newPiece = { ...piece, shape: rotated }; if (!collides(newPiece)) { piece.shape = rotated; } else { // Wall kick const kicks = [-1, 1, -2, 2]; for (const kick of kicks) { newPiece.col = piece.col + kick; if (!collides(newPiece)) { piece.shape = rotated; piece.col += kick; return; } } } } function placePiece() { const shape = currentPiece.shape; for (let r = 0; r < shape.length; r++) { for (let c = 0; c < shape[r].length; c++) { if (shape[r][c]) { const row = currentPiece.row + r; if (row >= 0) { grid[row][currentPiece.col + c] = currentPiece.name; } } } } clearLines(); currentPiece = nextPiece; nextPiece = getRandomPiece(); updateNextPreview(); if (collides(currentPiece)) { gameOver = true; } } function clearLines() { let linesCleared = 0; for (let r = ROWS - 1; r >= 0; r--) { if (grid[r].every(cell => cell !== 0)) { grid.splice(r, 1); grid.unshift(Array(COLS).fill(0)); linesCleared++; r++; } } if (linesCleared > 0) { const pointsTable = [0, 100, 300, 500, 800]; score += pointsTable[linesCleared] * level; lines += linesCleared; level = Math.floor(lines / 10) + 1; dropInterval = Math.max(100, 800 - (level - 1) * 70); scoreEl.textContent = score; levelEl.textContent = level; linesEl.textContent = lines; } } function drawCell(row, col, color) { if (!color) return; ctx.fillStyle = color; ctx.fillRect( MARGIN_LEFT + col * BLOCK, MARGIN_TOP + row * BLOCK, BLOCK - 1, BLOCK - 1 ); } function drawPiece(piece) { const shape = piece.shape; const color = COLORS[piece.name]; for (let r = 0; r < shape.length; r++) { for (let c = 0; c < shape[r].length; c++) { if (shape[r][c]) { drawCell(piece.row + r, piece.col + c, color); } } } } function drawGhost(piece) { if (!piece || !currentPiece) return; if (paused || gameOver) return; const ghost = createGhostPiece(piece); const shape = ghost.shape; const color = COLORS[ghost.name]; ctx.globalAlpha = 0.3; for (let r = 0; r < shape.length; r++) { for (let c = 0; c < shape[r].length; c++) { if (shape[r][c]) { drawCell(ghost.row + r, ghost.col + c, color); } } } ctx.globalAlpha = 1.0; } function drawGrid() { ctx.clearRect(0, 0, canvas.width, canvas.height); // Draw grid lines (subtle) ctx.strokeStyle = '#1a1a2e'; ctx.lineWidth = 0.5; for (let r = 0; r <= ROWS; r++) { ctx.beginPath(); ctx.moveTo(MARGIN_LEFT, MARGIN_TOP + r * BLOCK); ctx.lineTo(MARGIN_LEFT + COLS * BLOCK, MARGIN_TOP + r * BLOCK); ctx.stroke(); } for (let c = 0; c <= COLS; c++) { ctx.beginPath(); ctx.moveTo(MARGIN_LEFT + c * BLOCK, MARGIN_TOP); ctx.lineTo(MARGIN_LEFT + c * BLOCK, MARGIN_TOP + ROWS * BLOCK); ctx.stroke(); } // Draw placed blocks for (let r = 0; r < ROWS; r++) { for (let c = 0; c < COLS; c++) { drawCell(r, c, grid[r][c] ? COLORS[grid[r][c]] : null); } } // Draw ghost drawGhost(currentPiece); // Draw current piece if (currentPiece) drawPiece(currentPiece); } function drawNextPreview() { nextCtx.fillStyle = '#0f0f1a'; nextCtx.fillRect(0, 0, nextCanvas.width, nextCanvas.height); if (!nextPiece) return; const shape = nextPiece.shape; const colors = COLORS[nextPiece.name]; const cellSize = 30; const offsetX = (120 - shape[0].length * cellSize) / 2; const offsetY = (120 - shape.length * cellSize) / 2; for (let r = 0; r < shape.length; r++) { for (let c = 0; c < shape[r].length; c++) { if (shape[r][c]) { nextCtx.fillStyle = colors; nextCtx.fillRect( offsetX + c * cellSize + 2, offsetY + r * cellSize + 2, cellSize - 3, cellSize - 3 ); } } } } function update() { if (gameOver || paused) return; dropTimer += 16; if (dropTimer >= dropInterval) { dropTimer = 0; if (currentPiece) { currentPiece.row++; if (collides(currentPiece)) { currentPiece.row--; placePiece(); } } drawGrid(); } } function gameLoop() { update(); drawGrid(); requestAnimationFrame(gameLoop); } // Input handling document.addEventListener('keydown', (e) => { if (!currentPiece && !gameOver) { currentPiece = getRandomPiece(); nextPiece = getRandomPiece(); updateNextPreview(); } if (gameOver) return; if (e.key === 'p' || e.key === 'P') { paused = !paused; return; } if (!currentPiece) return; switch (e.key) { case 'ArrowLeft': currentPiece.col--; if (collides(currentPiece)) currentPiece.col++; break; case 'ArrowRight': currentPiece.col++; if (collides(currentPiece)) currentPiece.col--; break; case 'ArrowDown': currentPiece.row++; if (collides(currentPiece)) { currentPiece.row--; placePiece(); } break; case 'ArrowUp': rotatePiece(currentPiece); break; case ' ': // Hard drop while (!collides(currentPiece)) { currentPiece.row++; } currentPiece.row--; placePiece(); break; } }); // Touch controls for mobile let touchStartX = 0; let touchStartY = 0; canvas.addEventListener('touchstart', (e) => { e.preventDefault(); touchStartX = e.touches[0].clientX; touchStartY = e.touches[0].clientY; }); canvas.addEventListener('touchend', (e) => { e.preventDefault(); if (!currentPiece && !gameOver) { currentPiece = getRandomPiece(); nextPiece = getRandomPiece(); updateNextPreview(); return; } if (gameOver) return; const dx = e.changedTouches[0].clientX - touchStartX; const dy = e.changedTouches[0].clientY - touchStartY; const swipeThreshold = 20; const tapThreshold = 10; if (Math.abs(dx) < tapThreshold && Math.abs(dy) < tapThreshold) { // Rotate on tap right side, drop on tap left side const rect = canvas.getBoundingClientRect(); const tapX = e.changedTouches[0].clientX - rect.left; if (tapX > canvas.width / 2) { rotatePiece(currentPiece); } else { currentPiece.row++; if (collides(currentPiece)) { currentPiece.row--; placePiece(); } } } else if (Math.abs(dx) > Math.abs(dy)) { if (Math.abs(dx) > swipeThreshold) { if (dx < 0) { currentPiece.col--; if (collides(currentPiece)) currentPiece.col++; } else { currentPiece.col++; if (collides(currentPiece)) currentPiece.col--; } } } else { if (Math.abs(dy) > swipeThreshold && dy > 0) { currentPiece.row++; if (collides(currentPiece)) { currentPiece.row--; placePiece(); } } } }); // Start game currentPiece = getRandomPiece(); nextPiece = getRandomPiece(); updateNextPreview(); gameLoop(); </script> </body> </html> ``` This is a fully self-contained Tetris game with all the requested features: **Features:** - **7 Tetrominoes (I, O, T, S, Z, J, L)** with distinct colors - **Rotation** with wall-kick support (pieces nudge when rotation would hit walls) - **Line clears** with proper row removal and gravity - **Increasing speed** — level increases every 10 lines, reducing drop interval - **Next-piece preview** (120×120 preview window in the UI panel) - **Score tracking** with level-based multipliers (100/300/500/800 per 1/2/3/4 lines) - **Ghost piece** showing where the piece will land - **Controls**: ← → move, ↑ rotate, ↓ soft drop, Space hard drop, P pause - **Touch support** for mobile (tap left half to drop, tap right half to rotate, swipe to move) - **Game over detection** with a frozen screen (just reload to play again) - **Subtle grid lines** and centered piece rendering Save as `index.html` and open in a browser to play.
TAKE IT TO CODE
This run produced no artifact — reuse the prompt with a different model or your own pi-agent run.