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