Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Galaxy Defender</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| background-color: #000; | |
| overflow: hidden; | |
| font-family: 'Orbitron', sans-serif; | |
| color: white; | |
| } | |
| @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap'); | |
| #gameContainer { | |
| position: relative; | |
| width: 100vw; | |
| height: 100vh; | |
| background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiPgogIDxkZWZzPgogICAgPHBhdHRlcm4gaWQ9InBhdHRlcm4iIHBhdHRlcm5Vbml0cz0idXNlclNwYWNlT25Vc2UiIHBhdHRlcm5UcmFuc2Zvcm09InNjYWxlKDIpIHJvdGF0ZSgwKSI+CiAgICAgIDxyZWN0IHdpZHRoPSIxIiBoZWlnaHQ9IjEiIGZpbGw9IiNmZmZmZmYiIG9wYWNpdHk9IjAuMCIvPgogICAgICA8Y2lyY2xlIGN4PSIwLjUiIGN5PSIwLjUiIHI9IjAuNSIgZmlsbD0iIzIwMjBmZiIgb3BhY2l0eT0iMC4xIi8+CiAgICA8L3BhdHRlcm4+CiAgPC9kZWZzPgogIDxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjcGF0dGVybikiLz4KPC9zdmc+') #000; | |
| } | |
| #gameCanvas { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| } | |
| #startScreen, #gameOverScreen { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| background-color: rgba(0, 0, 0, 0.8); | |
| z-index: 10; | |
| } | |
| #gameOverScreen { | |
| display: none; | |
| } | |
| h1 { | |
| font-size: 3rem; | |
| color: #0ff; | |
| text-shadow: 0 0 10px #0ff, 0 0 20px #0ff; | |
| margin-bottom: 2rem; | |
| text-align: center; | |
| } | |
| button { | |
| background: none; | |
| border: 2px solid #0ff; | |
| color: #0ff; | |
| padding: 1rem 2rem; | |
| font-size: 1.5rem; | |
| font-family: 'Orbitron', sans-serif; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| margin-top: 1rem; | |
| text-shadow: 0 0 5px #0ff; | |
| } | |
| button:hover { | |
| background-color: rgba(0, 255, 255, 0.1); | |
| box-shadow: 0 0 10px #0ff; | |
| } | |
| #scoreDisplay, #healthDisplay { | |
| position: absolute; | |
| top: 20px; | |
| right: 20px; | |
| z-index: 5; | |
| font-size: 1.5rem; | |
| color: #0ff; | |
| text-shadow: 0 0 5px #0ff; | |
| } | |
| #healthDisplay { | |
| display: flex; | |
| align-items: center; | |
| } | |
| #healthBar { | |
| width: 100px; | |
| height: 20px; | |
| background-color: #333; | |
| margin-left: 10px; | |
| border: 1px solid #0ff; | |
| overflow: hidden; | |
| } | |
| #healthProgress { | |
| height: 100%; | |
| width: 100%; | |
| background-color: #0f0; | |
| transition: width 0.3s; | |
| } | |
| .controls { | |
| position: absolute; | |
| bottom: 20px; | |
| left: 20px; | |
| color: #0ff; | |
| font-size: 1rem; | |
| text-shadow: 0 0 5px #0ff; | |
| } | |
| #mobileControls { | |
| display: none; | |
| position: absolute; | |
| bottom: 20px; | |
| width: 100%; | |
| justify-content: space-around; | |
| z-index: 5; | |
| } | |
| .mobile-button { | |
| width: 60px; | |
| height: 60px; | |
| background-color: rgba(0, 255, 255, 0.2); | |
| border: 2px solid #0ff; | |
| border-radius: 50%; | |
| color: #0ff; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| font-size: 1.5rem; | |
| user-select: none; | |
| } | |
| @media (max-width: 768px) { | |
| #mobileControls { | |
| display: flex; | |
| } | |
| .controls { | |
| display: none; | |
| } | |
| h1 { | |
| font-size: 2rem; | |
| } | |
| button { | |
| padding: 0.8rem 1.5rem; | |
| font-size: 1.2rem; | |
| } | |
| } | |
| #stars { | |
| position: absolute; | |
| width: 100%; | |
| height: 100%; | |
| z-index: -1; | |
| } | |
| .star { | |
| position: absolute; | |
| background-color: white; | |
| border-radius: 50%; | |
| animation: twinkle 1s infinite alternate; | |
| } | |
| @keyframes twinkle { | |
| from { opacity: 0.3; } | |
| to { opacity: 1; } | |
| } | |
| .explosion { | |
| position: absolute; | |
| width: 30px; | |
| height: 30px; | |
| pointer-events: none; | |
| background-image: radial-gradient(circle, #ff0, #f80, #f00); | |
| border-radius: 50%; | |
| transform: scale(0); | |
| opacity: 1; | |
| animation: explode 0.5s forwards; | |
| } | |
| @keyframes explode { | |
| to { | |
| transform: scale(3); | |
| opacity: 0; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="gameContainer"> | |
| <div id="stars"></div> | |
| <canvas id="gameCanvas"></canvas> | |
| <div id="scoreDisplay">Score: 0</div> | |
| <div id="healthDisplay"> | |
| Health: <div id="healthBar"><div id="healthProgress"></div></div> | |
| </div> | |
| <div id="startScreen"> | |
| <h1>GALAXY DEFENDER</h1> | |
| <p>Defend the sector from enemy invaders!</p> | |
| <button id="startButton">START MISSION</button> | |
| <div class="controls"> | |
| Controls: Arrow Keys to Move, Space to Shoot | |
| </div> | |
| </div> | |
| <div id="gameOverScreen"> | |
| <h1>MISSION FAILED</h1> | |
| <p id="finalScore">Your score: 0</p> | |
| <button id="restartButton">TRY AGAIN</button> | |
| </div> | |
| <div id="mobileControls"> | |
| <div class="mobile-button" id="leftButton">←</div> | |
| <div class="mobile-button" id="rightButton">→</div> | |
| <div class="mobile-button" id="upButton">↑</div> | |
| <div class="mobile-button" id="downButton">↓</div> | |
| <div class="mobile-button" id="shootButton">💥</div> | |
| </div> | |
| </div> | |
| <script> | |
| // Game variables | |
| const canvas = document.getElementById('gameCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const startScreen = document.getElementById('startScreen'); | |
| const gameOverScreen = document.getElementById('gameOverScreen'); | |
| const startButton = document.getElementById('startButton'); | |
| const restartButton = document.getElementById('restartButton'); | |
| const scoreDisplay = document.getElementById('scoreDisplay'); | |
| const finalScore = document.getElementById('finalScore'); | |
| const healthProgress = document.getElementById('healthProgress'); | |
| const starsContainer = document.getElementById('stars'); | |
| // Mobile controls | |
| const leftButton = document.getElementById('leftButton'); | |
| const rightButton = document.getElementById('rightButton'); | |
| const upButton = document.getElementById('upButton'); | |
| const downButton = document.getElementById('downButton'); | |
| const shootButton = document.getElementById('shootButton'); | |
| // Set canvas size | |
| function resizeCanvas() { | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| } | |
| resizeCanvas(); | |
| window.addEventListener('resize', resizeCanvas); | |
| // Create stars | |
| function createStars() { | |
| starsContainer.innerHTML = ''; | |
| const starCount = Math.floor(window.innerWidth * window.innerHeight / 1000); | |
| for (let i = 0; i < starCount; i++) { | |
| const star = document.createElement('div'); | |
| star.className = 'star'; | |
| star.style.width = `${Math.random() * 3}px`; | |
| star.style.height = star.style.width; | |
| star.style.left = `${Math.random() * 100}%`; | |
| star.style.top = `${Math.random() * 100}%`; | |
| star.style.opacity = Math.random(); | |
| star.style.animationDelay = `${Math.random() * 2}s`; | |
| star.style.animationDuration = `${0.5 + Math.random() * 1.5}s`; | |
| starsContainer.appendChild(star); | |
| } | |
| } | |
| createStars(); | |
| // Game objects | |
| const player = { | |
| x: canvas.width / 2, | |
| y: canvas.height - 100, | |
| width: 50, | |
| height: 50, | |
| speed: 8, | |
| color: '#0ff', | |
| health: 100, | |
| maxHealth: 100, | |
| isShooting: false, | |
| shootCooldown: 0, | |
| shootInterval: 300 // ms | |
| }; | |
| const bullets = []; | |
| const enemies = []; | |
| const explosions = []; | |
| let score = 0; | |
| let gameRunning = false; | |
| let enemySpawnTimer = 0; | |
| let enemySpawnInterval = 1500; // ms | |
| let lastTime = 0; | |
| let animationFrameId = null; | |
| // Input handling | |
| const keys = { | |
| ArrowLeft: false, | |
| ArrowRight: false, | |
| ArrowUp: false, | |
| ArrowDown: false, | |
| ' ': false | |
| }; | |
| window.addEventListener('keydown', (e) => { | |
| if (keys.hasOwnProperty(e.key)) { | |
| keys[e.key] = true; | |
| e.preventDefault(); | |
| } | |
| }); | |
| window.addEventListener('keyup', (e) => { | |
| if (keys.hasOwnProperty(e.key)) { | |
| keys[e.key] = false; | |
| e.preventDefault(); | |
| } | |
| }); | |
| // Mobile controls | |
| function setupMobileControls() { | |
| const handlePress = (button, key) => { | |
| const startPress = () => { | |
| keys[key] = true; | |
| button.style.backgroundColor = 'rgba(0, 255, 255, 0.4)'; | |
| }; | |
| const endPress = () => { | |
| keys[key] = false; | |
| button.style.backgroundColor = 'rgba(0, 255, 255, 0.2)'; | |
| }; | |
| button.addEventListener('touchstart', (e) => { | |
| e.preventDefault(); | |
| startPress(); | |
| }); | |
| button.addEventListener('touchend', (e) => { | |
| e.preventDefault(); | |
| endPress(); | |
| }); | |
| button.addEventListener('mousedown', startPress); | |
| button.addEventListener('mouseup', endPress); | |
| button.addEventListener('mouseleave', endPress); | |
| }; | |
| handlePress(leftButton, 'ArrowLeft'); | |
| handlePress(rightButton, 'ArrowRight'); | |
| handlePress(upButton, 'ArrowUp'); | |
| handlePress(downButton, 'ArrowDown'); | |
| handlePress(shootButton, ' '); | |
| } | |
| setupMobileControls(); | |
| // Draw player ship | |
| function drawPlayer() { | |
| ctx.save(); | |
| // Ship body | |
| ctx.fillStyle = player.color; | |
| ctx.beginPath(); | |
| ctx.moveTo(player.x, player.y - player.height/2); | |
| ctx.lineTo(player.x + player.width/2, player.y + player.height/2); | |
| ctx.lineTo(player.x - player.width/2, player.y + player.height/2); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| // Ship cockpit | |
| ctx.fillStyle = '#80f0f0'; | |
| ctx.beginPath(); | |
| ctx.arc(player.x, player.y - player.height/6, player.width/6, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // Engine glow when moving | |
| if (keys.ArrowUp || keys.ArrowDown || keys.ArrowLeft || keys.ArrowRight) { | |
| ctx.fillStyle = '#ff0'; | |
| ctx.beginPath(); | |
| ctx.moveTo(player.x - player.width/2 + 5, player.y + player.height/2); | |
| ctx.lineTo(player.x + player.width/2 - 5, player.y + player.height/2); | |
| ctx.lineTo(player.x, player.y + player.height/1.5); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| } | |
| ctx.restore(); | |
| } | |
| // Player shooting | |
| function shoot() { | |
| const bullet = { | |
| x: player.x, | |
| y: player.y - player.height/2, // Start from ship's nose | |
| width: 6, | |
| height: 20, | |
| speed: -10, // Moving upwards (negative y direction) | |
| color: '#0ff' | |
| }; | |
| bullets.push(bullet); | |
| } | |
| // Draw and update bullets | |
| function updateBullets() { | |
| for (let i = bullets.length - 1; i >= 0; i--) { | |
| const bullet = bullets[i]; | |
| // Draw bullet | |
| ctx.fillStyle = bullet.color; | |
| ctx.fillRect(bullet.x - bullet.width/2, bullet.y - bullet.height/2, bullet.width, bullet.height); | |
| // Add glow effect | |
| ctx.save(); | |
| ctx.fillStyle = '#fff'; | |
| ctx.globalAlpha = 0.5; | |
| ctx.beginPath(); | |
| ctx.arc(bullet.x, bullet.y, bullet.width * 1.5, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.restore(); | |
| // Move bullet based on its speed (negative for player, positive for enemies) | |
| bullet.y += bullet.speed; | |
| // Remove if off screen | |
| if (bullet.y < -bullet.height || bullet.y > canvas.height + bullet.height) { | |
| bullets.splice(i, 1); | |
| } | |
| } | |
| } | |
| // Spawn enemies | |
| function spawnEnemy() { | |
| const size = 30 + Math.random() * 20; | |
| const enemy = { | |
| x: Math.random() * (canvas.width - size) + size/2, | |
| y: -size, | |
| width: size, | |
| height: size, | |
| speedX: (Math.random() - 0.5) * 2, | |
| speedY: 1 + Math.random() * 2, // Slower enemy speed | |
| color: `hsl(${Math.random() * 60}, 100%, 50%)`, | |
| health: Math.ceil(size / 15), // More balanced health | |
| lastShootTime: 0, | |
| shootInterval: 2500 + Math.random() * 3000, | |
| value: Math.floor(size / 5) | |
| }; | |
| enemies.push(enemy); | |
| } | |
| // Draw and update enemies | |
| function updateEnemies() { | |
| for (let i = enemies.length - 1; i >= 0; i--) { | |
| const enemy = enemies[i]; | |
| // Draw enemy ship | |
| ctx.save(); | |
| ctx.fillStyle = enemy.color; | |
| ctx.beginPath(); | |
| ctx.moveTo(enemy.x, enemy.y + enemy.height/2); | |
| ctx.lineTo(enemy.x + enemy.width/2, enemy.y - enemy.height/2); | |
| ctx.lineTo(enemy.x - enemy.width/2, enemy.y - enemy.height/2); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| // Enemy cockpit | |
| ctx.fillStyle = '#ff0'; | |
| ctx.beginPath(); | |
| ctx.arc(enemy.x, enemy.y - enemy.height/6, enemy.width/6, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.restore(); | |
| // Move enemy | |
| enemy.x += enemy.speedX; | |
| enemy.y += enemy.speedY; | |
| // Change direction near edges | |
| if (enemy.x < enemy.width/2 || enemy.x > canvas.width - enemy.width/2) { | |
| enemy.speedX *= -1; | |
| } | |
| // Enemy shooting | |
| const now = Date.now(); | |
| if (now - enemy.lastShootTime > enemy.shootInterval) { | |
| enemy.lastShootTime = now; | |
| const enemyBullet = { | |
| x: enemy.x, | |
| y: enemy.y + enemy.height/2, | |
| width: 6, | |
| height: 20, | |
| speed: 5, // Positive speed moves bullets downward | |
| color: enemy.color | |
| }; | |
| bullets.push(enemyBullet); | |
| } | |
| // Remove if off screen | |
| if (enemy.y > canvas.height + enemy.height) { | |
| enemies.splice(i, 1); | |
| } | |
| } | |
| } | |
| // Collision detection | |
| function checkCollisions() { | |
| // Player bullets hitting enemies | |
| for (let i = bullets.length - 1; i >= 0; i--) { | |
| const bullet = bullets[i]; | |
| // Skip enemy bullets (positive speed) | |
| if (bullet.speed > 0) continue; | |
| for (let j = enemies.length - 1; j >= 0; j--) { | |
| const enemy = enemies[j]; | |
| if ( | |
| bullet.x + bullet.width/2 > enemy.x - enemy.width/2 && | |
| bullet.x - bullet.width/2 < enemy.x + enemy.width/2 && | |
| bullet.y - bullet.height/2 < enemy.y + enemy.height/2 && | |
| bullet.y + bullet.height/2 > enemy.y - enemy.height/2 | |
| ) { | |
| // Hit detected | |
| createExplosion(enemy.x, enemy.y, enemy.color); | |
| enemy.health--; | |
| // Remove bullet | |
| bullets.splice(i, 1); | |
| // Remove enemy if health is 0 and add score | |
| if (enemy.health <= 0) { | |
| enemies.splice(j, 1); | |
| score += enemy.value; | |
| scoreDisplay.textContent = `Score: ${score}`; | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| // Enemy bullets hitting player | |
| for (let i = bullets.length - 1; i >= 0; i--) { | |
| const bullet = bullets[i]; | |
| // Skip player bullets (negative speed) | |
| if (bullet.speed < 0) continue; | |
| if ( | |
| bullet.x + bullet.width/2 > player.x - player.width/2 && | |
| bullet.x - bullet.width/2 < player.x + player.width/2 && | |
| bullet.y - bullet.height/2 < player.y + player.height/2 && | |
| bullet.y + bullet.height/2 > player.y - player.height/2 | |
| ) { | |
| // Hit detected | |
| createExplosion(bullet.x, bullet.y, '#f00'); | |
| player.health -= 10; | |
| updateHealthBar(); | |
| // Remove bullet | |
| bullets.splice(i, 1); | |
| // Check if player is dead | |
| if (player.health <= 0) { | |
| gameOver(); | |
| } | |
| } | |
| } | |
| // Enemies hitting player | |
| for (let i = enemies.length - 1; i >= 0; i--) { | |
| const enemy = enemies[i]; | |
| if ( | |
| player.x < enemy.x + enemy.width/2 && | |
| player.x > enemy.x - enemy.width/2 && | |
| player.y < enemy.y + enemy.height/2 && | |
| player.y > enemy.y - enemy.height/2 | |
| ) { | |
| // Collision detected | |
| createExplosion(enemy.x, enemy.y, enemy.color); | |
| player.health -= 20; | |
| updateHealthBar(); | |
| enemies.splice(i, 1); | |
| // Check if player is dead | |
| if (player.health <= 0) { | |
| gameOver(); | |
| } | |
| } | |
| } | |
| } | |
| // Create explosion effect | |
| function createExplosion(x, y, color) { | |
| const explosion = document.createElement('div'); | |
| explosion.className = 'explosion'; | |
| explosion.style.left = `${x - 15}px`; | |
| explosion.style.top = `${y - 15}px`; | |
| explosion.style.backgroundColor = color; | |
| document.getElementById('gameContainer').appendChild(explosion); | |
| // Remove after animation | |
| setTimeout(() => { | |
| explosion.remove(); | |
| }, 500); | |
| } | |
| // Update health bar display | |
| function updateHealthBar() { | |
| const percent = (player.health / player.maxHealth) * 100; | |
| healthProgress.style.width = `${percent}%`; | |
| // Change color based on health | |
| if (percent > 60) { | |
| healthProgress.style.backgroundColor = '#0f0'; | |
| } else if (percent > 30) { | |
| healthProgress.style.backgroundColor = '#ff0'; | |
| } else { | |
| healthProgress.style.backgroundColor = '#f00'; | |
| } | |
| } | |
| // Game over | |
| function gameOver() { | |
| gameRunning = false; | |
| finalScore.textContent = `Your score: ${score}`; | |
| gameOverScreen.style.display = 'flex'; | |
| if (animationFrameId) { | |
| cancelAnimationFrame(animationFrameId); | |
| } | |
| } | |
| // Reset game | |
| function resetGame() { | |
| player.x = canvas.width / 2; | |
| player.y = canvas.height - 100; | |
| player.health = player.maxHealth; | |
| bullets.length = 0; | |
| enemies.length = 0; | |
| score = 0; | |
| scoreDisplay.textContent = `Score: ${score}`; | |
| updateHealthBar(); | |
| enemySpawnInterval = 1500; | |
| enemySpawnTimer = 0; | |
| } | |
| // Game loop | |
| function gameLoop(timestamp) { | |
| if (!gameRunning) return; | |
| if (!lastTime) lastTime = timestamp; | |
| const deltaTime = timestamp - lastTime; | |
| lastTime = timestamp; | |
| // Clear canvas | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| // Update player position | |
| if (keys.ArrowLeft && player.x > player.width/2) { | |
| player.x -= player.speed; | |
| } | |
| if (keys.ArrowRight && player.x < canvas.width - player.width/2) { | |
| player.x += player.speed; | |
| } | |
| if (keys.ArrowUp && player.y > player.height/2) { | |
| player.y -= player.speed; | |
| } | |
| if (keys.ArrowDown && player.y < canvas.height - player.height/2) { | |
| player.y += player.speed; | |
| } | |
| // Handle shooting | |
| if (keys[' '] && player.shootCooldown <= 0) { | |
| shoot(); | |
| player.shootCooldown = player.shootInterval; | |
| } | |
| player.shootCooldown -= deltaTime; | |
| // Spawn enemies | |
| enemySpawnTimer += deltaTime; | |
| if (enemySpawnTimer >= enemySpawnInterval) { | |
| enemySpawnTimer = 0; | |
| spawnEnemy(); | |
| // Slightly increase spawn rate over time | |
| enemySpawnInterval = Math.max(1000, 1500 - score * 0.5); | |
| } | |
| // Update game objects | |
| drawPlayer(); | |
| updateBullets(); | |
| updateEnemies(); | |
| checkCollisions(); | |
| // Continue game loop | |
| animationFrameId = requestAnimationFrame(gameLoop); | |
| } | |
| // Start game | |
| startButton.addEventListener('click', () => { | |
| resetGame(); | |
| startScreen.style.display = 'none'; | |
| gameRunning = true; | |
| lastTime = 0; // Reset lastTime | |
| animationFrameId = requestAnimationFrame(gameLoop); | |
| }); | |
| // Restart game | |
| restartButton.addEventListener('click', () => { | |
| resetGame(); | |
| gameOverScreen.style.display = 'none'; | |
| gameRunning = true; | |
| lastTime = 0; // Reset lastTime | |
| animationFrameId = requestAnimationFrame(gameLoop); | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p></body> | |
| </html> |