document.addEventListener('DOMContentLoaded', () => { const canvas = document.getElementById('tetris'); const ctx = canvas.getContext('2d'); // Scale canvas const scale = window.devicePixelRatio; canvas.width = Math.floor(canvas.clientWidth * scale); canvas.height = Math.floor(canvas.clientHeight * scale); ctx.scale(scale, scale); // Game state const COLS = 10; const ROWS = 20; const BLOCK_SIZE = canvas.width / 10 / scale; const COLORS = [ null, '#3B82F6', // I '#8B5CF6', // J '#F59E0B', // L '#10B981', // O '#EF4444', // S '#EC4899', // T '#84CC16' // Z ]; let board = createBoard(); let piece = null; let score = 0; let gameOver = false; let dropCounter = 0; let lastTime = 0; let dropInterval = 1000; // Pieces const PIECES = [ null, { shape: [[0,0,0,0], [1,1,1,1], [0,0,0,0], [0,0,0,0]], color: 'I' }, { shape: [[2,0,0], [2,2,2], [0,0,0]], color: 'J' }, { shape: [[0,0,3], [3,3,3], [0,0,0]], color: 'L' }, { shape: [[0,4,4], [0,4,4], [0,0,0]], color: 'O' }, { shape: [[0,5,5], [5,5,0], [0,0,0]], color: 'S' }, { shape: [[0,6,0], [6,6,6], [0,0,0]], color: 'T' }, { shape: [[7,7,0], [0,7,7], [0,0,0]], color: 'Z' } ]; // Game functions function createBoard() { return Array.from({length: ROWS}, () => Array(COLS).fill(0)); } function createPiece() { const randomIndex = Math.floor(Math.random() * 7) + 1; return { position: {x: Math.floor(COLS / 2) - 1, y: 0}, shape: PIECES[randomIndex].shape, color: PIECES[randomIndex].color }; } function draw() { // Clear canvas ctx.fillStyle = '#111827'; ctx.fillRect(0, 0, canvas.width, canvas.height); // Draw board board.forEach((row, y) => { row.forEach((value, x) => { if (value !== 0) { ctx.fillStyle = COLORS[value]; ctx.fillRect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE); ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)'; ctx.strokeRect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE); } }); }); // Draw current piece if (piece) { piece.shape.forEach((row, y) => { row.forEach((value, x) => { if (value !== 0) { ctx.fillStyle = COLORS[value]; ctx.fillRect( (piece.position.x + x) * BLOCK_SIZE, (piece.position.y + y) * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE ); ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)'; ctx.strokeRect( (piece.position.x + x) * BLOCK_SIZE, (piece.position.y + y) * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE ); } }); }); } } function collide() { for (let y = 0; y < piece.shape.length; y++) { for (let x = 0; x < piece.shape[y].length; x++) { if (piece.shape[y][x] !== 0 && (board[y + piece.position.y] === undefined || board[y + piece.position.y][x + piece.position.x] === undefined || board[y + piece.position.y][x + piece.position.x] !== 0)) { return true; } } } return false; } function rotate() { const rotated = []; for (let y = 0; y < piece.shape[0].length; y++) { const row = []; for (let x = piece.shape.length - 1; x >= 0; x--) { row.push(piece.shape[x][y]); } rotated.push(row); } const previousShape = piece.shape; piece.shape = rotated; if (collide()) { piece.shape = previousShape; } } function movePiece(direction) { piece.position.x += direction; if (collide()) { piece.position.x -= direction; } } function hardDrop() { while (!collide()) { piece.position.y++; } piece.position.y--; merge(); removeRows(); piece = createPiece(); if (collide()) { gameOver = true; document.querySelector('custom-game-over').setAttribute('visible', 'true'); } } function merge() { piece.shape.forEach((row, y) => { row.forEach((value, x) => { if (value !== 0) { board[y + piece.position.y][x + piece.position.x] = value; } }); }); } function removeRows() { 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++; } if (linesCleared > 0) { score += calculateScore(linesCleared); updateScoreDisplay(); } } function calculateScore(lines) { switch(lines) { case 1: return 100; case 2: return 300; case 3: return 500; case 4: return 800; default: return 0; } } function updateScoreDisplay() { document.querySelector('custom-score-display').setAttribute('score', score.toString()); } function resetGame() { board = createBoard(); piece = createPiece(); score = 0; gameOver = false; dropInterval = 1000; updateScoreDisplay(); document.querySelector('custom-game-over').setAttribute('visible', 'false'); } // Game loop function update(time = 0) { if (gameOver) return; const deltaTime = time - lastTime; lastTime = time; dropCounter += deltaTime; if (dropCounter > dropInterval) { piece.position.y++; if (collide()) { piece.position.y--; merge(); removeRows(); piece = createPiece(); if (collide()) { gameOver = true; document.querySelector('custom-game-over').setAttribute('visible', 'true'); } } dropCounter = 0; } draw(); requestAnimationFrame(update); } // Controls document.addEventListener('keydown', event => { if (gameOver) return; switch(event.keyCode) { case 37: // Left movePiece(-1); break; case 39: // Right movePiece(1); break; case 40: // Down piece.position.y++; if (collide()) { piece.position.y--; merge(); removeRows(); piece = createPiece(); if (collide()) { gameOver = true; document.querySelector('custom-game-over').setAttribute('visible', 'true'); } } dropCounter = 0; break; case 38: // Up rotate(); break; case 32: // Space hardDrop(); break; case 80: // P // Pause logic would go here break; } }); // Initialize game piece = createPiece(); update(); // Custom element event listeners document.addEventListener('start-game', resetGame); document.addEventListener('reset-game', resetGame); });