TankGameSVG / index.html
awacke1's picture
Update index.html
d9c436b verified
<!DOCTYPE html>
<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>