/** * Tetris Game Logic * Implements core mechanics: movement, rotation, collision, line clearing, and scoring. */ class Tetris { constructor(width = 10, height = 20) { this.width = width; this.height = height; this.grid = this.createGrid(); this.score = 0; this.linesCleared = 0; this.gameOver = false; // Tetromino shapes definitions this.shapes = { '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]] }; this.colors = { 'I': '#00f0f0', 'J': '#0000f0', 'L': '#f0a000', 'O': '#f0f000', 'S': '#00f000', 'T': '#a000f0', 'Z': '#f00000' }; this.currentPiece = null; this.nextPiece = null; this.spawnPiece(); } /** * Creates an empty game grid */ createGrid() { return Array.from({ length: this.height }, () => Array(this.width).fill(0)); } /** * Spawns a new random tetromino */ spawnPiece() { const types = Object.keys(this.shapes); if (!this.nextPiece) { this.nextPiece = types[Math.floor(Math.random() * types.length)]; } const type = this.nextPiece; this.nextPiece = types[Math.floor(Math.random() * types.length)]; const shape = this.shapes[type]; this.currentPiece = { type: type, shape: shape, x: Math.floor((this.width - shape[0].length) / 2), y: 0 }; // Check for immediate collision (Game Over) if (this.checkCollision(this.currentPiece.x, this.currentPiece.y, this.currentPiece.shape)) { this.gameOver = true; } } /** * Checks if a piece collides with boundaries or other pieces */ checkCollision(x, y, shape) { for (let row = 0; row < shape.length; row++) { for (let col = 0; col < shape[row].length; col++) { if (shape[row][col] !== 0) { const newX = x + col; const newY = y + row; if (newX < 0 || newX >= this.width || newY >= this.height || (newY >= 0 && this.grid[newY][newX] !== 0)) { return true; } } } } return false; } /** * Moves the current piece in a given direction */ move(dx, dy) { if (this.gameOver) return false; if (!this.checkCollision(this.currentPiece.x + dx, this.currentPiece.y + dy, this.currentPiece.shape)) { this.currentPiece.x += dx; this.currentPiece.y += dy; return true; } // If moving down and collision occurs, lock the piece if (dy > 0) { this.lockPiece(); this.clearLines(); this.spawnPiece(); } return false; } /** * Rotates the current piece clockwise */ rotate() { if (this.gameOver) return; const originalShape = this.currentPiece.shape; const newShape = originalShape[0].map((_, index) => originalShape.map(row => row[index]).reverse() ); // Basic "Wall Kick" check let offset = 0; if (this.checkCollision(this.currentPiece.x, this.currentPiece.y, newShape)) { // Try shifting left/right to see if it fits if (!this.checkCollision(this.currentPiece.x - 1, this.currentPiece.y, newShape)) { offset = -1; } else if (!this.checkCollision(this.currentPiece.x + 1, this.currentPiece.y, newShape)) { offset = 1; } else { return; // Can't rotate } } this.currentPiece.x += offset; this.currentPiece.shape = newShape; } /** * Hard drop the current piece */ hardDrop() { while (this.move(0, 1)) { // Keep moving down } } /** * Locks the piece into the grid */ lockPiece() { const { shape, x, y, type } = this.currentPiece; shape.forEach((row, rowIndex) => { row.forEach((value, colIndex) => { if (value !== 0) { const gridY = y + rowIndex; const gridX = x + colIndex; if (gridY >= 0) { this.grid[gridY][gridX] = type; } } }); }); } /** * Clears full lines and updates score */ clearLines() { let linesCount = 0; for (let row = this.height - 1; row >= 0; row--) { if (this.grid[row].every(cell => cell !== 0)) { this.grid.splice(row, 1); this.grid.unshift(Array(this.width).fill(0)); linesCount++; row++; // Check the same row index again after splice } } if (linesCount > 0) { const scoring = [0, 100, 300, 500, 800]; // Standard scoring this.score += scoring[linesCount]; this.linesCleared += linesCount; } } /** * Returns the current state for rendering */ getState() { // Return a copy of the grid with the current piece superimposed const displayGrid = this.grid.map(row => [...row]); if (this.currentPiece && !this.gameOver) { const { shape, x, y, type } = this.currentPiece; shape.forEach((row, rowIndex) => { row.forEach((value, colIndex) => { if (value !== 0) { const gridY = y + rowIndex; const gridX = x + colIndex; if (gridY >= 0 && gridY < this.height && gridX >= 0 && gridX < this.width) { displayGrid[gridY][gridX] = type; } } }); }); } return { grid: displayGrid, score: this.score, lines: this.linesCleared, nextPiece: this.nextPiece, gameOver: this.gameOver }; } } // Export for usage if (typeof module !== 'undefined' && module.exports) { module.exports = Tetris; }