| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Neon Tetris</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <style> |
| @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); |
| |
| body { |
| font-family: 'Press Start 2P', cursive; |
| background-color: #0f172a; |
| overflow: hidden; |
| } |
| |
| .tetris-block { |
| box-sizing: border-box; |
| border: 2px solid rgba(255, 255, 255, 0.1); |
| transition: all 0.1s ease; |
| } |
| |
| .tetris-block.I { |
| background-color: #00f0f0; |
| box-shadow: inset 0 0 10px rgba(0, 240, 240, 0.5), 0 0 10px rgba(0, 240, 240, 0.5); |
| } |
| |
| .tetris-block.J { |
| background-color: #0000f0; |
| box-shadow: inset 0 0 10px rgba(0, 0, 240, 0.5), 0 0 10px rgba(0, 0, 240, 0.5); |
| } |
| |
| .tetris-block.L { |
| background-color: #f0a000; |
| box-shadow: inset 0 0 10px rgba(240, 160, 0, 0.5), 0 0 10px rgba(240, 160, 0, 0.5); |
| } |
| |
| .tetris-block.O { |
| background-color: #f0f000; |
| box-shadow: inset 0 0 10px rgba(240, 240, 0, 0.5), 0 0 10px rgba(240, 240, 0, 0.5); |
| } |
| |
| .tetris-block.S { |
| background-color: #00f000; |
| box-shadow: inset 0 0 10px rgba(0, 240, 0, 0.5), 0 0 10px rgba(0, 240, 0, 0.5); |
| } |
| |
| .tetris-block.T { |
| background-color: #a000f0; |
| box-shadow: inset 0 0 10px rgba(160, 0, 240, 0.5), 0 0 10px rgba(160, 0, 240, 0.5); |
| } |
| |
| .tetris-block.Z { |
| background-color: #f00000; |
| box-shadow: inset 0 0 10px rgba(240, 0, 0, 0.5), 0 0 10px rgba(240, 0, 0, 0.5); |
| } |
| |
| .grid-cell { |
| background-color: rgba(30, 41, 59, 0.5); |
| border: 1px solid rgba(255, 255, 255, 0.05); |
| } |
| |
| .next-piece-container { |
| background-color: rgba(30, 41, 59, 0.7); |
| border: 2px solid rgba(255, 255, 255, 0.1); |
| } |
| |
| .game-overlay { |
| background-color: rgba(15, 23, 42, 0.9); |
| } |
| |
| @keyframes pulse { |
| 0% { transform: scale(1); } |
| 50% { transform: scale(1.05); } |
| 100% { transform: scale(1); } |
| } |
| |
| .pulse { |
| animation: pulse 1.5s infinite; |
| } |
| |
| |
| .next-piece-grid { |
| display: grid; |
| width: 100px; |
| height: 100px; |
| position: relative; |
| } |
| |
| .next-piece-inner { |
| position: absolute; |
| display: grid; |
| gap: 0; |
| } |
| |
| .next-piece-cell { |
| width: 100%; |
| height: 100%; |
| } |
| </style> |
| </head> |
| <body class="min-h-screen flex flex-col items-center justify-center text-white p-4"> |
| <div class="text-center mb-6"> |
| <h1 class="text-4xl md:text-5xl font-bold mb-2 text-transparent bg-clip-text bg-gradient-to-r from-purple-400 via-pink-500 to-red-500"> |
| NEON TETRIS |
| </h1> |
| <p class="text-sm text-gray-400">Use arrow keys to play • ↑ to rotate • ↓ to drop faster</p> |
| </div> |
|
|
| <div class="flex flex-col md:flex-row items-center gap-8"> |
| |
| <div class="relative"> |
| <div id="game-board" class="grid grid-cols-10 grid-rows-20 gap-0 w-[250px] h-[500px] border-4 border-indigo-500/50 rounded-lg overflow-hidden"></div> |
| <div id="game-overlay" class="game-overlay absolute inset-0 flex flex-col items-center justify-center hidden"> |
| <h2 class="text-3xl font-bold mb-4 text-red-500">GAME OVER</h2> |
| <button id="restart-btn" class="px-6 py-3 bg-indigo-600 hover:bg-indigo-700 rounded-lg font-bold transition-all pulse"> |
| PLAY AGAIN |
| </button> |
| </div> |
| </div> |
|
|
| |
| <div class="flex flex-col gap-6 w-full max-w-[250px]"> |
| <div class="next-piece-container p-4 rounded-lg"> |
| <h3 class="text-center text-lg mb-3">NEXT PIECE</h3> |
| <div id="next-piece" class="next-piece-grid"> |
| <div id="next-piece-inner" class="next-piece-inner"></div> |
| </div> |
| </div> |
|
|
| <div class="bg-slate-800/50 p-4 rounded-lg"> |
| <div class="flex justify-between mb-2"> |
| <span>SCORE:</span> |
| <span id="score" class="font-bold">0</span> |
| </div> |
| <div class="flex justify-between mb-2"> |
| <span>LEVEL:</span> |
| <span id="level" class="font-bold">1</span> |
| </div> |
| <div class="flex justify-between"> |
| <span>LINES:</span> |
| <span id="lines" class="font-bold">0</span> |
| </div> |
| </div> |
|
|
| <div class="bg-slate-800/50 p-4 rounded-lg hidden md:block"> |
| <h3 class="text-center text-lg mb-2">CONTROLS</h3> |
| <div class="grid grid-cols-3 gap-2 text-xs"> |
| <div class="col-span-3 flex justify-center"> |
| <div class="bg-slate-700/50 p-2 rounded text-center">↑ Rotate</div> |
| </div> |
| <div class="flex justify-center"> |
| <div class="bg-slate-700/50 p-2 rounded text-center">← Left</div> |
| </div> |
| <div class="flex justify-center"> |
| <div class="bg-slate-700/50 p-2 rounded text-center">↓ Drop</div> |
| </div> |
| <div class="flex justify-center"> |
| <div class="bg-slate-700/50 p-2 rounded text-center">→ Right</div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="mt-8 text-xs text-gray-500 text-center"> |
| <p>Press any key to start • P to pause</p> |
| </div> |
|
|
| <script> |
| document.addEventListener('DOMContentLoaded', () => { |
| |
| const COLS = 10; |
| const ROWS = 20; |
| const BLOCK_SIZE = 25; |
| const EMPTY = 'empty'; |
| |
| |
| let board = createEmptyBoard(); |
| let currentPiece = null; |
| let nextPiece = null; |
| let score = 0; |
| let level = 1; |
| let lines = 0; |
| let gameOver = false; |
| let isPaused = false; |
| let dropInterval = null; |
| let dropSpeed = 1000; |
| |
| |
| const gameBoard = document.getElementById('game-board'); |
| const nextPieceDisplay = document.getElementById('next-piece-inner'); |
| const scoreDisplay = document.getElementById('score'); |
| const levelDisplay = document.getElementById('level'); |
| const linesDisplay = document.getElementById('lines'); |
| const gameOverlay = document.getElementById('game-overlay'); |
| const restartBtn = document.getElementById('restart-btn'); |
| |
| |
| function initializeBoard() { |
| gameBoard.innerHTML = ''; |
| for (let row = 0; row < ROWS; row++) { |
| for (let col = 0; col < COLS; col++) { |
| const cell = document.createElement('div'); |
| cell.className = 'grid-cell w-full h-full'; |
| cell.dataset.row = row; |
| cell.dataset.col = col; |
| gameBoard.appendChild(cell); |
| } |
| } |
| } |
| |
| |
| function createEmptyBoard() { |
| return Array.from({ length: ROWS }, () => Array(COLS).fill(EMPTY)); |
| } |
| |
| |
| const SHAPES = { |
| I: [ |
| [0, 0, 0, 0], |
| [1, 1, 1, 1], |
| [0, 0, 0, 0], |
| [0, 0, 0, 0] |
| ], |
| J: [ |
| [1, 0, 0], |
| [1, 1, 1], |
| [0, 0, 0] |
| ], |
| L: [ |
| [0, 0, 1], |
| [1, 1, 1], |
| [0, 0, 0] |
| ], |
| O: [ |
| [1, 1], |
| [1, 1] |
| ], |
| S: [ |
| [0, 1, 1], |
| [1, 1, 0], |
| [0, 0, 0] |
| ], |
| T: [ |
| [0, 1, 0], |
| [1, 1, 1], |
| [0, 0, 0] |
| ], |
| Z: [ |
| [1, 1, 0], |
| [0, 1, 1], |
| [0, 0, 0] |
| ] |
| }; |
| |
| |
| const COLORS = { |
| I: 'I', |
| J: 'J', |
| L: 'L', |
| O: 'O', |
| S: 'S', |
| T: 'T', |
| Z: 'Z' |
| }; |
| |
| |
| function getRandomPiece() { |
| const pieces = Object.keys(SHAPES); |
| const randomPiece = pieces[Math.floor(Math.random() * pieces.length)]; |
| return { |
| shape: SHAPES[randomPiece], |
| color: COLORS[randomPiece], |
| x: Math.floor(COLS / 2) - Math.floor(SHAPES[randomPiece][0].length / 2), |
| y: 0 |
| }; |
| } |
| |
| |
| function drawBoard() { |
| |
| document.querySelectorAll('#game-board div').forEach(cell => { |
| cell.className = 'grid-cell w-full h-full'; |
| }); |
| |
| |
| for (let row = 0; row < ROWS; row++) { |
| for (let col = 0; col < COLS; col++) { |
| if (board[row][col] !== EMPTY) { |
| const cell = document.querySelector(`#game-board div[data-row="${row}"][data-col="${col}"]`); |
| if (cell) { |
| cell.className = `tetris-block ${board[row][col]} w-full h-full`; |
| } |
| } |
| } |
| } |
| |
| |
| if (currentPiece) { |
| for (let row = 0; row < currentPiece.shape.length; row++) { |
| for (let col = 0; col < currentPiece.shape[row].length; col++) { |
| if (currentPiece.shape[row][col]) { |
| const boardRow = currentPiece.y + row; |
| const boardCol = currentPiece.x + col; |
| if (boardRow >= 0 && boardRow < ROWS && boardCol >= 0 && boardCol < COLS) { |
| const cell = document.querySelector(`#game-board div[data-row="${boardRow}"][data-col="${boardCol}"]`); |
| if (cell) { |
| cell.className = `tetris-block ${currentPiece.color} w-full h-full`; |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| |
| function drawNextPiece() { |
| if (!nextPiece) return; |
| |
| |
| nextPieceDisplay.innerHTML = ''; |
| |
| |
| const rows = nextPiece.shape.length; |
| const cols = nextPiece.shape[0].length; |
| |
| |
| const cellSize = Math.min(100 / cols, 100 / rows); |
| const containerWidth = cellSize * cols; |
| const containerHeight = cellSize * rows; |
| |
| |
| nextPieceDisplay.style.gridTemplateColumns = `repeat(${cols}, ${cellSize}px)`; |
| nextPieceDisplay.style.gridTemplateRows = `repeat(${rows}, ${cellSize}px)`; |
| nextPieceDisplay.style.width = `${containerWidth}px`; |
| nextPieceDisplay.style.height = `${containerHeight}px`; |
| nextPieceDisplay.style.left = `${(100 - containerWidth) / 2}px`; |
| nextPieceDisplay.style.top = `${(100 - containerHeight) / 2}px`; |
| |
| |
| for (let row = 0; row < rows; row++) { |
| for (let col = 0; col < cols; col++) { |
| const cell = document.createElement('div'); |
| if (nextPiece.shape[row][col]) { |
| cell.className = `tetris-block ${nextPiece.color} next-piece-cell`; |
| } else { |
| cell.className = 'next-piece-cell opacity-0'; |
| } |
| nextPieceDisplay.appendChild(cell); |
| } |
| } |
| } |
| |
| |
| function collision(x, y, shape) { |
| for (let row = 0; row < shape.length; row++) { |
| for (let col = 0; col < shape[row].length; col++) { |
| if (shape[row][col]) { |
| const newX = x + col; |
| const newY = y + row; |
| |
| |
| if (newX < 0 || newX >= COLS || newY >= ROWS) { |
| return true; |
| } |
| |
| |
| if (newY >= 0 && board[newY][newX] !== EMPTY) { |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| |
| function rotatePiece() { |
| if (!currentPiece || gameOver || isPaused) return; |
| |
| const newShape = []; |
| for (let col = currentPiece.shape[0].length - 1; col >= 0; col--) { |
| const newRow = []; |
| for (let row = 0; row < currentPiece.shape.length; row++) { |
| newRow.push(currentPiece.shape[row][col]); |
| } |
| newShape.push(newRow); |
| } |
| |
| |
| if (!collision(currentPiece.x, currentPiece.y, newShape)) { |
| currentPiece.shape = newShape; |
| drawBoard(); |
| } |
| } |
| |
| |
| function moveLeft() { |
| if (!currentPiece || gameOver || isPaused) return; |
| |
| if (!collision(currentPiece.x - 1, currentPiece.y, currentPiece.shape)) { |
| currentPiece.x--; |
| drawBoard(); |
| } |
| } |
| |
| |
| function moveRight() { |
| if (!currentPiece || gameOver || isPaused) return; |
| |
| if (!collision(currentPiece.x + 1, currentPiece.y, currentPiece.shape)) { |
| currentPiece.x++; |
| drawBoard(); |
| } |
| } |
| |
| |
| function moveDown() { |
| if (!currentPiece || gameOver || isPaused) return; |
| |
| if (!collision(currentPiece.x, currentPiece.y + 1, currentPiece.shape)) { |
| currentPiece.y++; |
| drawBoard(); |
| } else { |
| lockPiece(); |
| } |
| } |
| |
| |
| function dropPiece() { |
| if (!currentPiece || gameOver || isPaused) return; |
| |
| while (!collision(currentPiece.x, currentPiece.y + 1, currentPiece.shape)) { |
| currentPiece.y++; |
| } |
| lockPiece(); |
| drawBoard(); |
| } |
| |
| |
| function lockPiece() { |
| if (!currentPiece) return; |
| |
| |
| for (let row = 0; row < currentPiece.shape.length; row++) { |
| for (let col = 0; col < currentPiece.shape[row].length; col++) { |
| if (currentPiece.shape[row][col]) { |
| const boardRow = currentPiece.y + row; |
| const boardCol = currentPiece.x + col; |
| |
| if (boardRow >= 0 && boardRow < ROWS && boardCol >= 0 && boardCol < COLS) { |
| board[boardRow][boardCol] = currentPiece.color; |
| } |
| } |
| } |
| } |
| |
| |
| checkLines(); |
| |
| |
| if (currentPiece.y <= 0) { |
| gameOver = true; |
| clearInterval(dropInterval); |
| gameOverlay.classList.remove('hidden'); |
| return; |
| } |
| |
| |
| currentPiece = nextPiece; |
| nextPiece = getRandomPiece(); |
| drawNextPiece(); |
| } |
| |
| |
| function checkLines() { |
| let linesCleared = 0; |
| |
| for (let row = ROWS - 1; row >= 0; row--) { |
| if (board[row].every(cell => cell !== EMPTY)) { |
| |
| board.splice(row, 1); |
| |
| board.unshift(Array(COLS).fill(EMPTY)); |
| linesCleared++; |
| row++; |
| } |
| } |
| |
| if (linesCleared > 0) { |
| |
| const points = [0, 40, 100, 300, 1200][linesCleared] * level; |
| score += points; |
| lines += linesCleared; |
| |
| |
| const newLevel = Math.floor(lines / 10) + 1; |
| if (newLevel > level) { |
| level = newLevel; |
| |
| dropSpeed = Math.max(100, 1000 - (level - 1) * 100); |
| clearInterval(dropInterval); |
| dropInterval = setInterval(moveDown, dropSpeed); |
| } |
| |
| updateStats(); |
| } |
| } |
| |
| |
| function updateStats() { |
| scoreDisplay.textContent = score; |
| levelDisplay.textContent = level; |
| linesDisplay.textContent = lines; |
| } |
| |
| |
| function startGame() { |
| board = createEmptyBoard(); |
| score = 0; |
| level = 1; |
| lines = 0; |
| gameOver = false; |
| isPaused = false; |
| dropSpeed = 1000; |
| |
| updateStats(); |
| |
| currentPiece = getRandomPiece(); |
| nextPiece = getRandomPiece(); |
| |
| drawBoard(); |
| drawNextPiece(); |
| |
| gameOverlay.classList.add('hidden'); |
| |
| clearInterval(dropInterval); |
| dropInterval = setInterval(moveDown, dropSpeed); |
| } |
| |
| |
| function togglePause() { |
| if (gameOver) return; |
| |
| isPaused = !isPaused; |
| |
| if (isPaused) { |
| clearInterval(dropInterval); |
| document.querySelector('h1').textContent = 'PAUSED'; |
| } else { |
| dropInterval = setInterval(moveDown, dropSpeed); |
| document.querySelector('h1').textContent = 'NEON TETRIS'; |
| } |
| } |
| |
| |
| document.addEventListener('keydown', (e) => { |
| if (gameOver && e.key === ' ') { |
| startGame(); |
| return; |
| } |
| |
| switch (e.key) { |
| case 'ArrowLeft': |
| moveLeft(); |
| break; |
| case 'ArrowRight': |
| moveRight(); |
| break; |
| case 'ArrowDown': |
| moveDown(); |
| break; |
| case 'ArrowUp': |
| rotatePiece(); |
| break; |
| case ' ': |
| dropPiece(); |
| break; |
| case 'p': |
| case 'P': |
| togglePause(); |
| break; |
| } |
| }); |
| |
| restartBtn.addEventListener('click', startGame); |
| |
| |
| let touchStartX = 0; |
| let touchStartY = 0; |
| |
| gameBoard.addEventListener('touchstart', (e) => { |
| touchStartX = e.touches[0].clientX; |
| touchStartY = e.touches[0].clientY; |
| }, false); |
| |
| gameBoard.addEventListener('touchend', (e) => { |
| if (!touchStartX || !touchStartY || gameOver || isPaused) return; |
| |
| const touchEndX = e.changedTouches[0].clientX; |
| const touchEndY = e.changedTouches[0].clientY; |
| |
| const diffX = touchStartX - touchEndX; |
| const diffY = touchStartY - touchEndY; |
| |
| |
| if (diffX > 50 && Math.abs(diffY) < 100) { |
| moveLeft(); |
| } |
| |
| else if (diffX < -50 && Math.abs(diffY) < 100) { |
| moveRight(); |
| } |
| |
| else if (diffY < -50 && Math.abs(diffX) < 100) { |
| dropPiece(); |
| } |
| |
| else if (Math.abs(diffX) < 50 && Math.abs(diffY) < 50) { |
| rotatePiece(); |
| } |
| }, false); |
| |
| |
| initializeBoard(); |
| startGame(); |
| }); |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=FriedsU/tetris" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |