gemini
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>