tetris / index.html
FriedsU's picture
Add 2 files
a80016c verified
<!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;
}
/* New styles for next piece preview */
.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">
<!-- Game Board -->
<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>
<!-- Game Info -->
<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', () => {
// Game constants
const COLS = 10;
const ROWS = 20;
const BLOCK_SIZE = 25;
const EMPTY = 'empty';
// Game variables
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; // Initial speed (ms)
// DOM elements
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');
// Initialize the game board display
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);
}
}
}
// Create empty game board
function createEmptyBoard() {
return Array.from({ length: ROWS }, () => Array(COLS).fill(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]
]
};
// Tetromino colors
const COLORS = {
I: 'I',
J: 'J',
L: 'L',
O: 'O',
S: 'S',
T: 'T',
Z: 'Z'
};
// Get 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],
x: Math.floor(COLS / 2) - Math.floor(SHAPES[randomPiece][0].length / 2),
y: 0
};
}
// Draw the game board
function drawBoard() {
// Clear the board
document.querySelectorAll('#game-board div').forEach(cell => {
cell.className = 'grid-cell w-full h-full';
});
// Draw locked pieces
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`;
}
}
}
}
// Draw current piece
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`;
}
}
}
}
}
}
}
// Draw next piece preview
function drawNextPiece() {
if (!nextPiece) return;
// Clear the preview
nextPieceDisplay.innerHTML = '';
// Set up the grid based on the piece dimensions
const rows = nextPiece.shape.length;
const cols = nextPiece.shape[0].length;
// Calculate cell size to fit in the 100x100 container
const cellSize = Math.min(100 / cols, 100 / rows);
const containerWidth = cellSize * cols;
const containerHeight = cellSize * rows;
// Position the inner container in the center
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`;
// Draw the piece
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);
}
}
}
// Check for collisions
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;
// Check boundaries
if (newX < 0 || newX >= COLS || newY >= ROWS) {
return true;
}
// Check if already locked
if (newY >= 0 && board[newY][newX] !== EMPTY) {
return true;
}
}
}
}
return false;
}
// Rotate piece
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);
}
// Check if rotation is possible
if (!collision(currentPiece.x, currentPiece.y, newShape)) {
currentPiece.shape = newShape;
drawBoard();
}
}
// Move piece left
function moveLeft() {
if (!currentPiece || gameOver || isPaused) return;
if (!collision(currentPiece.x - 1, currentPiece.y, currentPiece.shape)) {
currentPiece.x--;
drawBoard();
}
}
// Move piece right
function moveRight() {
if (!currentPiece || gameOver || isPaused) return;
if (!collision(currentPiece.x + 1, currentPiece.y, currentPiece.shape)) {
currentPiece.x++;
drawBoard();
}
}
// Move piece down
function moveDown() {
if (!currentPiece || gameOver || isPaused) return;
if (!collision(currentPiece.x, currentPiece.y + 1, currentPiece.shape)) {
currentPiece.y++;
drawBoard();
} else {
lockPiece();
}
}
// Drop piece instantly
function dropPiece() {
if (!currentPiece || gameOver || isPaused) return;
while (!collision(currentPiece.x, currentPiece.y + 1, currentPiece.shape)) {
currentPiece.y++;
}
lockPiece();
drawBoard();
}
// Lock piece in place
function lockPiece() {
if (!currentPiece) return;
// Add piece to the board
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;
}
}
}
}
// Check for completed lines
checkLines();
// Check for game over
if (currentPiece.y <= 0) {
gameOver = true;
clearInterval(dropInterval);
gameOverlay.classList.remove('hidden');
return;
}
// Get next piece
currentPiece = nextPiece;
nextPiece = getRandomPiece();
drawNextPiece();
}
// Check for completed lines
function checkLines() {
let linesCleared = 0;
for (let row = ROWS - 1; row >= 0; row--) {
if (board[row].every(cell => cell !== EMPTY)) {
// Remove the line
board.splice(row, 1);
// Add new empty line at the top
board.unshift(Array(COLS).fill(EMPTY));
linesCleared++;
row++; // Check the same row again
}
}
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;
// Increase speed
dropSpeed = Math.max(100, 1000 - (level - 1) * 100);
clearInterval(dropInterval);
dropInterval = setInterval(moveDown, dropSpeed);
}
updateStats();
}
}
// Update score, level, and lines
function updateStats() {
scoreDisplay.textContent = score;
levelDisplay.textContent = level;
linesDisplay.textContent = lines;
}
// Start the game
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);
}
// Pause the game
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';
}
}
// Event listeners
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);
// Touch controls for mobile
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;
// Swipe left
if (diffX > 50 && Math.abs(diffY) < 100) {
moveLeft();
}
// Swipe right
else if (diffX < -50 && Math.abs(diffY) < 100) {
moveRight();
}
// Swipe down
else if (diffY < -50 && Math.abs(diffX) < 100) {
dropPiece();
}
// Swipe up or tap
else if (Math.abs(diffX) < 50 && Math.abs(diffY) < 50) {
rotatePiece();
}
}, false);
// Initialize the game
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>