ysharma's picture
ysharma HF Staff
Add 2 files
7521e29 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Modern 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>
@keyframes flash {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.flash-animation {
animation: flash 0.3s ease-in-out 3;
}
.game-container {
perspective: 1000px;
}
.tetris-block {
box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.3);
transition: all 0.1s ease;
}
.tetris-block.active {
transform: scale(0.95);
}
.next-piece-container {
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
}
</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">
<h1 class="text-4xl font-bold text-center mb-6 text-transparent bg-clip-text bg-gradient-to-r from-purple-400 to-pink-600">
<i class="fas fa-gamepad mr-2"></i> Modern Tetris
</h1>
<div class="flex flex-col md:flex-row gap-8 items-center justify-center">
<!-- Game Board -->
<div class="game-container relative">
<div class="bg-gray-800 rounded-lg overflow-hidden shadow-2xl">
<canvas id="tetris" width="300" height="600" class="block"></canvas>
</div>
<!-- Game Over Overlay -->
<div id="gameOver" class="absolute inset-0 bg-black bg-opacity-70 flex flex-col items-center justify-center hidden">
<h2 class="text-4xl font-bold mb-4 text-red-500">Game Over!</h2>
<p class="text-xl mb-6">Your score: <span id="finalScore">0</span></p>
<button id="restartBtn" class="px-6 py-3 bg-gradient-to-r from-purple-500 to-pink-600 rounded-full font-bold hover:opacity-90 transition">
<i class="fas fa-redo mr-2"></i> Play Again
</button>
</div>
<!-- Pause Overlay -->
<div id="pauseScreen" class="absolute inset-0 bg-black bg-opacity-70 flex flex-col items-center justify-center hidden">
<h2 class="text-4xl font-bold mb-4 text-yellow-400">Paused</h2>
<button id="resumeBtn" class="px-6 py-3 bg-gradient-to-r from-blue-500 to-teal-400 rounded-full font-bold hover:opacity-90 transition">
<i class="fas fa-play mr-2"></i> Resume
</button>
</div>
</div>
<!-- Game Info -->
<div class="flex flex-col gap-6 w-full md:w-auto">
<!-- Score and Level -->
<div class="bg-gray-800 p-6 rounded-xl shadow-lg">
<div class="flex justify-between items-center mb-4">
<div>
<p class="text-gray-400">Score</p>
<p id="score" class="text-3xl font-bold">0</p>
</div>
<div>
<p class="text-gray-400">Level</p>
<p id="level" class="text-3xl font-bold">1</p>
</div>
</div>
<div class="flex justify-between items-center">
<div>
<p class="text-gray-400">Lines</p>
<p id="lines" class="text-3xl font-bold">0</p>
</div>
<div>
<p class="text-gray-400">High Score</p>
<p id="highScore" class="text-3xl font-bold">0</p>
</div>
</div>
</div>
<!-- Next Piece -->
<div class="bg-gray-800 p-6 rounded-xl shadow-lg">
<h3 class="text-xl font-bold mb-4 text-center">Next Piece</h3>
<div class="next-piece-container p-4 flex justify-center">
<canvas id="nextPiece" width="120" height="120" class="block"></canvas>
</div>
</div>
<!-- Controls -->
<div class="bg-gray-800 p-6 rounded-xl shadow-lg">
<h3 class="text-xl font-bold mb-4 text-center">Controls</h3>
<div class="grid grid-cols-3 gap-3 text-center">
<div class="bg-gray-700 p-3 rounded-lg">
<div class="text-2xl mb-1"><i class="fas fa-arrow-up"></i></div>
<p class="text-sm">Rotate</p>
</div>
<div class="bg-gray-700 p-3 rounded-lg">
<div class="text-2xl mb-1"><i class="fas fa-arrow-left"></i></div>
<p class="text-sm">Left</p>
</div>
<div class="bg-gray-700 p-3 rounded-lg">
<div class="text-2xl mb-1"><i class="fas fa-arrow-right"></i></div>
<p class="text-sm">Right</p>
</div>
<div class="bg-gray-700 p-3 rounded-lg">
<div class="text-2xl mb-1"><i class="fas fa-arrow-down"></i></div>
<p class="text-sm">Down</p>
</div>
<div class="bg-gray-700 p-3 rounded-lg col-span-2">
<div class="text-2xl mb-1"><i class="fas fa-space-shuttle"></i></div>
<p class="text-sm">Hard Drop</p>
</div>
<div class="bg-gray-700 p-3 rounded-lg">
<div class="text-2xl mb-1"><i class="fas fa-pause"></i></div>
<p class="text-sm">Pause</p>
</div>
</div>
</div>
<!-- Game Buttons -->
<div class="flex gap-4">
<button id="startBtn" class="flex-1 px-6 py-3 bg-gradient-to-r from-green-500 to-emerald-600 rounded-full font-bold hover:opacity-90 transition">
<i class="fas fa-play mr-2"></i> Start
</button>
<button id="pauseBtn" class="flex-1 px-6 py-3 bg-gradient-to-r from-blue-500 to-teal-400 rounded-full font-bold hover:opacity-90 transition">
<i class="fas fa-pause mr-2"></i> Pause
</button>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// Game constants
const COLS = 10;
const ROWS = 20;
const BLOCK_SIZE = 30;
const COLORS = [
null,
'#FF0D72', // I - Pink
'#0DC2FF', // J - Blue
'#0DFF72', // L - Green
'#F538FF', // O - Purple
'#FF8E0D', // S - Orange
'#FFE138', // T - Yellow
'#3877FF' // Z - Dark Blue
];
// Game variables
let canvas = document.getElementById('tetris');
let ctx = canvas.getContext('2d');
let nextPieceCanvas = document.getElementById('nextPiece');
let nextPieceCtx = nextPieceCanvas.getContext('2d');
let scoreElement = document.getElementById('score');
let linesElement = document.getElementById('lines');
let levelElement = document.getElementById('level');
let highScoreElement = document.getElementById('highScore');
let finalScoreElement = document.getElementById('finalScore');
let gameOverElement = document.getElementById('gameOver');
let pauseScreenElement = document.getElementById('pauseScreen');
let startBtn = document.getElementById('startBtn');
let pauseBtn = document.getElementById('pauseBtn');
let resumeBtn = document.getElementById('resumeBtn');
let restartBtn = document.getElementById('restartBtn');
// Scale canvas for high DPI displays
scaleCanvas();
let score = 0;
let lines = 0;
let level = 1;
let highScore = localStorage.getItem('tetrisHighScore') || 0;
let gameOver = true;
let paused = false;
let dropCounter = 0;
let dropInterval = 1000;
let lastTime = 0;
let animationId = null;
highScoreElement.textContent = highScore;
// Game board
const board = createMatrix(COLS, ROWS);
// Current piece
let player = {
pos: {x: 0, y: 0},
matrix: null,
next: null
};
// Tetromino shapes
const SHAPES = [
null,
[[0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0], [0, 0, 0, 0]], // I
[[2, 0, 0], [2, 2, 2], [0, 0, 0]], // J
[[0, 0, 3], [3, 3, 3], [0, 0, 0]], // L
[[0, 4, 4], [0, 4, 4], [0, 0, 0]], // O
[[0, 5, 5], [5, 5, 0], [0, 0, 0]], // S
[[0, 6, 0], [6, 6, 6], [0, 0, 0]], // T
[[7, 7, 0], [0, 7, 7], [0, 0, 0]] // Z
];
// Event listeners
document.addEventListener('keydown', handleKeyPress);
startBtn.addEventListener('click', startGame);
pauseBtn.addEventListener('click', togglePause);
resumeBtn.addEventListener('click', togglePause);
restartBtn.addEventListener('click', startGame);
// Initialize the game
resetGame();
drawNextPiece();
// Game functions
function createMatrix(w, h) {
const matrix = [];
while (h--) {
matrix.push(new Array(w).fill(0));
}
return matrix;
}
function createPiece(type) {
return SHAPES[type];
}
function drawMatrix(matrix, offset) {
matrix.forEach((row, y) => {
row.forEach((value, x) => {
if (value !== 0) {
ctx.fillStyle = COLORS[value];
ctx.fillRect(
(x + offset.x) * BLOCK_SIZE,
(y + offset.y) * BLOCK_SIZE,
BLOCK_SIZE,
BLOCK_SIZE
);
// Add 3D effect
ctx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
ctx.lineWidth = 2;
ctx.strokeRect(
(x + offset.x) * BLOCK_SIZE,
(y + offset.y) * BLOCK_SIZE,
BLOCK_SIZE,
BLOCK_SIZE
);
// Add inner highlight
ctx.fillStyle = 'rgba(255, 255, 255, 0.1)';
ctx.fillRect(
(x + offset.x) * BLOCK_SIZE + 2,
(y + offset.y) * BLOCK_SIZE + 2,
BLOCK_SIZE - 4,
BLOCK_SIZE - 4
);
}
});
});
}
function drawNextPiece() {
nextPieceCtx.clearRect(0, 0, nextPieceCanvas.width, nextPieceCanvas.height);
if (player.next) {
const matrix = player.next;
const offsetX = (nextPieceCanvas.width / BLOCK_SIZE - matrix[0].length) / 2;
const offsetY = (nextPieceCanvas.height / BLOCK_SIZE - matrix.length) / 2;
matrix.forEach((row, y) => {
row.forEach((value, x) => {
if (value !== 0) {
nextPieceCtx.fillStyle = COLORS[value];
nextPieceCtx.fillRect(
(x + offsetX) * BLOCK_SIZE,
(y + offsetY) * BLOCK_SIZE,
BLOCK_SIZE,
BLOCK_SIZE
);
nextPieceCtx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
nextPieceCtx.lineWidth = 2;
nextPieceCtx.strokeRect(
(x + offsetX) * BLOCK_SIZE,
(y + offsetY) * BLOCK_SIZE,
BLOCK_SIZE,
BLOCK_SIZE
);
nextPieceCtx.fillStyle = 'rgba(255, 255, 255, 0.1)';
nextPieceCtx.fillRect(
(x + offsetX) * BLOCK_SIZE + 2,
(y + offsetY) * BLOCK_SIZE + 2,
BLOCK_SIZE - 4,
BLOCK_SIZE - 4
);
}
});
});
}
}
function draw() {
// Clear the board
ctx.fillStyle = '#111827';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw the board
drawMatrix(board, {x: 0, y: 0});
// Draw the current piece
if (player.matrix) {
drawMatrix(player.matrix, player.pos);
}
// Draw grid lines
ctx.strokeStyle = 'rgba(255, 255, 255, 0.05)';
ctx.lineWidth = 1;
// Vertical lines
for (let i = 0; i <= COLS; i++) {
ctx.beginPath();
ctx.moveTo(i * BLOCK_SIZE, 0);
ctx.lineTo(i * BLOCK_SIZE, ROWS * BLOCK_SIZE);
ctx.stroke();
}
// Horizontal lines
for (let i = 0; i <= ROWS; i++) {
ctx.beginPath();
ctx.moveTo(0, i * BLOCK_SIZE);
ctx.lineTo(COLS * BLOCK_SIZE, i * BLOCK_SIZE);
ctx.stroke();
}
}
function merge() {
player.matrix.forEach((row, y) => {
row.forEach((value, x) => {
if (value !== 0) {
board[y + player.pos.y][x + player.pos.x] = value;
}
});
});
}
function rotate(matrix) {
const N = matrix.length;
const result = createMatrix(N, N);
for (let y = 0; y < N; ++y) {
for (let x = 0; x < N; ++x) {
result[x][N - 1 - y] = matrix[y][x];
}
}
return result;
}
function playerRotate() {
const pos = player.pos.x;
let offset = 1;
const matrix = rotate(player.matrix);
if (collide(board, {matrix, pos: player.pos})) {
const left = player.pos.x - offset;
const right = player.pos.x + offset;
if (!collide(board, {matrix, pos: {...player.pos, x: left}})) {
player.pos.x = left;
} else if (!collide(board, {matrix, pos: {...player.pos, x: right}})) {
player.pos.x = right;
} else {
return;
}
}
player.matrix = matrix;
}
function playerMove(dir) {
player.pos.x += dir;
if (collide(board, player)) {
player.pos.x -= dir;
}
}
function playerDrop() {
player.pos.y++;
if (collide(board, player)) {
player.pos.y--;
merge();
playerReset();
arenaSweep();
updateScore();
}
dropCounter = 0;
}
function playerHardDrop() {
while (!collide(board, player)) {
player.pos.y++;
}
player.pos.y--;
merge();
playerReset();
arenaSweep();
updateScore();
dropCounter = 0;
}
function collide(board, player) {
const [m, o] = [player.matrix, player.pos];
for (let y = 0; y < m.length; ++y) {
for (let x = 0; x < m[y].length; ++x) {
if (m[y][x] !== 0 &&
(board[y + o.y] === undefined ||
board[y + o.y][x + o.x] === undefined ||
board[y + o.y][x + o.x] !== 0)) {
return true;
}
}
}
return false;
}
function playerReset() {
const pieces = 'IJLOSTZ';
player.matrix = player.next || createPiece(pieces.charCodeAt(Math.floor(Math.random() * pieces.length)) - 64);
player.next = createPiece(pieces.charCodeAt(Math.floor(Math.random() * pieces.length)) - 64);
player.pos.y = 0;
player.pos.x = Math.floor((COLS / 2) - (player.matrix[0].length / 2));
drawNextPiece();
if (collide(board, player)) {
gameOver = true;
gameOverElement.classList.remove('hidden');
finalScoreElement.textContent = score;
if (score > highScore) {
highScore = score;
localStorage.setItem('tetrisHighScore', highScore);
highScoreElement.textContent = highScore;
}
}
}
function arenaSweep() {
let linesCleared = 0;
outer: for (let y = board.length - 1; y >= 0; --y) {
for (let x = 0; x < board[y].length; ++x) {
if (board[y][x] === 0) {
continue outer;
}
}
// Remove the line and add a new empty one at the top
const row = board.splice(y, 1)[0].fill(0);
board.unshift(row);
++y;
linesCleared++;
}
if (linesCleared > 0) {
// Add visual effect for cleared lines
const linesElement = document.createElement('div');
linesElement.className = 'absolute inset-0 bg-white opacity-30 flash-animation';
document.querySelector('.game-container').appendChild(linesElement);
setTimeout(() => {
linesElement.remove();
}, 1000);
lines += linesCleared;
updateLevel();
}
}
function updateScore() {
const points = [0, 40, 100, 300, 1200]; // Points for 0, 1, 2, 3, 4 lines
const linesCleared = Math.min(4, lines - Math.floor(lines / 10) * 10);
if (linesCleared > 0) {
score += points[linesCleared] * level;
scoreElement.textContent = score;
linesElement.textContent = lines;
}
}
function updateLevel() {
level = Math.floor(lines / 10) + 1;
levelElement.textContent = level;
dropInterval = 1000 - (level - 1) * 100;
if (dropInterval < 100) dropInterval = 100;
}
function handleKeyPress(e) {
if (gameOver || paused) return;
switch (e.keyCode) {
case 37: // Left arrow
playerMove(-1);
break;
case 39: // Right arrow
playerMove(1);
break;
case 40: // Down arrow
playerDrop();
break;
case 38: // Up arrow
playerRotate();
break;
case 32: // Space
playerHardDrop();
break;
case 80: // P
togglePause();
break;
}
}
function togglePause() {
if (gameOver) return;
paused = !paused;
if (paused) {
pauseScreenElement.classList.remove('hidden');
cancelAnimationFrame(animationId);
} else {
pauseScreenElement.classList.add('hidden');
lastTime = 0;
animationId = requestAnimationFrame(update);
}
}
function resetGame() {
// Clear the board
for (let y = 0; y < ROWS; y++) {
for (let x = 0; x < COLS; x++) {
board[y][x] = 0;
}
}
// Reset game state
score = 0;
lines = 0;
level = 1;
dropInterval = 1000;
// Update UI
scoreElement.textContent = score;
linesElement.textContent = lines;
levelElement.textContent = level;
// Hide game over screen
gameOverElement.classList.add('hidden');
}
function startGame() {
if (!gameOver && !paused) return;
resetGame();
gameOver = false;
paused = false;
pauseScreenElement.classList.add('hidden');
playerReset();
if (animationId) {
cancelAnimationFrame(animationId);
}
lastTime = 0;
animationId = requestAnimationFrame(update);
}
function update(time = 0) {
const deltaTime = time - lastTime;
lastTime = time;
dropCounter += deltaTime;
if (dropCounter > dropInterval) {
playerDrop();
}
draw();
animationId = requestAnimationFrame(update);
}
function scaleCanvas() {
// For the main game canvas
const scale = window.devicePixelRatio;
canvas.style.width = canvas.width + 'px';
canvas.style.height = canvas.height + 'px';
canvas.width = canvas.width * scale;
canvas.height = canvas.height * scale;
ctx.scale(scale, scale);
// For the next piece canvas
nextPieceCanvas.style.width = nextPieceCanvas.width + 'px';
nextPieceCanvas.style.height = nextPieceCanvas.height + 'px';
nextPieceCanvas.width = nextPieceCanvas.width * scale;
nextPieceCanvas.height = nextPieceCanvas.height * scale;
nextPieceCtx.scale(scale, scale);
}
// Initial draw
draw();
});
</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=ysharma/modern-tetris-vibe-coded" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body>
</html>