tetris-one / index.html
mahen23's picture
Add 2 files
22ad172 verified
<!DOCTYPE html>
<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>