HTML
<!DOCTYPE html> <html lang="es"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>¡A Servir Cañas en GRAMYS!</title> <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet"> <link rel="stylesheet" href="style.css"> </head> <body class="bg-gray-900 flex justify-center items-center h-screen"> <div id="game-container" class="relative overflow-hidden bg-cover bg-center" style="width: 800px; height: 450px; background-image: url('https://i.imgur.com/8GOnjWv.png');"> <div id="start-button-container" class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"> <button id="start-button" class="bg-yellow-500 hover:bg-yellow-400 text-gray-800 font-bold py-4 px-8 rounded-full text-2xl shadow-lg focus:outline-none focus:ring-2 focus:ring-yellow-600">A BEBER</button> </div> <div id="game-elements" class="hidden"> <div class="absolute top-4 left-1/2 transform -translate-x-1/2 bg-yellow-500 bg-opacity-75 rounded-md p-2 shadow-lg flex flex-col items-center"> <h1 class="text-yellow-300 text-4xl font-bold uppercase tracking-wider text-center" style="text-shadow: 2px 2px 4px #000;">GRAMYS</h1> </div> <div id="beer-counter" class="absolute top-4 left-4 text-white text-lg font-bold" style="text-shadow: 1px 1px 2px #000;">TE QUEDAN: 55</div> <div id="waiter" class="absolute bottom-0 left-10 transition-all duration-100" style="width: 130px; height: 292.5px; background-image: url('https://i.imgur.com/2drKVvv.png'); background-size: contain;"></div> <div id="enemies-container"></div> <div id="beers-container"></div> <div class="absolute bottom-0 left-0 w-full bg-gray-800 bg-opacity-75 p-4 flex justify-center space-x-4"> <button id="control-left" class="bg-gray-700 hover:bg-gray-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">←</button> <button id="control-forward" class="bg-gray-700 hover:bg-gray-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">→</button> <button id="control-jump" class="bg-blue-500 hover:bg-blue-400 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">↑</button> <button id="control-attack" class="bg-red-500 hover:bg-red-400 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">¡Caña!</button> </div> <div id="game-message" class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-gray-800 bg-opacity-75 text-white text-center p-8 rounded-md shadow-lg text-3xl font-bold hidden"> <button id="restart-button" class="mt-4 bg-green-500 hover:bg-green-400 text-white font-bold py-3 px-6 rounded-full text-xl shadow-md focus:outline-none focus:ring-2 focus:ring-green-600 hidden">BEBE OTRA VEZ</button> </div> </div> </div> <script src="script.js"></script> </body> </html>
JavaScript
document.addEventListener('DOMContentLoaded', () => { const gameContainer = document.getElementById('game-container'); const startButtonContainer = document.getElementById('start-button-container'); const startButton = document.getElementById('start-button'); const gameElements = document.getElementById('game-elements'); const waiter = document.getElementById('waiter'); const enemiesContainer = document.getElementById('enemies-container'); const beersContainer = document.getElementById('beers-container'); const controlLeft = document.getElementById('control-left'); const controlForward = document.getElementById('control-forward'); const controlJump = document.getElementById('control-jump'); const controlAttack = document.getElementById('control-attack'); const gameMessage = document.getElementById('game-message'); const restartButton = document.getElementById('restart-button'); // Ya existe, solo lo mostraremos const beerCounterDisplay = document.getElementById('beer-counter'); // Dimensiones del juego y personajes const GAME_WIDTH = 800; const GAME_HEIGHT = 450; const WAITER_WIDTH = 130; const WAITER_HEIGHT = 292.5; const WAITER_SPEED = 5; const WAITER_JUMP_HEIGHT = 22; const BEER_WIDTH = 60; // El doble del tamaño original const BEER_HEIGHT = 90; // El doble del tamaño original const BEER_SPEED = 15; const ENEMY_WIDTH = 130; const ENEMY_HEIGHT = 292.5; const ENEMY_SPEED_LEVELS = [3, 4, 5, 6, 7]; const ENEMY_SPAWN_INTERVAL_LEVELS = [1000, 800, 600, 400, 200]; const ENEMIES_PER_LEVEL = 10; const TOTAL_ENEMIES = 50; const INITIAL_BEER_COUNT = 55; let beerCount = INITIAL_BEER_COUNT; // Estados del juego let waiterX = 10; let waiterY = 0; let isJumping = false; let jumpVelocity = 0; let enemies = []; let beers = []; let enemiesDefeated = 0; let enemiesSpawned = 0; let gameActive = false; let spawnTimeoutId = null; // Para controlar el setTimeout de la creación de enemigos // Imágenes const waiterImage = 'https://i.imgur.com/2drKVvv.png'; const enemyImage = 'https://i.imgur.com/AOJIsUm.png'; const beerImage = 'https://i.imgur.com/oXKmVBE.png'; // Funciones de utilidad function getRandom(min, max) { return Math.random() * (max - min) + min; } function checkCollision(rect1, rect2) { return ( rect1.x < rect2.x + rect2.width && rect1.x + rect1.width > rect2.x && rect1.y < rect2.y + rect2.height && rect1.y + rect1.height > rect2.y ); } function updateWaiter() { waiter.style.left = `${waiterX}px`; waiter.style.bottom = `${waiterY}px`; waiter.style.width = `${WAITER_WIDTH}px`; waiter.style.height = `${WAITER_HEIGHT}px`; waiter.style.backgroundImage = `url('${waiterImage}')`; } function createEnemy() { if (gameActive && enemiesSpawned < TOTAL_ENEMIES) { const enemy = document.createElement('div'); enemy.classList.add('absolute'); enemy.style.width = `${ENEMY_WIDTH}px`; enemy.style.height = `${ENEMY_HEIGHT}px`; enemy.style.bottom = '0px'; enemy.style.left = `${GAME_WIDTH}px`; enemy.style.backgroundImage = `url('${enemyImage}')`; enemy.style.backgroundSize = 'contain'; enemiesContainer.appendChild(enemy); enemies.push({ element: enemy, x: GAME_WIDTH, y: 0 }); enemiesSpawned++; spawnTimeoutId = setTimeout(createEnemy, getCurrentSpawnInterval()); } } function updateEnemies() { const currentLevel = Math.floor(enemiesSpawned / ENEMIES_PER_LEVEL); const currentSpeed = ENEMY_SPEED_LEVELS[Math.min(currentLevel, ENEMY_SPEED_LEVELS.length - 1)]; for (let i = 0; i < enemies.length; i++) { const enemy = enemies[i]; enemy.x -= currentSpeed; enemy.element.style.left = `${enemy.x}px`; enemy.element.style.width = `${ENEMY_WIDTH}px`; enemy.element.style.height = `${ENEMY_HEIGHT}px`; // Colisión con el camarero if (checkCollision( { x: waiterX, y: waiterY, width: WAITER_WIDTH, height: WAITER_HEIGHT }, { x: enemy.x, y: enemy.y, width: ENEMY_WIDTH, height: ENEMY_HEIGHT } )) { gameOver(); break; // Importante detener el bucle si el juego termina } // Eliminar enemigos que salen de la pantalla if (enemy.x < -ENEMY_WIDTH) { enemy.element.remove(); enemies.splice(i, 1); i--; // Ajustar el índice al eliminar un elemento } } } function createBeer() { if (gameActive && beerCount > 0) { const beer = document.createElement('div'); beer.classList.add('absolute'); beer.style.width = `${BEER_WIDTH}px`; beer.style.height = `${BEER_HEIGHT}px`; beer.style.bottom = `${waiterY + WAITER_HEIGHT * 0.6}px`; // Lanzamiento un poco más arriba beer.style.left = `${waiterX + WAITER_WIDTH * 0.7}px`; beer.style.backgroundImage = `url('${beerImage}')`; beer.style.backgroundSize = 'contain'; beersContainer.appendChild(beer); beers.push({ element: beer, x: waiterX + WAITER_WIDTH * 0.7, y: waiterY + WAITER_HEIGHT * 0.6 }); beerCount--; updateBeerCounter(); } } function updateBeers() { for (let i = 0; i < beers.length; i++) { const beer = beers[i]; beer.x += BEER_SPEED; beer.element.style.left = `${beer.x}px`; beer.element.style.width = `${BEER_WIDTH}px`; beer.element.style.height = `${BEER_HEIGHT}px`; // Colisión con enemigos for (let j = 0; j < enemies.length; j++) { const enemy = enemies[j]; if (checkCollision( { x: beer.x, y: beer.y, width: BEER_WIDTH, height: BEER_HEIGHT }, { x: enemy.x, y: enemy.y, width: ENEMY_WIDTH, height: ENEMY_HEIGHT } )) { beer.element.remove(); beers.splice(i, 1); i--; // Ajustar el índice al eliminar un elemento enemy.element.remove(); enemies.splice(j, 1); j--; // Ajustar el índice al eliminar un elemento enemiesDefeated++; break; // Romper el bucle de enemigos al impactar } } // Eliminar cervezas que salen de la pantalla if (beer.x > GAME_WIDTH) { beer.element.remove(); beers.splice(i, 1); i--; // Ajustar el índice al eliminar un elemento } } } function jump() { if (gameActive && !isJumping) { isJumping = true; jumpVelocity = WAITER_JUMP_HEIGHT; } } function updateJump() { if (isJumping) { waiterY += jumpVelocity; jumpVelocity -= 1; if (waiterY <= 0) { waiterY = 0; isJumping = false; jumpVelocity = 0; } } } function gameLoop() { if (!gameActive) return; updateWaiter(); updateEnemies(); updateBeers(); updateJump(); if (enemiesDefeated === TOTAL_ENEMIES && enemies.length === 0) { gameWin(); } else if (beerCount === 0 && enemies.length > 0) { gameOver(); // Game over si no quedan cervezas y aún hay enemigos } requestAnimationFrame(gameLoop); } function getCurrentSpawnInterval() { const currentLevel = Math.floor(enemiesSpawned / ENEMIES_PER_LEVEL); return ENEMY_SPAWN_INTERVAL_LEVELS[Math.min(currentLevel, ENEMY_SPAWN_INTERVAL_LEVELS.length - 1)]; } function updateBeerCounter() { beerCounterDisplay.textContent = `TE QUEDAN: ${beerCount}`; } function startGame() { gameActive = true; startButtonContainer.classList.add('hidden'); gameElements.classList.remove('hidden'); resetGame(); updateBeerCounter(); // Inicializar el contador al empezar createEnemy(); gameLoop(); } function gameOver() { gameActive = false; clearTimeout(spawnTimeoutId); // Detener la creación de nuevos enemigos gameMessage.textContent = (beerCount === 0 && enemies.length > 0) ? "¡Te quedaste sin cañas!" : "¡Te han echado del bar!"; restartButton.classList.remove('hidden'); // Mostrar el botón de reinicio gameMessage.classList.remove('hidden'); } function gameWin() { gameActive = false; clearTimeout(spawnTimeoutId); // Detener la creación de nuevos enemigos gameMessage.innerHTML = '<span style="font-size: 2em; color: gold; text-shadow: 2px 2px 4px #000;">¡CHUPITAZO</span> <span style="font-size: 3em; color: limegreen; text-shadow: 2px 2px 4px #000;">GRATIS!</span>'; restartButton.classList.remove('hidden'); // Mostrar el botón de reinicio también al ganar (por si quieres seguir bebiendo) gameMessage.classList.remove('hidden'); } function resetGame() { waiterX = 10; waiterY = 0; isJumping = false; jumpVelocity = 0; enemies.forEach(enemy => enemy.element.remove()); enemies = []; beers.forEach(beer => beer.element.remove()); beers = []; enemiesDefeated = 0; enemiesSpawned = 0; beerCount = INITIAL_BEER_COUNT; clearTimeout(spawnTimeoutId); // Asegurarse de que no haya timeouts pendientes spawnTimeoutId = null; gameMessage.classList.add('hidden'); // Ocultar el mensaje de fin de juego restartButton.classList.add('hidden'); // Ocultar el botón de reinicio al empezar } // Event listeners startButton.addEventListener('click', startGame); restartButton.addEventListener('click', startGame); // El botón de reinicio ahora también llama a startGame controlAttack.addEventListener('click', () => { createBeer(); }); document.addEventListener('keydown', (event) => { switch (event.key) { case ' ': createBeer(); break; } }); controlLeft.addEventListener('click', () => { if (gameActive) { waiterX = Math.max(0, waiterX - WAITER_SPEED); } }); controlForward.addEventListener('click', () => { if (gameActive) { waiterX = Math.min(GAME_WIDTH - WAITER_WIDTH, waiterX + WAITER_SPEED); } }); controlJump.addEventListener('click', () => { if (gameActive) { jump(); } }); });
CSS
body { background-color: #f0f0f0; font-family: sans-serif; } #gameCanvas { background-color: #fff; }
Run
Download Code
Open Demo
Live Preview