Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Tank Game with Random Spawns</title> | |
<style> | |
body { margin: 0; overflow: hidden; font-family: Arial, sans-serif; } | |
#gameArea { width: 100vw; height: 100vh; background-color: #f0f0f0; } | |
#score { position: absolute; top: 10px; left: 10px; font-size: 24px; font-weight: bold; } | |
#level { position: absolute; top: 10px; right: 10px; font-size: 24px; font-weight: bold; } | |
#nextLevel { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 48px; font-weight: bold; display: none; } | |
</style> | |
</head> | |
<body> | |
<div id="score">Score: 0</div> | |
<div id="level">Level: 1</div> | |
<div id="nextLevel">Next Level</div> | |
<svg id="gameArea"></svg> | |
<script> | |
const svg = document.getElementById('gameArea'); | |
const scoreElement = document.getElementById('score'); | |
const levelElement = document.getElementById('level'); | |
const nextLevelElement = document.getElementById('nextLevel'); | |
const svgNS = "http://www.w3.org/2000/svg"; | |
let GAME_WIDTH = window.innerWidth; | |
let GAME_HEIGHT = window.innerHeight; | |
const TANK_SIZE = 30; | |
const BULLET_SIZE = 5; | |
const TANK_SPEED = 2; | |
const AI_TANK_SPEED = 1.5; | |
const BULLET_SPEED = 5; | |
const MAX_HIT_POINTS = 20; | |
const MAX_TANKS = 5; | |
const MAX_TANKS_ON_SCREEN = 50; | |
const SPAWN_DELAY = 5000; // 5 seconds in milliseconds | |
const SHOCKWAVE_RADIUS = 150; | |
const SHOCKWAVE_DURATION = 1000; // 1 second in milliseconds | |
let score = 0; | |
let level = 1; | |
let spawnQueue = []; | |
let lastSpawnTime = 0; | |
let shockwaveActive = false; | |
let shockwaveStartTime = 0; | |
class Tank { | |
constructor(color, isPlayer = false) { | |
this.color = color; | |
this.isPlayer = isPlayer; | |
this.hitPoints = MAX_HIT_POINTS; | |
this.element = document.createElementNS(svgNS, "g"); | |
this.tankBody = document.createElementNS(svgNS, "polygon"); | |
this.shield = document.createElementNS(svgNS, "circle"); | |
this.hitPointsText = document.createElementNS(svgNS, "text"); | |
this.tankBody.setAttribute("fill", color); | |
this.shield.setAttribute("fill", "none"); | |
this.shield.setAttribute("stroke", color); | |
this.shield.setAttribute("stroke-width", this.hitPoints); | |
this.shield.setAttribute("r", TANK_SIZE + 10); | |
this.hitPointsText.setAttribute("text-anchor", "middle"); | |
this.hitPointsText.setAttribute("fill", "black"); | |
this.hitPointsText.setAttribute("font-size", "12px"); | |
this.element.appendChild(this.shield); | |
this.element.appendChild(this.tankBody); | |
this.element.appendChild(this.hitPointsText); | |
svg.appendChild(this.element); | |
this.velocity = { x: 0, y: 0 }; | |
this.isAlive = true; | |
this.respawn(); | |
} | |
respawn() { | |
this.x = Math.random() * (GAME_WIDTH - 2 * TANK_SIZE) + TANK_SIZE; | |
this.y = Math.random() * (GAME_HEIGHT - 2 * TANK_SIZE) + TANK_SIZE; | |
this.angle = Math.random() * 2 * Math.PI; | |
} | |
update(playerX, playerY) { | |
if (!this.isAlive) return; | |
if (!this.isPlayer) { | |
const dx = playerX - this.x; | |
const dy = playerY - this.y; | |
this.angle = Math.atan2(dy, dx); | |
this.x += Math.cos(this.angle) * AI_TANK_SPEED; | |
this.y += Math.sin(this.angle) * AI_TANK_SPEED; | |
} else { | |
this.x += this.velocity.x; | |
this.y += this.velocity.y; | |
} | |
// Bounce off walls | |
if (this.x < TANK_SIZE || this.x > GAME_WIDTH - TANK_SIZE) { | |
this.velocity.x *= -1; | |
this.x = Math.max(TANK_SIZE, Math.min(GAME_WIDTH - TANK_SIZE, this.x)); | |
} | |
if (this.y < TANK_SIZE || this.y > GAME_HEIGHT - TANK_SIZE) { | |
this.velocity.y *= -1; | |
this.y = Math.max(TANK_SIZE, Math.min(GAME_HEIGHT - TANK_SIZE, this.y)); | |
} | |
const points = [ | |
[TANK_SIZE, 0], | |
[-TANK_SIZE / 2, -TANK_SIZE / 2], | |
[-TANK_SIZE / 2, TANK_SIZE / 2] | |
].map(([x, y]) => { | |
const rotatedX = x * Math.cos(this.angle) - y * Math.sin(this.angle); | |
const rotatedY = x * Math.sin(this.angle) + y * Math.cos(this.angle); | |
return `${rotatedX},${rotatedY}`; | |
}).join(" "); | |
this.tankBody.setAttribute("points", points); | |
this.shield.setAttribute("cx", 0); | |
this.shield.setAttribute("cy", 0); | |
this.shield.setAttribute("stroke-width", this.hitPoints); | |
this.hitPointsText.textContent = this.hitPoints; | |
this.hitPointsText.setAttribute("x", 0); | |
this.hitPointsText.setAttribute("y", -TANK_SIZE - 5); | |
this.element.setAttribute("transform", `translate(${this.x},${this.y}) rotate(${this.angle * 180 / Math.PI})`); | |
} | |
shoot() { | |
return new Bullet(this.x + Math.cos(this.angle) * TANK_SIZE, | |
this.y + Math.sin(this.angle) * TANK_SIZE, | |
this.angle, | |
this.color, | |
this.isPlayer); | |
} | |
takeDamage(damage) { | |
this.hitPoints -= damage; | |
if (this.hitPoints <= 0) { | |
this.explode(); | |
} | |
} | |
explode() { | |
if (!this.isAlive) return; | |
this.isAlive = false; | |
this.element.remove(); | |
updateScore(1); | |
} | |
} | |
class Bullet { | |
constructor(x, y, angle, color, isPlayerBullet) { | |
this.x = x; | |
this.y = y; | |
this.angle = angle; | |
this.color = color; | |
this.isPlayerBullet = isPlayerBullet; | |
this.element = document.createElementNS(svgNS, "circle"); | |
this.element.setAttribute("r", BULLET_SIZE); | |
this.element.setAttribute("fill", color); | |
svg.appendChild(this.element); | |
} | |
update() { | |
this.x += Math.cos(this.angle) * BULLET_SPEED; | |
this.y += Math.sin(this.angle) * BULLET_SPEED; | |
this.element.setAttribute("cx", this.x); | |
this.element.setAttribute("cy", this.y); | |
return this.x > 0 && this.x < GAME_WIDTH && this.y > 0 && this.y < GAME_HEIGHT; | |
} | |
remove() { | |
this.element.remove(); | |
} | |
} | |
const player = new Tank("blue", true); | |
let aiTanks = []; | |
function queueEnemySpawn() { | |
const tanksToSpawn = MAX_TANKS - aiTanks.length; | |
for (let i = 0; i < tanksToSpawn; i++) { | |
spawnQueue.push({ | |
color: `hsl(${Math.random() * 360}, 100%, 50%)` | |
}); | |
} | |
} | |
function spawnEnemies() { | |
const currentTime = Date.now(); | |
if (currentTime - lastSpawnTime > SPAWN_DELAY && spawnQueue.length > 0 && aiTanks.length < MAX_TANKS_ON_SCREEN) { | |
const spawnData = spawnQueue.shift(); | |
let newTank = new Tank(spawnData.color); | |
// Ensure the new tank doesn't overlap with existing tanks | |
while (aiTanks.some(tank => distance(newTank, tank) < 3 * TANK_SIZE)) { | |
newTank.respawn(); | |
} | |
aiTanks.push(newTank); | |
lastSpawnTime = currentTime; | |
} | |
} | |
let bullets = []; | |
let keys = {}; | |
document.addEventListener('keydown', (event) => { | |
keys[event.key.toLowerCase()] = true; | |
if (event.code === 'Space' && !shockwaveActive) { | |
activateShockwave(); | |
} | |
}); | |
document.addEventListener('keyup', (event) => { | |
keys[event.key.toLowerCase()] = false; | |
}); | |
svg.addEventListener('mousemove', (event) => { | |
const rect = svg.getBoundingClientRect(); | |
const mouseX = event.clientX - rect.left; | |
const mouseY = event.clientY - rect.top; | |
player.angle = Math.atan2(mouseY - player.y, mouseX - player.x); | |
}); | |
svg.addEventListener('click', () => { | |
bullets.push(player.shoot()); | |
}); | |
function updatePlayerPosition() { | |
let dx = 0, dy = 0; | |
if (keys['w']) dy -= TANK_SPEED; | |
if (keys['s']) dy += TANK_SPEED; | |
if (keys['a']) dx -= TANK_SPEED; | |
if (keys['d']) dx += TANK_SPEED; | |
// Normalize diagonal movement | |
if (dx !== 0 && dy !== 0) { | |
dx /= Math.sqrt(2); | |
dy /= Math.sqrt(2); | |
} | |
player.velocity = { x: dx, y: dy }; | |
} | |
function checkCollisions() { | |
bullets.forEach((bullet, bulletIndex) => { | |
if (bullet.isPlayerBullet) { | |
aiTanks.forEach((tank, tankIndex) => { | |
if (tank.isAlive && distance(bullet, tank) < TANK_SIZE) { | |
bullet.remove(); | |
bullets.splice(bulletIndex, 1); | |
tank.takeDamage(1); | |
} | |
}); | |
} | |
}); | |
} | |
function distance(obj1, obj2) { | |
return Math.sqrt((obj1.x - obj2.x)**2 + (obj1.y - obj2.y)**2); | |
} | |
function updateScore(points) { | |
score += points; | |
scoreElement.textContent = `Score: ${score}`; | |
} | |
function updateLevel() { | |
level++; | |
levelElement.textContent = `Level: ${level}`; | |
nextLevelElement.style.display = 'block'; | |
setTimeout(() => { | |
nextLevelElement.style.display = 'none'; | |
queueEnemySpawn(); | |
}, 2000); | |
} | |
function activateShockwave() { | |
shockwaveActive = true; | |
shockwaveStartTime = Date.now(); | |
const shockwave = document.createElementNS(svgNS, "circle"); | |
shockwave.setAttribute("cx", player.x); | |
shockwave.setAttribute("cy", player.y); | |
shockwave.setAttribute("r", 0); | |
shockwave.setAttribute("fill", "none"); | |
shockwave.setAttribute("stroke", "rgba(0, 255, 255, 0.5)"); | |
shockwave.setAttribute("stroke-width", "5"); | |
svg.appendChild(shockwave); | |
const expandShockwave = setInterval(() => { | |
const currentRadius = parseFloat(shockwave.getAttribute("r")); | |
if (currentRadius < SHOCKWAVE_RADIUS) { | |
shockwave.setAttribute("r", currentRadius + 5); | |
} else { | |
clearInterval(expandShockwave); | |
setTimeout(() => { | |
shockwave.remove(); | |
}, 200); | |
} | |
}, 20); | |
// Check for enemy tanks within shockwave radius | |
aiTanks.forEach(tank => { | |
if (distance(player, tank) <= SHOCKWAVE_RADIUS) { | |
tank.explode(); | |
updateScore(10); | |
} | |
}); | |
} | |
function handleResize() { | |
GAME_WIDTH = window.innerWidth; | |
GAME_HEIGHT = window.innerHeight; | |
svg.setAttribute("width", GAME_WIDTH); | |
svg.setAttribute("height", GAME_HEIGHT); | |
// Reposition all tanks | |
[player, ...aiTanks].forEach(tank => { | |
tank.x = Math.min(Math.max(tank.x, TANK_SIZE), GAME_WIDTH - TANK_SIZE); | |
tank.y = Math.min(Math.max(tank.y, TANK_SIZE), GAME_HEIGHT - TANK_SIZE); | |
}); | |
} | |
window.addEventListener('resize', handleResize); | |
function gameLoop() { | |
updatePlayerPosition(); | |
player.update(); | |
aiTanks = aiTanks.filter(tank => tank.isAlive); | |
aiTanks.forEach(tank => { | |
tank.update(player.x, player.y); | |
if (Math.random() < 0.02) { | |
bullets.push(tank.shoot()); | |
} | |
}); | |
bullets = bullets.filter(bullet => bullet.update()); | |
checkCollisions(); | |
spawnEnemies(); | |
if (shockwaveActive && Date.now() - shockwaveStartTime > SHOCKWAVE_DURATION) { | |
shockwaveActive = false; | |
} | |
if (aiTanks.length === 0 && spawnQueue.length === 0) { | |
updateLevel(); | |
} | |
requestAnimationFrame(gameLoop); | |
} | |
// Set initial SVG size | |
svg.setAttribute("width", GAME_WIDTH); | |
svg.setAttribute("height", GAME_HEIGHT); | |
queueEnemySpawn(); // Initial spawn queue | |
gameLoop(); | |
</script> | |
</body> | |
</html> |