Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Simple Tetris</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<style> | |
.cell { | |
width: 30px; | |
height: 30px; | |
border: 1px solid #ccc; | |
} | |
.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; } | |
#game-board { | |
display: grid; | |
grid-template-rows: repeat(20, 30px); | |
grid-template-columns: repeat(10, 30px); | |
gap: 1px; | |
background-color: #111; | |
} | |
#next-piece { | |
display: grid; | |
grid-template-rows: repeat(4, 30px); | |
grid-template-columns: repeat(4, 30px); | |
gap: 1px; | |
background-color: #111; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-900 text-white min-h-screen flex flex-col items-center justify-center p-4"> | |
<div class="text-center mb-4"> | |
<h1 class="text-4xl font-bold mb-2">Tetris</h1> | |
<div class="flex justify-center gap-8"> | |
<div> | |
<div class="text-xl font-semibold mb-2">Score: <span id="score">0</span></div> | |
<div class="text-xl font-semibold mb-2">Level: <span id="level">1</span></div> | |
<div class="text-xl font-semibold mb-2">Lines: <span id="lines">0</span></div> | |
</div> | |
<div> | |
<div class="text-xl font-semibold mb-2">High Score: <span id="high-score">0</span></div> | |
<div class="text-lg mb-2">Next:</div> | |
<div id="next-piece" class="mb-4"></div> | |
</div> | |
</div> | |
</div> | |
<div class="flex gap-8 items-start"> | |
<div id="game-board"></div> | |
</div> | |
<div class="mt-6 flex gap-4"> | |
<button id="start-btn" class="bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded"> | |
Start Game | |
</button> | |
<button id="pause-btn" class="bg-yellow-600 hover:bg-yellow-700 text-white font-bold py-2 px-4 rounded" disabled> | |
Pause | |
</button> | |
<button id="reset-btn" class="bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-4 rounded"> | |
Reset | |
</button> | |
</div> | |
<div class="mt-6 text-center"> | |
<p class="mb-2">Controls:</p> | |
<p>β β : Move Left/Right</p> | |
<p>β : Rotate</p> | |
<p>β : Soft Drop</p> | |
<p>Space : Hard Drop</p> | |
<p>P : Pause</p> | |
</div> | |
<script> | |
// Game constants | |
const COLS = 10; | |
const ROWS = 20; | |
const BLOCK_SIZE = 30; | |
const EMPTY = 'empty'; | |
// Tetromino shapes | |
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: 'piece-I', | |
J: 'piece-J', | |
L: 'piece-L', | |
O: 'piece-O', | |
S: 'piece-S', | |
T: 'piece-T', | |
Z: 'piece-Z' | |
}; | |
// Game variables | |
let board = Array(ROWS).fill().map(() => Array(COLS).fill(EMPTY)); | |
let currentPiece = null; | |
let nextPiece = null; | |
let currentX = 0; | |
let currentY = 0; | |
let score = 0; | |
let level = 1; | |
let lines = 0; | |
let gameInterval = null; | |
let gameSpeed = 1000; | |
let isPaused = false; | |
let isGameOver = false; | |
// DOM elements | |
const gameBoard = document.getElementById('game-board'); | |
const nextPieceDisplay = document.getElementById('next-piece'); | |
const scoreDisplay = document.getElementById('score'); | |
const levelDisplay = document.getElementById('level'); | |
const linesDisplay = document.getElementById('lines'); | |
const highScoreDisplay = document.getElementById('high-score'); | |
const startBtn = document.getElementById('start-btn'); | |
const pauseBtn = document.getElementById('pause-btn'); | |
const resetBtn = document.getElementById('reset-btn'); | |
// Initialize the game board | |
function initBoard() { | |
gameBoard.innerHTML = ''; | |
for (let y = 0; y < ROWS; y++) { | |
for (let x = 0; x < COLS; x++) { | |
const cell = document.createElement('div'); | |
cell.className = 'cell'; | |
cell.id = `cell-${y}-${x}`; | |
gameBoard.appendChild(cell); | |
} | |
} | |
} | |
// Initialize the next piece display | |
function initNextPieceDisplay() { | |
nextPieceDisplay.innerHTML = ''; | |
for (let y = 0; y < 4; y++) { | |
for (let x = 0; x < 4; x++) { | |
const cell = document.createElement('div'); | |
cell.className = 'cell'; | |
cell.id = `next-cell-${y}-${x}`; | |
nextPieceDisplay.appendChild(cell); | |
} | |
} | |
} | |
// Get a random tetromino | |
function getRandomPiece() { | |
const pieces = Object.keys(SHAPES); | |
const randomPiece = pieces[Math.floor(Math.random() * pieces.length)]; | |
return { | |
shape: SHAPES[randomPiece], | |
color: COLORS[randomPiece], | |
type: randomPiece | |
}; | |
} | |
// Draw the game board | |
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'; | |
if (board[y][x] !== EMPTY) { | |
cell.classList.add(board[y][x]); | |
} | |
} | |
} | |
} | |
// Draw the current piece | |
function drawPiece() { | |
if (!currentPiece) return; | |
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 = currentY + y; | |
const boardX = currentX + x; | |
if (boardY >= 0 && boardY < ROWS && boardX >= 0 && boardX < COLS) { | |
const cell = document.getElementById(`cell-${boardY}-${boardX}`); | |
cell.classList.add(currentPiece.color); | |
} | |
} | |
} | |
} | |
} | |
// Draw the next piece | |
function drawNextPiece() { | |
if (!nextPiece) return; | |
// Clear next piece display | |
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'; | |
} | |
} | |
// Draw the next piece centered | |
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 displayY = y + offsetY; | |
const displayX = x + offsetX; | |
const cell = document.getElementById(`next-cell-${displayY}-${displayX}`); | |
cell.classList.add(nextPiece.color); | |
} | |
} | |
} | |
} | |
// Check for collisions | |
function collision(x, y, piece) { | |
for (let py = 0; py < piece.shape.length; py++) { | |
for (let px = 0; px < piece.shape[py].length; px++) { | |
if (!piece.shape[py][px]) continue; | |
const newX = x + px; | |
const newY = y + py; | |
if (newX < 0 || newX >= COLS || newY >= ROWS) { | |
return true; | |
} | |
if (newY >= 0 && board[newY][newX] !== EMPTY) { | |
return true; | |
} | |
} | |
} | |
return false; | |
} | |
// Rotate the current piece | |
function rotate() { | |
if (!currentPiece || isPaused || isGameOver) return; | |
const newShape = currentPiece.shape[0].map((_, i) => | |
currentPiece.shape.map(row => row[i]).reverse() | |
); | |
const oldShape = currentPiece.shape; | |
currentPiece.shape = newShape; | |
if (collision(currentX, currentY, currentPiece)) { | |
currentPiece.shape = oldShape; | |
} else { | |
draw(); | |
} | |
} | |
// Move the current piece | |
function move(direction) { | |
if (!currentPiece || isPaused || isGameOver) return; | |
let newX = currentX; | |
let newY = currentY; | |
switch (direction) { | |
case 'left': | |
newX--; | |
break; | |
case 'right': | |
newX++; | |
break; | |
case 'down': | |
newY++; | |
break; | |
} | |
if (!collision(newX, newY, currentPiece)) { | |
currentX = newX; | |
currentY = newY; | |
draw(); | |
return true; | |
} | |
// If we couldn't move down, lock the piece | |
if (direction === 'down') { | |
lockPiece(); | |
clearLines(); | |
spawnPiece(); | |
if (collision(currentX, currentY, currentPiece)) { | |
gameOver(); | |
} | |
} | |
return false; | |
} | |
// Hard drop the current piece | |
function hardDrop() { | |
if (!currentPiece || isPaused || isGameOver) return; | |
while (move('down')) { | |
// Keep moving down until we can't anymore | |
} | |
} | |
// Lock the current piece to the board | |
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 = currentY + y; | |
const boardX = currentX + x; | |
if (boardY >= 0 && boardX >= 0 && boardY < ROWS && boardX < COLS) { | |
board[boardY][boardX] = currentPiece.color; | |
} | |
} | |
} | |
} | |
} | |
// Clear completed lines | |
function clearLines() { | |
let linesCleared = 0; | |
for (let y = ROWS - 1; y >= 0; y--) { | |
if (board[y].every(cell => cell !== EMPTY)) { | |
// Remove the line | |
board.splice(y, 1); | |
// Add a new empty line at the top | |
board.unshift(Array(COLS).fill(EMPTY)); | |
linesCleared++; | |
y++; // Check the same row again (now with new content) | |
} | |
} | |
if (linesCleared > 0) { | |
// Update score | |
const points = [0, 40, 100, 300, 1200][linesCleared] * level; | |
score += points; | |
lines += linesCleared; | |
// Update level every 10 lines | |
const newLevel = Math.floor(lines / 10) + 1; | |
if (newLevel > level) { | |
level = newLevel; | |
gameSpeed = Math.max(100, 1000 - (level - 1) * 100); | |
clearInterval(gameInterval); | |
gameInterval = setInterval(gameLoop, gameSpeed); | |
} | |
updateDisplay(); | |
} | |
} | |
// Spawn a new piece | |
function spawnPiece() { | |
currentPiece = nextPiece || getRandomPiece(); | |
nextPiece = getRandomPiece(); | |
// Start position (centered at top) | |
currentX = Math.floor(COLS / 2) - Math.floor(currentPiece.shape[0].length / 2); | |
currentY = 0; | |
drawNextPiece(); | |
} | |
// Game loop | |
function gameLoop() { | |
if (!isPaused && !isGameOver) { | |
move('down'); | |
} | |
} | |
// Start the game | |
function startGame() { | |
if (gameInterval) { | |
clearInterval(gameInterval); | |
} | |
// Reset game state | |
board = Array(ROWS).fill().map(() => Array(COLS).fill(EMPTY)); | |
score = 0; | |
level = 1; | |
lines = 0; | |
gameSpeed = 1000; | |
isPaused = false; | |
isGameOver = false; | |
// Get high score from local storage | |
const highScore = localStorage.getItem('tetrisHighScore') || 0; | |
highScoreDisplay.textContent = highScore; | |
// Initialize pieces | |
nextPiece = getRandomPiece(); | |
spawnPiece(); | |
// Start game loop | |
gameInterval = setInterval(gameLoop, gameSpeed); | |
// Update UI | |
updateDisplay(); | |
startBtn.disabled = true; | |
pauseBtn.disabled = false; | |
pauseBtn.textContent = 'Pause'; | |
} | |
// Pause the game | |
function togglePause() { | |
if (isGameOver) return; | |
isPaused = !isPaused; | |
pauseBtn.textContent = isPaused ? 'Resume' : 'Pause'; | |
} | |
// Reset the game | |
function resetGame() { | |
clearInterval(gameInterval); | |
gameInterval = null; | |
isGameOver = true; | |
startBtn.disabled = false; | |
pauseBtn.disabled = true; | |
// Clear the board | |
board = Array(ROWS).fill().map(() => Array(COLS).fill(EMPTY)); | |
draw(); | |
} | |
// Game over | |
function gameOver() { | |
clearInterval(gameInterval); | |
isGameOver = true; | |
// Update high score if needed | |
const highScore = localStorage.getItem('tetrisHighScore') || 0; | |
if (score > highScore) { | |
localStorage.setItem('tetrisHighScore', score); | |
highScoreDisplay.textContent = score; | |
} | |
alert(`Game Over! Your score: ${score}`); | |
startBtn.disabled = false; | |
pauseBtn.disabled = true; | |
} | |
// Update the display | |
function updateDisplay() { | |
scoreDisplay.textContent = score; | |
levelDisplay.textContent = level; | |
linesDisplay.textContent = lines; | |
} | |
// Draw everything | |
function draw() { | |
drawBoard(); | |
drawPiece(); | |
} | |
// Event listeners | |
document.addEventListener('keydown', (e) => { | |
if (isGameOver) return; | |
switch (e.key) { | |
case 'ArrowLeft': | |
move('left'); | |
break; | |
case 'ArrowRight': | |
move('right'); | |
break; | |
case 'ArrowDown': | |
move('down'); | |
break; | |
case 'ArrowUp': | |
rotate(); | |
break; | |
case ' ': | |
hardDrop(); | |
break; | |
case 'p': | |
case 'P': | |
togglePause(); | |
break; | |
} | |
}); | |
startBtn.addEventListener('click', startGame); | |
pauseBtn.addEventListener('click', togglePause); | |
resetBtn.addEventListener('click', resetGame); | |
// Initialize the game | |
initBoard(); | |
initNextPieceDisplay(); | |
// Load high score | |
const highScore = localStorage.getItem('tetrisHighScore') || 0; | |
highScoreDisplay.textContent = highScore; | |
</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=mahen23/tetris-one" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |