Spaces:
Paused
Paused
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&display=swap" rel="stylesheet"> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" xintegrity="sha512-9usAa10IRO0HhonpyAIVpjrylPvoDwiPUiKdWk5t3PyolY1cOd4DSE0Ga+ri4AuTroPR5aQvXU9xC6qOPnzFeg==" crossorigin="anonymous" referrerpolicy="no-referrer" /> | |
| <style> | |
| /* Base styles for the body and main layout container */ | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| background-color: #1a1a1a; /* Dark gray from Mini Bonanza example */ | |
| color: #d0d0d0; /* Light gray text from Mini Bonanza example */ | |
| min-height: 100vh; | |
| overflow: hidden; /* Prevent scrollbars */ | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| /* Main layout container to hold sidebar and game content */ | |
| .main-layout-container { | |
| display: flex; | |
| flex-direction: column; /* Default to column for small screens */ | |
| width: 100%; | |
| height: 100vh; /* Full viewport height */ | |
| max-width: 1200px; /* Max width for desktop */ | |
| background-color: #1a1a1a; /* Match body background */ | |
| } | |
| /* Sidebar styling from Mini Bonanza */ | |
| .sidebar { | |
| background: linear-gradient(145deg, #282828, #1c1c1c); /* Dark gray gradient for sidebar */ | |
| padding: 1.5rem; /* p-6 from Mini Bonanza */ | |
| width: 100%; /* Full width on small screens */ | |
| height: auto; /* Auto height on small screens */ | |
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); /* Lighter shadow for mobile */ | |
| color: #d0d0d0; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: flex-start; | |
| flex-shrink: 0; | |
| } | |
| @media (min-width: 1024px) { /* lg breakpoint */ | |
| .main-layout-container { | |
| flex-direction: row; /* Row for desktop */ | |
| height: 100vh; /* Full height */ | |
| border-radius: 0.75rem; /* rounded-lg */ | |
| box-shadow: 0 0 35px rgba(255, 255, 255, 0.15); /* Enhanced shadow */ | |
| overflow: hidden; | |
| } | |
| .sidebar { | |
| width: 16rem; /* w-64 from Mini Bonanza */ | |
| height: 100%; /* Full height of parent */ | |
| border-radius: 0.75rem 0 0 0.75rem; /* rounded-r-lg */ | |
| box-shadow: 8px 0 20px rgba(0, 0, 0, 0.4); /* Deeper shadow */ | |
| position: relative; /* Not fixed on desktop */ | |
| transform: translateX(0) ; /* Ensure visible on desktop */ | |
| } | |
| .mobile-menu-button { | |
| display: none; /* Hide on desktop */ | |
| } | |
| .sidebar-overlay { | |
| display: none ; /* Hide on desktop */ | |
| } | |
| } | |
| /* Sidebar specific styles from Mini Bonanza */ | |
| .sidebar-title-group { | |
| display: flex; | |
| align-items: center; | |
| margin-bottom: 2.5rem; /* mb-10 */ | |
| align-self: flex-start; /* self-start */ | |
| width: 100%; | |
| } | |
| .sidebar-title-group .icon { | |
| color: #a0aec0; /* text-gray-400 */ | |
| font-size: 1.875rem; /* text-3xl */ | |
| margin-right: 0.75rem; /* mr-3 */ | |
| } | |
| .sidebar-title-group .title { | |
| font-size: 1.875rem; /* text-3xl */ | |
| font-weight: 700; /* font-bold */ | |
| color: #fff; /* text-white */ | |
| } | |
| .balance-display-container { | |
| background-color: #2d2d2d; /* bg-gray-800 */ | |
| padding: 1rem; /* p-4 */ | |
| border-radius: 0.5rem; /* rounded-lg */ | |
| box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06); /* shadow-inner */ | |
| margin-bottom: 1.5rem; /* mb-6 */ | |
| width: 100%; | |
| text-align: center; | |
| } | |
| .balance-label { | |
| color: #a0aec0; /* text-gray-400 */ | |
| font-size: 1.125rem; /* text-lg */ | |
| font-weight: 500; /* font-medium */ | |
| display: block; | |
| } | |
| #totalBalanceDisplay { | |
| font-size: 2.25rem; /* text-4xl */ | |
| font-weight: 800; /* font-extrabold */ | |
| color: #68d391; /* text-green-400 */ | |
| margin-top: 0.5rem; /* mt-2 */ | |
| display: block; | |
| } | |
| #userIdDisplay { | |
| color: #a0aec0; /* text-gray-500 */ | |
| font-size: 0.75rem; /* text-xs */ | |
| margin-top: 0.5rem; /* mt-2 */ | |
| display: block; | |
| } | |
| .qr-deposit-container { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| margin-bottom: 1rem; /* mb-4 */ | |
| } | |
| .qr-deposit-container .qr-box { | |
| background-color: #fff; /* bg-white */ | |
| padding: 1rem; /* p-4 */ | |
| border-radius: 0.5rem; /* rounded-lg */ | |
| box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); /* shadow-md */ | |
| border: 1px solid #cbd5e0; /* border border-gray-300 */ | |
| } | |
| .qr-deposit-container img { | |
| width: 8rem; /* w-32 */ | |
| height: 8rem; /* h-32 */ | |
| } | |
| .qr-deposit-container label { | |
| color: #a0aec0; /* text-gray-400 */ | |
| font-size: 0.875rem; /* text-sm */ | |
| margin-top: 0.5rem; /* mt-2 */ | |
| } | |
| .game-button { | |
| padding: 0.75rem 1.5rem; | |
| border-radius: 0.75rem; | |
| font-weight: 700; | |
| font-size: 1rem; | |
| cursor: pointer; | |
| transition: all 0.2s ease-in-out; | |
| box-shadow: 0 4px 6px rgba(0,0,0,0.1); | |
| border: none; | |
| width: 100%; | |
| margin-bottom: 1rem; | |
| } | |
| .game-button:last-of-type { | |
| margin-bottom: 0; | |
| } | |
| .game-button:disabled { | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| box-shadow: none; | |
| } | |
| .start-button { | |
| background-color: #5cb85c; | |
| color: white; | |
| background-image: linear-gradient(to bottom right, #5cb85c, #4cae4c); | |
| } | |
| .start-button:hover:not(:disabled) { | |
| background-color: #4cae4c; | |
| transform: translateY(-2px); | |
| box-shadow: 0 6px 8px rgba(0,0,0,0.2); | |
| } | |
| .stop-button { | |
| background-color: #d9534f; | |
| color: white; | |
| background-image: linear-gradient(to bottom right, #d9534f, #c9302c); | |
| } | |
| .stop-button:hover:not(:disabled) { | |
| background-color: #c9302c; | |
| transform: translateY(-2px); | |
| box-shadow: 0 6px 8px rgba(0,0,0,0.2); | |
| } | |
| .activity-log { | |
| color: #d0d0d0; /* text-gray-300 */ | |
| margin-top: 1.5rem; /* mt-6 */ | |
| width: 100%; | |
| border-top: 1px solid #4a5568; /* border-t border-gray-700 */ | |
| padding-top: 1rem; /* pt-4 */ | |
| } | |
| .activity-log h3 { | |
| font-size: 1.125rem; /* text-lg */ | |
| font-weight: 600; /* font-semibold */ | |
| color: #e0e0e0; /* text-gray-200 */ | |
| margin-bottom: 0.75rem; /* mb-3 */ | |
| } | |
| .activity-log ul { | |
| color: #a0aec0; /* text-gray-400 */ | |
| border-radius: 0.5rem; /* rounded-lg */ | |
| border: 1px solid #4a5568; /* border border-gray-700 */ | |
| padding: 0.75rem; /* p-3 */ | |
| max-height: 10rem; /* max-h-40 */ | |
| overflow-y: auto; | |
| font-size: 0.875rem; /* text-sm */ | |
| } | |
| .activity-log ul li { | |
| margin-bottom: 0.25rem; | |
| } | |
| /* Game content styling */ | |
| .game-content-main { | |
| flex-grow: 1; | |
| padding: 1rem; /* p-4 */ | |
| background-color: #1a1a1a; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| overflow: auto; | |
| } | |
| /* Game specific styles */ | |
| .game-container { | |
| width: 90%; | |
| max-width: 600px; | |
| aspect-ratio: 3/4; /* Adjust aspect ratio for a taller game area */ | |
| background-color: #0d1117; /* Dark space background */ | |
| border: 3px solid #667eea; /* Blue border */ | |
| position: relative; | |
| overflow: hidden; | |
| border-radius: 0.75rem; | |
| box-shadow: 0 0 20px rgba(102, 126, 234, 0.5); | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: space-between; /* Distribute content vertically */ | |
| padding: 1rem; | |
| } | |
| .game-title { | |
| font-family: 'Orbitron', sans-serif; | |
| font-size: clamp(1.5rem, 4vw, 2.5rem); | |
| color: #fff; | |
| text-shadow: 0 0 10px #667eea; | |
| margin-bottom: 1rem; | |
| } | |
| .game-area { | |
| position: relative; | |
| width: 100%; | |
| flex-grow: 1; /* Take available space */ | |
| overflow: hidden; /* Crucial for asteroid movement */ | |
| background-color: rgba(0,0,0,0.2); /* Slightly transparent background */ | |
| border-radius: 0.5rem; | |
| margin-bottom: 1rem; | |
| } | |
| .player-element { | |
| position: absolute; | |
| bottom: 10px; /* Distance from bottom of game-area */ | |
| left: 50%; | |
| transform: translateX(-50%); | |
| font-size: clamp(1.5rem, 5vw, 2.5rem); /* Responsive player size */ | |
| user-select: none; | |
| z-index: 10; | |
| transition: left 0.05s linear; /* Smooth player movement */ | |
| } | |
| .asteroid-element { | |
| position: absolute; | |
| top: -50px; /* Start above visible area */ | |
| font-size: clamp(1rem, 4vw, 2rem); /* Responsive asteroid size */ | |
| user-select: none; | |
| z-index: 5; | |
| } | |
| .game-info-panel { | |
| display: flex; | |
| justify-content: space-around; | |
| align-items: center; | |
| width: 100%; | |
| padding: 0.5rem; | |
| background-color: rgba(0,0,0,0.5); | |
| border-radius: 0.5rem; | |
| margin-top: 0.5rem; | |
| } | |
| .score-display, .message-area { | |
| font-size: clamp(1rem, 2.5vw, 1.2rem); | |
| font-weight: bold; | |
| color: #39FF14; /* Neon green */ | |
| text-shadow: 0 0 8px rgba(57, 255, 20, 0.4); | |
| text-align: center; | |
| width: 100%; | |
| margin-top: 0.5rem; | |
| min-height: 1.5em; /* Ensure space even when empty */ | |
| } | |
| .message-area { | |
| color: #ef4444; /* Red for game over */ | |
| } | |
| .game-controls-bottom { | |
| display: flex; | |
| gap: 1rem; | |
| margin-top: 1rem; | |
| justify-content: center; /* Center buttons */ | |
| width: 100%; | |
| } | |
| .game-controls-bottom .game-button { | |
| padding: 0.5rem 1rem; | |
| font-size: clamp(0.9rem, 2vw, 1.1rem); | |
| border-radius: 0.5rem; | |
| box-shadow: 0 2px 5px rgba(0,0,0,0.2); | |
| } | |
| /* Custom Modal Styling from Mini Bonanza */ | |
| .modal-overlay { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-color: rgba(0,0,0,0.7); | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 1000; | |
| opacity: 0; | |
| visibility: hidden; | |
| transition: opacity 0.3s ease, visibility 0.3s ease; | |
| } | |
| .modal-overlay.active { | |
| opacity: 1; | |
| visibility: visible; | |
| } | |
| .modal-content { | |
| background-color: #2d2d2d; | |
| padding: 2rem; | |
| border-radius: 1rem; | |
| box-shadow: 0 10px 20px rgba(0,0,0,0.3); | |
| text-align: center; | |
| max-width: 400px; | |
| width: 90%; | |
| position: relative; | |
| transform: translateY(-20px); | |
| transition: transform 0.3s ease; | |
| } | |
| .modal-overlay.active .modal-content { | |
| transform: translateY(0); | |
| } | |
| .modal-content h3 { | |
| font-size: 1.8rem; | |
| font-weight: 700; | |
| margin-bottom: 1rem; | |
| color: #d0d0d0; | |
| } | |
| .modal-content p { | |
| font-size: 1.1rem; | |
| color: #a0a0a0; | |
| margin-bottom: 1.5rem; | |
| } | |
| .modal-close-button { | |
| position: absolute; | |
| top: 1rem; | |
| right: 1rem; | |
| background: none; | |
| border: none; | |
| color: #a0a0a0; | |
| font-size: 1.5rem; | |
| cursor: pointer; | |
| transition: color 0.2s; | |
| } | |
| .modal-close-button:hover { | |
| color: #d9534f; | |
| } | |
| /* Mobile menu specific styles */ | |
| .mobile-menu-button { | |
| position: fixed; | |
| top: 1rem; | |
| left: 1rem; | |
| z-index: 50; | |
| padding: 0.5rem; | |
| border-radius: 9999px; /* rounded-full */ | |
| background-color: #4a5568; /* bg-gray-700 */ | |
| color: #fff; /* text-white */ | |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); /* shadow-lg */ | |
| outline: none; | |
| transition: background-color 0.2s; | |
| } | |
| .mobile-menu-button:focus { | |
| box-shadow: 0 0 0 3px rgba(108, 117, 125, 0.5); /* focus:ring-2 focus:ring-gray-600 */ | |
| } | |
| .sidebar.translate-x-0 { | |
| transform: translateX(0); | |
| } | |
| .sidebar.-translate-x-full { | |
| transform: translateX(-100%); | |
| } | |
| .sidebar-overlay { | |
| position: fixed; | |
| inset: 0; | |
| background-color: rgba(0, 0, 0, 0.5); /* bg-black bg-opacity-50 */ | |
| z-index: 30; | |
| display: none; /* hidden */ | |
| } | |
| .sidebar-overlay.active { | |
| display: block; | |
| } | |
| /* Mobile-only control buttons */ | |
| .mobile-only-button { | |
| display: none; /* Hidden by default */ | |
| } | |
| @media (max-width: 1023px) { /* Show on screens smaller than lg (desktop) */ | |
| .mobile-only-button { | |
| display: block; /* Show on small screens */ | |
| padding: 1rem 1.5rem; /* Larger touch area */ | |
| font-size: 1.5rem; | |
| } | |
| .game-controls-bottom { | |
| flex-direction: row; /* Keep buttons in a row on mobile */ | |
| justify-content: space-around; /* Distribute space */ | |
| } | |
| /* Hide keyboard control instructions on mobile if needed */ | |
| /* You might add a message here to instruct mobile users to use buttons */ | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="main-layout-container"> | |
| <button | |
| id="mobileMenuButton" | |
| class="mobile-menu-button lg:hidden" | |
| aria-label="Open menu"> | |
| <i class="fas fa-bars"></i> | |
| </button> | |
| <aside | |
| id="sidebar" | |
| class="sidebar fixed inset-y-0 left-0 z-40 transform -translate-x-full transition-transform duration-300 ease-in-out lg:relative lg:translate-x-0 lg:flex-shrink-0 lg:rounded-r-lg"> | |
| <nav class="mb-8 w-full"> | |
| <ul> | |
| <li class="mb-4"> | |
| <a href="#" class="flex items-center p-3 rounded-lg bg-gray-600 shadow-md"> | |
| <i class="fas fa-gamepad mr-3"></i> | |
| <span class="text-lg">Play</span> | |
| </a> | |
| </li> | |
| </ul> | |
| </nav> | |
| <div class="sidebar-title-group"> | |
| <i class="fas fa-gem icon"></i> | |
| <h1 class="title">Asteroid</h1> | |
| </div> | |
| <div class="balance-display-container"> | |
| <p class="balance-label">Total Balance</p> | |
| <h3 id="totalBalanceDisplay">$0.00</h3> | |
| <p id="userIdDisplay" class="user-id-text"></p> | |
| </div> | |
| <div class="mt-6 flex flex-col gap-4 w-full"> | |
| <div class="qr-deposit-container"> | |
| <div class="qr-box"> | |
| <img src="https://huggingface.co/spaces/hologramicon/roulette/resolve/main/1.png" alt="QR Code for Deposit"> | |
| </div> | |
| <label>Scan to Deposit</label> | |
| </div> | |
| <button id="depositViaQrButton" class="game-button start-button">Deposit via QR</button> | |
| <button id="withdrawButton" class="game-button stop-button">Withdraw</button> | |
| <div id="betLog" class="activity-log"> | |
| <h3>Recent Activity</h3> | |
| <ul id="activityList"> | |
| <li>Balance initialized to $100.00</li> | |
| </ul> | |
| </div> | |
| </div> | |
| </aside> | |
| <div id="sidebarOverlay" class="sidebar-overlay lg:hidden"></div> | |
| <main class="game-content-main"> | |
| <div class="game-container"> | |
| <div id="gameArea" class="game-area"> | |
| <div id="player" class="player-element">🚀</div> | |
| </div> | |
| <div class="game-info-panel"> | |
| <span id="scoreDisplay" class="score-display">Score: 0</span> | |
| </div> | |
| <div id="messageArea" class="message-area"></div> | |
| <div class="game-controls-bottom"> | |
| <button id="leftArrow" class="game-button mobile-only-button start-button">◀</button> | |
| <button id="startButton" class="game-button start-button">Start</button> | |
| <button id="restartButton" class="game-button stop-button hidden">Reload</button> | |
| <button id="rightArrow" class="game-button mobile-only-button start-button">▶</button> | |
| </div> | |
| </div> | |
| </main> | |
| </div> | |
| <div id="customModal" class="modal-overlay"> | |
| <div class="modal-content"> | |
| <button class="modal-close-button" id="modalCloseButton">×</button> | |
| <h3 id="modalTitle"></h3> | |
| <p id="modalMessage"></p> | |
| </div> | |
| </div> | |
| <script> | |
| // --- Global Game State --- | |
| let gameInterval; | |
| let asteroidSpawnInterval; | |
| let score = 0; | |
| let isGameRunning = false; | |
| let playerX = 0; // Player's X position relative to gameArea | |
| let playerWidth = 0; // Will be set dynamically | |
| let gameAreaWidth = 0; // Will be set dynamically | |
| let gameAreaHeight = 0; // Will be set dynamically | |
| const playerSpeed = 8; | |
| let asteroidSpeed = 2; | |
| const asteroids = []; // Array to store asteroid elements | |
| const gameEntryFee = 5.00; // Tarifa de entrada al juego | |
| // --- DOM Elements --- | |
| const gameArea = document.getElementById('gameArea'); | |
| const player = document.getElementById('player'); | |
| const scoreDisplay = document.getElementById('scoreDisplay'); | |
| const messageArea = document.getElementById('messageArea'); | |
| const startButton = document.getElementById('startButton'); | |
| const restartButton = document.getElementById('restartButton'); | |
| const leftArrowButton = document.getElementById('leftArrow'); | |
| const rightArrowButton = document.getElementById('rightArrow'); | |
| // Sidebar elements (for balance and user ID persistence) | |
| const totalBalanceDisplay = document.getElementById('totalBalanceDisplay'); | |
| const userIdDisplay = document.getElementById('userIdDisplay'); | |
| const activityListEl = document.getElementById('activityList'); | |
| const depositViaQrButton = document.getElementById('depositViaQrButton'); | |
| const withdrawButton = document.getElementById('withdrawButton'); | |
| // Modal elements | |
| const customModal = document.getElementById('customModal'); | |
| const modalTitle = document.getElementById('modalTitle'); | |
| const modalMessage = document.getElementById('modalMessage'); | |
| const modalCloseButton = document.getElementById('modalCloseButton'); | |
| // --- Balance Management (from Mini Bonanza) --- | |
| window.userBalance = parseFloat(localStorage.getItem('esquivaAsteroideBalance')) || 100.00; | |
| /** | |
| * Updates the user's balance and refreshes the display. | |
| * Also saves the balance to localStorage. | |
| * @param {number} newAmount - The new balance amount. | |
| */ | |
| function updateUserBalance(newAmount) { | |
| window.userBalance = newAmount; | |
| totalBalanceDisplay.textContent = `$${window.userBalance.toFixed(2)}`; | |
| localStorage.setItem('esquivaAsteroideBalance', window.userBalance.toFixed(2)); | |
| addActivityToLog(`Balance actualizado a $${newAmount.toFixed(2)}`); | |
| } | |
| /** | |
| * Adds a new entry to the activity log displayed in the sidebar. | |
| * @param {string} message - The message to add to the log. | |
| */ | |
| function addActivityToLog(message) { | |
| const li = document.createElement('li'); | |
| li.textContent = message; | |
| activityListEl.prepend(li); | |
| if (activityListEl.children.length > 20) { | |
| activityListEl.removeChild(activityListEl.lastChild); | |
| } | |
| } | |
| // --- Modal Functions --- | |
| /** | |
| * Displays a custom modal with a given title and message. | |
| * @param {string} title - The title of the modal. | |
| * @param {string} msg - The message content of the modal. | |
| */ | |
| function showCustomModal(title, msg) { | |
| modalTitle.textContent = title; | |
| modalMessage.textContent = msg; | |
| customModal.classList.add('active'); | |
| } | |
| /** | |
| * Hides the custom modal. | |
| */ | |
| function hideCustomModal() { | |
| customModal.classList.remove('active'); | |
| } | |
| // --- Game Initialization & Reset --- | |
| /** | |
| * Initializes the game state, resets score, clears asteroids, and sets up UI. | |
| */ | |
| function initializeGame() { | |
| // Set initial dimensions based on gameArea | |
| const gameAreaRect = gameArea.getBoundingClientRect(); | |
| gameAreaWidth = gameAreaRect.width; | |
| gameAreaHeight = gameAreaRect.height; | |
| playerWidth = player.offsetWidth; | |
| playerX = (gameAreaWidth / 2) - (playerWidth / 2); | |
| player.style.left = playerX + 'px'; | |
| player.style.bottom = '10px'; // Ensure player is at the bottom | |
| score = 0; | |
| asteroidSpeed = 2; | |
| scoreDisplay.textContent = `Puntuación: ${score}`; | |
| messageArea.textContent = 'Esquiva los asteroides!'; | |
| restartButton.classList.add('hidden'); | |
| startButton.classList.remove('hidden'); | |
| // Clear existing asteroids | |
| asteroids.forEach(asteroid => asteroid.element.remove()); | |
| asteroids.length = 0; // Clear the array | |
| } | |
| /** | |
| * Starts the game, deducting entry fee and initiating game loop. | |
| */ | |
| function startGame() { | |
| if (isGameRunning) return; | |
| if (window.userBalance < gameEntryFee) { | |
| messageArea.textContent = `¡Saldo insuficiente! Necesitas $${gameEntryFee.toFixed(2)} para jugar.`; | |
| addActivityToLog(`Intento de juego fallido: saldo insuficiente ($${window.userBalance.toFixed(2)}).`); | |
| return; | |
| } | |
| // Deduct game entry fee | |
| updateUserBalance(window.userBalance - gameEntryFee); | |
| addActivityToLog(`Comenzó el juego: se deducen $${gameEntryFee.toFixed(2)}.`); | |
| isGameRunning = true; | |
| startButton.classList.add('hidden'); | |
| messageArea.textContent = '¡Vamos!'; | |
| score = 0; | |
| scoreDisplay.textContent = `Puntuación: ${score}`; | |
| asteroidSpeed = 2; // Reset speed on start | |
| gameLoop(); | |
| asteroidSpawnInterval = setInterval(createAsteroid, 1000); // Spawn every 1 second | |
| } | |
| /** | |
| * Ends the current game, updates balance with score, and shows final message. | |
| */ | |
| function endGame() { | |
| isGameRunning = false; | |
| clearInterval(gameInterval); | |
| clearInterval(asteroidSpawnInterval); | |
| // Add score to balance | |
| updateUserBalance(window.userBalance + score); | |
| addActivityToLog(`Juego terminado. Puntuación: ${score}. Ganancia añadida al balance.`); | |
| messageArea.textContent = `¡Juego Terminado! Puntuación final: ${score}`; | |
| restartButton.classList.remove('hidden'); | |
| startButton.classList.add('hidden'); // Hide start button after game over | |
| } | |
| /** | |
| * Resets the game to its initial state. | |
| */ | |
| function resetGame() { | |
| endGame(); // Stop any ongoing game and finalize score/balance | |
| initializeGame(); // Re-initialize state and clear board | |
| } | |
| // --- Player Movement --- | |
| let keys = {}; | |
| let isMovingLeft = false; | |
| let isMovingRight = false; | |
| document.addEventListener('keydown', (e) => { | |
| keys[e.key] = true; | |
| }); | |
| document.addEventListener('keyup', (e) => { | |
| keys[e.key] = false; | |
| }); | |
| /** | |
| * Handles player movement based on keyboard input and mobile button states. | |
| */ | |
| function handlePlayerMovement() { | |
| if (keys['ArrowLeft'] || keys['a'] || isMovingLeft) { | |
| playerX -= playerSpeed; | |
| } | |
| if (keys['ArrowRight'] || keys['d'] || isMovingRight) { | |
| playerX += playerSpeed; | |
| } | |
| // Keep player within game area bounds | |
| if (playerX < 0) playerX = 0; | |
| if (playerX > gameAreaWidth - playerWidth) playerX = gameAreaWidth - playerWidth; | |
| player.style.left = playerX + 'px'; | |
| } | |
| // --- Touch Controls for Mobile --- | |
| let touchStartX = 0; | |
| let playerInitialX = 0; | |
| gameArea.addEventListener('touchstart', (e) => { | |
| if (!isGameRunning) return; | |
| touchStartX = e.touches[0].clientX; | |
| playerInitialX = playerX; | |
| e.preventDefault(); // Prevent scrolling | |
| }, { passive: false }); // Use passive: false to allow preventDefault | |
| gameArea.addEventListener('touchmove', (e) => { | |
| if (!isGameRunning) return; | |
| const touchCurrentX = e.touches[0].clientX; | |
| const deltaX = touchCurrentX - touchStartX; | |
| let newPlayerX = playerInitialX + deltaX; | |
| if (newPlayerX < 0) newPlayerX = 0; | |
| if (newPlayerX > gameAreaWidth - playerWidth) newPlayerX = gameAreaWidth - playerWidth; | |
| playerX = newPlayerX; | |
| player.style.left = playerX + 'px'; | |
| e.preventDefault(); // Prevent scrolling | |
| }, { passive: false }); // Use passive: false to allow preventDefault | |
| // Event listeners for mobile arrow buttons | |
| leftArrowButton.addEventListener('touchstart', (e) => { | |
| isMovingLeft = true; | |
| e.preventDefault(); | |
| }); | |
| leftArrowButton.addEventListener('touchend', () => { | |
| isMovingLeft = false; | |
| }); | |
| leftArrowButton.addEventListener('mousedown', () => { // For desktop click support | |
| isMovingLeft = true; | |
| }); | |
| leftArrowButton.addEventListener('mouseup', () => { | |
| isMovingLeft = false; | |
| }); | |
| rightArrowButton.addEventListener('touchstart', (e) => { | |
| isMovingRight = true; | |
| e.preventDefault(); | |
| }); | |
| rightArrowButton.addEventListener('touchend', () => { | |
| isMovingRight = false; | |
| }); | |
| rightArrowButton.addEventListener('mousedown', () => { // For desktop click support | |
| isMovingRight = true; | |
| }); | |
| rightArrowButton.addEventListener('mouseup', () => { | |
| isMovingRight = false; | |
| }); | |
| // --- Asteroid Logic --- | |
| /** | |
| * Creates a new asteroid element and adds it to the game. | |
| */ | |
| function createAsteroid() { | |
| if (!isGameRunning) return; | |
| const asteroid = document.createElement('div'); | |
| asteroid.classList.add('asteroid-element'); | |
| const size = Math.random() * 20 + 20; // Size between 20 and 40 | |
| asteroid.style.width = size + 'px'; | |
| asteroid.style.height = size + 'px'; | |
| asteroid.style.left = Math.random() * (gameAreaWidth - size) + 'px'; | |
| asteroid.textContent = '🪨'; // Rock emoji | |
| gameArea.appendChild(asteroid); | |
| asteroids.push({ | |
| element: asteroid, | |
| x: parseFloat(asteroid.style.left), | |
| y: -size, // Start above the game area | |
| size: size, | |
| speed: asteroidSpeed + Math.random() * 1.5 // Random variation | |
| }); | |
| } | |
| /** | |
| * Moves all active asteroids down the screen. | |
| */ | |
| function moveAsteroids() { | |
| for (let i = asteroids.length - 1; i >= 0; i--) { | |
| const asteroid = asteroids[i]; | |
| asteroid.y += asteroid.speed; | |
| asteroid.element.style.top = asteroid.y + 'px'; | |
| // Remove asteroid if it goes off screen | |
| if (asteroid.y > gameAreaHeight) { | |
| asteroid.element.remove(); | |
| asteroids.splice(i, 1); | |
| score++; // Increase score for each asteroid dodged | |
| scoreDisplay.textContent = `Puntuación: ${score}`; | |
| // Increase difficulty over time | |
| if (score % 10 === 0 && asteroidSpeed < 10) { // Increase speed every 10 points up to a limit | |
| asteroidSpeed += 0.5; | |
| messageArea.textContent = `¡Velocidad aumentada!`; | |
| } | |
| } | |
| } | |
| } | |
| // --- Collision Detection --- | |
| /** | |
| * Checks for collisions between the player and any asteroids. | |
| */ | |
| function checkCollision() { | |
| const playerRect = player.getBoundingClientRect(); | |
| const gameAreaRect = gameArea.getBoundingClientRect(); // Get game area position | |
| // Adjust playerRect coordinates relative to gameArea | |
| const adjustedPlayerRect = { | |
| left: playerRect.left - gameAreaRect.left, | |
| right: playerRect.right - gameAreaRect.left, | |
| top: playerRect.top - gameAreaRect.top, | |
| bottom: playerRect.bottom - gameAreaRect.top, | |
| width: playerRect.width, | |
| height: playerRect.height | |
| }; | |
| for (let i = asteroids.length - 1; i >= 0; i--) { | |
| const asteroid = asteroids[i]; | |
| const asteroidRect = asteroid.element.getBoundingClientRect(); | |
| // Adjust asteroidRect coordinates relative to gameArea | |
| const adjustedAsteroidRect = { | |
| left: asteroidRect.left - gameAreaRect.left, | |
| right: asteroidRect.right - gameAreaRect.left, | |
| top: asteroidRect.top - gameAreaRect.top, | |
| bottom: asteroidRect.bottom - gameAreaRect.top, | |
| width: asteroidRect.width, | |
| height: asteroidRect.height | |
| }; | |
| // Simple AABB collision detection | |
| if ( | |
| adjustedPlayerRect.left < adjustedAsteroidRect.left + adjustedAsteroidRect.width && | |
| adjustedPlayerRect.left + adjustedPlayerRect.width > adjustedAsteroidRect.left && | |
| adjustedPlayerRect.top < adjustedAsteroidRect.top + adjustedAsteroidRect.height && | |
| adjustedPlayerRect.top + adjustedPlayerRect.height > adjustedAsteroidRect.top | |
| ) { | |
| endGame(); | |
| break; | |
| } | |
| } | |
| } | |
| // --- Game Loop --- | |
| /** | |
| * The main game loop, updates game state and renders frames. | |
| */ | |
| function gameLoop() { | |
| if (!isGameRunning) return; | |
| handlePlayerMovement(); | |
| moveAsteroids(); | |
| checkCollision(); | |
| gameInterval = requestAnimationFrame(gameLoop); | |
| } | |
| // --- Event Listeners --- | |
| startButton.addEventListener('click', startGame); | |
| restartButton.addEventListener('click', resetGame); | |
| // Modal event listeners | |
| modalCloseButton.addEventListener('click', hideCustomModal); | |
| customModal.addEventListener('click', (e) => { | |
| if (e.target === customModal) hideCustomModal(); | |
| }); | |
| // Sidebar deposit/withdraw buttons | |
| depositViaQrButton.addEventListener('click', () => { | |
| showCustomModal("Depositar Fondos", "Usa el código QR para depositar fondos en tu cuenta."); | |
| addActivityToLog("Botón de depósito clicado."); | |
| }); | |
| withdrawButton.addEventListener('click', () => { | |
| showCustomModal("Información", "Deposita $50 USDT para habilitar retiros."); | |
| addActivityToLog("Botón de retiro clicado."); | |
| }); | |
| // Mobile menu toggle functionality | |
| const mobileMenuButton = document.getElementById('mobileMenuButton'); | |
| const sidebar = document.getElementById('sidebar'); | |
| const sidebarOverlay = document.getElementById('sidebarOverlay'); | |
| mobileMenuButton.addEventListener('click', () => { | |
| sidebar.classList.toggle('-translate-x-full'); | |
| sidebarOverlay.classList.toggle('active'); | |
| }); | |
| sidebarOverlay.addEventListener('click', () => { | |
| sidebar.classList.add('-translate-x-full'); | |
| sidebarOverlay.classList.remove('active'); | |
| }); | |
| // Initial setup on page load | |
| window.onload = () => { | |
| // Generate a simple unique ID for the user if not already present | |
| let userId = localStorage.getItem('esquivaAsteroideUserId'); | |
| if (!userId) { | |
| userId = 'user_' + Math.random().toString(36).substr(2, 9); | |
| localStorage.setItem('esquivaAsteroideUserId', userId); | |
| } | |
| userIdDisplay.textContent = `User ID: ${userId}`; | |
| updateUserBalance(window.userBalance); // Set initial balance display | |
| initializeGame(); // Set up initial game state | |
| }; | |
| // Handle window resize to adjust game area dimensions | |
| window.addEventListener('resize', () => { | |
| const gameAreaRect = gameArea.getBoundingClientRect(); | |
| gameAreaWidth = gameAreaRect.width; | |
| gameAreaHeight = gameAreaRect.height; | |
| // Re-position player if necessary after resize | |
| playerX = (gameAreaWidth / 2) - (playerWidth / 2); | |
| player.style.left = playerX + 'px'; | |
| }); | |
| </script> | |
| </body> | |
| </html> |