Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Pixel Cosmos Invaders</title> | |
| <link rel="icon" type="image/x-icon" href="/static/favicon.ico"> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> | |
| <script src="https://unpkg.com/feather-icons"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.globe.min.js"></script> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); | |
| @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&display=swap'); | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Orbitron', sans-serif; | |
| overflow: hidden; | |
| background: #0a0a0a; | |
| } | |
| .game-container { | |
| position: relative; | |
| width: 100vw; | |
| height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .vanta-background { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| z-index: -1; | |
| } | |
| .game-title { | |
| font-family: 'Press Start 2P', cursive; | |
| font-size: 3rem; | |
| background: linear-gradient(45deg, #ff00cc, #3333ff); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| text-shadow: 0 0 30px rgba(255, 0, 204, 0.5); | |
| margin-bottom: 2rem; | |
| animation: glow 2s ease-in-out infinite alternate; | |
| } | |
| @keyframes glow { | |
| from { | |
| text-shadow: 0 0 20px rgba(255, 0, 204, 0.5); | |
| } | |
| to { | |
| text-shadow: 0 0 30px rgba(51, 51, 255, 0.8), 0 0 40px rgba(51, 51, 255, 0.6); | |
| } | |
| } | |
| .game-canvas { | |
| background: rgba(10, 10, 30, 0.8); | |
| border: 3px solid #ff00cc; | |
| border-radius: 10px; | |
| box-shadow: 0 0 50px rgba(255, 0, 204, 0.3), | |
| inset 0 0 50px rgba(51, 51, 255, 0.2); | |
| } | |
| .controls { | |
| display: flex; | |
| gap: 2rem; | |
| margin-top: 2rem; | |
| } | |
| .btn { | |
| font-family: 'Press Start 2P', cursive; | |
| padding: 1rem 2rem; | |
| background: linear-gradient(45deg, #ff00cc, #3333ff); | |
| border: none; | |
| border-radius: 8px; | |
| color: white; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| text-transform: uppercase; | |
| font-size: 0.8rem; | |
| box-shadow: 0 0 20px rgba(255, 0, 204, 0.4); | |
| } | |
| .btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 0 30px rgba(51, 51, 255, 0.6); | |
| } | |
| .stats { | |
| display: flex; | |
| gap: 3rem; | |
| margin-top: 1.5rem; | |
| color: #00ffcc; | |
| font-size: 1.2rem; | |
| } | |
| .stat-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .mobile-controls { | |
| display: none; | |
| margin-top: 2rem; | |
| gap: 1rem; | |
| } | |
| .mobile-btn { | |
| width: 80px; | |
| height: 80px; | |
| border-radius: 50%; | |
| background: linear-gradient(45deg, #ff00cc, #3333ff); | |
| border: none; | |
| color: white; | |
| font-size: 2rem; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| box-shadow: 0 0 20px rgba(255, 0, 204, 0.4); | |
| } | |
| @media (max-width: 768px) { | |
| .game-title { | |
| font-size: 1.5rem; | |
| } | |
| .controls { | |
| flex-direction: column; | |
| gap: 1rem; | |
| } | |
| .mobile-controls { | |
| display: flex; | |
| } | |
| .stats { | |
| flex-direction: column; | |
| gap: 1rem; | |
| font-size: 1rem; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="game-container"> | |
| <div class="vanta-background" id="vanta-bg"></div> | |
| <h1 class="game-title">PIXEL COSMOS INVADERS</h1> | |
| <canvas id="gameCanvas" class="game-canvas" width="800" height="600"></canvas> | |
| <div class="controls"> | |
| <button class="btn" onclick="startGame()"> | |
| <i data-feather="play"></i> START GAME | |
| </button> | |
| <button class="btn" onclick="pauseGame()"> | |
| <i data-feather="pause"></i> PAUSE | |
| </button> | |
| <button class="btn" onclick="resetGame()"> | |
| <i data-feather="refresh-cw"></i> RESTART | |
| </button> | |
| </div> | |
| <div class="mobile-controls"> | |
| <button class="mobile-btn" onmousedown="moveLeft()" ontouchstart="moveLeft()"> | |
| <i data-feather="chevron-left"></i> | |
| </button> | |
| <button class="mobile-btn" onmousedown="shoot()" ontouchstart="shoot()"> | |
| <i data-feather="zap"></i> | |
| </button> | |
| <button class="mobile-btn" onmousedown="moveRight()" ontouchstart="moveRight()"> | |
| <i data-feather="chevron-right"></i> | |
| </button> | |
| </div> | |
| <div class="stats"> | |
| <div class="stat-item"> | |
| <i data-feather="target"></i> | |
| <span>SCORE: <span id="score">0</span></span> | |
| </div> | |
| <div class="stat-item"> | |
| <i data-feather="heart"></i> | |
| <span>LIVES: <span id="lives">3</span></span> | |
| </div> | |
| <div class="stat-item"> | |
| <i data-feather="award"></i> | |
| <span>LEVEL: <span id="level">1</span></span> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Vanta.js background | |
| VANTA.GLOBE({ | |
| el: "#vanta-bg", | |
| mouseControls: true, | |
| touchControls: true, | |
| gyroControls: false, | |
| minHeight: 200.00, | |
| minWidth: 200.00, | |
| scale: 1.00, | |
| scaleMobile: 1.00, | |
| color: 0xff00cc, | |
| backgroundColor: 0x0a0a0a, | |
| size: 0.8 | |
| }); | |
| // Game variables | |
| const canvas = document.getElementById('gameCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| let gameRunning = false; | |
| let score = 0; | |
| let lives = 3; | |
| let level = 1; | |
| // Player | |
| const player = { | |
| x: canvas.width / 2 - 25, | |
| y: canvas.height - 50, | |
| width: 50, | |
| height: 30, | |
| speed: 8, | |
| movingLeft: false, | |
| movingRight: false | |
| }; | |
| // Bullets | |
| const bullets = []; | |
| const bulletSpeed = 10; | |
| // Enemies | |
| const enemies = []; | |
| const enemyRows = 5; | |
| const enemyCols = 10; | |
| let enemySpeed = 1; | |
| let enemyDirection = 1; | |
| // Initialize enemies | |
| function initEnemies() { | |
| enemies.length = 0; | |
| for (let row = 0; row < enemyRows; row++) { | |
| for (let col = 0; col < enemyCols; col++) { | |
| enemies.push({ | |
| x: col * 60 + 50, | |
| y: row * 50 + 50, | |
| width: 40, | |
| height: 30, | |
| alive: true | |
| }); | |
| } | |
| } | |
| } | |
| // Draw player | |
| function drawPlayer() { | |
| ctx.fillStyle = '#3333ff'; | |
| ctx.beginPath(); | |
| ctx.moveTo(player.x, player.y + player.height); | |
| ctx.lineTo(player.x + player.width / 2, player.y); | |
| ctx.lineTo(player.x + player.width, player.y + player.height); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| // Glow effect | |
| ctx.shadowColor = '#3333ff'; | |
| ctx.shadowBlur = 20; | |
| ctx.fill(); | |
| ctx.shadowBlur = 0; | |
| } | |
| // Draw bullets | |
| function drawBullets() { | |
| ctx.fillStyle = '#ff00cc'; | |
| bullets.forEach(bullet => { | |
| ctx.beginPath(); | |
| ctx.arc(bullet.x, bullet.y, 5, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.shadowColor = '#ff00cc'; | |
| ctx.shadowBlur = 15; | |
| ctx.fill(); | |
| ctx.shadowBlur = 0; | |
| }); | |
| } | |
| // Draw enemies | |
| function drawEnemies() { | |
| ctx.fillStyle = '#00ffcc'; | |
| enemies.forEach(enemy => { | |
| if (enemy.alive) { | |
| ctx.beginPath(); | |
| ctx.arc(enemy.x + enemy.width / 2, enemy.y + enemy.height / 2, 15, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.shadowColor = '#00ffcc'; | |
| ctx.shadowBlur = 15; | |
| ctx.fill(); | |
| ctx.shadowBlur = 0; | |
| // Alien details | |
| ctx.fillStyle = '#ff00cc'; | |
| ctx.beginPath(); | |
| ctx.arc(enemy.x + enemy.width / 2 - 8, enemy.y + enemy.height / 2 - 5, 3, 0, Math.PI * 2); | |
| ctx.arc(enemy.x + enemy.width / 2 + 8, enemy.y + enemy.height / 2 - 5, 3, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| }); | |
| } | |
| // Update game state | |
| function update() { | |
| // Move player | |
| if (player.movingLeft && player.x > 0) { | |
| player.x -= player.speed; | |
| } | |
| if (player.movingRight && player.x < canvas.width - player.width) { | |
| player.x += player.speed; | |
| } | |
| // Move bullets | |
| for (let i = bullets.length - 1; i >= 0; i--) { | |
| bullets[i].y -= bulletSpeed; | |
| // Remove bullets that go off screen | |
| if (bullets[i].y < 0) { | |
| bullets.splice(i, 1); | |
| } | |
| } | |
| // Move enemies | |
| let edgeReached = false; | |
| enemies.forEach(enemy => { | |
| if (enemy.alive) { | |
| enemy.x += enemySpeed * enemyDirection; | |
| if (enemy.x <= 0 || enemy.x + enemy.width >= canvas.width) { | |
| edgeReached = true; | |
| } | |
| } | |
| }); | |
| // Change direction if edge reached | |
| if (edgeReached) { | |
| enemyDirection *= -1; | |
| enemies.forEach(enemy => { | |
| if (enemy.alive) { | |
| enemy.y += 20; | |
| } | |
| }); | |
| } | |
| // Check collisions | |
| checkCollisions(); | |
| // Check level completion | |
| if (enemies.every(enemy => !enemy.alive)) { | |
| level++; | |
| document.getElementById('level').textContent = level; | |
| enemySpeed += 0.5; | |
| initEnemies(); | |
| } | |
| } | |
| // Check collisions | |
| function checkCollisions() { | |
| // Bullet-enemy collisions | |
| bullets.forEach((bullet, bulletIndex) => { | |
| enemies.forEach((enemy, enemyIndex) => { | |
| if (enemy.alive && | |
| bullet.x > enemy.x && | |
| bullet.x < enemy.x + enemy.width && | |
| bullet.y > enemy.y && | |
| bullet.y < enemy.y + enemy.height) { | |
| enemy.alive = false; | |
| bullets.splice(bulletIndex, 1); | |
| score += 100; | |
| document.getElementById('score').textContent = score; | |
| } | |
| }); | |
| }); | |
| // Enemy-player collisions | |
| enemies.forEach(enemy => { | |
| if (enemy.alive && | |
| enemy.y + enemy.height >= player.y && | |
| enemy.x < player.x + player.width && | |
| enemy.x + enemy.width > player.x) { | |
| lives--; | |
| document.getElementById('lives').textContent = lives; | |
| enemy.alive = false; | |
| if (lives <= 0) { | |
| gameOver(); | |
| } | |
| } | |
| }); | |
| } | |
| // Draw everything | |
| function draw() { | |
| // Clear canvas | |
| ctx.fillStyle = 'rgba(10, 10, 30, 0.3)'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| drawPlayer(); | |
| drawBullets(); | |
| drawEnemies(); | |
| } | |
| // Game loop | |
| function gameLoop() { | |
| if (gameRunning) { | |
| update(); | |
| draw(); | |
| requestAnimationFrame(gameLoop); | |
| } | |
| } | |
| // Start game | |
| function startGame() { | |
| if (!gameRunning) { | |
| gameRunning = true; | |
| gameLoop(); | |
| } | |
| } | |
| // Pause game | |
| function pauseGame() { | |
| gameRunning = !gameRunning; | |
| if (gameRunning) { | |
| gameLoop(); | |
| } | |
| } | |
| // Reset game | |
| function resetGame() { | |
| gameRunning = false; | |
| score = 0; | |
| lives = 3; | |
| level = 1; | |
| bullets.length = 0; | |
| document.getElementById('score').textContent = score; | |
| document.getElementById('lives').textContent = lives; | |
| document.getElementById('level').textContent = level; | |
| initEnemies(); | |
| player.x = canvas.width / 2 - 25; | |
| player.y = canvas.height - 50; | |
| } | |
| // Game over | |
| function gameOver() { | |
| gameRunning = false; | |
| ctx.fillStyle = 'rgba(0, 0, 0, 0.8)'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| ctx.fillStyle = '#ff00cc'; | |
| ctx.font = '48px "Press Start 2P"'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('GAME OVER', canvas.width / 2, canvas.height / 2); | |
| ctx.font = '24px "Press Start 2P"'; | |
| ctx.fillText(`FINAL SCORE: ${score}`, canvas.width / 2, canvas.height / 2 + 50); | |
| } | |
| // Shoot bullet | |
| function shoot() { | |
| if (gameRunning) { | |
| bullets.push({ | |
| x: player.x + player.width / 2, | |
| y: player.y | |
| }); | |
| } | |
| } | |
| // Mobile controls | |
| function moveLeft() { | |
| player.movingLeft = true; | |
| } | |
| function moveRight() { | |
| player.movingRight = true; | |
| } | |
| // Keyboard controls | |
| document.addEventListener('keydown', (e) => { | |
| if (e.key === 'ArrowLeft') player.movingLeft = true; | |
| if (e.key === 'ArrowRight') player.movingRight = true; | |
| if (e.key === ' ') shoot(); | |
| }); | |
| document.addEventListener('keyup', (e) => { | |
| if (e.key === 'ArrowLeft') player.movingLeft = false; | |
| if (e.key === 'ArrowRight') player.movingRight = false; | |
| }); | |
| // Touch end events for mobile | |
| document.addEventListener('touchend', () => { | |
| player.movingLeft = false; | |
| player.movingRight = false; | |
| }); | |
| document.addEventListener('mouseup', () => { | |
| player.movingLeft = false; | |
| player.movingRight = false; | |
| }); | |
| // Initialize game | |
| initEnemies(); | |
| feather.replace(); | |
| // Responsive canvas | |
| function resizeCanvas() { | |
| const container = canvas.parentElement; | |
| const containerWidth = container.clientWidth; | |
| const containerHeight = container.clientHeight; | |
| const scale = Math.min(containerWidth / 800, containerHeight / 700); | |
| canvas.style.width = (800 * scale) + 'px'; | |
| canvas.style.height = (600 * scale) + 'px'; | |
| } | |
| window.addEventListener('resize', resizeCanvas); | |
| resizeCanvas(); | |
| </script> | |
| </body> | |
| </html> | |