| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Subspace Continuum: Zone Warfare</title> |
| <style> |
| :root { |
| --primary: #00ffcc; |
| --danger: #ff3366; |
| --bg: #0a0a12; |
| --ui-bg: rgba(10, 10, 18, 0.85); |
| --font-main: 'Courier New', Courier, monospace; |
| } |
| |
| * { |
| box-sizing: border-box; |
| user-select: none; |
| } |
| |
| body { |
| margin: 0; |
| overflow: hidden; |
| background-color: var(--bg); |
| font-family: var(--font-main); |
| color: white; |
| height: 100vh; |
| width: 100vw; |
| } |
| |
| |
| #game-container { |
| position: relative; |
| width: 100%; |
| height: 100%; |
| } |
| |
| canvas { |
| display: block; |
| width: 100%; |
| height: 100%; |
| } |
| |
| |
| .overlay { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| display: flex; |
| flex-direction: column; |
| justify-content: center; |
| align-items: center; |
| background: rgba(0, 0, 0, 0.7); |
| backdrop-filter: blur(5px); |
| z-index: 10; |
| transition: opacity 0.3s; |
| } |
| |
| .hidden { |
| opacity: 0; |
| pointer-events: none; |
| z-index: -1; |
| } |
| |
| |
| h1 { |
| font-size: 3rem; |
| text-transform: uppercase; |
| letter-spacing: 5px; |
| text-shadow: 0 0 20px var(--primary); |
| margin-bottom: 10px; |
| text-align: center; |
| } |
| |
| .zone-grid { |
| display: grid; |
| grid-template-columns: repeat(2, 1fr); |
| gap: 20px; |
| margin-top: 30px; |
| } |
| |
| .zone-card { |
| background: rgba(255, 255, 255, 0.05); |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| padding: 20px; |
| width: 200px; |
| text-align: center; |
| cursor: pointer; |
| transition: all 0.2s; |
| border-radius: 8px; |
| } |
| |
| .zone-card:hover { |
| transform: translateY(-5px); |
| background: rgba(255, 255, 255, 0.1); |
| border-color: var(--primary); |
| box-shadow: 0 0 15px rgba(0, 255, 204, 0.3); |
| } |
| |
| .zone-title { |
| font-weight: bold; |
| font-size: 1.2rem; |
| margin-bottom: 5px; |
| display: block; |
| } |
| |
| .zone-desc { |
| font-size: 0.8rem; |
| color: #aaa; |
| } |
| |
| |
| #hud { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| padding: 20px; |
| display: flex; |
| justify-content: space-between; |
| pointer-events: none; |
| z-index: 5; |
| } |
| |
| .hud-panel { |
| background: var(--ui-bg); |
| padding: 10px 20px; |
| border-radius: 4px; |
| border-left: 3px solid var(--primary); |
| font-size: 1.2rem; |
| text-shadow: 1px 1px 0 #000; |
| } |
| |
| .hud-right { |
| text-align: right; |
| border-left: none; |
| border-right: 3px solid var(--primary); |
| } |
| |
| #zone-indicator { |
| position: absolute; |
| top: 80px; |
| left: 20px; |
| font-size: 0.9rem; |
| color: #888; |
| text-transform: uppercase; |
| letter-spacing: 2px; |
| } |
| |
| |
| #chat-box { |
| position: absolute; |
| bottom: 20px; |
| left: 20px; |
| width: 300px; |
| height: 150px; |
| overflow: hidden; |
| display: flex; |
| flex-direction: column; |
| justify-content: flex-end; |
| pointer-events: none; |
| mask-image: linear-gradient(to bottom, transparent, black 20%); |
| } |
| |
| .chat-msg { |
| background: rgba(0,0,0,0.5); |
| padding: 4px 8px; |
| margin-bottom: 2px; |
| font-size: 0.8rem; |
| border-radius: 2px; |
| } |
| |
| .chat-name { color: var(--primary); font-weight: bold; } |
| .chat-text { color: #eee; } |
| |
| |
| #controller-hint { |
| position: absolute; |
| bottom: 20px; |
| right: 20px; |
| font-size: 0.8rem; |
| color: #666; |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| } |
| |
| .icon { |
| width: 20px; |
| height: 20px; |
| background: #333; |
| border-radius: 50%; |
| display: inline-block; |
| } |
| |
| |
| .branding { |
| position: absolute; |
| top: 10px; |
| right: 10px; |
| z-index: 100; |
| } |
| .branding a { |
| color: rgba(255,255,255,0.5); |
| text-decoration: none; |
| font-size: 0.8rem; |
| transition: color 0.2s; |
| } |
| .branding a:hover { color: white; } |
| |
| |
| @media (max-width: 768px) { |
| .zone-grid { grid-template-columns: 1fr; } |
| h1 { font-size: 2rem; } |
| #hud { flex-direction: column; gap: 10px; } |
| .hud-right { border-right: none; border-bottom: 3px solid var(--primary); } |
| } |
| </style> |
| |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| </head> |
| <body> |
|
|
| <div id="game-container"> |
| <canvas id="gameCanvas"></canvas> |
|
|
| |
| <div class="branding"> |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">Built with anycoder</a> |
| </div> |
|
|
| |
| <div id="menu-overlay" class="overlay"> |
| <h1>Subspace Continuum</h1> |
| <p>Choose Your Zone</p> |
| |
| <div class="zone-grid"> |
| <div class="zone-card" onclick="startGame('green')"> |
| <span class="zone-title" style="color:#4caf50">Green Zone</span> |
| <span class="zone-desc">Standard Physics<br>Low Threat</span> |
| </div> |
| <div class="zone-card" onclick="startGame('ice')"> |
| <span class="zone-title" style="color:#00bcd4">Ice Fields</span> |
| <span class="zone-desc">Low Friction<br>Slippery Combat</span> |
| </div> |
| <div class="zone-card" onclick="startGame('war')"> |
| <span class="zone-title" style="color:#f44336">War Zone</span> |
| <span class="zone-desc">High Friction<br>Aggressive AI</span> |
| </div> |
| <div class="zone-card" onclick="startGame('chaos')"> |
| <span class="zone-title" style="color:#9c27b0">Chaos Sector</span> |
| <span class="zone-desc">Zero Gravity<br>Erratic Enemies</span> |
| </div> |
| </div> |
| |
| <p style="margin-top: 30px; font-size: 0.8rem; color: #888;"> |
| Controls: WASD/Arrows to Move • Space to Shoot<br> |
| Controller: Left Stick to Move • A/Cross to Shoot |
| </p> |
| </div> |
|
|
| |
| <div id="hud" class="hidden"> |
| <div class="hud-panel"> |
| <div>SCORE: <span id="score-display">0</span></div> |
| <div style="font-size: 0.8em; color: #aaa;">KILLS: <span id="kills-display">0</span></div> |
| </div> |
| <div id="zone-indicator">Current Zone: <span id="zone-name-display">GREEN</span></div> |
| <div class="hud-panel hud-right"> |
| <div style="color: var(--danger)">HP: <span id="hp-display">100</span>%</div> |
| <div>ENERGY: <span id="energy-display">100</span>%</div> |
| </div> |
| </div> |
|
|
| |
| <div id="chat-box" class="hidden"></div> |
|
|
| |
| <div id="controller-hint"> |
| <i class="fas fa-gamepad"></i> <span id="controller-status">No Controller Detected</span> |
| </div> |
| </div> |
|
|
| <script> |
| |
| |
| |
| |
| |
| |
| const CANVAS = document.getElementById('gameCanvas'); |
| const CTX = CANVAS.getContext('2d'); |
| const UI = { |
| menu: document.getElementById('menu-overlay'), |
| hud: document.getElementById('hud'), |
| chat: document.getElementById('chat-box'), |
| score: document.getElementById('score-display'), |
| kills: document.getElementById('kills-display'), |
| hp: document.getElementById('hp-display'), |
| energy: document.getElementById('energy-display'), |
| zoneName: document.getElementById('zone-name-display'), |
| ctrlStatus: document.getElementById('controller-status') |
| }; |
| |
| const ZONES = { |
| green: { name: 'GREEN', color: '#1a2f1a', grid: '#2d4a2d', friction: 0.92, botCount: 5, botAggro: 0.01 }, |
| ice: { name: 'ICE', color: '#0f1f2f', grid: '#1a3a5a', friction: 0.98, botCount: 8, botAggro: 0.02 }, |
| war: { name: 'WAR', color: '#2f0f0f', grid: '#5a1a1a', friction: 0.85, botCount: 15, botAggro: 0.05 }, |
| chaos: { name: 'CHAOS', color: '#1f0f2f', grid: '#3a1a5a', friction: 0.995, botCount: 10, botAggro: 0.03 } |
| }; |
| |
| let GAME_STATE = 'MENU'; |
| let CURRENT_ZONE = 'green'; |
| let LAST_TIME = 0; |
| let GAMEPAD_INDEX = null; |
| |
| |
| |
| class Entity { |
| constructor(x, y, radius, color) { |
| this.x = x; |
| this.y = y; |
| this.radius = radius; |
| this.color = color; |
| this.vx = 0; |
| this.vy = 0; |
| this.angle = 0; |
| this.dead = false; |
| } |
| |
| update(dt) { |
| |
| this.x += this.vx; |
| this.y += this.vy; |
| |
| |
| if (this.x < -this.radius) this.x = CANVAS.width + this.radius; |
| if (this.x > CANVAS.width + this.radius) this.x = -this.radius; |
| if (this.y < -this.radius) this.y = CANVAS.height + this.radius; |
| if (this.y > CANVAS.height + this.radius) this.y = -this.radius; |
| } |
| |
| draw(ctx) { |
| ctx.beginPath(); |
| ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); |
| ctx.fillStyle = this.color; |
| ctx.fill(); |
| } |
| } |
| |
| class Ship extends Entity { |
| constructor(x, y, isPlayer = false, name = "Bot") { |
| super(x, y, 12, isPlayer ? '#00ffcc' : '#ff4444'); |
| this.isPlayer = isPlayer; |
| this.name = name; |
| this.hp = 100; |
| this.maxHp = 100; |
| this.energy = 100; |
| this.score = 0; |
| this.kills = 0; |
| this.reloadTime = 0; |
| this.thrusting = false; |
| |
| |
| } |
| |
| thrust(force) { |
| this.thrusting = true; |
| this.vx += Math.cos(this.angle) * force; |
| this.vy += Math.sin(this.angle) * force; |
| } |
| |
| shoot() { |
| if (this.reloadTime <= 0 && this.energy >= 5) { |
| this.energy -= 5; |
| this.reloadTime = 15; |
| |
| |
| const bVx = Math.cos(this.angle) * 12 + this.vx * 0.5; |
| const bVy = Math.sin(this.angle) * 12 + this.vy * 0.5; |
| const bullet = new Bullet(this.x, this.y, this.angle, bVx, bVy, this.isPlayer); |
| bullets.push(bullet); |
| return true; |
| } |
| return false; |
| } |
| |
| update(dt) { |
| super.update(dt); |
| |
| |
| const zoneData = ZONES[CURRENT_ZONE]; |
| this.vx *= zoneData.friction; |
| this.vy *= zoneData.friction; |
| |
| |
| const speed = Math.sqrt(this.vx*this.vx + this.vy*this.vy); |
| const maxSpeed = 8; |
| if (speed > maxSpeed) { |
| const ratio = maxSpeed / speed; |
| this.vx *= ratio; |
| this.vy *= ratio; |
| } |
| |
| if (this.reloadTime > 0) this.reloadTime--; |
| if (this.energy < 100) this.energy += 0.2; |
| |
| |
| if (this.hp <= 0) { |
| this.dead = true; |
| createExplosion(this.x, this.y, this.color); |
| if (!this.isPlayer) { |
| |
| player.kills++; |
| player.score += 100; |
| spawnPrize(this.x, this.y); |
| addChatMessage("System", `${this.name} was destroyed.`); |
| } else { |
| |
| addChatMessage("System", "You were destroyed! Respawn in 3s..."); |
| setTimeout(() => respawnPlayer(), 3000); |
| } |
| } |
| } |
| |
| draw(ctx) { |
| ctx.save(); |
| ctx.translate(this.x, this.y); |
| ctx.rotate(this.angle); |
| |
| |
| ctx.beginPath(); |
| ctx.moveTo(15, 0); |
| ctx.lineTo(-10, 10); |
| ctx.lineTo(-5, 0); |
| ctx.lineTo(-10, -10); |
| ctx.closePath(); |
| |
| ctx.fillStyle = this.color; |
| ctx.fill(); |
| |
| |
| if (this.thrusting) { |
| ctx.beginPath(); |
| ctx.moveTo(-8, 0); |
| ctx.lineTo(-18, 0); |
| ctx.strokeStyle = '#ffaa00'; |
| ctx.lineWidth = 4; |
| ctx.stroke(); |
| this.thrusting = false; |
| } |
| |
| |
| if (!this.isPlayer) { |
| ctx.rotate(-this.angle); |
| ctx.fillStyle = 'white'; |
| ctx.font = '10px Arial'; |
| ctx.textAlign = 'center'; |
| ctx.fillText(this.name, 0, -20); |
| } |
| |
| ctx.restore(); |
| } |
| } |
| |
| class Bullet extends Entity { |
| constructor(x, y, angle, vx, vy, isPlayerBullet) { |
| super(x, y, 3, isPlayerBullet ? '#ffff00' : '#ff8800'); |
| this.vx = vx; |
| this.vy = vy; |
| this.isPlayerBullet = isPlayerBullet; |
| this.life = 60; |
| } |
| |
| update(dt) { |
| super.update(dt); |
| this.life--; |
| if (this.life <= 0) this.dead = true; |
| } |
| |
| draw(ctx) { |
| ctx.beginPath(); |
| ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); |
| ctx.fillStyle = this.color; |
| ctx.fill(); |
| |
| ctx.shadowBlur = 5; |
| ctx.shadowColor = this.color; |
| ctx.fill(); |
| ctx.shadowBlur = 0; |
| } |
| } |
| |
| class Prize extends Entity { |
| constructor(x, y, type) { |
| super(x, y, 6, type === 'green' ? '#00ff00' : '#ffd700'); |
| this.type = type; |
| this.bobOffset = Math.random() * Math.PI * 2; |
| } |
| |
| draw(ctx) { |
| const bob = Math.sin(Date.now() / 200 + this.bobOffset) * 2; |
| ctx.beginPath(); |
| ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); |
| ctx.fillStyle = this.color; |
| ctx.fill(); |
| |
| ctx.fillStyle = 'white'; |
| ctx.font = '10px Arial'; |
| ctx.textAlign = 'center'; |
| ctx.textBaseline = 'middle'; |
| ctx.fillText(this.type === 'green' ? '+' : '⚡', this.x, this.y + bob); |
| } |
| } |
| |
| class Particle extends Entity { |
| constructor(x, y, color) { |
| super(x, y, Math.random() * 3 + 1, color); |
| const angle = Math.random() * Math.PI * 2; |
| const speed = Math.random() * 3; |
| this.vx = Math.cos(angle) * speed; |
| this.vy = Math.sin(angle) * speed; |
| this.life = 30; |
| } |
| update(dt) { |
| super.update(dt); |
| this.life--; |
| if(this.life <= 0) this.dead = true; |
| } |
| draw(ctx) { |
| ctx.globalAlpha = this.life / 30; |
| super.draw(ctx); |
| ctx.globalAlpha = 1; |
| } |
| } |
| |
| |
| let player; |
| let bots = []; |
| let bullets = []; |
| let prizes = []; |
| let particles = []; |
| let chatMessages = []; |
| |
| |
| const keys = {}; |
| |
| window.addEventListener('keydown', e => keys[e.code] = true); |
| window.addEventListener('keyup', e => keys[e.code] = false); |
| |
| |
| function handleGamepad() { |
| const gamepads = navigator.getGamepads(); |
| if (!gamepads[0]) { |
| UI.ctrlStatus.innerText = "No Controller"; |
| UI.ctrlStatus.style.color = "#666"; |
| GAMEPAD_INDEX = null; |
| return; |
| } |
| |
| const gp = gamepads[0]; |
| if (gp) { |
| UI.ctrlStatus.innerText = "Controller Active"; |
| UI.ctrlStatus.style.color = "#00ffcc"; |
| GAMEPAD_INDEX = 0; |
| |
| |
| const deadzone = 0.1; |
| let lx = gp.axes[0]; |
| let ly = gp.axes[1]; |
| |
| if (Math.abs(lx) < deadzone) lx = 0; |
| if (Math.abs(ly) < deadzone) ly = 0; |
| |
| |
| if (lx !== 0 || ly !== 0) { |
| player.angle = Math.atan2(ly, lx); |
| |
| const mag = Math.sqrt(lx*lx + ly*ly); |
| if (mag > 0.2) player.thrust(0.3); |
| } |
| |
| |
| if (gp.buttons[0].pressed) { |
| player.shoot(); |
| } |
| } |
| } |
| |
| |
| |
| function initGame(zoneKey) { |
| CURRENT_ZONE = zoneKey; |
| const zoneData = ZONES[zoneKey]; |
| |
| |
| bots = []; |
| bullets = []; |
| prizes = []; |
| particles = []; |
| |
| |
| player = new Ship(CANVAS.width/2, CANVAS.height/2, true, "You"); |
| |
| |
| for(let i=0; i<zoneData.botCount; i++) { |
| spawnBot(); |
| } |
| |
| |
| UI.zoneName.innerText = zoneData.name; |
| UI.menu.classList.add('hidden'); |
| UI.hud.classList.remove('hidden'); |
| UI.chat.classList.remove('hidden'); |
| |
| GAME_STATE = 'PLAYING'; |
| requestAnimationFrame(gameLoop); |
| |
| |
| setInterval(simulateChat, 4000); |
| } |
| |
| function spawnBot() { |
| const names = ["Viper", "Wraith", "Nova", "Echo", "Razor", "Spectre", "Jinx", "Blade"]; |
| const name = names[Math.floor(Math.random() * names.length)] + Math.floor(Math.random()*99); |
| const x = Math.random() * CANVAS.width; |
| const y = Math.random() * CANVAS.height; |
| bots.push(new Ship(x, y, false, name)); |
| } |
| |
| function spawnPrize(x, y) { |
| const type = Math.random() > 0.5 ? 'green' : 'gold'; |
| prizes.push(new Prize(x, y, type)); |
| } |
| |
| function createExplosion(x, y, color) { |
| for(let i=0; i<15; i++) { |
| particles.push(new Particle(x, y, color)); |
| } |
| } |
| |
| function respawnPlayer() { |
| player.x = CANVAS.width/2; |
| player.y = CANVAS.height/2; |
| player.vx = 0; |
| player.vy = 0; |
| player.hp = 100; |
| player.energy = 100; |
| player.dead = false; |
| } |
| |
| function checkCollisions() { |
| |
| bullets.forEach(b => { |
| if (b.dead) return; |
| |
| |
| if (b.isPlayerBullet) { |
| bots.forEach(bot => { |
| if (!bot.dead && dist(b, bot) < bot.radius + b.radius) { |
| b.dead = true; |
| bot.hp -= 10; |
| createExplosion(b.x, b.y, '#fff'); |
| } |
| }); |
| } else { |
| |
| if (!player.dead && dist(b, player) < player.radius + b.radius) { |
| b.dead = true; |
| player.hp -= 5; |
| createExplosion(b.x, b.y, '#f00'); |
| } |
| } |
| }); |
| |
| |
| prizes.forEach((p, index) => { |
| if (dist(player, p) < player.radius + p.radius) { |
| prizes.splice(index, 1); |
| if (p.type === 'green') player.hp = Math.min(player.hp + 20, 100); |
| if (p.type === 'gold') player.energy = 100; |
| } |
| }); |
| } |
| |
| function dist(e1, e2) { |
| return Math.sqrt((e1.x - e2.x)**2 + (e1.y - e2.y)**2); |
| } |
| |
| |
| function updateBots() { |
| bots.forEach(bot => { |
| if (bot.dead) return; |
| |
| const dToPlayer = dist(bot, player); |
| |
| |
| if (dToPlayer < 300 && !player.dead) { |
| |
| bot.angle = Math.atan2(player.y - bot.y, player.x - bot.x); |
| bot.thrust(0.2); |
| |
| |
| const angleDiff = Math.abs(bot.angle - Math.atan2(player.y - bot.y, player.x - bot.x)); |
| if (angleDiff < 0.5) bot.shoot(); |
| } else { |
| |
| if (Math.random() < 0.02) bot.angle += (Math.random() - 0.5); |
| bot.thrust(0.1); |
| |
| |
| if (Math.random() < ZONES[CURRENT_ZONE].botAggro) bot.shoot(); |
| } |
| |
| |
| bot.vx *= ZONES[CURRENT_ZONE].friction; |
| bot.vy *= ZONES[CURRENT_ZONE].friction; |
| bot.x += bot.vx; |
| bot.y += bot.vy; |
| |
| |
| if (bot.x < 0) bot.x = CANVAS.width; |
| if (bot.x > CANVAS.width) bot.x = 0; |
| if (bot.y < 0) bot.y = CANVAS.height; |
| if (bot.y > CANVAS.height) bot.y = 0; |
| |
| if (bot.hp <= 0) bot.dead = true; |
| }); |
| |
| |
| bots = bots.filter(b => !b.dead); |
| if (bots.length < ZONES[CURRENT_ZONE].botCount) { |
| if (Math.random() < 0.01) spawnBot(); |
| } |
| } |
| |
| |
| const CHAT_PHRASES = [ |
| "Lag?", "Nice shot!", "Anyone want to team?", "Where are the prizes?", |
| "Campers...", "GG", "brb", "Is this zone dead?", "Warbird incoming!" |
| ]; |
| const BOT_NAMES = ["Viper", "Wraith", "Nova", "Echo", "Razor"]; |
| |
| function simulateChat() { |
| if (GAME_STATE !== 'PLAYING') return; |
| if (Math.random() > 0.3) return; |
| |
| const name = BOT_NAMES[Math.floor(Math.random() * BOT_NAMES.length)]; |
| const text = CHAT_PHRASES[Math.floor(Math.random() * CHAT_PHRASES.length)]; |
| addChatMessage(name, text); |
| } |
| |
| function addChatMessage(name, text) { |
| const div = document.createElement('div'); |
| div.className = 'chat-msg'; |
| div.innerHTML = `<span class="chat-name">${name}:</span> <span class="chat-text">${text}</span>`; |
| UI.chat.appendChild(div); |
| |
| |
| if (UI.chat.children.length > 6) { |
| UI.chat.removeChild(UI.chat.firstChild); |
| } |
| } |
| |
| |
| |
| function gameLoop(timestamp) { |
| if (GAME_STATE !== 'PLAYING') return; |
| |
| const dt = timestamp - LAST_TIME; |
| LAST_TIME = timestamp; |
| |
| |
| CTX.fillStyle = ZONES[CURRENT_ZONE].color; |
| CTX.fillRect(0, 0, CANVAS.width, CANVAS.height); |
| |
| |
| CTX.strokeStyle = ZONES[CURRENT_ZONE].grid; |
| CTX.lineWidth = 1; |
| CTX.beginPath(); |
| for (let x = 0; x < CANVAS.width; x += 50) { |
| CTX.moveTo(x, 0); |
| CTX.lineTo(x, CANVAS.height); |
| } |
| for (let y = 0; y < CANVAS.height; y += 50) { |
| CTX.moveTo(0, y); |
| CTX.lineTo(CANVAS.width, y); |
| } |
| CTX.stroke(); |
| |
| |
| if (!GAMEPAD_INDEX) { |
| if (keys['ArrowUp'] || keys['KeyW']) { player.thrust(0.3); } |
| if (keys['ArrowDown'] || keys['KeyS']) { player.thrust(-0.1); } |
| if (keys['ArrowLeft'] || keys['KeyA']) { player.angle -= 0.08; } |
| if (keys['ArrowRight'] || keys['KeyD']) { player.angle += 0.08; } |
| if (keys['Space']) { player.shoot(); } |
| } else { |
| handleGamepad(); |
| } |
| |
| |
| if (!player.dead) player.update(dt); |
| updateBots(); |
| |
| bullets.forEach(b => b.update(dt)); |
| bullets = bullets.filter(b => !b.dead); |
| |
| prizes.forEach(p => p.draw(CTX)); |
| |
| particles.forEach(p => p.update(dt)); |
| particles = particles.filter(p => !p.dead); |
| particles.forEach(p => p.draw(CTX)); |
| |
| checkCollisions(); |
| |
| |
| prizes.forEach(p => p.draw(CTX)); |
| bots.forEach(b => b.draw(CTX)); |
| bullets.forEach(b => b.draw(CTX)); |
| if (!player.dead) player.draw(CTX); |
| |
| |
| UI.score.innerText = player.score; |
| UI.kills.innerText = player.kills; |
| UI.hp.innerText = Math.floor(player.hp); |
| UI.energy.innerText = Math.floor(player.energy); |
| |
| |
| UI.hp.parentElement.style.color = player.hp < 30 ? 'red' : 'var(--danger)'; |
| |
| requestAnimationFrame(gameLoop); |
| } |
| |
| |
| function resize() { |
| CANVAS.width = window.innerWidth; |
| CANVAS.height = window.innerHeight; |
| } |
| window.addEventListener('resize', resize); |
| resize(); |
| |
| |
| window.startGame = initGame; |
| |
| </script> |
| </body> |
| </html> |