Spaces:
Running
Running
| <html lang="es"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Tetris - Juego Completo</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| :root { | |
| --primary-color: #1a1a2e; | |
| --secondary-color: #16213e; | |
| --accent-color: #0f3460; | |
| --tetris-blue: #00d9ff; | |
| --tetris-purple: #e94560; | |
| --tetris-yellow: #ffbe0b; | |
| --tetris-green: #8ac926; | |
| --tetris-red: #ff595e; | |
| --tetris-orange: #ff9f1c; | |
| --tetris-pink: #ff006e; | |
| --tetris-cyan: #00f5ff; | |
| --text-color: #ffffff; | |
| --grid-size: 30px; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); | |
| color: var(--text-color); | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| padding: 20px; | |
| overflow-x: hidden; | |
| } | |
| header { | |
| width: 100%; | |
| text-align: center; | |
| margin-bottom: 20px; | |
| } | |
| h1 { | |
| font-size: 3rem; | |
| margin-bottom: 10px; | |
| background: linear-gradient(90deg, var(--tetris-blue), var(--tetris-purple)); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| text-shadow: 0 0 30px rgba(0, 217, 255, 0.5); | |
| } | |
| .built-with { | |
| font-size: 0.9rem; | |
| opacity: 0.8; | |
| margin-top: 5px; | |
| } | |
| .built-with a { | |
| color: var(--tetris-blue); | |
| text-decoration: none; | |
| transition: all 0.3s ease; | |
| } | |
| .built-with a:hover { | |
| text-decoration: underline; | |
| transform: scale(1.05); | |
| } | |
| .game-container { | |
| display: flex; | |
| flex-wrap: wrap; | |
| justify-content: center; | |
| gap: 30px; | |
| max-width: 1200px; | |
| width: 100%; | |
| } | |
| .game-board { | |
| background: rgba(0, 0, 0, 0.3); | |
| border-radius: 10px; | |
| padding: 10px; | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); | |
| border: 2px solid rgba(255, 255, 255, 0.1); | |
| } | |
| .grid { | |
| display: grid; | |
| grid-template-columns: repeat(10, var(--grid-size)); | |
| grid-template-rows: repeat(20, var(--grid-size)); | |
| gap: 1px; | |
| background: rgba(0, 0, 0, 0.5); | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| } | |
| .cell { | |
| width: var(--grid-size); | |
| height: var(--grid-size); | |
| background: rgba(0, 0, 0, 0.8); | |
| border-radius: 2px; | |
| transition: all 0.1s ease; | |
| } | |
| .cell.filled { | |
| border: 1px solid rgba(255, 255, 255, 0.3); | |
| } | |
| .cell.I { background-color: var(--tetris-cyan); } | |
| .cell.O { background-color: var(--tetris-yellow); } | |
| .cell.T { background-color: var(--tetris-purple); } | |
| .cell.S { background-color: var(--tetris-green); } | |
| .cell.Z { background-color: var(--tetris-red); } | |
| .cell.J { background-color: var(--tetris-blue); } | |
| .cell.L { background-color: var(--tetris-orange); } | |
| .sidebar { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 20px; | |
| min-width: 250px; | |
| } | |
| .info-panel { | |
| background: rgba(0, 0, 0, 0.3); | |
| border-radius: 10px; | |
| padding: 20px; | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); | |
| border: 2px solid rgba(255, 255, 255, 0.1); | |
| } | |
| .info-panel h2 { | |
| font-size: 1.5rem; | |
| margin-bottom: 15px; | |
| color: var(--tetris-blue); | |
| } | |
| .score-display, .level-display, .lines-display { | |
| font-size: 1.2rem; | |
| margin: 10px 0; | |
| display: flex; | |
| justify-content: space-between; | |
| } | |
| .score-value, .level-value, .lines-value { | |
| font-weight: bold; | |
| color: var(--tetris-yellow); | |
| } | |
| .next-piece-container { | |
| margin-top: 20px; | |
| } | |
| .next-piece-grid { | |
| display: grid; | |
| grid-template-columns: repeat(4, 20px); | |
| grid-template-rows: repeat(4, 20px); | |
| gap: 1px; | |
| background: rgba(0, 0, 0, 0.5); | |
| padding: 10px; | |
| border-radius: 5px; | |
| margin: 10px auto; | |
| width: fit-content; | |
| } | |
| .next-cell { | |
| width: 20px; | |
| height: 20px; | |
| background: rgba(0, 0, 0, 0.8); | |
| border-radius: 2px; | |
| } | |
| .next-cell.filled { | |
| border: 1px solid rgba(255, 255, 255, 0.3); | |
| } | |
| .controls { | |
| margin-top: 20px; | |
| } | |
| .control-button { | |
| background: linear-gradient(135deg, var(--accent-color), var(--secondary-color)); | |
| color: var(--text-color); | |
| border: none; | |
| padding: 12px 24px; | |
| border-radius: 5px; | |
| font-size: 1rem; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| margin: 5px; | |
| width: 100%; | |
| box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); | |
| } | |
| .control-button:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3); | |
| } | |
| .control-button:active { | |
| transform: translateY(0); | |
| } | |
| .control-button.play { | |
| background: linear-gradient(135deg, var(--tetris-green), var(--tetris-blue)); | |
| } | |
| .control-button.pause { | |
| background: linear-gradient(135deg, var(--tetris-purple), var(--tetris-pink)); | |
| } | |
| .instructions { | |
| margin-top: 20px; | |
| font-size: 0.9rem; | |
| opacity: 0.8; | |
| line-height: 1.5; | |
| } | |
| .game-over { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| background: rgba(0, 0, 0, 0.9); | |
| padding: 30px; | |
| border-radius: 10px; | |
| text-align: center; | |
| display: none; | |
| z-index: 100; | |
| box-shadow: 0 0 50px rgba(0, 0, 0, 0.8); | |
| } | |
| .game-over h2 { | |
| color: var(--tetris-red); | |
| margin-bottom: 20px; | |
| font-size: 2rem; | |
| } | |
| .game-over p { | |
| margin-bottom: 20px; | |
| font-size: 1.2rem; | |
| } | |
| .mobile-controls { | |
| display: none; | |
| flex-direction: column; | |
| gap: 10px; | |
| margin-top: 20px; | |
| } | |
| .control-row { | |
| display: flex; | |
| justify-content: center; | |
| gap: 10px; | |
| } | |
| .mobile-button { | |
| width: 60px; | |
| height: 60px; | |
| background: rgba(255, 255, 255, 0.1); | |
| border: 2px solid rgba(255, 255, 255, 0.3); | |
| border-radius: 10px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1.5rem; | |
| cursor: pointer; | |
| transition: all 0.2s ease; | |
| } | |
| .mobile-button:active { | |
| background: rgba(255, 255, 255, 0.2); | |
| transform: scale(0.95); | |
| } | |
| @media (max-width: 768px) { | |
| :root { | |
| --grid-size: 20px; | |
| } | |
| h1 { | |
| font-size: 2rem; | |
| } | |
| .sidebar { | |
| min-width: 200px; | |
| } | |
| .next-piece-grid { | |
| grid-template-columns: repeat(4, 15px); | |
| grid-template-rows: repeat(4, 15px); | |
| } | |
| .next-cell { | |
| width: 15px; | |
| height: 15px; | |
| } | |
| .mobile-controls { | |
| display: flex; | |
| } | |
| .control-button { | |
| padding: 10px 20px; | |
| font-size: 0.9rem; | |
| } | |
| } | |
| @media (max-width: 480px) { | |
| :root { | |
| --grid-size: 15px; | |
| } | |
| h1 { | |
| font-size: 1.5rem; | |
| } | |
| .game-container { | |
| gap: 15px; | |
| } | |
| .sidebar { | |
| min-width: 150px; | |
| } | |
| } | |
| .particle { | |
| position: absolute; | |
| pointer-events: none; | |
| opacity: 0; | |
| animation: particle-float 1s ease-out forwards; | |
| } | |
| @keyframes particle-float { | |
| 0% { | |
| opacity: 1; | |
| transform: translateY(0) scale(1); | |
| } | |
| 100% { | |
| opacity: 0; | |
| transform: translateY(-50px) scale(0.5); | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <h1>Tetris</h1> | |
| <div class="built-with"> | |
| Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">anycoder</a> | |
| </div> | |
| </header> | |
| <main class="game-container"> | |
| <div class="game-board"> | |
| <div class="grid" id="gameGrid"></div> | |
| </div> | |
| <aside class="sidebar"> | |
| <div class="info-panel"> | |
| <h2>Puntuación</h2> | |
| <div class="score-display"> | |
| <span>Puntos:</span> | |
| <span class="score-value" id="score">0</span> | |
| </div> | |
| <div class="level-display"> | |
| <span>Nivel:</span> | |
| <span class="level-value" id="level">1</span> | |
| </div> | |
| <div class="lines-display"> | |
| <span>Líneas:</span> | |
| <span class="lines-value" id="lines">0</span> | |
| </div> | |
| </div> | |
| <div class="info-panel next-piece-container"> | |
| <h2>Siguiente Pieza</h2> | |
| <div class="next-piece-grid" id="nextPieceGrid"></div> | |
| </div> | |
| <div class="info-panel controls"> | |
| <button class="control-button play" id="startBtn">Iniciar Juego</button> | |
| <button class="control-button pause" id="pauseBtn">Pausar</button> | |
| <div class="instructions"> | |
| <p><strong>Controles:</strong></p> | |
| <p>← → Mover</p> | |
| <p>↑ Rotar</p> | |
| <p>↓ Bajar rápido</p> | |
| <p>Espacio: Dropear</p> | |
| </div> | |
| </div> | |
| <div class="mobile-controls"> | |
| <div class="control-row"> | |
| <div class="mobile-button" id="rotateBtn">↻</div> | |
| </div> | |
| <div class="control-row"> | |
| <div class="mobile-button" id="leftBtn">←</div> | |
| <div class="mobile-button" id="downBtn">↓</div> | |
| <div class="mobile-button" id="rightBtn">→</div> | |
| </div> | |
| <div class="control-row"> | |
| <div class="mobile-button" id="dropBtn">⇓</div> | |
| </div> | |
| </div> | |
| </aside> | |
| </main> | |
| <div class="game-over" id="gameOver"> | |
| <h2>¡Juego Terminado!</h2> | |
| <p>Puntuación final: <span id="finalScore">0</span></p> | |
| <button class="control-button play" id="restartBtn">Reiniciar</button> | |
| </div> | |
| <script> | |
| class Tetris { | |
| constructor() { | |
| this.grid = document.getElementById('gameGrid'); | |
| this.nextPieceGrid = document.getElementById('nextPieceGrid'); | |
| this.scoreElement = document.getElementById('score'); | |
| this.levelElement = document.getElementById('level'); | |
| this.linesElement = document.getElementById('lines'); | |
| this.gameOverElement = document.getElementById('gameOver'); | |
| this.finalScoreElement = document.getElementById('finalScore'); | |
| this.board = []; | |
| this.currentPiece = null; | |
| this.nextPiece = null; | |
| this.score = 0; | |
| this.level = 1; | |
| this.lines = 0; | |
| this.gameRunning = false; | |
| this.gamePaused = false; | |
| this.dropInterval = null; | |
| this.dropSpeed = 1000; | |
| this.boardWidth = 10; | |
| this.boardHeight = 20; | |
| this.pieces = [ | |
| { name: 'I', shape: [[1,1,1,1]], color: 'I' }, | |
| { name: 'O', shape: [[1,1],[1,1]], color: 'O' }, | |
| { name: 'T', shape: [[0,1,0],[1,1,1]], color: 'T' }, | |
| { name: 'S', shape: [[0,1,1],[1,1,0]], color: 'S' }, | |
| { name: 'Z', shape: [[1,1,0],[0,1,1]], color: 'Z' }, | |
| { name: 'J', shape: [[1,0,0],[1,1,1]], color: 'J' }, | |
| { name: 'L', shape: [[0,0,1],[1,1,1]], color: 'L' } | |
| ]; | |
| this.init(); | |
| } | |
| init() { | |
| this.createBoard(); | |
| this.setupEventListeners(); | |
| this.renderNextPiece(); | |
| } | |
| createBoard() { | |
| this.board = Array(this.boardHeight).fill().map(() => Array(this.boardWidth).fill(0)); | |
| this.grid.innerHTML = ''; | |
| for (let y = 0; y < this.boardHeight; y++) { | |
| for (let x = 0; x < this.boardWidth; x++) { | |
| const cell = document.createElement('div'); | |
| cell.className = 'cell'; | |
| cell.dataset.x = x; | |
| cell.dataset.y = y; | |
| this.grid.appendChild(cell); | |
| } | |
| } | |
| } | |
| setupEventListeners() { | |
| document.getElementById('startBtn').addEventListener('click', () => this.startGame()); | |
| document.getElementById('pauseBtn').addEventListener('click', () => this.togglePause()); | |
| document.getElementById('restartBtn').addEventListener('click', () => this.restartGame()); | |
| // Mobile controls | |
| document.getElementById('rotateBtn').addEventListener('click', () => this.rotatePiece()); | |
| document.getElementById('leftBtn').addEventListener('click', () => this.movePiece(-1, 0)); | |
| document.getElementById('rightBtn').addEventListener('click', () => this.movePiece(1, 0)); | |
| document.getElementById('downBtn').addEventListener('click', () => this.movePiece(0, 1)); | |
| document.getElementById('dropBtn').addEventListener('click', () => this.dropPiece()); | |
| document.addEventListener('keydown', (e) => { | |
| if (!this.gameRunning || this.gamePaused) return; | |
| switch(e.key) { | |
| case 'ArrowLeft': | |
| e.preventDefault(); | |
| this.movePiece(-1, 0); | |
| break; | |
| case 'ArrowRight': | |
| e.preventDefault(); | |
| this.movePiece(1, 0); | |
| break; | |
| case 'ArrowDown': | |
| e.preventDefault(); | |
| this.movePiece(0, 1); | |
| break; | |
| case 'ArrowUp': | |
| e.preventDefault(); | |
| this.rotatePiece(); | |
| break; | |
| case ' ': | |
| e.preventDefault(); | |
| this.dropPiece(); | |
| break; | |
| } | |
| }); | |
| } | |
| startGame() { | |
| if (this.gameRunning) return; | |
| this.gameRunning = true; | |
| this.gamePaused = false; | |
| this.score = 0; | |
| this.level = 1; | |
| this.lines = 0; | |
| this.dropSpeed = 1000; | |
| this.updateScore(); | |
| this.createBoard(); | |
| this.spawnPiece(); | |
| this.startDrop(); | |
| } | |
| togglePause() { | |
| if (!this.gameRunning) return; | |
| this.gamePaused = !this.gamePaused; | |
| if (this.gamePaused) { | |
| clearInterval(this.dropInterval); | |
| } else { | |
| this.startDrop(); | |
| } | |
| } | |
| restartGame() { | |
| this.gameOverElement.style.display = 'none'; | |
| this.startGame(); | |
| } | |
| spawnPiece() { | |
| if (!this.nextPiece) { | |
| this.nextPiece = this.getRandomPiece(); | |
| } | |
| this.currentPiece = { | |
| ...this.nextPiece, | |
| x: Math.floor(this.boardWidth / 2) - Math.floor(this.nextPiece.shape[0].length / 2), | |
| y: 0 | |
| }; | |
| this.nextPiece = this.getRandomPiece(); | |
| this.renderNextPiece(); | |
| if (this.collision()) { | |
| this.endGame(); | |
| } | |
| } | |
| getRandomPiece() { | |
| const piece = this.pieces[Math.floor(Math.random() * this.pieces.length)]; | |
| return { | |
| shape: piece.shape, | |
| color: piece.color, | |
| name: piece.name | |
| }; | |
| } | |
| renderNextPiece() { | |
| this.nextPieceGrid.innerHTML = ''; | |
| for (let y = 0; y < 4; y++) { | |
| for (let x = 0; x < 4; x++) { | |
| const cell = document.createElement('div'); | |
| cell.className = 'next-cell'; | |
| if (this.nextPiece && | |
| y < this.nextPiece.shape.length && | |
| x < this.nextPiece.shape[0].length && | |
| this.nextPiece.shape[y][x]) { | |
| cell.classList.add('filled', `cell.${this.nextPiece.color}`); | |
| } | |
| this.nextPieceGrid.appendChild(cell); | |
| } | |
| } | |
| } | |
| startDrop() { | |
| clearInterval(this.dropInterval); | |
| this.dropInterval = setInterval(() => { | |
| this.movePiece(0, 1); | |
| }, this.dropSpeed); | |
| } | |
| movePiece(dx, dy) { | |
| if (!this.currentPiece || this.gamePaused) return; | |
| this.currentPiece.x += dx; | |
| this.currentPiece.y += dy; | |
| if (this.collision()) { | |
| this.currentPiece.x -= dx; | |
| this.currentPiece.y -= dy; | |
| if (dy > 0) { | |
| this.lockPiece(); | |
| } | |
| } | |
| this.render(); | |
| } | |
| rotatePiece() { | |
| if (!this.currentPiece || this.gamePaused) return; | |
| const rotated = this.rotateMatrix(this.currentPiece.shape); | |
| const previousShape = this.currentPiece.shape; | |
| this.currentPiece.shape = rotated; | |
| if (this.collision()) { | |
| this.currentPiece.shape = previousShape; | |
| } else { | |
| this.render(); | |
| } | |
| } | |
| rotateMatrix(matrix) { | |
| const rows = matrix.length; | |
| const cols = matrix[0].length; | |
| const rotated = Array(cols).fill().map(() => Array(rows).fill(0)); | |
| for (let i = 0; i < rows; i++) { | |
| for (let j = 0; j < cols; j++) { | |
| rotated[j][rows - 1 - i] = matrix[i][j]; | |
| } | |
| } | |
| return rotated; | |
| } | |
| dropPiece() { | |
| if (!this.currentPiece || this.gamePaused) return; | |
| while (!this.collision()) { | |
| this.currentPiece.y++; | |
| } | |
| this.currentPiece.y--; | |
| this.lockPiece(); | |
| } | |
| collision() { | |
| for (let y = 0; y < this.currentPiece.shape.length; y++) { | |
| for (let x = 0; x < this.currentPiece.shape[y].length; x++) { | |
| if (this.currentPiece.shape[y][x]) { | |
| const boardX = this.currentPiece.x + x; | |
| const boardY = this.currentPiece.y + y; | |
| if (boardX < 0 || boardX >= this.boardWidth || | |
| boardY >= this.boardHeight || | |
| (boardY >= 0 && this.board[boardY][boardX])) { | |
| return true; | |
| } | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| lockPiece() { | |
| for (let y = 0; y < this.currentPiece.shape.length; y++) { | |
| for (let x = 0; x < this.currentPiece.shape[y].length; x++) { | |
| if (this.currentPiece.shape[y][x]) { | |
| const boardY = this.currentPiece.y + y; | |
| const boardX = this.currentPiece.x + x; | |
| if (boardY >= 0) { | |
| this.board[boardY][boardX] = this.currentPiece.color; | |
| } | |
| } | |
| } | |
| } | |
| this.clearLines(); | |
| this.spawnPiece(); | |
| } | |
| clearLines() { | |
| let linesCleared = 0; | |
| for (let y = this.boardHeight - 1; y >= 0; y--) { | |
| if (this.board[y].every(cell => cell !== 0)) { | |
| this.board.splice(y, 1); | |
| this.board.unshift(Array(this.boardWidth).fill(0)); | |
| linesCleared++; | |
| y++; | |
| } | |
| } | |
| if (linesCleared > 0) { | |
| this.lines += linesCleared; | |
| this.score += linesCleared * 100 * this.level; | |
| if (this.lines >= this.level * 10) { | |
| this.level++; | |
| this.dropSpeed = Math.max(100, this.dropSpeed - 100); | |
| if (this.gameRunning && !this.gamePaused) { | |
| this.startDrop(); | |
| } | |
| } | |
| this.updateScore(); | |
| this.createParticles(); | |
| } | |
| } | |
| createParticles() { | |
| const colors = ['var(--tetris-blue)', 'var(--tetris-purple)', 'var(--tetris-yellow)', | |
| 'var(--tetris-green)', 'var(--tetris-red)', 'var(--tetris-orange)']; | |
| for (let i = 0; i < 10; i++) { | |
| const particle = document.createElement('div'); | |
| particle.className = 'particle'; | |
| particle.style.left = Math.random() * window.innerWidth + 'px'; | |
| particle.style.top = window.innerHeight / 2 + 'px'; | |
| particle.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)]; | |
| particle.style.width = Math.random() * 10 + 5 + 'px'; | |
| particle.style.height = particle.style.width; | |
| document.body.appendChild(particle); | |
| setTimeout(() => particle.remove(), 1000); | |
| } | |
| } | |
| updateScore() { | |
| this.scoreElement.textContent = this.score; | |
| this.levelElement.textContent = this.level; | |
| this.linesElement.textContent = this.lines; | |
| } | |
| render() { | |
| const cells = this.grid.querySelectorAll('.cell'); | |
| cells.forEach(cell => { | |
| cell.className = 'cell'; | |
| }); | |
| // Render locked pieces | |
| for (let y = 0; y < this.boardHeight; y++) { | |
| for (let x = 0; x < this.boardWidth; x++) { | |
| if (this.board[y][x]) { | |
| const index = y * this.boardWidth + x; | |
| cells[index].classList.add('filled', `cell.${this.board[y][x]}`); | |
| } | |
| } | |
| } | |
| // Render current piece | |
| if (this.currentPiece) { | |
| for (let y = 0; y < this.currentPiece.shape.length; y++) { | |
| for (let x = 0; x < this.currentPiece.shape[y].length; x++) { | |
| if (this.currentPiece.shape[y][x]) { | |
| const boardX = this.currentPiece.x + x; | |
| const boardY = this.currentPiece.y + y; | |
| if (boardY >= 0 && boardY < this.boardHeight && | |
| boardX >= 0 && boardX < this.boardWidth) { | |
| const index = boardY * this.boardWidth + boardX; | |
| cells[index].classList.add('filled', `cell.${this.currentPiece.color}`); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| endGame() { | |
| this.gameRunning = false; | |
| clearInterval(this.dropInterval); | |
| this.finalScoreElement.textContent = this.score; | |
| this.gameOverElement.style.display = 'block'; | |
| } | |
| } | |
| // Initialize the game when the page loads | |
| document.addEventListener('DOMContentLoaded', () => { | |
| new Tetris(); | |
| }); | |
| </script> | |
| </body> | |
| </html> |