retrotetris / index.html
MitchiMitch's picture
it doesnt work now, please fix the game and make sure it works - Initial Deployment
bdf8912 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Retro Tetris</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.particle {
position: absolute;
width: 4px;
height: 4px;
border-radius: 50%;
pointer-events: none;
z-index: 10;
}
@keyframes flash {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
.flash {
animation: flash 0.5s infinite;
}
@keyframes lineClear {
0% { transform: scaleY(1); opacity: 1; }
50% { transform: scaleY(0.1); opacity: 0.5; }
100% { transform: scaleY(1); opacity: 1; }
}
.line-clear {
animation: lineClear 0.3s ease-out;
}
.cell {
width: 30px;
height: 30px;
border: 1px solid rgba(255, 255, 255, 0.1);
box-sizing: border-box;
}
@media (max-width: 640px) {
.cell {
width: 20px;
height: 20px;
}
}
.game-container {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
box-shadow: 0 0 30px rgba(0, 0, 0, 0.5);
}
.grid-line {
position: absolute;
background-color: rgba(255, 255, 255, 0.05);
}
.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; }
.ghost {
opacity: 0.3;
}
</style>
</head>
<body class="bg-gray-900 text-white min-h-screen flex flex-col items-center justify-center p-4 font-mono">
<div class="text-center mb-4">
<h1 class="text-4xl md:text-5xl font-bold mb-2 text-transparent bg-clip-text bg-gradient-to-r from-purple-400 to-cyan-400">
RETRO TETRIS
</h1>
<p class="text-gray-400">Use arrow keys to move, space to drop, and 'P' to pause</p>
</div>
<div class="flex flex-col md:flex-row items-center justify-center gap-8">
<div class="game-container relative rounded-lg overflow-hidden">
<div id="game-board" class="grid grid-cols-10 grid-rows-20 gap-0 relative"></div>
<!-- Grid lines for visual enhancement -->
<div class="grid-lines absolute inset-0 pointer-events-none">
<!-- Vertical lines -->
<div class="grid-line w-px h-full" style="left: 0%;"></div>
<div class="grid-line w-px h-full" style="left: 10%;"></div>
<div class="grid-line w-px h-full" style="left: 20%;"></div>
<div class="grid-line w-px h-full" style="left: 30%;"></div>
<div class="grid-line w-px h-full" style="left: 40%;"></div>
<div class="grid-line w-px h-full" style="left: 50%;"></div>
<div class="grid-line w-px h-full" style="left: 60%;"></div>
<div class="grid-line w-px h-full" style="left: 70%;"></div>
<div class="grid-line w-px h-full" style="left: 80%;"></div>
<div class="grid-line w-px h-full" style="left: 90%;"></div>
<div class="grid-line w-px h-full" style="left: 100%;"></div>
<!-- Horizontal lines -->
<div class="grid-line w-full h-px" style="top: 0%;"></div>
<div class="grid-line w-full h-px" style="top: 5%;"></div>
<div class="grid-line w-full h-px" style="top: 10%;"></div>
<div class="grid-line w-full h-px" style="top: 15%;"></div>
<div class="grid-line w-full h-px" style="top: 20%;"></div>
<div class="grid-line w-full h-px" style="top: 25%;"></div>
<div class="grid-line w-full h-px" style="top: 30%;"></div>
<div class="grid-line w-full h-px" style="top: 35%;"></div>
<div class="grid-line w-full h-px" style="top: 40%;"></div>
<div class="grid-line w-full h-px" style="top: 45%;"></div>
<div class="grid-line w-full h-px" style="top: 50%;"></div>
<div class="grid-line w-full h-px" style="top: 55%;"></div>
<div class="grid-line w-full h-px" style="top: 60%;"></div>
<div class="grid-line w-full h-px" style="top: 65%;"></div>
<div class="grid-line w-full h-px" style="top: 70%;"></div>
<div class="grid-line w-full h-px" style="top: 75%;"></div>
<div class="grid-line w-full h-px" style="top: 80%;"></div>
<div class="grid-line w-full h-px" style="top: 85%;"></div>
<div class="grid-line w-full h-px" style="top: 90%;"></div>
<div class="grid-line w-full h-px" style="top: 95%;"></div>
<div class="grid-line w-full h-px" style="top: 100%;"></div>
</div>
</div>
<div class="game-info bg-gray-800 p-6 rounded-lg w-full max-w-xs">
<div class="mb-6">
<h2 class="text-xl font-bold mb-2 text-cyan-400">STATS</h2>
<div class="grid grid-cols-2 gap-4">
<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>
<p class="text-gray-400">Lines</p>
<p id="lines" class="text-2xl font-bold">0</p>
</div>
</div>
</div>
<div class="mb-6">
<h2 class="text-xl font-bold mb-2 text-cyan-400">HOLD</h2>
<div id="hold-piece" class="grid grid-cols-4 grid-rows-4 gap-1 bg-gray-700 p-2 rounded h-32 mb-4"></div>
<h2 class="text-xl font-bold mb-2 text-cyan-400">NEXT</h2>
<div id="next-piece" class="grid grid-cols-4 grid-rows-4 gap-1 bg-gray-700 p-2 rounded h-32"></div>
<div id="next-piece-2" class="grid grid-cols-4 grid-rows-4 gap-1 bg-gray-700 p-2 rounded h-32 mt-2"></div>
<div id="next-piece-3" class="grid grid-cols-4 grid-rows-4 gap-1 bg-gray-700 p-2 rounded h-32 mt-2"></div>
</div>
<div class="mb-6">
<h2 class="text-xl font-bold mb-2 text-cyan-400">CONTROLS</h2>
<div class="grid grid-cols-3 gap-2 text-center">
<div class="bg-gray-700 p-2 rounded">
<i class="fas fa-arrow-up"></i>
<p class="text-xs mt-1">Rotate</p>
</div>
<div class="bg-gray-700 p-2 rounded">
<i class="fas fa-arrow-left"></i>
<p class="text-xs mt-1">Left</p>
</div>
<div class="bg-gray-700 p-2 rounded">
<i class="fas fa-arrow-right"></i>
<p class="text-xs mt-1">Right</p>
</div>
<div class="bg-gray-700 p-2 rounded">
<i class="fas fa-arrow-down"></i>
<p class="text-xs mt-1">Soft Drop</p>
</div>
<div class="bg-gray-700 p-2 rounded">
<i class="fas fa-space-shuttle"></i>
<p class="text-xs mt-1">Hard Drop</p>
</div>
<div class="bg-gray-700 p-2 rounded">
<i class="fas fa-pause"></i>
<p class="text-xs mt-1">Pause</p>
</div>
</div>
</div>
<button id="start-btn" class="w-full bg-gradient-to-r from-purple-500 to-cyan-500 py-2 rounded-lg font-bold hover:opacity-90 transition">
START GAME
</button>
<button id="pause-btn" class="w-full bg-yellow-500 py-2 rounded-lg font-bold hover:opacity-90 transition hidden mt-2">
PAUSE
</button>
</div>
</div>
<div id="game-over" class="fixed inset-0 bg-black bg-opacity-80 flex items-center justify-center hidden z-50">
<div class="bg-gray-800 p-8 rounded-lg max-w-md w-full text-center">
<h2 class="text-3xl font-bold text-red-500 mb-4">GAME OVER</h2>
<p class="text-xl mb-2">Final Score: <span id="final-score" class="font-bold">0</span></p>
<p class="text-lg mb-6">Lines Cleared: <span id="final-lines" class="font-bold">0</span></p>
<button id="restart-btn" class="bg-gradient-to-r from-purple-500 to-cyan-500 py-2 px-6 rounded-lg font-bold hover:opacity-90 transition">
PLAY AGAIN
</button>
</div>
</div>
<div id="pause-screen" class="fixed inset-0 bg-black bg-opacity-80 flex items-center justify-center hidden z-50">
<div class="bg-gray-800 p-8 rounded-lg max-w-md w-full text-center">
<h2 class="text-3xl font-bold text-yellow-500 mb-6">GAME PAUSED</h2>
<button id="resume-btn" class="bg-gradient-to-r from-purple-500 to-cyan-500 py-2 px-6 rounded-lg font-bold hover:opacity-90 transition">
RESUME
</button>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// Sound effects
const sounds = {
rotate: new Audio('https://assets.mixkit.co/sfx/preview/mixkit-game-click-1114.mp3'),
move: new Audio('https://assets.mixkit.co/sfx/preview/mixkit-arcade-game-jump-coin-216.mp3'),
drop: new Audio('https://assets.mixkit.co/sfx/preview/mixkit-quick-jump-arcade-game-239.mp3'),
clear: new Audio('https://assets.mixkit.co/sfx/preview/mixkit-unlock-game-notification-253.mp3'),
gameover: new Audio('https://assets.mixkit.co/sfx/preview/mixkit-retro-arcade-lose-2027.mp3')
};
// Game constants
const COLS = 10;
const ROWS = 20;
const BLOCK_SIZE = 30;
const NEXT_PIECE_SIZE = 4;
// Game variables
let board = Array(ROWS).fill().map(() => Array(COLS).fill(0));
let currentPiece = null;
let nextPiece = null;
let holdPiece = null;
let canHold = true;
let currentPosition = { x: 0, y: 0 };
let score = 0;
let level = 1;
let lines = 0;
let gameInterval = null;
let gameSpeed = 1000;
let isGameRunning = false;
let isPaused = false;
let lastDropTime = 0;
// DOM elements
const gameBoard = document.getElementById('game-board');
const nextPieceDisplay = document.getElementById('next-piece');
const holdPieceDisplay = document.getElementById('hold-piece');
const scoreDisplay = document.getElementById('score');
const levelDisplay = document.getElementById('level');
const linesDisplay = document.getElementById('lines');
const startBtn = document.getElementById('start-btn');
const pauseBtn = document.getElementById('pause-btn');
const gameOverScreen = document.getElementById('game-over');
const finalScoreDisplay = document.getElementById('final-score');
const finalLinesDisplay = document.getElementById('final-lines');
const restartBtn = document.getElementById('restart-btn');
const pauseScreen = document.getElementById('pause-screen');
const resumeBtn = document.getElementById('resume-btn');
// 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'
};
// Initialize game board
function initBoard() {
gameBoard.innerHTML = '';
gameBoard.style.width = `${COLS * BLOCK_SIZE}px`;
gameBoard.style.height = `${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';
cell.id = `cell-${y}-${x}`;
gameBoard.appendChild(cell);
}
}
}
// Initialize piece displays
function initPieceDisplays() {
// Next piece display
nextPieceDisplay.innerHTML = '';
nextPieceDisplay.style.width = `${NEXT_PIECE_SIZE * BLOCK_SIZE}px`;
nextPieceDisplay.style.height = `${NEXT_PIECE_SIZE * BLOCK_SIZE}px`;
for (let y = 0; y < NEXT_PIECE_SIZE; y++) {
for (let x = 0; x < NEXT_PIECE_SIZE; x++) {
const cell = document.createElement('div');
cell.className = 'cell bg-gray-700';
cell.id = `next-cell-${y}-${x}`;
nextPieceDisplay.appendChild(cell);
}
}
// Hold piece display
holdPieceDisplay.innerHTML = '';
holdPieceDisplay.style.width = `${NEXT_PIECE_SIZE * BLOCK_SIZE}px`;
holdPieceDisplay.style.height = `${NEXT_PIECE_SIZE * BLOCK_SIZE}px`;
for (let y = 0; y < NEXT_PIECE_SIZE; y++) {
for (let x = 0; x < NEXT_PIECE_SIZE; x++) {
const cell = document.createElement('div');
cell.className = 'cell bg-gray-700';
cell.id = `hold-cell-${y}-${x}`;
holdPieceDisplay.appendChild(cell);
}
}
}
// Get random piece
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 piece on board
function drawPiece(piece, position, isGhost = false) {
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 (boardY >= 0 && boardY < ROWS && boardX >= 0 && boardX < COLS) {
const cell = document.getElementById(`cell-${boardY}-${boardX}`);
if (cell) {
cell.className = `cell ${piece.color} ${isGhost ? 'ghost' : ''}`;
}
}
}
}
}
}
// Draw next piece
function drawNextPiece() {
// Clear next piece display
const cells = nextPieceDisplay.querySelectorAll('.cell');
cells.forEach(cell => {
cell.className = 'cell bg-gray-700';
});
// Center the piece in the next piece display
const offsetX = Math.floor((NEXT_PIECE_SIZE - nextPiece.shape[0].length) / 2);
const offsetY = Math.floor((NEXT_PIECE_SIZE - 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}`);
if (cell) {
cell.className = `cell ${nextPiece.color}`;
}
}
}
}
}
// Clear board (except locked pieces)
function clearBoard() {
for (let y = 0; y < ROWS; y++) {
for (let x = 0; x < COLS; x++) {
if (board[y][x] === 0) {
const cell = document.getElementById(`cell-${y}-${x}`);
if (cell) {
cell.className = 'cell';
}
}
}
}
}
// Draw locked pieces
function drawLockedPieces() {
for (let y = 0; y < ROWS; y++) {
for (let x = 0; x < COLS; x++) {
if (board[y][x] !== 0) {
const cell = document.getElementById(`cell-${y}-${x}`);
if (cell) {
cell.className = `cell ${board[y][x]}`;
}
}
}
}
}
// Check collision
function checkCollision(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;
// Check boundaries
if (boardX < 0 || boardX >= COLS || boardY >= ROWS) {
return true;
}
// Check if already occupied (and not above the board)
if (boardY >= 0 && board[boardY][boardX] !== 0) {
return true;
}
}
}
}
return false;
}
// Rotate piece
function rotatePiece() {
if (!currentPiece) return;
const rotated = [];
for (let x = 0; x < currentPiece.shape[0].length; x++) {
const newRow = [];
for (let y = currentPiece.shape.length - 1; y >= 0; y--) {
newRow.push(currentPiece.shape[y][x]);
}
rotated.push(newRow);
}
const originalShape = currentPiece.shape;
currentPiece.shape = rotated;
// Check if rotation causes collision
if (checkCollision(currentPiece, currentPosition)) {
// Try wall kicks
const originalX = currentPosition.x;
// Try moving left
currentPosition.x--;
if (checkCollision(currentPiece, currentPosition)) {
// Try moving right
currentPosition.x = originalX + 1;
if (checkCollision(currentPiece, currentPosition)) {
// Try moving left twice
currentPosition.x = originalX - 2;
if (checkCollision(currentPiece, currentPosition)) {
// Try moving right twice
currentPosition.x = originalX + 2;
if (checkCollision(currentPiece, currentPosition)) {
// Revert if all wall kicks fail
currentPosition.x = originalX;
currentPiece.shape = originalShape;
}
}
}
}
}
updateDisplay();
}
// Move piece
function movePiece(direction) {
if (!currentPiece || isPaused) return;
const newPosition = { ...currentPosition };
switch (direction) {
case 'left':
newPosition.x--;
break;
case 'right':
newPosition.x++;
break;
case 'down':
newPosition.y++;
break;
}
if (!checkCollision(currentPiece, newPosition)) {
currentPosition = newPosition;
updateDisplay();
return true;
}
// If moving down and collision, lock the piece
if (direction === 'down') {
lockPiece();
return false;
}
return false;
}
// Hard drop
function hardDrop() {
if (!currentPiece || isPaused) return;
let dropDistance = 0;
while (!checkCollision(currentPiece, { ...currentPosition, y: currentPosition.y + dropDistance + 1 })) {
dropDistance++;
}
if (dropDistance > 0) {
currentPosition.y += dropDistance;
updateDisplay();
}
lockPiece();
}
// Lock piece
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 piece is locked above the board, game over
if (boardY < 0) {
gameOver();
return;
}
board[boardY][boardX] = currentPiece.color;
}
}
}
// Check for completed lines
checkLines();
// Get next piece
spawnPiece();
}
// Create particle effects
function createParticles(x, y, color, count = 10) {
for (let i = 0; i < count; i++) {
const particle = document.createElement('div');
particle.className = 'particle';
particle.style.backgroundColor = color;
particle.style.left = `${x}px`;
particle.style.top = `${y}px`;
const angle = Math.random() * Math.PI * 2;
const velocity = 2 + Math.random() * 3;
const lifetime = 500 + Math.random() * 500;
document.body.appendChild(particle);
const startTime = Date.now();
function update() {
const elapsed = Date.now() - startTime;
const progress = elapsed / lifetime;
if (progress >= 1) {
particle.remove();
return;
}
particle.style.opacity = 1 - progress;
particle.style.transform = `translate(${Math.cos(angle) * velocity * elapsed / 20}px, ${Math.sin(angle) * velocity * elapsed / 20}px)`;
requestAnimationFrame(update);
}
update();
}
}
// Check for completed lines
function checkLines() {
let linesCleared = 0;
let completedLines = [];
for (let y = ROWS - 1; y >= 0; y--) {
if (board[y].every(cell => cell !== 0)) {
completedLines.push(y);
}
}
if (completedLines.length > 0) {
// Visual feedback for line clears
completedLines.forEach(y => {
for (let x = 0; x < COLS; x++) {
const cell = document.getElementById(`cell-${y}-${x}`);
if (cell) cell.classList.add('line-clear');
}
});
// Wait for animation to complete
setTimeout(() => {
completedLines.sort((a, b) => a - b);
completedLines.forEach(y => {
// Remove the line
board.splice(y, 1);
// Add new empty line at top
board.unshift(Array(COLS).fill(0));
linesCleared++;
});
// Update score with combo bonus and back-to-back
const basePoints = [0, 100, 300, 500, 800];
const comboBonus = (completedLines.length - 1) * 100;
const backToBackBonus = completedLines.length >= 4 ? 1200 : 0;
score += (basePoints[completedLines.length] + comboBonus + backToBackBonus) * level;
// Play clear sound
sounds.clear.currentTime = 0;
sounds.clear.play();
// Create particles
completedLines.forEach(y => {
for (let x = 0; x < COLS; x++) {
const cell = document.getElementById(`cell-${y}-${x}`);
if (cell) {
const rect = cell.getBoundingClientRect();
createParticles(
rect.left + rect.width/2,
rect.top + rect.height/2,
getComputedStyle(cell).backgroundColor,
5
);
}
}
});
lines += completedLines.length;
// Update level (every 10 lines)
const newLevel = Math.floor(lines / 10) + 1;
if (newLevel > level) {
level = newLevel;
// Increase game speed (capped at 100ms)
gameSpeed = Math.max(100, 1000 - (level - 1) * 75);
clearInterval(gameInterval);
gameInterval = setInterval(gameTick, gameSpeed);
}
updateStats();
updateDisplay();
}, 300);
}
}
// Hold current piece
function holdCurrentPiece() {
if (!canHold) return;
if (holdPiece) {
// Swap current piece with hold piece
const temp = currentPiece;
currentPiece = holdPiece;
holdPiece = temp;
} else {
// First hold - just store current piece
holdPiece = currentPiece;
currentPiece = nextPiece;
nextPiece = getRandomPiece();
}
// Reset position
currentPosition = {
x: Math.floor((COLS - currentPiece.shape[0].length) / 2),
y: -2
};
// Update displays
drawHoldPiece();
drawNextPiece();
updateDisplay();
canHold = false;
}
// Draw hold piece
function drawHoldPiece() {
// Clear hold piece display
const cells = holdPieceDisplay.querySelectorAll('.cell');
cells.forEach(cell => {
cell.className = 'cell bg-gray-700';
});
if (!holdPiece) return;
// Center the piece in the hold display
const offsetX = Math.floor((NEXT_PIECE_SIZE - holdPiece.shape[0].length) / 2);
const offsetY = Math.floor((NEXT_PIECE_SIZE - holdPiece.shape.length) / 2);
for (let y = 0; y < holdPiece.shape.length; y++) {
for (let x = 0; x < holdPiece.shape[y].length; x++) {
if (holdPiece.shape[y][x]) {
const displayY = y + offsetY;
const displayX = x + offsetX;
const cell = document.getElementById(`hold-cell-${displayY}-${displayX}`);
if (cell) {
cell.className = `cell ${holdPiece.color}`;
}
}
}
}
}
// Spawn new piece
function spawnPiece() {
currentPiece = nextPiece || getRandomPiece();
nextPiece = getRandomPiece();
// Set starting position (centered at top)
currentPosition = {
x: Math.floor((COLS - currentPiece.shape[0].length) / 2),
y: -2 // Start slightly above the board
};
// If collision immediately, game over
if (checkCollision(currentPiece, currentPosition)) {
gameOver();
return;
}
canHold = true;
drawHoldPiece();
drawNextPiece();
updateDisplay();
}
// Update display
function updateDisplay() {
clearBoard();
drawLockedPieces();
// Draw ghost piece (shows where piece will land)
if (currentPiece) {
const ghostPosition = { ...currentPosition };
while (!checkCollision(currentPiece, { ...ghostPosition, y: ghostPosition.y + 1 })) {
ghostPosition.y++;
}
drawPiece(currentPiece, ghostPosition, true);
}
// Draw current piece
if (currentPiece) {
drawPiece(currentPiece, currentPosition);
}
}
// Update stats display
function updateStats() {
scoreDisplay.textContent = score;
levelDisplay.textContent = level;
linesDisplay.textContent = lines;
}
// Game tick
function gameTick() {
if (!isPaused) {
movePiece('down');
}
}
// Start game
function startGame() {
// Reset game state
board = Array(ROWS).fill().map(() => Array(COLS).fill(0));
score = 0;
level = 1;
lines = 0;
gameSpeed = 1000;
isGameRunning = true;
isPaused = false;
updateStats();
initBoard();
initPieceDisplays();
// Get first pieces
nextPiece = getRandomPiece();
spawnPiece();
// Start game loop
clearInterval(gameInterval);
gameInterval = setInterval(gameTick, gameSpeed);
// Update UI
startBtn.classList.add('hidden');
pauseBtn.classList.remove('hidden');
gameOverScreen.classList.add('hidden');
pauseScreen.classList.add('hidden');
}
// Pause game
function pauseGame() {
isPaused = !isPaused;
if (isPaused) {
pauseBtn.textContent = 'RESUME';
pauseScreen.classList.remove('hidden');
} else {
pauseBtn.textContent = 'PAUSE';
pauseScreen.classList.add('hidden');
}
}
// Game over
function gameOver() {
isGameRunning = false;
clearInterval(gameInterval);
// Flash the board
const cells = gameBoard.querySelectorAll('.cell');
cells.forEach(cell => {
cell.classList.add('flash');
});
// Show game over screen
setTimeout(() => {
cells.forEach(cell => {
cell.classList.remove('flash');
});
finalScoreDisplay.textContent = score;
finalLinesDisplay.textContent = lines;
gameOverScreen.classList.remove('hidden');
pauseBtn.classList.add('hidden');
startBtn.classList.remove('hidden');
}, 1000);
}
// Event listeners
startBtn.addEventListener('click', startGame);
restartBtn.addEventListener('click', startGame);
pauseBtn.addEventListener('click', pauseGame);
resumeBtn.addEventListener('click', pauseGame);
// Keyboard controls
document.addEventListener('keydown', (e) => {
if (!isGameRunning) return;
switch (e.key) {
case 'ArrowLeft':
movePiece('left');
break;
case 'ArrowRight':
movePiece('right');
break;
case 'ArrowDown':
movePiece('down');
break;
case 'ArrowUp':
rotatePiece();
break;
case ' ':
hardDrop();
break;
case 'p':
case 'P':
pauseGame();
break;
case 'c':
case 'C':
case 'Shift':
holdCurrentPiece();
break;
}
});
// Touch controls for mobile
let touchStartX = 0;
let touchStartY = 0;
gameBoard.addEventListener('touchstart', (e) => {
if (!isGameRunning) return;
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
e.preventDefault();
}, { passive: false });
gameBoard.addEventListener('touchmove', (e) => {
if (!isGameRunning) return;
const touchX = e.touches[0].clientX;
const touchY = e.touches[0].clientY;
const diffX = touchX - touchStartX;
const diffY = touchY - touchStartY;
// Horizontal swipe
if (Math.abs(diffX) > Math.abs(diffY)) {
if (diffX > 30) {
movePiece('right');
touchStartX = touchX;
} else if (diffX < -30) {
movePiece('left');
touchStartX = touchX;
}
}
// Vertical swipe down
else if (diffY > 30) {
movePiece('down');
touchStartY = touchY;
}
e.preventDefault();
}, { passive: false });
gameBoard.addEventListener('touchend', (e) => {
if (!isGameRunning) return;
const touchEndX = e.changedTouches[0].clientX;
const touchEndY = e.changedTouches[0].clientY;
const diffX = touchEndX - touchStartX;
const diffY = touchEndY - touchStartY;
// Tap (small movement)
if (Math.abs(diffX) < 10 && Math.abs(diffY) < 10) {
rotatePiece();
}
// Quick swipe up
else if (diffY < -30) {
hardDrop();
}
e.preventDefault();
}, { passive: false });
// Initialize displays
initBoard();
initNextPieceDisplay();
});
</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=MitchiMitch/retrotetris" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>