gemini

Tetris Clásico Revisado

Tetris Clásico Revisado

Puntuación:0
Líneas:0
Nivel:1
GAME OVER

Tetris

Puntos: 0
Nivel: 1
Líneas: 0

Controles:

– ← → : Mover izquierda/derecha

– ↓ : Bajar rápido

– ↑ : Rotar pieza

– Espacio : Caída inmediata

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Tetris Clásico</title> <link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
    <style>
        /* Estilos generales del cuerpo (igual que antes) */
        body{background:#202028;color:#fff;font-family:'Press Start 2P',cursive;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;margin:0;text-align:center}
        h1{margin-bottom:15px}
        /* Contenedor principal (igual que antes) */
        .game-container{border:4px solid #444;border-radius:10px;padding:20px;background-color:#181820;box-shadow:0 0 20px rgba(0,0,0,.5);display:flex;gap:20px;align-items:flex-start;flex-wrap:wrap;justify-content:center;margin-bottom:20px}
        /* Canvas (igual que antes) */
        #gameCanvas{border:3px solid #555;background-color:#000;display:block;border-radius:5px;max-width:100%;height:auto}
        /* Info (igual que antes) */
        .game-info{display:flex;flex-direction:column;gap:15px;font-size:.8em;min-width:120px;text-align:left}
        .info-item{background-color:#333;padding:8px;border-radius:5px;border:2px solid #444}
        .info-item span{display:block;margin-top:4px;color:#0f0;font-size:1.1em}
        /* Contenedor controles (igual que antes) */
        .all-controls-container{display:flex;flex-direction:column;align-items:center;gap:15px}
        /* Controles juego (igual que antes) */
        .game-controls{display:flex;gap:10px;justify-content:center;margin-bottom:10px;flex-wrap:wrap}
        /* Controles movimiento (igual que antes) */
        .movement-controls{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;max-width:250px;width:70%}
        /* Botones (igual que antes) */
        button{font-family:'Press Start 2P',cursive;font-size:.8em;padding:10px 15px;cursor:pointer;background:linear-gradient(145deg,#5a5a5a,#3a3a3a);color:#fff;border:2px solid #666;border-radius:8px;box-shadow:3px 3px 5px rgba(0,0,0,.3),inset -2px -2px 3px rgba(255,255,255,.1);transition:all .1s ease;white-space:nowrap}
        button:active{transform:translateY(2px);box-shadow:1px 1px 3px rgba(0,0,0,.3),inset -1px -1px 2px rgba(255,255,255,.1)}
        .control-button{padding:12px 18px}
        #startButton{background:linear-gradient(145deg,#4caf50,#388e3c);border-color:#66bb6a}
        #pauseButton{background:linear-gradient(145deg,#ffc107,#ffa000);border-color:#ffca28}
        #resetButton{background:linear-gradient(145deg,#f44336,#d32f2f);border-color:#ef5350}
        button:disabled{background:linear-gradient(145deg,#777,#555);color:#aaa;cursor:not-allowed;box-shadow:none;border-color:#888}
        /* Posicionamiento botones movimiento (igual que antes) */
        .rotate-button{grid-column:2/3;grid-row:1/2}
        .left-button{grid-column:1/2;grid-row:2/3}
        .down-button{grid-column:2/3;grid-row:2/3}
        .right-button{grid-column:3/4;grid-row:2/3}
        /* Game Over (igual que antes) */
        #gameOverMessage{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background-color:rgba(0,0,0,.85);color:red;padding:25px;border-radius:10px;font-size:1.3em;display:none;flex-direction:column;align-items:center;gap:15px;border:3px solid red;z-index:1000}
        #restartButton{padding:10px 20px;font-size:.8em;background:linear-gradient(145deg,#ff4b4b,#cc0000);border-color:#ff6b6b}
        /* Responsivo (igual que antes) */
        @media (max-width:600px){.game-container{flex-direction:column;align-items:center;padding:10px}.game-info{min-width:unset;width:80%;text-align:center;flex-direction:row;justify-content:space-around;gap:5px}.info-item{font-size:.7em;padding:5px}.info-item span{font-size:1em}h1{font-size:1.5em}.all-controls-container{width:90%}.game-controls{flex-wrap:wrap}.movement-controls{max-width:200px}button{font-size:.7em;padding:8px 10px}.control-button{font-size:.75em;padding:10px 15px}#gameOverMessage{width:80%;font-size:1.1em;padding:20px}}
    </style>
</head>
<body data-rsssl=1 data-rsssl=1>

    <h1>Tetris Clásico</h1> <div class="game-container">
        <canvas id="gameCanvas" width="200" height="400"></canvas>
        <div class="game-info">
            <div class="info-item">Puntuación:<span id="score">0</span></div>
            <div class="info-item">Líneas:<span id="lines">0</span></div>
            <div class="info-item">Nivel:<span id="level">1</span></div>
        </div>
    </div>

    <div class="all-controls-container">
        <div class="game-controls">
            <button id="startButton" class="control-button">Iniciar</button>
            <button id="pauseButton" class="control-button" disabled>Pausa</button>
            <button id="resetButton" class="control-button" disabled>Reiniciar</button>
        </div>
        <div class="movement-controls">
            <button class="rotate-button" id="rotateButton" disabled>Rotar</button>
            <button class="left-button" id="leftButton" disabled>←</button>
            <button class="down-button" id="downButton" disabled>↓</button>
            <button class="right-button" id="rightButton" disabled>→</button>
        </div>
    </div>

    <div id="gameOverMessage">
        GAME OVER
        <button id="restartButton">Jugar de Nuevo</button>
    </div>

    <script>
        console.log("Tetris Completo: Cargando script...");

        // --- Elementos del DOM (igual que antes) ---
        const canvas = document.getElementById('gameCanvas');
        const scoreElement = document.getElementById('score');
        const linesElement = document.getElementById('lines');
        const levelElement = document.getElementById('level');
        const gameOverMessage = document.getElementById('gameOverMessage');
        const startButton = document.getElementById('startButton');
        const pauseButton = document.getElementById('pauseButton');
        const resetButton = document.getElementById('resetButton');
        const restartButtonGameOver = document.getElementById('restartButton');
        const rotateButton = document.getElementById('rotateButton');
        const leftButton = document.getElementById('leftButton');
        const rightButton = document.getElementById('rightButton');
        const downButton = document.getElementById('downButton');
        let context = null;
        let domReady = false;

        // --- Constantes y Variables del Juego (igual que antes) ---
        const COLS = 10;
        const ROWS = 20;
        let BLOCK_SIZE = 20;

        let board = [];
        let currentPiece = null;
        let score = 0;
        let linesCleared = 0;
        let level = 1;
        let dropStart = Date.now(); // Restaurado para controlar caída
        let gameLoopTimeout;

        let isGameOver = false;
        let isPaused = false;
        let isGameRunning = false;

        const COLORS = [ null, '#FF0D72', '#0DC2FF', '#0DFF72', '#F538FF', '#FF8E0D', '#FFE138', '#3877FF' ];
        const SHAPES = [ [], [[1, 1, 1, 1]], [[1, 0, 0], [1, 1, 1]], [[0, 0, 1], [1, 1, 1]], [[1, 1], [1, 1]], [[0, 1, 1], [1, 1, 0]], [[0, 1, 0], [1, 1, 1]], [[1, 1, 0], [0, 1, 1]] ];

        // --- Funciones de Dibujo (igual que antes) ---
         function drawBlock(x, y, color) { /* ... igual que antes ... */
            if (!context) return;
            context.fillStyle = color;
            context.fillRect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
            context.strokeStyle = '#111';
            context.lineWidth = 1;
            context.strokeRect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
         }
         function draw() { /* ... igual que antes ... */
            if (!context || !canvas) return;
            context.fillStyle = '#000';
            context.fillRect(0, 0, canvas.width, canvas.height);
            board.forEach((row, y) => { row.forEach((value, x) => { if (value > 0) { drawBlock(x, y, COLORS[value]); } }); });
            if (currentPiece) {
                currentPiece.shape.forEach((row, dy) => {
                    row.forEach((value, dx) => {
                        if (value) {
                            if (currentPiece.y + dy >= 0) { drawBlock(currentPiece.x + dx, currentPiece.y + dy, currentPiece.color); }
                        }
                    });
                });
            }
         }

        // --- Funciones de Juego (Lógica Restaurada) ---
         function createPiece() { /* ... igual que antes ... */
            // console.log("Tetris Completo: createPiece()");
            const typeId = Math.floor(Math.random() * (SHAPES.length - 1)) + 1;
            const shape = SHAPES[typeId];
            let initialX = Math.floor(COLS / 2) - Math.floor(shape[0].length / 2);
            initialX = Math.max(0, Math.min(initialX, COLS - shape[0].length));
            return { x: initialX, y: 0, shape: shape, color: COLORS[typeId], typeId: typeId };
         }
         function createBoard() { /* ... igual que antes ... */
             // console.log("Tetris Completo: createBoard()");
             board = Array.from({ length: ROWS }, () => Array(COLS).fill(0));
         }
         function updateInfo() { /* ... igual que antes ... */
            if (!domReady || !scoreElement || !linesElement || !levelElement) return;
            scoreElement.textContent = score;
            linesElement.textContent = linesCleared;
            levelElement.textContent = level;
         }

        // --- Funciones de Movimiento y Colisión (Restauradas) ---
         function isValidMove(piece, newX, newY, newShape) {
            if (!piece) return false;
            const shape = newShape || piece.shape;
            for (let y = 0; y < shape.length; y++) {
                for (let x = 0; x < shape[y].length; x++) {
                    if (shape[y][x]) { // Si es parte de la pieza
                        let nextX = newX + x;
                        let nextY = newY + y;

                        // 1. Comprobar límites del tablero
                        if (nextX < 0 || nextX >= COLS || nextY >= ROWS) {
                            return false; // Fuera de límites horizontales o inferiores
                        }

                        // 2. Comprobar colisiones con piezas fijadas (solo si está dentro del tablero visible Y >= 0)
                        if (nextY >= 0 && board[nextY] && board[nextY][nextX] !== 0) {
                            return false; // Choca con una pieza existente
                        }
                    }
                }
            }
            return true; // Movimiento válido
         }

          function freezePiece() {
             if (!currentPiece) return;
             currentPiece.shape.forEach((row, dy) => {
                 row.forEach((value, dx) => {
                     if (value) {
                         const boardX = currentPiece.x + dx;
                         const boardY = currentPiece.y + dy;
                         // Asegurarse de fijar solo dentro del tablero
                         if (boardY >= 0 && boardY < ROWS && boardX >= 0 && boardX < COLS) {
                             board[boardY][boardX] = currentPiece.typeId;
                         } else {
                              // Si intenta fijar fuera (ej. justo al empezar), podría ser un problema
                              console.warn("Intento de fijar pieza fuera de límites", boardX, boardY);
                         }
                     }
                 });
             });
             // console.log("Tetris Completo: Pieza fijada.");
         }

         // clearLines - ¡Restaurada completamente!
         function clearLines() {
            let linesToClear = 0;
            for (let y = ROWS - 1; y >= 0; y--) { // Recorrer desde abajo
                // Si la fila existe y todas sus celdas son mayores que 0 (ocupadas)
                if (board[y] && board[y].every(cell => cell > 0)) {
                    linesToClear++;
                    // Eliminar la fila completa
                    board.splice(y, 1);
                    // Añadir una nueva fila vacía arriba
                    board.unshift(Array(COLS).fill(0));
                    // Como las filas se movieron hacia abajo, necesitamos volver a
                    // comprobar la misma posición 'y' en la siguiente iteración
                    y++;
                }
            }

            // Actualizar puntuación y nivel si se limpiaron líneas
            if (linesToClear > 0) {
                console.log(`Tetris Completo: ${linesToClear} línea(s) eliminada(s).`);
                linesCleared += linesToClear;
                // Puntuación (ejemplo simple)
                if (linesToClear === 1) score += 40 * level;
                else if (linesToClear === 2) score += 100 * level;
                else if (linesToClear === 3) score += 300 * level;
                else if (linesToClear >= 4) score += 1200 * level; // ¡Tetris!

                // Subir nivel cada 10 líneas (ejemplo)
                level = Math.floor(linesCleared / 10) + 1;

                updateInfo(); // Actualizar marcadores
            }
         }


         // moveDown - ¡Restaurada completamente! (Maneja fijación y Game Over)
         function moveDown() {
             if (!currentPiece || isPaused || isGameOver) return; // No mover si no aplica

             if (isValidMove(currentPiece, currentPiece.x, currentPiece.y + 1)) {
                 // Si puede bajar, simplemente aumenta la coordenada Y
                 currentPiece.y++;
                 dropStart = Date.now(); // Reiniciar temporizador para la próxima caída automática
             } else {
                 // Si no puede bajar más:
                 // 1. Fijar la pieza actual en el tablero
                 freezePiece();
                 // 2. Comprobar y limpiar líneas completas
                 clearLines();
                 // 3. Crear la siguiente pieza
                 currentPiece = createPiece();
                 // 4. Comprobar si la nueva pieza colisiona inmediatamente (GAME OVER)
                 if (!isValidMove(currentPiece, currentPiece.x, currentPiece.y)) {
                     handleGameOver(); // ¡Fin del juego!
                 }
             }
         }

         // moveLeft - ¡Restaurada!
         function moveLeft() {
             if (!currentPiece || isPaused || isGameOver) return;
             if (isValidMove(currentPiece, currentPiece.x - 1, currentPiece.y)) {
                 currentPiece.x--;
                 // Podrías reiniciar dropStart aquí si quieres que el movimiento lateral retrase la caída
                 // dropStart = Date.now();
             }
         }

         // moveRight - ¡Restaurada!
         function moveRight() {
             if (!currentPiece || isPaused || isGameOver) return;
             if (isValidMove(currentPiece, currentPiece.x + 1, currentPiece.y)) {
                 currentPiece.x++;
                 // dropStart = Date.now();
             }
         }

         // rotate - ¡Restaurada! (con wall kick simple)
         function rotate() {
             if (!currentPiece || isPaused || isGameOver) return;

             const originalShape = currentPiece.shape;
             const numRows = originalShape.length;
             const numCols = originalShape[0].length;
             const rotatedShape = [];

             // Crear matriz rotada vacía
             for (let i = 0; i < numCols; i++) {
                 rotatedShape[i] = Array(numRows).fill(0);
             }
             // Llenar matriz rotada (rotación 90 grados horario)
             for (let y = 0; y < numRows; y++) {
                 for (let x = 0; x < numCols; x++) {
                     if (originalShape[y][x]) {
                         rotatedShape[x][numRows - 1 - y] = originalShape[y][x];
                     }
                 }
             }

             // Intentar colocar la pieza rotada
             if (isValidMove(currentPiece, currentPiece.x, currentPiece.y, rotatedShape)) {
                 currentPiece.shape = rotatedShape;
             } else {
                 // Intento de "wall kick" simple (mover 1 a la derecha o izquierda)
                 if (isValidMove(currentPiece, currentPiece.x + 1, currentPiece.y, rotatedShape)) {
                     currentPiece.x++; // Mover a la derecha
                     currentPiece.shape = rotatedShape;
                 } else if (isValidMove(currentPiece, currentPiece.x - 1, currentPiece.y, rotatedShape)) {
                     currentPiece.x--; // Mover a la izquierda
                     currentPiece.shape = rotatedShape;
                 }
                 // Si ninguna posición es válida, no se rota
             }
             // dropStart = Date.now(); // Opcional: reiniciar caída al rotar
         }


        // --- Funciones de Control (igual que antes) ---
         function startGame() { /* ... igual que antes ... */
            if (isGameRunning || !domReady) return;
            console.log("Tetris Completo: startGame() llamado.");
            createBoard();
            score = 0; linesCleared = 0; level = 1; updateInfo();
            isGameRunning = true; isGameOver = false; isPaused = false;
            currentPiece = createPiece();
            if (!isValidMove(currentPiece, currentPiece.x, currentPiece.y)) { // Re-check validity
                 console.error("Tetris Completo: Game Over inmediato al iniciar.");
                 handleGameOver(); return;
            }
            dropStart = Date.now(); // Iniciar temporizador de caída
            updateUIState();
            console.log("Tetris Completo: Iniciando gameLoop...");
            gameLoop();
         }
         function pauseGame() { /* ... igual que antes ... */
             if (!isGameRunning || isGameOver || !domReady) return;
             isPaused = !isPaused;
             if (isPaused) {
                 clearTimeout(gameLoopTimeout);
                 if(pauseButton) pauseButton.textContent = "Reanudar";
                 console.log("Tetris Completo: Juego pausado.");
             } else {
                 dropStart = Date.now(); // Reajustar tiempo de caída al reanudar
                 gameLoop();
                 if(pauseButton) pauseButton.textContent = "Pausa";
                 console.log("Tetris Completo: Juego reanudado.");
             }
             updateUIState();
         }
         function resetGame() { /* ... igual que antes ... */
             if (!domReady) return;
             console.log("Tetris Completo: resetGame() llamado.");
             clearTimeout(gameLoopTimeout);
             createBoard(); score = 0; linesCleared = 0; level = 1; updateInfo();
             currentPiece = null; isGameRunning = false; isPaused = false; isGameOver = false;
             if(gameOverMessage) gameOverMessage.style.display = 'none';
             draw(); updateUIState();
         }
         function updateUIState() { /* ... igual que antes ... */
             if (!domReady) return;
             if(startButton) startButton.disabled = isGameRunning && !isGameOver;
             if(pauseButton) pauseButton.disabled = !isGameRunning || isGameOver;
             if(resetButton) resetButton.disabled = !isGameRunning && !isGameOver;
             const movementDisabled = !isGameRunning || isPaused || isGameOver;
             if(rotateButton) rotateButton.disabled = movementDisabled;
             if(leftButton) leftButton.disabled = movementDisabled;
             if(rightButton) rightButton.disabled = movementDisabled;
             if(downButton) downButton.disabled = movementDisabled;
             if (isGameRunning && !isGameOver && pauseButton) { pauseButton.textContent = isPaused ? "Reanudar" : "Pausa"; }
             else if (pauseButton) { pauseButton.textContent = "Pausa"; }
        }
         // handleGameOver - ¡Restaurada!
         function handleGameOver() {
            console.log("Tetris Completo: GAME OVER.");
            isGameOver = true;
            isGameRunning = false;
            clearTimeout(gameLoopTimeout); // Detener el bucle
            if (domReady && gameOverMessage) {
                 gameOverMessage.style.display = 'flex'; // Mostrar mensaje
                 updateUIState(); // Desactivar botones de juego
            }
        }


        // --- Bucle Principal (¡Llamada a moveDown restaurada!) ---
        function gameLoop() {
            if (isGameOver || isPaused) { return; } // Salir si pausado o terminado

            let now = Date.now();
            let delta = now - dropStart;
            // Calcular intervalo de caída basado en el nivel (más rápido a mayor nivel)
            let currentLevel = Math.max(1, level); // Asegurar nivel mínimo 1
            // Fórmula de ejemplo: empieza en 1 seg (1000ms), se acelera con nivel
            let dropInterval = 1000 / (currentLevel * 0.8 + 1);
            // Asegurar un intervalo mínimo para que no sea demasiado rápido
            dropInterval = Math.max(100, dropInterval); // Mínimo 100ms (10 caídas/seg)

            // Si ha pasado suficiente tiempo, mover la pieza hacia abajo
            if (delta > dropInterval) {
                moveDown(); // ¡LA PIEZA AHORA CAE!
                // dropStart se actualiza dentro de moveDown si el movimiento es válido
            }

            // Dibujar siempre el estado actual
            draw();

            // Continuar el bucle
            gameLoopTimeout = setTimeout(gameLoop, 1000 / 60); // Intentar refrescar a ~60fps para animaciones fluidas
        }

        // --- Controles de Teclado (¡Lógica Restaurada!) ---
        function handleKeyDown(event) {
            if (!isGameRunning || isPaused || isGameOver) return; // Ignorar si no aplica

            let moved = false; // Para saber si redibujar
            switch (event.keyCode) {
                case 37: // Flecha izquierda
                    moveLeft(); moved = true; event.preventDefault(); break;
                case 39: // Flecha derecha
                    moveRight(); moved = true; event.preventDefault(); break;
                case 40: // Flecha abajo (acelerar caída)
                    moveDown(); moved = true; event.preventDefault(); break;
                case 38: // Flecha arriba (rotar)
                case 88: // Tecla X (rotar)
                    rotate(); moved = true; event.preventDefault(); break;
                case 80: // Tecla P (Pausa/Reanudar)
                    pauseGame(); break; // No necesita redibujar inmediatamente
            }
            // Redibujar solo si la pieza se movió o rotó
            if (moved) {
                draw();
            }
        }


        // --- Inicialización (igual que antes) ---
        function checkDOM() { /* ... igual que antes ... */
             const essentialElements = canvas && scoreElement && linesElement && levelElement && gameOverMessage && startButton && pauseButton && resetButton && restartButtonGameOver && rotateButton && leftButton && rightButton && downButton;
            if (!essentialElements) { console.error("Tetris Completo: Faltan elementos del DOM."); return false; }
            context = canvas.getContext('2d');
            if (!context) { console.error("Tetris Completo: No se pudo obtener contexto 2D."); return false; }
            return true;
        }
        function init() { /* ... igual que antes, pero añade listeners de movimiento funcionales ... */
            console.log("Tetris Completo: init() llamado.");
            if (!checkDOM()) { return; }
            domReady = true;
            console.log("Tetris Completo: DOM y contexto verificados.");
            BLOCK_SIZE = canvas.width / COLS;
            createBoard(); draw(); updateInfo();

            // Listeners Control
            if(startButton) startButton.addEventListener('click', startGame);
            if(pauseButton) pauseButton.addEventListener('click', pauseGame);
            if(resetButton) resetButton.addEventListener('click', resetGame);
            if(restartButtonGameOver) restartButtonGameOver.addEventListener('click', () => {
                 if (!domReady) return;
                 if(gameOverMessage) gameOverMessage.style.display = 'none';
                 resetGame(); startGame();
            });

            // Listeners Movimiento (¡Ahora llaman a las funciones reales!)
            if(leftButton) leftButton.addEventListener('click', () => { if (!isPaused && !isGameOver && currentPiece) { moveLeft(); draw();} });
            if(rightButton) rightButton.addEventListener('click', () => { if (!isPaused && !isGameOver && currentPiece) { moveRight(); draw();} });
            if(downButton) downButton.addEventListener('click', () => { if (!isPaused && !isGameOver && currentPiece) { moveDown(); draw();} }); // Acelera caída
            if(rotateButton) rotateButton.addEventListener('click', () => { if (!isPaused && !isGameOver && currentPiece) { rotate(); draw();} });

            document.addEventListener('keydown', handleKeyDown); // Listener teclado

            updateUIState(); // Estado inicial botones
            console.log("Tetris Completo: Inicialización completa. Listo para 'Iniciar'.");
        }

        // --- Inicio del Script (igual que antes) ---
        window.addEventListener('load', init);
        console.log("Tetris Completo: Event listener 'load' añadido.");

    </script>

</body>
</html>