tetris-2 / index.html
C50BARZ's picture
Add 2 files
cb5b41c verified
<!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">
<!-- Game Board -->
<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>
<!-- Side Panel -->
<div class="flex flex-col gap-6 w-full md:w-64">
<!-- Score Display -->
<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>
<!-- Next Piece -->
<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>
<!-- Controls -->
<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>
<!-- Game Buttons -->
<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', () => {
// Game constants
const COLS = 10;
const ROWS = 20;
const BLOCK_SIZE = 30;
const EMPTY = 'empty';
// Game variables
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; // Initial speed in ms
// Piece shapes and colors
const PIECES = [
{ shape: [[1, 1, 1, 1]], color: 'piece-i' }, // I
{ shape: [[1, 0, 0], [1, 1, 1]], color: 'piece-j' }, // J
{ shape: [[0, 0, 1], [1, 1, 1]], color: 'piece-l' }, // L
{ shape: [[1, 1], [1, 1]], color: 'piece-o' }, // O
{ shape: [[0, 1, 1], [1, 1, 0]], color: 'piece-s' }, // S
{ shape: [[0, 1, 0], [1, 1, 1]], color: 'piece-t' }, // T
{ shape: [[1, 1, 0], [0, 1, 1]], color: 'piece-z' } // Z
];
// DOM elements
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');
// Initialize the game board
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);
}
}
}
// Initialize the next piece display
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);
}
}
}
// Generate a random piece
function randomPiece() {
const randomIndex = Math.floor(Math.random() * PIECES.length);
return {
shape: PIECES[randomIndex].shape,
color: PIECES[randomIndex].color
};
}
// 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 w-full h-full border border-gray-800';
if (board[y][x] !== EMPTY) {
cell.classList.add(board[y][x]);
}
}
}
}
// Draw the current piece
function drawPiece() {
// Draw ghost piece (shows where piece will land)
const ghostY = getDropPosition();
drawGhostPiece(ghostY);
// Draw current piece
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}`;
}
}
}
}
}
}
// Draw ghost piece (shows where piece will land)
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');
}
}
}
}
}
}
// Draw the next piece
function drawNextPiece() {
// 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 w-full h-full border border-gray-800';
}
}
// Calculate center position for next piece
const offsetX = Math.floor((4 - nextPiece.shape[0].length) / 2);
const offsetY = Math.floor((4 - nextPiece.shape.length) / 2);
// Draw next piece
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);
}
}
}
}
// Check if the current position is valid
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;
// Check if out of bounds
if (boardX < 0 || boardX >= COLS || boardY >= ROWS) {
return false;
}
// Check if collides with existing blocks (but allow above the board)
if (boardY >= 0 && board[boardY][boardX] !== EMPTY) {
return false;
}
}
}
}
return true;
}
// Rotate the current piece
function rotatePiece() {
if (!currentPiece) return;
// Create a deep copy of the current piece
const newPiece = {
shape: [],
color: currentPiece.color
};
// Transpose the matrix (rotate 90 degrees)
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]);
}
}
// Check if the new position is valid
if (isValidPosition(newPiece, currentPosition)) {
currentPiece = newPiece;
draw();
} else {
// Try wall kicks (adjust position if rotation would cause collision)
const kicks = [
{ x: -1, y: 0 }, { x: 1, y: 0 }, // Left and right
{ x: 0, y: -1 }, { x: 0, y: 1 }, // Up and down
{ x: -1, y: -1 }, { x: 1, y: -1 }, // Diagonal
{ 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;
}
}
}
}
// Move the piece left
function moveLeft() {
if (!currentPiece) return;
const newPosition = {
x: currentPosition.x - 1,
y: currentPosition.y
};
if (isValidPosition(currentPiece, newPosition)) {
currentPosition = newPosition;
draw();
}
}
// Move the piece right
function moveRight() {
if (!currentPiece) return;
const newPosition = {
x: currentPosition.x + 1,
y: currentPosition.y
};
if (isValidPosition(currentPiece, newPosition)) {
currentPosition = newPosition;
draw();
}
}
// Move the piece down (soft drop)
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 {
// Piece can't move down anymore, lock it in place
lockPiece();
return false;
}
}
// Hard drop - instantly drop the piece to the bottom
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();
}
// Get the position where the piece would land
function getDropPosition() {
let dropPosition = currentPosition.y;
while (isValidPosition(currentPiece, { x: currentPosition.x, y: dropPosition + 1 })) {
dropPosition++;
}
return dropPosition;
}
// Lock the current piece in place
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) { // Only lock if within the board
board[boardY][boardX] = currentPiece.color;
}
}
}
}
// Check for completed lines
checkLines();
// Get the next piece
spawnPiece();
// Check if game over (new piece can't be placed)
if (!isValidPosition(currentPiece, currentPosition)) {
gameOver();
}
}
// Check for completed lines
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) {
// Remove the line
for (let x = 0; x < COLS; x++) {
board[y][x] = EMPTY;
}
// Move all lines above down
for (let yy = y; yy > 0; yy--) {
for (let x = 0; x < COLS; x++) {
board[yy][x] = board[yy - 1][x];
}
}
// Clear the top line
for (let x = 0; x; x++) {
board[0][x] = EMPTY;
}
linesCleared++;
y++; // Check the same row again (now with new blocks)
}
}
if (linesCleared > 0) {
// Update score based on lines cleared
const points = [0, 40, 100, 300, 1200]; // Points for 0, 1, 2, 3, 4 lines
score += points[linesCleared] * level;
lines += linesCleared;
// Update level every 10 lines
level = Math.floor(lines / 10) + 1;
// Increase speed with level (capped at level 20)
dropSpeed = Math.max(100, 1000 - (level - 1) * 50);
updateScore();
// Flash animation for cleared lines
flashBoard();
}
}
// Flash animation when lines are cleared
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);
}
// Spawn a new piece
function spawnPiece() {
currentPiece = nextPiece;
nextPiece = randomPiece();
// Set starting position (centered at top)
currentPosition = {
x: Math.floor(COLS / 2) - Math.floor(currentPiece.shape[0].length / 2),
y: -1 // Start above the board
};
// If starting position is invalid, adjust y until it's valid
while (!isValidPosition(currentPiece, currentPosition) && currentPosition.y < 0) {
currentPosition.y++;
}
drawNextPiece();
}
// Update the score display
function updateScore() {
scoreElement.textContent = score;
linesElement.textContent = lines;
levelElement.textContent = level;
}
// Game over
function gameOver() {
clearInterval(gameInterval);
isGameOver = true;
gameOverElement.classList.remove('hidden');
}
// Start the game
function startGame() {
// Reset game state
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');
// Generate first pieces
nextPiece = randomPiece();
spawnPiece();
// Start game loop
gameInterval = setInterval(() => {
if (!isPaused && !isGameOver) {
moveDown();
}
}, dropSpeed);
draw();
}
// Pause the game
function togglePause() {
isPaused = !isPaused;
if (isPaused) {
pauseBtn.textContent = 'Resume';
clearInterval(gameInterval);
} else {
pauseBtn.textContent = 'Pause';
gameInterval = setInterval(() => {
if (!isPaused && !isGameOver) {
moveDown();
}
}, dropSpeed);
}
}
// Draw everything
function draw() {
drawBoard();
drawPiece();
}
// Initialize the game
function init() {
initBoard();
initNextPiece();
// Event listeners
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>