Spaces:
Running
Running
create 3d tetris game which we can rotate. The blocks should have emoji like simley etc. - Initial Deployment
52ed030
verified
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>3D Emoji Tetris</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <style> | |
| body { | |
| perspective: 1000px; | |
| overflow: hidden; | |
| background: linear-gradient(135deg, #1a1a2e, #16213e); | |
| } | |
| .game-container { | |
| transform-style: preserve-3d; | |
| transition: transform 0.5s ease; | |
| } | |
| .block { | |
| transform-style: preserve-3d; | |
| transition: all 0.2s ease; | |
| position: relative; | |
| } | |
| .block-face { | |
| position: absolute; | |
| width: 100%; | |
| height: 100%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1.5rem; | |
| backface-visibility: hidden; | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| box-sizing: border-box; | |
| } | |
| .front { transform: translateZ(25px); } | |
| .back { transform: rotateY(180deg) translateZ(25px); } | |
| .right { transform: rotateY(90deg) translateZ(25px); } | |
| .left { transform: rotateY(-90deg) translateZ(25px); } | |
| .top { transform: rotateX(90deg) translateZ(25px); } | |
| .bottom { transform: rotateX(-90deg) translateZ(25px); } | |
| @keyframes fall { | |
| from { transform: translateY(-100px); } | |
| to { transform: translateY(0); } | |
| } | |
| .falling { | |
| animation: fall 0.3s ease-out; | |
| } | |
| </style> | |
| </head> | |
| <body class="min-h-screen flex flex-col items-center justify-center text-white font-mono"> | |
| <h1 class="text-4xl font-bold mb-4 text-center bg-clip-text text-transparent bg-gradient-to-r from-pink-500 to-violet-500"> | |
| 3D Emoji Tetris | |
| </h1> | |
| <div class="mb-4 flex gap-4"> | |
| <div class="text-center"> | |
| <p class="text-xl">Score</p> | |
| <p id="score" class="text-3xl font-bold">0</p> | |
| </div> | |
| <div class="text-center"> | |
| <p class="text-xl">Level</p> | |
| <p id="level" class="text-3xl font-bold">1</p> | |
| </div> | |
| </div> | |
| <div class="relative w-full max-w-md h-96 flex items-center justify-center"> | |
| <div id="game" class="game-container w-64 h-96 relative"> | |
| <!-- Game grid will be generated here --> | |
| </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">Game Over!</h2> | |
| <p id="final-score" class="text-xl mb-6">Your score: 0</p> | |
| <button id="restart" class="px-6 py-2 bg-pink-600 rounded-lg hover:bg-pink-700 transition"> | |
| Play Again | |
| </button> | |
| </div> | |
| </div> | |
| <div class="mt-6 text-center"> | |
| <p class="mb-2">Controls:</p> | |
| <div class="grid grid-cols-3 gap-2 max-w-xs mx-auto"> | |
| <div class="bg-gray-800 p-2 rounded">← Left</div> | |
| <div class="bg-gray-800 p-2 rounded">→ Right</div> | |
| <div class="bg-gray-800 p-2 rounded">↑ Rotate</div> | |
| <div class="bg-gray-800 p-2 rounded">↓ Down</div> | |
| <div class="bg-gray-800 p-2 rounded">Space Drop</div> | |
| <div class="bg-gray-800 p-2 rounded">R 3D Rotate</div> | |
| </div> | |
| </div> | |
| <div id="next-piece" class="mt-6 text-center"> | |
| <p class="mb-2">Next Piece:</p> | |
| <div id="next-piece-display" class="w-24 h-24 mx-auto grid grid-cols-4 gap-1"></div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', () => { | |
| // Game constants | |
| const COLS = 10; | |
| const ROWS = 20; | |
| const BLOCK_SIZE = 30; | |
| const EMOJIS = ['😀', '😎', '🤩', '😍', '🤪', '🧐', '🥳', '🤯', '👻', '🤠', '👽', '🤖']; | |
| // Game variables | |
| let grid = Array(ROWS).fill().map(() => Array(COLS).fill(0)); | |
| let currentPiece = null; | |
| let nextPiece = null; | |
| let gameOver = false; | |
| let score = 0; | |
| let level = 1; | |
| let gameRotation = 0; | |
| let dropInterval = 1000; | |
| let dropStart = null; | |
| let animationFrameId = null; | |
| // DOM elements | |
| const gameEl = document.getElementById('game'); | |
| const scoreEl = document.getElementById('score'); | |
| const levelEl = document.getElementById('level'); | |
| const gameOverEl = document.getElementById('game-over'); | |
| const finalScoreEl = document.getElementById('final-score'); | |
| const restartBtn = document.getElementById('restart'); | |
| const nextPieceDisplay = document.getElementById('next-piece-display'); | |
| // Initialize game | |
| function init() { | |
| createGrid(); | |
| generateNewPiece(); | |
| generateNextPiece(); | |
| updateScore(); | |
| gameLoop(); | |
| addEventListeners(); | |
| } | |
| // Create game grid | |
| function createGrid() { | |
| gameEl.innerHTML = ''; | |
| gameEl.style.width = `${COLS * BLOCK_SIZE}px`; | |
| gameEl.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 = 'absolute border border-gray-800 bg-gray-900'; | |
| cell.style.width = `${BLOCK_SIZE}px`; | |
| cell.style.height = `${BLOCK_SIZE}px`; | |
| cell.style.left = `${x * BLOCK_SIZE}px`; | |
| cell.style.top = `${y * BLOCK_SIZE}px`; | |
| cell.dataset.x = x; | |
| cell.dataset.y = y; | |
| gameEl.appendChild(cell); | |
| } | |
| } | |
| } | |
| // Game pieces | |
| const PIECES = [ | |
| { shape: [[1,1,1,1]], color: 'bg-red-500', emoji: EMOJIS[0] }, // I | |
| { shape: [[1,1,1], [0,1,0]], color: 'bg-blue-500', emoji: EMOJIS[1] }, // T | |
| { shape: [[1,1,0], [0,1,1]], color: 'bg-green-500', emoji: EMOJIS[2] }, // Z | |
| { shape: [[0,1,1], [1,1,0]], color: 'bg-yellow-500', emoji: EMOJIS[3] }, // S | |
| { shape: [[1,1], [1,1]], color: 'bg-purple-500', emoji: EMOJIS[4] }, // O | |
| { shape: [[1,1,1], [1,0,0]], color: 'bg-pink-500', emoji: EMOJIS[5] }, // L | |
| { shape: [[1,1,1], [0,0,1]], color: 'bg-indigo-500', emoji: EMOJIS[6] } // J | |
| ]; | |
| // Generate a new random piece | |
| function generateNewPiece() { | |
| if (nextPiece) { | |
| currentPiece = nextPiece; | |
| } else { | |
| const randomIndex = Math.floor(Math.random() * PIECES.length); | |
| currentPiece = { | |
| ...PIECES[randomIndex], | |
| x: Math.floor(COLS / 2) - Math.floor(PIECES[randomIndex].shape[0].length / 2), | |
| y: 0, | |
| rotation: 0 | |
| }; | |
| } | |
| generateNextPiece(); | |
| // Check if game over | |
| if (collision()) { | |
| gameOver = true; | |
| gameOverEl.classList.remove('hidden'); | |
| finalScoreEl.textContent = `Your score: ${score}`; | |
| } | |
| } | |
| // Generate next piece for preview | |
| function generateNextPiece() { | |
| const randomIndex = Math.floor(Math.random() * PIECES.length); | |
| nextPiece = { | |
| ...PIECES[randomIndex], | |
| x: Math.floor(COLS / 2) - Math.floor(PIECES[randomIndex].shape[0].length / 2), | |
| y: 0, | |
| rotation: 0 | |
| }; | |
| updateNextPieceDisplay(); | |
| } | |
| // Update next piece display | |
| function updateNextPieceDisplay() { | |
| nextPieceDisplay.innerHTML = ''; | |
| const shape = nextPiece.shape; | |
| // Center the piece in the display | |
| const offsetX = Math.floor((4 - shape[0].length) / 2); | |
| const offsetY = Math.floor((4 - shape.length) / 2); | |
| for (let y = 0; y < 4; y++) { | |
| for (let x = 0; x < 4; x++) { | |
| const cell = document.createElement('div'); | |
| cell.className = 'w-6 h-6 flex items-center justify-center'; | |
| if (y - offsetY >= 0 && y - offsetY < shape.length && | |
| x - offsetX >= 0 && x - offsetX < shape[0].length && | |
| shape[y - offsetY][x - offsetX]) { | |
| cell.innerHTML = `<div class="w-full h-full ${nextPiece.color} flex items-center justify-center">${nextPiece.emoji}</div>`; | |
| } | |
| nextPieceDisplay.appendChild(cell); | |
| } | |
| } | |
| } | |
| // Draw the game | |
| function draw() { | |
| // Clear the grid | |
| document.querySelectorAll('#game > div').forEach(cell => { | |
| cell.innerHTML = ''; | |
| cell.className = 'absolute border border-gray-800 bg-gray-900'; | |
| cell.style.width = `${BLOCK_SIZE}px`; | |
| cell.style.height = `${BLOCK_SIZE}px`; | |
| }); | |
| // Draw locked pieces | |
| for (let y = 0; y < ROWS; y++) { | |
| for (let x = 0; x < COLS; x++) { | |
| if (grid[y][x]) { | |
| const cell = document.querySelector(`[data-x="${x}"][data-y="${y}"]`); | |
| if (cell) { | |
| cell.innerHTML = create3DBlock(grid[y][x].color, grid[y][x].emoji); | |
| cell.classList.add(grid[y][x].color); | |
| } | |
| } | |
| } | |
| } | |
| // Draw current piece | |
| if (currentPiece) { | |
| const shape = rotateShape(currentPiece.shape, currentPiece.rotation); | |
| for (let y = 0; y < shape.length; y++) { | |
| for (let x = 0; x < shape[y].length; x++) { | |
| if (shape[y][x]) { | |
| const posX = currentPiece.x + x; | |
| const posY = currentPiece.y + y; | |
| if (posY >= 0 && posX >= 0 && posX < COLS) { | |
| const cell = document.querySelector(`[data-x="${posX}"][data-y="${posY}"]`); | |
| if (cell) { | |
| cell.innerHTML = create3DBlock(currentPiece.color, currentPiece.emoji); | |
| cell.classList.add(currentPiece.color, 'falling'); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Create 3D block with emoji | |
| function create3DBlock(color, emoji) { | |
| return ` | |
| <div class="block w-full h-full"> | |
| <div class="block-face front ${color}">${emoji}</div> | |
| <div class="block-face back ${color}">${emoji}</div> | |
| <div class="block-face right ${color}">${emoji}</div> | |
| <div class="block-face left ${color}">${emoji}</div> | |
| <div class="block-face top ${color}">${emoji}</div> | |
| <div class="block-face bottom ${color}">${emoji}</div> | |
| </div> | |
| `; | |
| } | |
| // Rotate shape | |
| function rotateShape(shape, rotation) { | |
| rotation = rotation % 4; | |
| if (rotation < 0) rotation += 4; | |
| if (rotation === 0) return shape; | |
| let rotated = shape; | |
| for (let i = 0; i < rotation; i++) { | |
| rotated = rotated[0].map((_, index) => | |
| rotated.map(row => row[index]).reverse() | |
| ); | |
| } | |
| return rotated; | |
| } | |
| // Check for collisions | |
| function collision() { | |
| const shape = rotateShape(currentPiece.shape, currentPiece.rotation); | |
| for (let y = 0; y < shape.length; y++) { | |
| for (let x = 0; x < shape[y].length; x++) { | |
| if (shape[y][x]) { | |
| const posX = currentPiece.x + x; | |
| const posY = currentPiece.y + y; | |
| if (posY >= ROWS || posX < 0 || posX >= COLS || (posY >= 0 && grid[posY][posX])) { | |
| return true; | |
| } | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| // Lock piece in place | |
| function lockPiece() { | |
| const shape = rotateShape(currentPiece.shape, currentPiece.rotation); | |
| for (let y = 0; y < shape.length; y++) { | |
| for (let x = 0; x < shape[y].length; x++) { | |
| if (shape[y][x]) { | |
| const posY = currentPiece.y + y; | |
| if (posY >= 0) { | |
| grid[posY][currentPiece.x + x] = { | |
| color: currentPiece.color, | |
| emoji: currentPiece.emoji | |
| }; | |
| } | |
| } | |
| } | |
| } | |
| // Check for completed lines | |
| checkLines(); | |
| // Generate new piece | |
| generateNewPiece(); | |
| } | |
| // Check for completed lines | |
| function checkLines() { | |
| let linesCleared = 0; | |
| for (let y = ROWS - 1; y >= 0; y--) { | |
| if (grid[y].every(cell => cell !== 0)) { | |
| // Remove the line | |
| grid.splice(y, 1); | |
| // Add new empty line at top | |
| grid.unshift(Array(COLS).fill(0)); | |
| linesCleared++; | |
| y++; // Check the same row again | |
| } | |
| } | |
| if (linesCleared > 0) { | |
| // Update score | |
| score += [0, 40, 100, 300, 1200][linesCleared] * level; | |
| // Increase level every 10 lines | |
| const newLevel = Math.floor(score / 1000) + 1; | |
| if (newLevel > level) { | |
| level = newLevel; | |
| dropInterval = Math.max(100, 1000 - (level - 1) * 100); | |
| } | |
| updateScore(); | |
| } | |
| } | |
| // Update score display | |
| function updateScore() { | |
| scoreEl.textContent = score; | |
| levelEl.textContent = level; | |
| } | |
| // Game loop | |
| function gameLoop(timestamp) { | |
| if (gameOver) return; | |
| if (!dropStart) dropStart = timestamp; | |
| const delta = timestamp - dropStart; | |
| if (delta > dropInterval) { | |
| moveDown(); | |
| dropStart = null; | |
| } | |
| draw(); | |
| animationFrameId = requestAnimationFrame(gameLoop); | |
| } | |
| // Move piece down | |
| function moveDown() { | |
| currentPiece.y++; | |
| if (collision()) { | |
| currentPiece.y--; | |
| lockPiece(); | |
| } | |
| } | |
| // Move piece left | |
| function moveLeft() { | |
| currentPiece.x--; | |
| if (collision()) { | |
| currentPiece.x++; | |
| } | |
| } | |
| // Move piece right | |
| function moveRight() { | |
| currentPiece.x++; | |
| if (collision()) { | |
| currentPiece.x--; | |
| } | |
| } | |
| // Rotate piece | |
| function rotatePiece() { | |
| currentPiece.rotation++; | |
| if (collision()) { | |
| // Try wall kicks | |
| const originalX = currentPiece.x; | |
| const kicks = [-1, 1, -2, 2]; | |
| for (const kick of kicks) { | |
| currentPiece.x += kick; | |
| if (!collision()) return; | |
| currentPiece.x = originalX; | |
| } | |
| currentPiece.rotation--; | |
| } | |
| } | |
| // Drop piece instantly | |
| function hardDrop() { | |
| while (!collision()) { | |
| currentPiece.y++; | |
| } | |
| currentPiece.y--; | |
| lockPiece(); | |
| } | |
| // Rotate game view | |
| function rotateGameView() { | |
| gameRotation = (gameRotation + 90) % 360; | |
| gameEl.style.transform = `rotateY(${gameRotation}deg)`; | |
| } | |
| // Event listeners | |
| function addEventListeners() { | |
| document.addEventListener('keydown', e => { | |
| if (gameOver) return; | |
| switch (e.key) { | |
| case 'ArrowLeft': | |
| moveLeft(); | |
| break; | |
| case 'ArrowRight': | |
| moveRight(); | |
| break; | |
| case 'ArrowDown': | |
| moveDown(); | |
| break; | |
| case 'ArrowUp': | |
| rotatePiece(); | |
| break; | |
| case ' ': | |
| hardDrop(); | |
| break; | |
| case 'r': | |
| case 'R': | |
| rotateGameView(); | |
| break; | |
| } | |
| }); | |
| restartBtn.addEventListener('click', () => { | |
| // Reset game | |
| grid = Array(ROWS).fill().map(() => Array(COLS).fill(0)); | |
| currentPiece = null; | |
| nextPiece = null; | |
| gameOver = false; | |
| score = 0; | |
| level = 1; | |
| dropInterval = 1000; | |
| gameRotation = 0; | |
| gameEl.style.transform = 'rotateY(0deg)'; | |
| gameOverEl.classList.add('hidden'); | |
| // Start new game | |
| generateNewPiece(); | |
| generateNextPiece(); | |
| updateScore(); | |
| dropStart = null; | |
| if (animationFrameId) { | |
| cancelAnimationFrame(animationFrameId); | |
| } | |
| gameLoop(); | |
| }); | |
| } | |
| // Start the game | |
| 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=manmohanai/3dimojitetris" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |