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> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <style> | |
| body { | |
| margin: 0; | |
| overflow: hidden; | |
| background-color: #000; | |
| cursor: none; | |
| } | |
| #gameCanvas { | |
| display: block; | |
| background: linear-gradient(to bottom, #000428, #004e92); | |
| } | |
| #gameUI { | |
| position: absolute; | |
| top: 10px; | |
| left: 10px; | |
| color: white; | |
| font-family: 'Arial', sans-serif; | |
| pointer-events: none; | |
| } | |
| #startScreen, #gameOverScreen, #levelCompleteScreen { | |
| 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.7); | |
| color: white; | |
| font-family: 'Arial', sans-serif; | |
| } | |
| #gameOverScreen, #levelCompleteScreen { | |
| display: none; | |
| } | |
| .custom-cursor { | |
| position: absolute; | |
| width: 20px; | |
| height: 20px; | |
| background-color: rgba(255, 255, 255, 0.7); | |
| border-radius: 50%; | |
| pointer-events: none; | |
| z-index: 9999; | |
| transform: translate(-50%, -50%); | |
| mix-blend-mode: difference; | |
| } | |
| .powerup { | |
| animation: pulse 1s infinite; | |
| } | |
| @keyframes pulse { | |
| 0% { transform: scale(1); } | |
| 50% { transform: scale(1.2); } | |
| 100% { transform: scale(1); } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <canvas id="gameCanvas"></canvas> | |
| <div id="gameUI"> | |
| <div class="flex space-x-4"> | |
| <div class="bg-gray-800 bg-opacity-70 px-4 py-2 rounded"> | |
| Level: <span id="levelDisplay">1</span> | |
| </div> | |
| <div class="bg-gray-800 bg-opacity-70 px-4 py-2 rounded"> | |
| Score: <span id="scoreDisplay">0</span> | |
| </div> | |
| <div class="bg-gray-800 bg-opacity-70 px-4 py-2 rounded"> | |
| Health: <span id="healthDisplay">100</span>% | |
| </div> | |
| <div class="bg-gray-800 bg-opacity-70 px-4 py-2 rounded"> | |
| Weapon: <span id="weaponDisplay">Basic</span> | |
| </div> | |
| <div class="bg-gray-800 bg-opacity-70 px-4 py-2 rounded"> | |
| Bombs: <span id="bombDisplay">3</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="startScreen"> | |
| <h1 class="text-5xl font-bold mb-6 text-blue-400">GALAXY DEFENDER</h1> | |
| <p class="text-xl mb-8 text-gray-300">Defend the galaxy against alien invaders!</p> | |
| <button id="startButton" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-6 rounded-full text-xl transition-all duration-300 transform hover:scale-110"> | |
| START MISSION | |
| </button> | |
| <div class="mt-8 text-gray-400"> | |
| <p>Controls: Move with mouse | Hold left click to shoot | Right click for bomb</p> | |
| <p class="mt-2">Power-ups will appear during gameplay</p> | |
| </div> | |
| </div> | |
| <div id="gameOverScreen"> | |
| <h1 class="text-5xl font-bold mb-6 text-red-400">MISSION FAILED</h1> | |
| <p class="text-xl mb-4">Your score: <span id="finalScore">0</span></p> | |
| <p class="text-xl mb-8">Level reached: <span id="finalLevel">1</span></p> | |
| <button id="restartButton" class="bg-red-600 hover:bg-red-700 text-white font-bold py-3 px-6 rounded-full text-xl transition-all duration-300 transform hover:scale-110"> | |
| TRY AGAIN | |
| </button> | |
| </div> | |
| <div id="levelCompleteScreen"> | |
| <h1 class="text-5xl font-bold mb-6 text-green-400">LEVEL COMPLETE!</h1> | |
| <p class="text-xl mb-8">Preparing for next mission...</p> | |
| <div class="w-64 h-3 bg-gray-700 rounded-full overflow-hidden"> | |
| <div id="levelProgress" class="h-full bg-green-500 transition-all duration-1000"></div> | |
| </div> | |
| </div> | |
| <div class="custom-cursor" id="customCursor"></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 levelCompleteScreen = document.getElementById('levelCompleteScreen'); | |
| const startButton = document.getElementById('startButton'); | |
| const restartButton = document.getElementById('restartButton'); | |
| const scoreDisplay = document.getElementById('scoreDisplay'); | |
| const healthDisplay = document.getElementById('healthDisplay'); | |
| const levelDisplay = document.getElementById('levelDisplay'); | |
| const weaponDisplay = document.getElementById('weaponDisplay'); | |
| const bombDisplay = document.getElementById('bombDisplay'); | |
| const finalScore = document.getElementById('finalScore'); | |
| const finalLevel = document.getElementById('finalLevel'); | |
| const levelProgress = document.getElementById('levelProgress'); | |
| const customCursor = document.getElementById('customCursor'); | |
| // Set canvas size | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| // Game state | |
| let gameRunning = false; | |
| let score = 0; | |
| let health = 100; | |
| let level = 1; | |
| let enemies = []; | |
| let bullets = []; | |
| let enemyBullets = []; | |
| let powerups = []; | |
| let explosions = []; | |
| let bombs = []; | |
| let lastEnemySpawn = 0; | |
| let lastPowerupSpawn = 0; | |
| let enemiesDefeated = 0; | |
| let enemiesToDefeat = 10; | |
| let currentWeapon = 'basic'; | |
| let weaponCooldown = 0; | |
| let bombCount = 3; | |
| let isMouseDown = false; | |
| let lastShotTime = 0; | |
| let mouseX = canvas.width / 2; | |
| let mouseY = canvas.height / 2; | |
| let player = { | |
| x: canvas.width / 2, | |
| y: canvas.height - 100, | |
| width: 50, | |
| height: 60, | |
| speed: 8 | |
| }; | |
| // Weapon types | |
| const weapons = { | |
| basic: { | |
| cooldown: 15, | |
| damage: 10, | |
| color: '#00FFFF', | |
| size: 5, | |
| speed: 10, | |
| spread: 0 | |
| }, | |
| double: { | |
| cooldown: 20, | |
| damage: 10, | |
| color: '#FF00FF', | |
| size: 5, | |
| speed: 10, | |
| spread: 0.2 | |
| }, | |
| laser: { | |
| cooldown: 5, | |
| damage: 5, | |
| color: '#FFFF00', | |
| size: 3, | |
| speed: 15, | |
| spread: 0 | |
| }, | |
| plasma: { | |
| cooldown: 40, | |
| damage: 25, | |
| color: '#FF4500', | |
| size: 8, | |
| speed: 7, | |
| spread: 0 | |
| } | |
| }; | |
| // Enemy types | |
| const enemyTypes = [ | |
| { | |
| name: 'Scout', | |
| width: 40, | |
| height: 40, | |
| speed: 2, | |
| health: 30, | |
| color: '#FF5555', | |
| score: 50, | |
| fireRate: 120, | |
| bulletSpeed: 5, | |
| bulletColor: '#FF0000', | |
| movementPattern: 'straight', | |
| directionChangeRate: 1000 | |
| }, | |
| { | |
| name: 'Fighter', | |
| width: 50, | |
| height: 50, | |
| speed: 3, | |
| health: 50, | |
| color: '#FFAA00', | |
| score: 100, | |
| fireRate: 90, | |
| bulletSpeed: 6, | |
| bulletColor: '#FF6600', | |
| movementPattern: 'zigzag', | |
| directionChangeRate: 800 | |
| }, | |
| { | |
| name: 'Bomber', | |
| width: 70, | |
| height: 60, | |
| speed: 1.5, | |
| health: 100, | |
| color: '#AA55FF', | |
| score: 200, | |
| fireRate: 150, | |
| bulletSpeed: 4, | |
| bulletColor: '#AA00FF', | |
| movementPattern: 'sinusoidal', | |
| directionChangeRate: 500 | |
| }, | |
| { | |
| name: 'Elite', | |
| width: 45, | |
| height: 45, | |
| speed: 4, | |
| health: 80, | |
| color: '#00FFAA', | |
| score: 250, | |
| fireRate: 60, | |
| bulletSpeed: 7, | |
| bulletColor: '#00FF00', | |
| movementPattern: 'random', | |
| directionChangeRate: 300 | |
| } | |
| ]; | |
| // Powerup types | |
| const powerupTypes = [ | |
| { | |
| name: 'Health', | |
| color: '#00FF00', | |
| effect: (game) => { | |
| game.health = Math.min(100, game.health + 30); | |
| createFloatingText('+30 Health', game.player.x, game.player.y, '#00FF00'); | |
| } | |
| }, | |
| { | |
| name: 'Double Shot', | |
| color: '#FF00FF', | |
| effect: (game) => { | |
| game.currentWeapon = 'double'; | |
| setTimeout(() => { | |
| if (game.currentWeapon === 'double') { | |
| game.currentWeapon = 'basic'; | |
| createFloatingText('Double Shot Ended', game.player.x, game.player.y, '#FF00FF'); | |
| } | |
| }, 10000); | |
| createFloatingText('Double Shot!', game.player.x, game.player.y, '#FF00FF'); | |
| } | |
| }, | |
| { | |
| name: 'Laser', | |
| color: '#FFFF00', | |
| effect: (game) => { | |
| game.currentWeapon = 'laser'; | |
| setTimeout(() => { | |
| if (game.currentWeapon === 'laser') { | |
| game.currentWeapon = 'basic'; | |
| createFloatingText('Laser Ended', game.player.x, game.player.y, '#FFFF00'); | |
| } | |
| }, 10000); | |
| createFloatingText('Rapid Laser!', game.player.x, game.player.y, '#FFFF00'); | |
| } | |
| }, | |
| { | |
| name: 'Plasma', | |
| color: '#FF4500', | |
| effect: (game) => { | |
| game.currentWeapon = 'plasma'; | |
| setTimeout(() => { | |
| if (game.currentWeapon === 'plasma') { | |
| game.currentWeapon = 'basic'; | |
| createFloatingText('Plasma Ended', game.player.x, game.player.y, '#FF4500'); | |
| } | |
| }, 10000); | |
| createFloatingText('Plasma Cannon!', game.player.x, game.player.y, '#FF4500'); | |
| } | |
| }, | |
| { | |
| name: 'Bomb', | |
| color: '#FFFFFF', | |
| effect: (game) => { | |
| game.bombCount = Math.min(5, game.bombCount + 1); | |
| createFloatingText('+1 Bomb', game.player.x, game.player.y, '#FFFFFF'); | |
| updateUI(); | |
| } | |
| } | |
| ]; | |
| // Floating text for effects | |
| let floatingTexts = []; | |
| function createFloatingText(text, x, y, color) { | |
| floatingTexts.push({ | |
| text, | |
| x, | |
| y, | |
| color, | |
| alpha: 1, | |
| lifetime: 60 | |
| }); | |
| } | |
| // Initialize game | |
| function initGame() { | |
| gameRunning = true; | |
| score = 0; | |
| health = 100; | |
| level = 1; | |
| enemies = []; | |
| bullets = []; | |
| enemyBullets = []; | |
| powerups = []; | |
| explosions = []; | |
| bombs = []; | |
| floatingTexts = []; | |
| enemiesDefeated = 0; | |
| enemiesToDefeat = 10; | |
| currentWeapon = 'basic'; | |
| bombCount = 3; | |
| player.x = canvas.width / 2; | |
| player.y = canvas.height - 100; | |
| updateUI(); | |
| startScreen.style.display = 'none'; | |
| gameOverScreen.style.display = 'none'; | |
| levelCompleteScreen.style.display = 'none'; | |
| // Start game loop | |
| requestAnimationFrame(gameLoop); | |
| } | |
| // Game loop | |
| function gameLoop(timestamp) { | |
| if (!gameRunning) return; | |
| // Clear canvas | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| // Draw starfield background | |
| drawBackground(); | |
| // Update and draw player | |
| updatePlayer(); | |
| drawPlayer(); | |
| // Spawn enemies | |
| if (timestamp - lastEnemySpawn > 1000 - (level * 50) && enemies.length < 5 + Math.floor(level / 2)) { | |
| spawnEnemy(); | |
| lastEnemySpawn = timestamp; | |
| } | |
| // Spawn powerups | |
| if (timestamp - lastPowerupSpawn > 15000 && Math.random() < 0.02) { | |
| spawnPowerup(); | |
| lastPowerupSpawn = timestamp; | |
| } | |
| // Continuous firing when mouse is held down | |
| if (isMouseDown && timestamp - lastShotTime > weapons[currentWeapon].cooldown) { | |
| shoot(); | |
| lastShotTime = timestamp; | |
| } | |
| // Update and draw enemies | |
| updateEnemies(timestamp); | |
| drawEnemies(); | |
| // Update and draw bullets | |
| updateBullets(); | |
| drawBullets(); | |
| // Update and draw enemy bullets | |
| updateEnemyBullets(); | |
| drawEnemyBullets(); | |
| // Update and draw powerups | |
| updatePowerups(); | |
| drawPowerups(); | |
| // Update and draw explosions | |
| updateExplosions(); | |
| drawExplosions(); | |
| // Update and draw bombs | |
| updateBombs(); | |
| drawBombs(); | |
| // Update and draw floating text | |
| updateFloatingTexts(); | |
| drawFloatingTexts(); | |
| // Check level completion | |
| if (enemiesDefeated >= enemiesToDefeat) { | |
| levelComplete(); | |
| return; | |
| } | |
| // Check game over | |
| if (health <= 0) { | |
| gameOver(); | |
| return; | |
| } | |
| // Update weapon cooldown | |
| if (weaponCooldown > 0) { | |
| weaponCooldown--; | |
| } | |
| // Continue game loop | |
| requestAnimationFrame(gameLoop); | |
| } | |
| // Draw starfield background | |
| function drawBackground() { | |
| // Draw gradient background | |
| const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height); | |
| gradient.addColorStop(0, '#000428'); | |
| gradient.addColorStop(1, '#004e92'); | |
| ctx.fillStyle = gradient; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| // Draw stars | |
| for (let i = 0; i < 100; i++) { | |
| const x = (i * canvas.width / 100 + Date.now() / 1000 * 10) % canvas.width; | |
| const y = (i * canvas.height / 100 + Date.now() / 500 * 5) % canvas.height; | |
| const size = Math.random() * 2; | |
| const opacity = Math.random() * 0.8 + 0.2; | |
| ctx.fillStyle = `rgba(255, 255, 255, ${opacity})`; | |
| ctx.beginPath(); | |
| ctx.arc(x, y, size, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| } | |
| // Update player position based on mouse | |
| function updatePlayer() { | |
| // Smooth movement towards mouse | |
| player.x += (mouseX - player.x) * 0.1; | |
| player.y += (mouseY - player.y) * 0.1; | |
| // Keep player within bounds | |
| player.x = Math.max(player.width / 2, Math.min(canvas.width - player.width / 2, player.x)); | |
| player.y = Math.max(player.height / 2, Math.min(canvas.height - player.height / 2, player.y)); | |
| } | |
| // Draw player ship | |
| function drawPlayer() { | |
| ctx.save(); | |
| ctx.translate(player.x, player.y); | |
| // Draw ship body | |
| ctx.fillStyle = '#3498db'; | |
| ctx.beginPath(); | |
| ctx.moveTo(0, -player.height / 2); | |
| ctx.lineTo(-player.width / 2, player.height / 2); | |
| ctx.lineTo(player.width / 2, player.height / 2); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| // Draw ship details | |
| ctx.fillStyle = '#2980b9'; | |
| ctx.beginPath(); | |
| ctx.moveTo(0, -player.height / 4); | |
| ctx.lineTo(-player.width / 4, player.height / 4); | |
| ctx.lineTo(player.width / 4, player.height / 4); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| // Draw engine glow | |
| if (Math.random() < 0.3) { | |
| ctx.fillStyle = '#e74c3c'; | |
| ctx.beginPath(); | |
| ctx.moveTo(-player.width / 4, player.height / 2); | |
| ctx.lineTo(0, player.height / 2 + 10 + Math.random() * 5); | |
| ctx.lineTo(player.width / 4, player.height / 2); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| } | |
| ctx.restore(); | |
| } | |
| // Spawn a new enemy | |
| function spawnEnemy() { | |
| const typeIndex = Math.min(Math.floor(Math.random() * (level / 2)), enemyTypes.length - 1); | |
| const type = enemyTypes[typeIndex]; | |
| const enemy = { | |
| x: Math.random() * (canvas.width - type.width) + type.width / 2, | |
| y: -type.height, | |
| width: type.width, | |
| height: type.height, | |
| speed: type.speed, | |
| health: type.health, | |
| maxHealth: type.health, | |
| color: type.color, | |
| score: type.score, | |
| type: typeIndex, | |
| lastShot: 0, | |
| fireRate: type.fireRate, | |
| bulletSpeed: type.bulletSpeed, | |
| bulletColor: type.bulletColor, | |
| movementPattern: type.movementPattern, | |
| directionChangeRate: type.directionChangeRate, | |
| lastDirectionChange: 0, | |
| directionX: Math.random() > 0.5 ? 1 : -1, | |
| directionY: 1, | |
| angle: 0, | |
| amplitude: Math.random() * 50 + 50, | |
| frequency: Math.random() * 0.01 + 0.01 | |
| }; | |
| enemies.push(enemy); | |
| } | |
| // Update all enemies | |
| function updateEnemies(timestamp) { | |
| for (let i = enemies.length - 1; i >= 0; i--) { | |
| const enemy = enemies[i]; | |
| const type = enemyTypes[enemy.type]; | |
| // Move enemy based on movement pattern | |
| switch(enemy.movementPattern) { | |
| case 'straight': | |
| enemy.y += enemy.speed; | |
| break; | |
| case 'zigzag': | |
| if (timestamp - enemy.lastDirectionChange > enemy.directionChangeRate) { | |
| enemy.directionX = Math.random() > 0.5 ? 1 : -1; | |
| enemy.lastDirectionChange = timestamp; | |
| } | |
| enemy.x += enemy.speed * 0.5 * enemy.directionX; | |
| enemy.y += enemy.speed * 0.8; | |
| break; | |
| case 'sinusoidal': | |
| enemy.angle += enemy.frequency; | |
| enemy.x = enemy.x + Math.sin(enemy.angle) * 0.5; | |
| enemy.y += enemy.speed * 0.7; | |
| break; | |
| case 'random': | |
| if (timestamp - enemy.lastDirectionChange > enemy.directionChangeRate) { | |
| enemy.directionX = Math.random() * 2 - 1; | |
| enemy.directionY = Math.random() * 0.5 + 0.5; | |
| enemy.lastDirectionChange = timestamp; | |
| } | |
| enemy.x += enemy.speed * enemy.directionX; | |
| enemy.y += enemy.speed * enemy.directionY; | |
| break; | |
| } | |
| // Keep enemies within bounds | |
| enemy.x = Math.max(enemy.width / 2, Math.min(canvas.width - enemy.width / 2, enemy.x)); | |
| // Enemy shooting with random spread | |
| if (timestamp - enemy.lastShot > enemy.fireRate) { | |
| const spread = Math.random() * 0.5 - 0.25; | |
| enemyBullets.push({ | |
| x: enemy.x, | |
| y: enemy.y + enemy.height / 2, | |
| width: 5, | |
| height: 10, | |
| speed: enemy.bulletSpeed, | |
| damage: 10, | |
| color: enemy.bulletColor, | |
| directionX: spread | |
| }); | |
| enemy.lastShot = timestamp; | |
| } | |
| // Check if enemy is out of bounds | |
| if (enemy.y > canvas.height + enemy.height) { | |
| enemies.splice(i, 1); | |
| } | |
| } | |
| } | |
| // Draw all enemies | |
| function drawEnemies() { | |
| for (const enemy of enemies) { | |
| ctx.save(); | |
| ctx.translate(enemy.x, enemy.y); | |
| // Draw enemy body | |
| ctx.fillStyle = enemy.color; | |
| ctx.beginPath(); | |
| ctx.moveTo(0, -enemy.height / 2); | |
| ctx.lineTo(-enemy.width / 2, enemy.height / 2); | |
| ctx.lineTo(0, enemy.height / 4); | |
| ctx.lineTo(enemy.width / 2, enemy.height / 2); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| // Draw enemy details | |
| ctx.fillStyle = '#000'; | |
| ctx.beginPath(); | |
| ctx.arc(0, -enemy.height / 4, enemy.width / 8, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // Draw health bar | |
| const healthPercent = enemy.health / enemy.maxHealth; | |
| ctx.fillStyle = '#FF0000'; | |
| ctx.fillRect(-enemy.width / 2, -enemy.height / 2 - 10, enemy.width, 5); | |
| ctx.fillStyle = '#00FF00'; | |
| ctx.fillRect(-enemy.width / 2, -enemy.height / 2 - 10, enemy.width * healthPercent, 5); | |
| ctx.restore(); | |
| } | |
| } | |
| // Spawn a powerup | |
| function spawnPowerup() { | |
| const typeIndex = Math.floor(Math.random() * powerupTypes.length); | |
| const type = powerupTypes[typeIndex]; | |
| const powerup = { | |
| x: Math.random() * (canvas.width - 30) + 15, | |
| y: -30, | |
| width: 30, | |
| height: 30, | |
| speed: 2, | |
| type: typeIndex, | |
| color: type.color | |
| }; | |
| powerups.push(powerup); | |
| } | |
| // Update all powerups | |
| function updatePowerups() { | |
| for (let i = powerups.length - 1; i >= 0; i--) { | |
| const powerup = powerups[i]; | |
| // Move powerup | |
| powerup.y += powerup.speed; | |
| // Check collision with player | |
| if ( | |
| powerup.x + powerup.width / 2 > player.x - player.width / 2 && | |
| powerup.x - powerup.width / 2 < player.x + player.width / 2 && | |
| powerup.y + powerup.height / 2 > player.y - player.height / 2 && | |
| powerup.y - powerup.height / 2 < player.y + player.height / 2 | |
| ) { | |
| // Apply powerup effect | |
| powerupTypes[powerup.type].effect({ | |
| player, | |
| health, | |
| currentWeapon, | |
| score, | |
| bombCount | |
| }); | |
| // Remove powerup | |
| powerups.splice(i, 1); | |
| continue; | |
| } | |
| // Check if powerup is out of bounds | |
| if (powerup.y > canvas.height + powerup.height) { | |
| powerups.splice(i, 1); | |
| } | |
| } | |
| } | |
| // Draw all powerups | |
| function drawPowerups() { | |
| for (const powerup of powerups) { | |
| ctx.save(); | |
| ctx.translate(powerup.x, powerup.y); | |
| // Draw powerup | |
| ctx.fillStyle = powerup.color; | |
| ctx.beginPath(); | |
| ctx.arc(0, 0, powerup.width / 2, 0, Math.PI * 2); | |
| ctx.fill(); | |
| // Draw plus sign for health | |
| if (powerup.type === 0) { | |
| ctx.fillStyle = '#FFF'; | |
| ctx.fillRect(-powerup.width / 4, -2, powerup.width / 2, 4); | |
| ctx.fillRect(-2, -powerup.width / 4, 4, powerup.width / 2); | |
| } | |
| // Draw bomb icon | |
| else if (powerup.type === 4) { | |
| ctx.fillStyle = '#000'; | |
| ctx.beginPath(); | |
| ctx.moveTo(0, -powerup.width / 4); | |
| ctx.lineTo(-powerup.width / 4, powerup.width / 8); | |
| ctx.lineTo(powerup.width / 4, powerup.width / 8); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| ctx.fillStyle = '#FF0000'; | |
| ctx.beginPath(); | |
| ctx.arc(0, -powerup.width / 6, powerup.width / 8, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| ctx.restore(); | |
| } | |
| } | |
| // Player shooting | |
| function shoot() { | |
| if (weaponCooldown > 0) return; | |
| const weapon = weapons[currentWeapon]; | |
| weaponCooldown = weapon.cooldown; | |
| if (currentWeapon === 'basic' || currentWeapon === 'plasma') { | |
| // Single shot | |
| bullets.push({ | |
| x: player.x, | |
| y: player.y - player.height / 2, | |
| width: weapon.size, | |
| height: weapon.size * 2, | |
| speed: -weapon.speed, | |
| damage: weapon.damage, | |
| color: weapon.color | |
| }); | |
| } else if (currentWeapon === 'double') { | |
| // Double shot | |
| bullets.push({ | |
| x: player.x - 15, | |
| y: player.y - player.height / 2, | |
| width: weapon.size, | |
| height: weapon.size * 2, | |
| speed: -weapon.speed, | |
| damage: weapon.damage, | |
| color: weapon.color | |
| }); | |
| bullets.push({ | |
| x: player.x + 15, | |
| y: player.y - player.height / 2, | |
| width: weapon.size, | |
| height: weapon.size * 2, | |
| speed: -weapon.speed, | |
| damage: weapon.damage, | |
| color: weapon.color | |
| }); | |
| } else if (currentWeapon === 'laser') { | |
| // Rapid laser | |
| bullets.push({ | |
| x: player.x + (Math.random() - 0.5) * 10, | |
| y: player.y - player.height / 2, | |
| width: weapon.size, | |
| height: weapon.size * 2, | |
| speed: -weapon.speed, | |
| damage: weapon.damage, | |
| color: weapon.color | |
| }); | |
| } | |
| } | |
| // Update all bullets | |
| function updateBullets() { | |
| for (let i = bullets.length - 1; i >= 0; i--) { | |
| const bullet = bullets[i]; | |
| // Move bullet | |
| bullet.y += bullet.speed; | |
| // Check collision with enemies | |
| 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 | |
| ) { | |
| // Damage enemy | |
| enemy.health -= bullet.damage; | |
| // Create explosion | |
| createExplosion(bullet.x, bullet.y, bullet.color); | |
| // Remove bullet | |
| bullets.splice(i, 1); | |
| // Check if enemy is defeated | |
| if (enemy.health <= 0) { | |
| // Add score | |
| score += enemy.score; | |
| enemiesDefeated++; | |
| // Create bigger explosion | |
| createExplosion(enemy.x, enemy.y, enemy.color, 2); | |
| // Remove enemy | |
| enemies.splice(j, 1); | |
| // Random chance to drop powerup | |
| if (Math.random() < 0.1) { | |
| spawnPowerup(); | |
| } | |
| } | |
| updateUI(); | |
| break; | |
| } | |
| } | |
| // Check if bullet is out of bounds | |
| if (bullet.y < -bullet.height) { | |
| bullets.splice(i, 1); | |
| } | |
| } | |
| } | |
| // Draw all bullets | |
| function drawBullets() { | |
| for (const bullet of bullets) { | |
| ctx.save(); | |
| ctx.translate(bullet.x, bullet.y); | |
| // Draw bullet | |
| ctx.fillStyle = bullet.color; | |
| ctx.beginPath(); | |
| ctx.rect(-bullet.width / 2, -bullet.height / 2, bullet.width, bullet.height); | |
| ctx.fill(); | |
| // Add glow effect | |
| if (bullet.damage > 10) { | |
| ctx.fillStyle = `rgba(255, 255, 255, 0.3)`; | |
| ctx.beginPath(); | |
| ctx.arc(0, 0, bullet.width * 1.5, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| ctx.restore(); | |
| } | |
| } | |
| // Update all enemy bullets | |
| function updateEnemyBullets() { | |
| for (let i = enemyBullets.length - 1; i >= 0; i--) { | |
| const bullet = enemyBullets[i]; | |
| // Move bullet with possible spread | |
| bullet.y += bullet.speed; | |
| bullet.x += bullet.directionX * 2; | |
| // Check collision with player | |
| 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 | |
| ) { | |
| // Damage player | |
| health -= 10; | |
| // Create explosion | |
| createExplosion(bullet.x, bullet.y, bullet.color); | |
| // Remove bullet | |
| enemyBullets.splice(i, 1); | |
| updateUI(); | |
| continue; | |
| } | |
| // Check if bullet is out of bounds | |
| if (bullet.y > canvas.height + bullet.height) { | |
| enemyBullets.splice(i, 1); | |
| } | |
| } | |
| } | |
| // Draw all enemy bullets | |
| function drawEnemyBullets() { | |
| for (const bullet of enemyBullets) { | |
| ctx.save(); | |
| ctx.translate(bullet.x, bullet.y); | |
| // Draw bullet | |
| ctx.fillStyle = bullet.color; | |
| ctx.beginPath(); | |
| ctx.rect(-bullet.width / 2, -bullet.height / 2, bullet.width, bullet.height); | |
| ctx.fill(); | |
| ctx.restore(); | |
| } | |
| } | |
| // Create explosion effect | |
| function createExplosion(x, y, color, sizeMultiplier = 1) { | |
| const particleCount = 10 + Math.floor(Math.random() * 10); | |
| for (let i = 0; i < particleCount; i++) { | |
| explosions.push({ | |
| x, | |
| y, | |
| radius: Math.random() * 3 * sizeMultiplier + 1, | |
| color, | |
| speedX: (Math.random() - 0.5) * 5, | |
| speedY: (Math.random() - 0.5) * 5, | |
| alpha: 1, | |
| lifetime: 30 + Math.random() * 30 | |
| }); | |
| } | |
| } | |
| // Update all explosions | |
| function updateExplosions() { | |
| for (let i = explosions.length - 1; i >= 0; i--) { | |
| const explosion = explosions[i]; | |
| // Move particle | |
| explosion.x += explosion.speedX; | |
| explosion.y += explosion.speedY; | |
| // Fade out | |
| explosion.alpha -= 1 / explosion.lifetime; | |
| // Remove when faded out | |
| if (explosion.alpha <= 0) { | |
| explosions.splice(i, 1); | |
| } | |
| } | |
| } | |
| // Draw all explosions | |
| function drawExplosions() { | |
| for (const explosion of explosions) { | |
| ctx.save(); | |
| ctx.globalAlpha = explosion.alpha; | |
| // Draw particle | |
| ctx.fillStyle = explosion.color; | |
| ctx.beginPath(); | |
| ctx.arc(explosion.x, explosion.y, explosion.radius, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.restore(); | |
| } | |
| } | |
| // Activate bomb | |
| function activateBomb() { | |
| if (bombCount <= 0) return; | |
| bombCount--; | |
| updateUI(); | |
| // Create a big explosion that covers most of the screen | |
| createExplosion(player.x, player.y, '#FFFFFF', 5); | |
| // Damage all enemies on screen | |
| for (let i = enemies.length - 1; i >= 0; i--) { | |
| const enemy = enemies[i]; | |
| // Damage enemy | |
| enemy.health -= 50; | |
| // Check if enemy is defeated | |
| if (enemy.health <= 0) { | |
| // Add score | |
| score += enemy.score; | |
| enemiesDefeated++; | |
| // Create bigger explosion | |
| createExplosion(enemy.x, enemy.y, enemy.color, 2); | |
| // Remove enemy | |
| enemies.splice(i, 1); | |
| } | |
| } | |
| // Clear all enemy bullets | |
| enemyBullets = []; | |
| // Show bomb effect | |
| bombs.push({ | |
| x: player.x, | |
| y: player.y, | |
| radius: 0, | |
| maxRadius: Math.max(canvas.width, canvas.height) * 0.8, | |
| color: 'rgba(255, 255, 255, 0.3)', | |
| lifetime: 30 | |
| }); | |
| } | |
| // Update all bombs | |
| function updateBombs() { | |
| for (let i = bombs.length - 1; i >= 0; i--) { | |
| const bomb = bombs[i]; | |
| // Expand bomb radius | |
| bomb.radius += (bomb.maxRadius - bomb.radius) * 0.1; | |
| // Fade out | |
| bomb.lifetime--; | |
| // Remove when expired | |
| if (bomb.lifetime <= 0) { | |
| bombs.splice(i, 1); | |
| } | |
| } | |
| } | |
| // Draw all bombs | |
| function drawBombs() { | |
| for (const bomb of bombs) { | |
| ctx.save(); | |
| // Draw bomb shockwave | |
| ctx.fillStyle = bomb.color; | |
| ctx.beginPath(); | |
| ctx.arc(bomb.x, bomb.y, bomb.radius, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.restore(); | |
| } | |
| } | |
| // Update floating texts | |
| function updateFloatingTexts() { | |
| for (let i = floatingTexts.length - 1; i >= 0; i--) { | |
| const text = floatingTexts[i]; | |
| // Move up | |
| text.y -= 1; | |
| // Fade out | |
| text.alpha -= 1 / text.lifetime; | |
| // Remove when faded out | |
| if (text.alpha <= 0) { | |
| floatingTexts.splice(i, 1); | |
| } | |
| } | |
| } | |
| // Draw floating texts | |
| function drawFloatingTexts() { | |
| for (const text of floatingTexts) { | |
| ctx.save(); | |
| ctx.globalAlpha = text.alpha; | |
| ctx.font = '16px Arial'; | |
| ctx.fillStyle = text.color; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText(text.text, text.x, text.y); | |
| ctx.restore(); | |
| } | |
| } | |
| // Update UI displays | |
| function updateUI() { | |
| scoreDisplay.textContent = score; | |
| healthDisplay.textContent = health; | |
| levelDisplay.textContent = level; | |
| weaponDisplay.textContent = currentWeapon.charAt(0).toUpperCase() + currentWeapon.slice(1); | |
| bombDisplay.textContent = bombCount; | |
| } | |
| // Level complete | |
| function levelComplete() { | |
| gameRunning = false; | |
| levelCompleteScreen.style.display = 'flex'; | |
| // Animate progress bar | |
| let progress = 0; | |
| const interval = setInterval(() => { | |
| progress += 0.01; | |
| levelProgress.style.width = `${progress * 100}%`; | |
| if (progress >= 1) { | |
| clearInterval(interval); | |
| nextLevel(); | |
| } | |
| }, 10); | |
| } | |
| // Start next level | |
| function nextLevel() { | |
| level++; | |
| enemiesDefeated = 0; | |
| enemiesToDefeat = 10 + level * 2; | |
| bombCount = Math.min(bombCount + 1, 5); // Add one bomb per level, max 5 | |
| // Clear bullets and powerups | |
| bullets = []; | |
| enemyBullets = []; | |
| powerups = []; | |
| updateUI(); | |
| levelCompleteScreen.style.display = 'none'; | |
| gameRunning = true; | |
| requestAnimationFrame(gameLoop); | |
| } | |
| // Game over | |
| function gameOver() { | |
| gameRunning = false; | |
| finalScore.textContent = score; | |
| finalLevel.textContent = level; | |
| gameOverScreen.style.display = 'flex'; | |
| } | |
| // Event listeners | |
| startButton.addEventListener('click', initGame); | |
| restartButton.addEventListener('click', initGame); | |
| // Mouse movement | |
| document.addEventListener('mousemove', (e) => { | |
| mouseX = e.clientX; | |
| mouseY = e.clientY; | |
| // Update custom cursor position | |
| customCursor.style.left = `${e.clientX}px`; | |
| customCursor.style.top = `${e.clientY}px`; | |
| }); | |
| // Mouse click to shoot | |
| document.addEventListener('mousedown', (e) => { | |
| if (gameRunning) { | |
| if (e.button === 0) { // Left click | |
| isMouseDown = true; | |
| shoot(); | |
| lastShotTime = Date.now(); | |
| } else if (e.button === 2) { // Right click | |
| activateBomb(); | |
| } | |
| } | |
| }); | |
| // Mouse release to stop shooting | |
| document.addEventListener('mouseup', (e) => { | |
| if (e.button === 0) { // Left click | |
| isMouseDown = false; | |
| } | |
| }); | |
| // Prevent context menu on right click | |
| document.addEventListener('contextmenu', (e) => { | |
| e.preventDefault(); | |
| }); | |
| // Window resize | |
| window.addEventListener('resize', () => { | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| // Keep player on screen | |
| player.x = Math.max(player.width / 2, Math.min(canvas.width - player.width / 2, player.x)); | |
| player.y = Math.max(player.height / 2, Math.min(canvas.height - player.height / 2, player.y)); | |
| }); | |
| // Set canvas size on load | |
| window.addEventListener('load', () => { | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| }); | |
| </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 <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=adeism/rocket-game" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |