pull down to refresh
<!DOCTYPE html> <html> <head> <title>Tetris - Game Boy Style</title> <style> body { background-color: #9bbc0f; /* Game Boy screen greenish tint */ display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; font-family: 'Courier New', Courier, monospace; } #gameContainer { background-color: #8bac0f; /* Slightly darker green */ padding: 20px; border: 4px solid #0f380f; /* Dark border like Game Boy */ border-radius: 10px; display: flex; align-items: flex-start; } #gameCanvas { background-color: #8bac0f; image-rendering: pixelated; border: 2px solid #306230; /* Visible border around playfield */ } #previewCanvas { background-color: #8bac0f; image-rendering: pixelated; border: 2px solid #306230; width: 64px; /* 4 blocks wide */ height: 64px; /* 4 blocks tall */ margin-left: 20px; } #scoreboard { color: #0f380f; font-size: 16px; margin-left: 20px; text-align: left; } </style> </head> <body> <div id="gameContainer"> <canvas id="gameCanvas" width="160" height="320"></canvas> <div> <canvas id="previewCanvas" width="64" height="64"></canvas> <div id="scoreboard"> Score: 0<br> Lines: 0<br> Level: 1 </div> </div> </div> <script> const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); const previewCanvas = document.getElementById('previewCanvas'); const previewCtx = previewCanvas.getContext('2d'); const scoreboard = document.getElementById('scoreboard'); // Game constants const BLOCK_SIZE = 16; const COLS = 10; const ROWS = 20; const canvasWidth = COLS * BLOCK_SIZE; const canvasHeight = ROWS * BLOCK_SIZE; canvas.width = canvasWidth; canvas.height = canvasHeight; const TETROMINOES = [ [[1,1,1,1]], // I [[1,1],[1,1]], // O [[0,1,0],[1,1,1]], // T [[0,1,1],[1,1,0]], // S [[1,1,0],[0,1,1]], // Z [[1,0,0],[1,1,1]], // J [[0,0,1],[1,1,1]] // L ]; const COLORS = ['#0f380f']; // Dark green for blocks let board = Array(ROWS).fill().map(() => Array(COLS).fill(0)); let score = 0; let linesCleared = 0; let level = 1; let currentPiece, currentX, currentY; let nextPiece; let gameOver = false; // Piece spawning function spawnPiece() { if (!nextPiece) { nextPiece = TETROMINOES[Math.floor(Math.random() * TETROMINOES.length)]; } currentPiece = nextPiece; nextPiece = TETROMINOES[Math.floor(Math.random() * TETROMINOES.length)]; currentX = Math.floor(COLS / 2) - Math.floor(currentPiece[0].length / 2); currentY = 0; if (!canMove(0, 0)) { gameOver = true; alert(`Game Over! Score: ${score}, Lines: ${linesCleared}, Level: ${level}`); document.location.reload(); } drawPreview(); } // Drawing function drawBlock(x, y, context = ctx) { context.fillStyle = COLORS[0]; context.fillRect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE - 1, BLOCK_SIZE - 1); context.strokeStyle = '#306230'; // Lighter green for outline context.strokeRect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE - 1, BLOCK_SIZE - 1); } function drawBoard() { ctx.clearRect(0, 0, canvasWidth, canvasHeight); for (let r = 0; r < ROWS; r++) { for (let c = 0; c < COLS; c++) { if (board[r][c]) drawBlock(c, r); } } for (let r = 0; r < currentPiece.length; r++) { for (let c = 0; c < currentPiece[r].length; c++) { if (currentPiece[r][c]) drawBlock(currentX + c, currentY + r); } } } function drawPreview() { previewCtx.clearRect(0, 0, previewCanvas.width, previewCanvas.height); const offsetX = (4 - nextPiece[0].length) / 2; // Center horizontally const offsetY = (4 - nextPiece.length) / 2; // Center vertically for (let r = 0; r < nextPiece.length; r++) { for (let c = 0; c < nextPiece[r].length; c++) { if (nextPiece[r][c]) drawBlock(c + offsetX, r + offsetY, previewCtx); } } } // Movement and collision function canMove(dx, dy, newPiece = currentPiece) { for (let r = 0; r < newPiece.length; r++) { for (let c = 0; c < newPiece[r].length; c++) { if (!newPiece[r][c]) continue; const newX = currentX + c + dx; const newY = currentY + r + dy; if (newX < 0 || newX >= COLS || newY >= ROWS) return false; if (newY >= 0 && board[newY][newX]) return false; } } return true; } function mergePiece() { for (let r = 0; r < currentPiece.length; r++) { for (let c = 0; c < currentPiece[r].length; c++) { if (currentPiece[r][c]) { board[currentY + r][currentX + c] = 1; } } } } function clearLines() { let linesThisTime = 0; for (let r = ROWS - 1; r >= 0; r--) { if (board[r].every(cell => cell)) { board.splice(r, 1); board.unshift(Array(COLS).fill(0)); linesThisTime++; r++; // Check the same row again after shifting } } if (linesThisTime > 0) { linesCleared += linesThisTime; score += [0, 40, 100, 300, 1200][linesThisTime] * level; // Level multiplier level = Math.floor(linesCleared / 10) + 1; // Level up every 10 lines updateScoreboard(); } } function updateScoreboard() { scoreboard.innerHTML = `Score: ${score}<br>Lines: ${linesCleared}<br>Level: ${level}`; } // Rotation function rotatePiece() { const rotated = currentPiece[0].map((_, i) => currentPiece.map(row => row[i]).reverse() ); if (canMove(0, 0, rotated)) currentPiece = rotated; } // Game loop let dropCounter = 0; function getDropInterval() { const baseSpeed = 800; // Starting speed in ms (Level 1) return baseSpeed / (1 + (level - 1) * 0.2); // 20% faster per level } function update() { if (gameOver) return; dropCounter += 16.67; // ~60 FPS const dropInterval = getDropInterval(); if (dropCounter >= dropInterval) { if (canMove(0, 1)) { currentY++; } else { mergePiece(); clearLines(); spawnPiece(); } dropCounter = 0; } drawBoard(); requestAnimationFrame(update); } // Controls document.addEventListener('keydown', e => { if (gameOver) return; switch (e.key) { case 'ArrowLeft': if (canMove(-1, 0)) currentX--; break; case 'ArrowRight': if (canMove(1, 0)) currentX++; break; case 'ArrowDown': if (canMove(0, 1)) currentY++; break; case 'ArrowUp': rotatePiece(); break; } drawBoard(); }); // Start game spawnPiece(); // Initial spawn sets both current and next updateScoreboard(); update(); </script> </body> </html>