Spaces:
Running
Running
<html> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Space Shooter</title> | |
<style> | |
body { | |
margin: 0; | |
overflow: hidden; | |
background: black; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
height: 100vh; | |
font-family: Arial; | |
color: white; | |
} | |
#gameCanvas { | |
border: 2px solid white; | |
background-image: | |
radial-gradient(white 1px, transparent 0), | |
radial-gradient(white 1px, transparent 0); | |
background-size: 50px 50px; | |
background-position: 0 0, 25px 25px; | |
display: none; | |
} | |
#menu { | |
text-align: center; | |
} | |
.difficulty-btn { | |
padding: 10px 20px; | |
margin: 10px; | |
font-size: 18px; | |
cursor: pointer; | |
background: #333; | |
color: white; | |
border: 2px solid white; | |
border-radius: 5px; | |
} | |
.difficulty-btn:hover { | |
background: #555; | |
} | |
#score { | |
position: absolute; | |
top: 20px; | |
left: 20px; | |
font-size: 24px; | |
} | |
#gameOver { | |
position: absolute; | |
font-size: 48px; | |
text-align: center; | |
display: none; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="menu"> | |
<h1>Space Shooter</h1> | |
<h2>Select Difficulty:</h2> | |
<button class="difficulty-btn" onclick="startGame('easy')">Easy</button> | |
<button class="difficulty-btn" onclick="startGame('medium')">Medium</button> | |
<button class="difficulty-btn" onclick="startGame('hard')">Hard</button> | |
</div> | |
<div id="score">Score: <span id="scoreValue">0</span></div> | |
<canvas id="gameCanvas" width="800" height="600"></canvas> | |
<div id="gameOver">Game Over!<br>Press R to Restart</div> | |
<script> | |
const canvas = document.getElementById('gameCanvas'); | |
const ctx = canvas.getContext('2d'); | |
const scoreEl = document.getElementById('scoreValue'); | |
const gameOverEl = document.getElementById('gameOver'); | |
const menuEl = document.getElementById('menu'); | |
let audioCtx; | |
let player = { | |
x: canvas.width/2, | |
y: 500, | |
speed: 5, | |
size: 30 | |
}; | |
let bullets = []; | |
let enemies = []; | |
let score = 0; | |
let gameOver = false; | |
let keys = { | |
left: false, | |
right: false | |
}; | |
let gameConfig = { | |
easy: { | |
enemySpeed: 2, | |
spawnRate: 0.01, | |
playerSpeed: 5 | |
}, | |
medium: { | |
enemySpeed: 3, | |
spawnRate: 0.02, | |
playerSpeed: 6 | |
}, | |
hard: { | |
enemySpeed: 4, | |
spawnRate: 0.03, | |
playerSpeed: 7 | |
} | |
}; | |
let currentConfig; | |
function startGame(difficulty) { | |
menuEl.style.display = 'none'; | |
canvas.style.display = 'block'; | |
currentConfig = gameConfig[difficulty]; | |
player.speed = currentConfig.playerSpeed; | |
resetGame(); | |
if (!audioCtx) { | |
audio = setupAudio(); | |
} | |
} | |
function setupAudio() { | |
audioCtx = new (window.AudioContext || window.webkitAudioContext)(); | |
function playNote(freq, startTime, duration) { | |
const osc = audioCtx.createOscillator(); | |
const gain = audioCtx.createGain(); | |
osc.connect(gain); | |
gain.connect(audioCtx.destination); | |
osc.type = 'square'; | |
osc.frequency.setValueAtTime(freq, startTime); | |
gain.gain.setValueAtTime(0.1, startTime); | |
gain.gain.exponentialRampToValueAtTime(0.01, startTime + duration); | |
osc.start(startTime); | |
osc.stop(startTime + duration); | |
} | |
function playBGM() { | |
const notes = [440, 523, 659, 784]; | |
setInterval(() => { | |
if (!gameOver) { | |
playNote(notes[Math.floor(Math.random() * notes.length)], | |
audioCtx.currentTime, | |
0.1); | |
} | |
}, 200); | |
} | |
function playShoot() { | |
playNote(880, audioCtx.currentTime, 0.1); | |
} | |
playBGM(); | |
return { playShoot }; | |
} | |
function drawPlayer() { | |
ctx.fillStyle = 'yellow'; | |
ctx.beginPath(); | |
for (let i = 0; i < 5; i++) { | |
const angle = (i * 4 * Math.PI) / 5 - Math.PI / 2; | |
const x = player.x + player.size * Math.cos(angle); | |
const y = player.y + player.size * Math.sin(angle); | |
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); | |
} | |
ctx.closePath(); | |
ctx.fill(); | |
} | |
function createEnemy() { | |
if (Math.random() < currentConfig.spawnRate) { | |
enemies.push({ | |
x: Math.random() * (canvas.width - 30) + 15, | |
y: -30, | |
speed: currentConfig.enemySpeed | |
}); | |
} | |
} | |
function updatePlayer() { | |
if (keys.left) { | |
player.x = Math.max(player.size, player.x - player.speed); | |
} | |
if (keys.right) { | |
player.x = Math.min(canvas.width - player.size, player.x + player.speed); | |
} | |
} | |
function updateGame() { | |
updatePlayer(); | |
bullets = bullets.filter(bullet => { | |
bullet.y -= 8; | |
return bullet.y > 0; | |
}); | |
enemies = enemies.filter(enemy => { | |
enemy.y += enemy.speed; | |
if (Math.abs(enemy.x - player.x) < 30 && | |
Math.abs(enemy.y - player.y) < 30) { | |
gameOver = true; | |
gameOverEl.style.display = 'block'; | |
} | |
return enemy.y < canvas.height; | |
}); | |
bullets.forEach((bullet, bi) => { | |
enemies.forEach((enemy, ei) => { | |
if (Math.abs(bullet.x - enemy.x) < 30 && | |
Math.abs(bullet.y - enemy.y) < 30) { | |
bullets.splice(bi, 1); | |
enemies.splice(ei, 1); | |
score += 100; | |
scoreEl.textContent = score; | |
} | |
}); | |
}); | |
createEnemy(); | |
} | |
function draw() { | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
drawPlayer(); | |
ctx.fillStyle = 'white'; | |
bullets.forEach(bullet => { | |
ctx.fillRect(bullet.x - 2, bullet.y - 8, 4, 16); | |
}); | |
ctx.font = '30px Arial'; | |
enemies.forEach(enemy => { | |
ctx.fillText('🐱', enemy.x - 15, enemy.y + 10); | |
}); | |
} | |
function gameLoop() { | |
if (!gameOver) { | |
updateGame(); | |
draw(); | |
requestAnimationFrame(gameLoop); | |
} | |
} | |
function resetGame() { | |
player.x = canvas.width/2; | |
bullets = []; | |
enemies = []; | |
score = 0; | |
gameOver = false; | |
scoreEl.textContent = '0'; | |
gameOverEl.style.display = 'none'; | |
gameLoop(); | |
} | |
function returnToMenu() { | |
canvas.style.display = 'none'; | |
menuEl.style.display = 'block'; | |
gameOverEl.style.display = 'none'; | |
} | |
let audio; | |
document.addEventListener('keydown', (e) => { | |
if (gameOver) { | |
if (e.key.toLowerCase() === 'r') { | |
returnToMenu(); | |
} | |
return; | |
} | |
switch(e.key) { | |
case 'ArrowLeft': | |
keys.left = true; | |
break; | |
case 'ArrowRight': | |
keys.right = true; | |
break; | |
case ' ': | |
bullets.push({ | |
x: player.x, | |
y: player.y - 20 | |
}); | |
if (audio) audio.playShoot(); | |
break; | |
} | |
}); | |
document.addEventListener('keyup', (e) => { | |
switch(e.key) { | |
case 'ArrowLeft': | |
keys.left = false; | |
break; | |
case 'ArrowRight': | |
keys.right = false; | |
break; | |
} | |
}); | |
</script> | |
</body> | |
</html><script async data-explicit-opt-in="true" data-cookie-opt-in="true" src="https://vercel.live/_next-live/feedback/feedback.js"></script> |