| | <!DOCTYPE html> |
| | <html lang="en"> |
| | <head> |
| | <meta charset="UTF-8"> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | <title>The Aspiring Ed Tetris Game</title> |
| | <script src="https://cdn.tailwindcss.com"></script> |
| | <style> |
| | @keyframes flash { |
| | 0% { opacity: 1; } |
| | 50% { opacity: 0.5; } |
| | 100% { opacity: 1; } |
| | } |
| | .flash-animation { |
| | animation: flash 0.3s infinite; |
| | } |
| | .cell { |
| | transition: all 0.1s ease; |
| | } |
| | .game-container { |
| | perspective: 1000px; |
| | } |
| | .game-board { |
| | transform-style: preserve-3d; |
| | box-shadow: 0 0 30px rgba(59, 130, 246, 0.5); |
| | } |
| | .piece-i { background-color: #00f0f0; } |
| | .piece-j { background-color: #0000f0; } |
| | .piece-l { background-color: #f0a000; } |
| | .piece-o { background-color: #f0f000; } |
| | .piece-s { background-color: #00f000; } |
| | .piece-t { background-color: #a000f0; } |
| | .piece-z { background-color: #f00000; } |
| | .piece-ghost { opacity: 0.3; } |
| | .logo { |
| | max-height: 120px; |
| | object-fit: contain; |
| | } |
| | </style> |
| | </head> |
| | <body class="bg-gray-900 text-white min-h-screen flex flex-col items-center justify-center p-4"> |
| | <div class="max-w-4xl w-full"> |
| | <div class="flex flex-col items-center mb-6"> |
| | <img src="https://i.ytimg.com/vi/fPDyAbG9s0A/maxresdefault.jpg" alt="Tetris Logo" class="logo mb-2"> |
| | <h1 class="text-4xl font-bold text-center text-blue-400">Modern Tetris</h1> |
| | </div> |
| | |
| | <div class="flex flex-col md:flex-row gap-8 items-center justify-center"> |
| | |
| | <div class="game-container relative"> |
| | <div class="game-board bg-gray-800 rounded-lg overflow-hidden border-2 border-blue-500"> |
| | <div id="board" class="grid grid-cols-10 grid-rows-20 gap-0.5 p-1 bg-gray-700"></div> |
| | </div> |
| | <div id="game-over" class="absolute inset-0 bg-black bg-opacity-70 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-2 bg-blue-600 hover:bg-blue-700 rounded-lg font-semibold transition">Play Again</button> |
| | </div> |
| | </div> |
| | |
| | |
| | <div class="flex flex-col gap-6 w-full md:w-64"> |
| | |
| | <div class="bg-gray-800 p-4 rounded-lg border border-blue-500"> |
| | <h2 class="text-xl font-semibold mb-2 text-blue-400">Score</h2> |
| | <div class="flex justify-between items-center"> |
| | <div> |
| | <p class="text-gray-400">Score</p> |
| | <p id="score" class="text-2xl font-bold">0</p> |
| | </div> |
| | <div> |
| | <p class="text-gray-400">Level</p> |
| | <p id="level" class="text-2xl font-bold">1</p> |
| | </div> |
| | </div> |
| | <div class="mt-2"> |
| | <p class="text-gray-400">Lines</p> |
| | <p id="lines" class="text-xl font-bold">0</p> |
| | </div> |
| | </div> |
| | |
| | |
| | <div class="bg-gray-800 p-4 rounded-lg border border-blue-500"> |
| | <h2 class="text-xl font-semibold mb-2 text-blue-400">Next Piece</h2> |
| | <div id="next-piece" class="grid grid-cols-4 grid-rows-4 gap-1 h-32 w-32 bg-gray-700 p-1"></div> |
| | </div> |
| | |
| | |
| | <div class="bg-gray-800 p-4 rounded-lg border border-blue-500"> |
| | <h2 class="text-xl font-semibold mb-2 text-blue-400">Controls</h2> |
| | <ul class="space-y-2 text-sm"> |
| | <li class="flex items-center"><span class="bg-gray-700 px-2 py-1 rounded mr-2">← →</span> Move</li> |
| | <li class="flex items-center"><span class="bg-gray-700 px-2 py-1 rounded mr-2">↑</span> Rotate</li> |
| | <li class="flex items-center"><span class="bg-gray-700 px-2 py-1 rounded mr-2">↓</span> Soft Drop</li> |
| | <li class="flex items-center"><span class="bg-gray-700 px-2 py-1 rounded mr-2">Space</span> Hard Drop</li> |
| | <li class="flex items-center"><span class="bg-gray-700 px-2 py-1 rounded mr-2">P</span> Pause</li> |
| | </ul> |
| | </div> |
| | |
| | |
| | <div class="flex gap-2"> |
| | <button id="start-btn" class="flex-1 px-4 py-2 bg-green-600 hover:bg-green-700 rounded-lg font-semibold transition">Start</button> |
| | <button id="pause-btn" class="flex-1 px-4 py-2 bg-yellow-600 hover:bg-yellow-700 rounded-lg font-semibold transition hidden">Pause</button> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | <script> |
| | document.addEventListener('DOMContentLoaded', () => { |
| | |
| | const COLS = 10; |
| | const ROWS = 20; |
| | const BLOCK_SIZE = 30; |
| | const EMPTY = 'empty'; |
| | |
| | |
| | let board = Array(ROWS).fill().map(() => Array(COLS).fill(EMPTY)); |
| | let currentPiece = null; |
| | let nextPiece = null; |
| | let currentPosition = { x: 0, y: 0 }; |
| | let score = 0; |
| | let lines = 0; |
| | let level = 1; |
| | let gameInterval = null; |
| | let isPaused = false; |
| | let isGameOver = false; |
| | let dropSpeed = 1000; |
| | |
| | |
| | const PIECES = [ |
| | { shape: [[1, 1, 1, 1]], color: 'piece-i' }, |
| | { shape: [[1, 0, 0], [1, 1, 1]], color: 'piece-j' }, |
| | { shape: [[0, 0, 1], [1, 1, 1]], color: 'piece-l' }, |
| | { shape: [[1, 1], [1, 1]], color: 'piece-o' }, |
| | { shape: [[0, 1, 1], [1, 1, 0]], color: 'piece-s' }, |
| | { shape: [[0, 1, 0], [1, 1, 1]], color: 'piece-t' }, |
| | { shape: [[1, 1, 0], [0, 1, 1]], color: 'piece-z' } |
| | ]; |
| | |
| | |
| | const boardElement = document.getElementById('board'); |
| | const nextPieceElement = document.getElementById('next-piece'); |
| | const scoreElement = document.getElementById('score'); |
| | const linesElement = document.getElementById('lines'); |
| | const levelElement = document.getElementById('level'); |
| | const startBtn = document.getElementById('start-btn'); |
| | const pauseBtn = document.getElementById('pause-btn'); |
| | const restartBtn = document.getElementById('restart-btn'); |
| | const gameOverElement = document.getElementById('game-over'); |
| | |
| | |
| | function initBoard() { |
| | boardElement.innerHTML = ''; |
| | boardElement.style.gridTemplateColumns = `repeat(${COLS}, ${BLOCK_SIZE}px)`; |
| | boardElement.style.gridTemplateRows = `repeat(${ROWS}, ${BLOCK_SIZE}px)`; |
| | |
| | for (let y = 0; y < ROWS; y++) { |
| | for (let x = 0; x < COLS; x++) { |
| | const cell = document.createElement('div'); |
| | cell.className = 'cell w-full h-full border border-gray-800'; |
| | cell.id = `cell-${y}-${x}`; |
| | boardElement.appendChild(cell); |
| | } |
| | } |
| | } |
| | |
| | |
| | function initNextPiece() { |
| | nextPieceElement.innerHTML = ''; |
| | nextPieceElement.style.gridTemplateColumns = `repeat(4, 1fr)`; |
| | nextPieceElement.style.gridTemplateRows = `repeat(4, 1fr)`; |
| | |
| | for (let y = 0; y < 4; y++) { |
| | for (let x = 0; x < 4; x++) { |
| | const cell = document.createElement('div'); |
| | cell.className = 'cell w-full h-full border border-gray-800'; |
| | cell.id = `next-cell-${y}-${x}`; |
| | nextPieceElement.appendChild(cell); |
| | } |
| | } |
| | } |
| | |
| | |
| | function randomPiece() { |
| | const randomIndex = Math.floor(Math.random() * PIECES.length); |
| | return { |
| | shape: PIECES[randomIndex].shape, |
| | color: PIECES[randomIndex].color |
| | }; |
| | } |
| | |
| | |
| | function drawBoard() { |
| | for (let y = 0; y < ROWS; y++) { |
| | for (let x = 0; x < COLS; x++) { |
| | const cell = document.getElementById(`cell-${y}-${x}`); |
| | cell.className = 'cell w-full h-full border border-gray-800'; |
| | |
| | if (board[y][x] !== EMPTY) { |
| | cell.classList.add(board[y][x]); |
| | } |
| | } |
| | } |
| | } |
| | |
| | |
| | function drawPiece() { |
| | |
| | const ghostY = getDropPosition(); |
| | drawGhostPiece(ghostY); |
| | |
| | |
| | for (let y = 0; y < currentPiece.shape.length; y++) { |
| | for (let x = 0; x < currentPiece.shape[y].length; x++) { |
| | if (currentPiece.shape[y][x]) { |
| | const boardY = currentPosition.y + y; |
| | const boardX = currentPosition.x + x; |
| | |
| | if (boardY >= 0 && boardX >= 0 && boardX < COLS) { |
| | const cell = document.getElementById(`cell-${boardY}-${boardX}`); |
| | if (cell) { |
| | cell.className = `cell w-full h-full border border-gray-800 ${currentPiece.color}`; |
| | } |
| | } |
| | } |
| | } |
| | } |
| | } |
| | |
| | |
| | function drawGhostPiece(ghostY) { |
| | for (let y = 0; y < currentPiece.shape.length; y++) { |
| | for (let x = 0; x < currentPiece.shape[y].length; x++) { |
| | if (currentPiece.shape[y][x]) { |
| | const boardY = ghostY + y; |
| | const boardX = currentPosition.x + x; |
| | |
| | if (boardY >= 0 && boardX >= 0 && boardX < COLS) { |
| | const cell = document.getElementById(`cell-${boardY}-${boardX}`); |
| | if (cell && !cell.classList.contains(currentPiece.color)) { |
| | cell.classList.add(currentPiece.color, 'piece-ghost'); |
| | } |
| | } |
| | } |
| | } |
| | } |
| | } |
| | |
| | |
| | function drawNextPiece() { |
| | |
| | for (let y = 0; y < 4; y++) { |
| | for (let x = 0; x < 4; x++) { |
| | const cell = document.getElementById(`next-cell-${y}-${x}`); |
| | cell.className = 'cell w-full h-full border border-gray-800'; |
| | } |
| | } |
| | |
| | |
| | const offsetX = Math.floor((4 - nextPiece.shape[0].length) / 2); |
| | const offsetY = Math.floor((4 - nextPiece.shape.length) / 2); |
| | |
| | |
| | for (let y = 0; y < nextPiece.shape.length; y++) { |
| | for (let x = 0; x < nextPiece.shape[y].length; x++) { |
| | if (nextPiece.shape[y][x]) { |
| | const cell = document.getElementById(`next-cell-${y + offsetY}-${x + offsetX}`); |
| | cell.classList.add(nextPiece.color); |
| | } |
| | } |
| | } |
| | } |
| | |
| | |
| | function isValidPosition(piece, position) { |
| | for (let y = 0; y < piece.shape.length; y++) { |
| | for (let x = 0; x < piece.shape[y].length; x++) { |
| | if (piece.shape[y][x]) { |
| | const boardY = position.y + y; |
| | const boardX = position.x + x; |
| | |
| | |
| | if (boardX < 0 || boardX >= COLS || boardY >= ROWS) { |
| | return false; |
| | } |
| | |
| | |
| | if (boardY >= 0 && board[boardY][boardX] !== EMPTY) { |
| | return false; |
| | } |
| | } |
| | } |
| | } |
| | return true; |
| | } |
| | |
| | |
| | function rotatePiece() { |
| | if (!currentPiece) return; |
| | |
| | |
| | const newPiece = { |
| | shape: [], |
| | color: currentPiece.color |
| | }; |
| | |
| | |
| | for (let x = 0; x < currentPiece.shape[0].length; x++) { |
| | newPiece.shape.push([]); |
| | for (let y = currentPiece.shape.length - 1; y >= 0; y--) { |
| | newPiece.shape[x].push(currentPiece.shape[y][x]); |
| | } |
| | } |
| | |
| | |
| | if (isValidPosition(newPiece, currentPosition)) { |
| | currentPiece = newPiece; |
| | draw(); |
| | } else { |
| | |
| | const kicks = [ |
| | { x: -1, y: 0 }, { x: 1, y: 0 }, |
| | { x: 0, y: -1 }, { x: 0, y: 1 }, |
| | { x: -1, y: -1 }, { x: 1, y: -1 }, |
| | { x: -1, y: 1 }, { x: 1, y: 1 } |
| | ]; |
| | |
| | for (const kick of kicks) { |
| | const newPosition = { |
| | x: currentPosition.x + kick.x, |
| | y: currentPosition.y + kick.y |
| | }; |
| | |
| | if (isValidPosition(newPiece, newPosition)) { |
| | currentPiece = newPiece; |
| | currentPosition = newPosition; |
| | draw(); |
| | break; |
| | } |
| | } |
| | } |
| | } |
| | |
| | |
| | function moveLeft() { |
| | if (!currentPiece) return; |
| | |
| | const newPosition = { |
| | x: currentPosition.x - 1, |
| | y: currentPosition.y |
| | }; |
| | |
| | if (isValidPosition(currentPiece, newPosition)) { |
| | currentPosition = newPosition; |
| | draw(); |
| | } |
| | } |
| | |
| | |
| | function moveRight() { |
| | if (!currentPiece) return; |
| | |
| | const newPosition = { |
| | x: currentPosition.x + 1, |
| | y: currentPosition.y |
| | }; |
| | |
| | if (isValidPosition(currentPiece, newPosition)) { |
| | currentPosition = newPosition; |
| | draw(); |
| | } |
| | } |
| | |
| | |
| | function moveDown() { |
| | if (!currentPiece || isPaused || isGameOver) return; |
| | |
| | const newPosition = { |
| | x: currentPosition.x, |
| | y: currentPosition.y + 1 |
| | }; |
| | |
| | if (isValidPosition(currentPiece, newPosition)) { |
| | currentPosition = newPosition; |
| | draw(); |
| | return true; |
| | } else { |
| | |
| | lockPiece(); |
| | return false; |
| | } |
| | } |
| | |
| | |
| | function hardDrop() { |
| | if (!currentPiece || isPaused || isGameOver) return; |
| | |
| | let dropPosition = currentPosition.y; |
| | while (isValidPosition(currentPiece, { x: currentPosition.x, y: dropPosition + 1 })) { |
| | dropPosition++; |
| | } |
| | |
| | currentPosition.y = dropPosition; |
| | lockPiece(); |
| | } |
| | |
| | |
| | function getDropPosition() { |
| | let dropPosition = currentPosition.y; |
| | while (isValidPosition(currentPiece, { x: currentPosition.x, y: dropPosition + 1 })) { |
| | dropPosition++; |
| | } |
| | return dropPosition; |
| | } |
| | |
| | |
| | function lockPiece() { |
| | for (let y = 0; y < currentPiece.shape.length; y++) { |
| | for (let x = 0; x < currentPiece.shape[y].length; x++) { |
| | if (currentPiece.shape[y][x]) { |
| | const boardY = currentPosition.y + y; |
| | const boardX = currentPosition.x + x; |
| | |
| | if (boardY >= 0) { |
| | board[boardY][boardX] = currentPiece.color; |
| | } |
| | } |
| | } |
| | } |
| | |
| | |
| | checkLines(); |
| | |
| | |
| | spawnPiece(); |
| | |
| | |
| | if (!isValidPosition(currentPiece, currentPosition)) { |
| | gameOver(); |
| | } |
| | } |
| | |
| | |
| | function checkLines() { |
| | let linesCleared = 0; |
| | |
| | for (let y = ROWS - 1; y >= 0; y--) { |
| | let isLineComplete = true; |
| | |
| | for (let x = 0; x < COLS; x++) { |
| | if (board[y][x] === EMPTY) { |
| | isLineComplete = false; |
| | break; |
| | } |
| | } |
| | |
| | if (isLineComplete) { |
| | |
| | for (let x = 0; x < COLS; x++) { |
| | board[y][x] = EMPTY; |
| | } |
| | |
| | |
| | for (let yy = y; yy > 0; yy--) { |
| | for (let x = 0; x < COLS; x++) { |
| | board[yy][x] = board[yy - 1][x]; |
| | } |
| | } |
| | |
| | |
| | for (let x = 0; x; x++) { |
| | board[0][x] = EMPTY; |
| | } |
| | |
| | linesCleared++; |
| | y++; |
| | } |
| | } |
| | |
| | if (linesCleared > 0) { |
| | |
| | const points = [0, 40, 100, 300, 1200]; |
| | score += points[linesCleared] * level; |
| | lines += linesCleared; |
| | |
| | |
| | level = Math.floor(lines / 10) + 1; |
| | |
| | |
| | dropSpeed = Math.max(100, 1000 - (level - 1) * 50); |
| | |
| | updateScore(); |
| | |
| | |
| | flashBoard(); |
| | } |
| | } |
| | |
| | |
| | function flashBoard() { |
| | const cells = document.querySelectorAll('.cell'); |
| | cells.forEach(cell => { |
| | cell.classList.add('flash-animation'); |
| | }); |
| | |
| | setTimeout(() => { |
| | cells.forEach(cell => { |
| | cell.classList.remove('flash-animation'); |
| | }); |
| | }, 300); |
| | } |
| | |
| | |
| | function spawnPiece() { |
| | currentPiece = nextPiece; |
| | nextPiece = randomPiece(); |
| | |
| | |
| | currentPosition = { |
| | x: Math.floor(COLS / 2) - Math.floor(currentPiece.shape[0].length / 2), |
| | y: -1 |
| | }; |
| | |
| | |
| | while (!isValidPosition(currentPiece, currentPosition) && currentPosition.y < 0) { |
| | currentPosition.y++; |
| | } |
| | |
| | drawNextPiece(); |
| | } |
| | |
| | |
| | function updateScore() { |
| | scoreElement.textContent = score; |
| | linesElement.textContent = lines; |
| | levelElement.textContent = level; |
| | } |
| | |
| | |
| | function gameOver() { |
| | clearInterval(gameInterval); |
| | isGameOver = true; |
| | gameOverElement.classList.remove('hidden'); |
| | } |
| | |
| | |
| | function startGame() { |
| | |
| | board = Array(ROWS).fill().map(() => Array(COLS).fill(EMPTY)); |
| | score = 0; |
| | lines = 0; |
| | level = 1; |
| | dropSpeed = 1000; |
| | isPaused = false; |
| | isGameOver = false; |
| | |
| | updateScore(); |
| | gameOverElement.classList.add('hidden'); |
| | startBtn.classList.add('hidden'); |
| | pauseBtn.classList.remove('hidden'); |
| | |
| | |
| | nextPiece = randomPiece(); |
| | spawnPiece(); |
| | |
| | |
| | gameInterval = setInterval(() => { |
| | if (!isPaused && !isGameOver) { |
| | moveDown(); |
| | } |
| | }, dropSpeed); |
| | |
| | draw(); |
| | } |
| | |
| | |
| | function togglePause() { |
| | isPaused = !isPaused; |
| | |
| | if (isPaused) { |
| | pauseBtn.textContent = 'Resume'; |
| | clearInterval(gameInterval); |
| | } else { |
| | pauseBtn.textContent = 'Pause'; |
| | gameInterval = setInterval(() => { |
| | if (!isPaused && !isGameOver) { |
| | moveDown(); |
| | } |
| | }, dropSpeed); |
| | } |
| | } |
| | |
| | |
| | function draw() { |
| | drawBoard(); |
| | drawPiece(); |
| | } |
| | |
| | |
| | function init() { |
| | initBoard(); |
| | initNextPiece(); |
| | |
| | |
| | document.addEventListener('keydown', (e) => { |
| | if (isGameOver) return; |
| | |
| | switch (e.key) { |
| | case 'ArrowLeft': |
| | moveLeft(); |
| | break; |
| | case 'ArrowRight': |
| | moveRight(); |
| | break; |
| | case 'ArrowDown': |
| | moveDown(); |
| | break; |
| | case 'ArrowUp': |
| | rotatePiece(); |
| | break; |
| | case ' ': |
| | hardDrop(); |
| | break; |
| | case 'p': |
| | case 'P': |
| | if (!isGameOver) togglePause(); |
| | break; |
| | } |
| | }); |
| | |
| | startBtn.addEventListener('click', startGame); |
| | pauseBtn.addEventListener('click', togglePause); |
| | restartBtn.addEventListener('click', startGame); |
| | } |
| | |
| | init(); |
| | }); |
| | </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=C50BARZ/tetris-2" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| | </html> |