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