Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Tower Defense</title> | |
<style> | |
* { | |
margin: 0; | |
padding: 0; | |
box-sizing: border-box; | |
} | |
body { | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
min-height: 100vh; | |
background: #1a1a1a; | |
font-family: Arial, sans-serif; | |
color: white; | |
} | |
#gameContainer { | |
position: relative; | |
width: 800px; | |
height: 600px; | |
background: #2a2a2a; | |
border-radius: 10px; | |
overflow: hidden; | |
} | |
#gameCanvas { | |
position: absolute; | |
left: 0; | |
top: 0; | |
} | |
.ui-panel { | |
position: absolute; | |
right: 0; | |
top: 0; | |
width: 200px; | |
height: 100%; | |
background: rgba(0, 0, 0, 0.8); | |
padding: 10px; | |
} | |
.tower-btn { | |
display: block; | |
width: 100%; | |
padding: 10px; | |
margin: 5px 0; | |
background: #4a4a4a; | |
border: none; | |
border-radius: 5px; | |
color: white; | |
cursor: pointer; | |
transition: background 0.3s; | |
} | |
.tower-btn:hover { | |
background: #5a5a5a; | |
} | |
#stats { | |
position: absolute; | |
left: 10px; | |
top: 10px; | |
font-size: 16px; | |
text-shadow: 2px 2px 2px rgba(0,0,0,0.5); | |
z-index: 1; | |
} | |
#waveMessage { | |
position: absolute; | |
left: 50%; | |
top: 50%; | |
transform: translate(-50%, -50%); | |
font-size: 24px; | |
color: white; | |
text-shadow: 2px 2px 4px rgba(0,0,0,0.7); | |
display: none; | |
z-index: 2; | |
} | |
@keyframes fadeInOut { | |
0% { opacity: 0; transform: translate(-50%, -50%) scale(0.5); } | |
10% { opacity: 1; transform: translate(-50%, -50%) scale(1.1); } | |
20% { transform: translate(-50%, -50%) scale(1); } | |
80% { opacity: 1; } | |
100% { opacity: 0; } | |
} | |
</style> | |
</head> | |
<body> | |
<div id="gameContainer"> | |
<canvas id="gameCanvas"></canvas> | |
<div id="stats"> | |
Lives: <span id="lives">20</span> | |
Gold: <span id="gold">200</span> | |
Wave: <span id="wave">1</span> | |
</div> | |
<div id="waveMessage"></div> | |
<div class="ui-panel"> | |
<button class="tower-btn" data-type="basic">Basic Tower ($100)</button> | |
<button class="tower-btn" data-type="sniper">Sniper Tower ($200)</button> | |
<button class="tower-btn" data-type="splash">Splash Tower ($300)</button> | |
</div> | |
</div> | |
<script> | |
const canvas = document.getElementById('gameCanvas'); | |
const ctx = canvas.getContext('2d'); | |
const waveMessage = document.getElementById('waveMessage'); | |
canvas.width = 600; | |
canvas.height = 600; | |
let gameState = { | |
lives: 20, | |
gold: 200, | |
wave: 0, | |
towers: [], | |
enemies: [], | |
path: [], | |
projectiles: [], | |
selectedTower: null, | |
waveInProgress: false, | |
enemiesSpawned: 0, | |
totalEnemiesInWave: 10, | |
spawnInterval: null, | |
timeBetweenWaves: 5000 | |
}; | |
const towerTypes = { | |
basic: { | |
cost: 100, | |
range: 120, | |
damage: 30, | |
fireRate: 2, | |
color: '#4a90e2', | |
projectileColor: '#4a90e2', | |
projectileSize: 5 | |
}, | |
sniper: { | |
cost: 200, | |
range: 250, | |
damage: 100, | |
fireRate: 1, | |
color: '#e24a4a', | |
projectileColor: '#e24a4a', | |
projectileSize: 7 | |
}, | |
splash: { | |
cost: 300, | |
range: 100, | |
damage: 45, | |
fireRate: 3, | |
color: '#4ae24a', | |
projectileColor: '#4ae24a', | |
projectileSize: 6 | |
} | |
}; | |
function generatePath() { | |
const path = []; | |
let x = 0; | |
let y = Math.random() * (canvas.height - 200) + 100; | |
let direction = 1; | |
while (x < canvas.width - 50) { | |
path.push({ x, y }); | |
x += 20; | |
y += Math.random() * 40 * direction; | |
direction = -direction; | |
y = Math.max(50, Math.min(canvas.height - 50, y)); | |
} | |
return path; | |
} | |
function showWaveMessage(message, duration = 3000) { | |
waveMessage.textContent = message; | |
waveMessage.style.display = 'block'; | |
waveMessage.style.animation = 'fadeInOut 3s ease-in-out'; | |
setTimeout(() => { | |
waveMessage.style.display = 'none'; | |
}, duration); | |
} | |
class Enemy { | |
constructor() { | |
this.pathIndex = 0; | |
this.x = gameState.path[0].x; | |
this.y = gameState.path[0].y; | |
this.health = 100; | |
this.maxHealth = 100; | |
this.speed = 1; | |
this.value = 20; | |
} | |
update() { | |
if (this.pathIndex < gameState.path.length - 1) { | |
const target = gameState.path[this.pathIndex + 1]; | |
const dx = target.x - this.x; | |
const dy = target.y - this.y; | |
const distance = Math.sqrt(dx * dx + dy * dy); | |
if (distance < this.speed) { | |
this.pathIndex++; | |
} else { | |
this.x += (dx / distance) * this.speed; | |
this.y += (dy / distance) * this.speed; | |
} | |
} else { | |
gameState.lives--; | |
return true; | |
} | |
if (this.health <= 0) { | |
gameState.gold += this.value; | |
return true; | |
} | |
return false; | |
} | |
draw() { | |
ctx.fillStyle = '#ff0000'; | |
ctx.beginPath(); | |
ctx.arc(this.x, this.y, 10, 0, Math.PI * 2); | |
ctx.fill(); | |
ctx.fillStyle = '#000000'; | |
ctx.fillRect(this.x - 15, this.y - 20, 30, 5); | |
ctx.fillStyle = '#00ff00'; | |
ctx.fillRect(this.x - 15, this.y - 20, (this.health / this.maxHealth) * 30, 5); | |
} | |
} | |
class Projectile { | |
constructor(x, y, target, damage, color, size, speed = 8) { | |
this.x = x; | |
this.y = y; | |
this.target = target; | |
this.damage = damage; | |
this.color = color; | |
this.size = size; | |
this.speed = speed; | |
} | |
update() { | |
if (!this.target) return true; | |
const dx = this.target.x - this.x; | |
const dy = this.target.y - this.y; | |
const distance = Math.sqrt(dx * dx + dy * dy); | |
if (distance < this.speed) { | |
this.target.health -= this.damage; | |
return true; | |
} | |
this.x += (dx / distance) * this.speed; | |
this.y += (dy / distance) * this.speed; | |
return false; | |
} | |
draw() { | |
ctx.fillStyle = this.color; | |
ctx.beginPath(); | |
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); | |
ctx.fill(); | |
} | |
} | |
class Tower { | |
constructor(x, y, type) { | |
this.x = x; | |
this.y = y; | |
this.type = type; | |
this.lastShot = 0; | |
this.level = 1; | |
Object.assign(this, towerTypes[type]); | |
} | |
update(time) { | |
if (time - this.lastShot > 1000 / this.fireRate) { | |
for (let enemy of gameState.enemies) { | |
const dx = enemy.x - this.x; | |
const dy = enemy.y - this.y; | |
const distance = Math.sqrt(dx * dx + dy * dy); | |
if (distance <= this.range) { | |
gameState.projectiles.push( | |
new Projectile( | |
this.x, | |
this.y, | |
enemy, | |
this.damage * this.level, | |
this.projectileColor, | |
this.projectileSize | |
) | |
); | |
this.lastShot = time; | |
break; | |
} | |
} | |
} | |
} | |
draw() { | |
ctx.fillStyle = this.color; | |
ctx.beginPath(); | |
ctx.arc(this.x, this.y, 20, 0, Math.PI * 2); | |
ctx.fill(); | |
if (this === gameState.selectedTower) { | |
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)'; | |
ctx.beginPath(); | |
ctx.arc(this.x, this.y, this.range, 0, Math.PI * 2); | |
ctx.stroke(); | |
} | |
} | |
} | |
function drawPath() { | |
ctx.strokeStyle = '#666666'; | |
ctx.lineWidth = 30; | |
ctx.lineCap = 'round'; | |
ctx.lineJoin = 'round'; | |
ctx.beginPath(); | |
ctx.moveTo(gameState.path[0].x, gameState.path[0].y); | |
for (let i = 1; i < gameState.path.length; i++) { | |
ctx.lineTo(gameState.path[i].x, gameState.path[i].y); | |
} | |
ctx.stroke(); | |
} | |
function startNewWave() { | |
gameState.wave++; | |
gameState.waveInProgress = true; | |
gameState.enemiesSpawned = 0; | |
gameState.totalEnemiesInWave = Math.floor(10 + gameState.wave * 2); | |
gameState.path = generatePath(); | |
gameState.towers = []; | |
gameState.projectiles = []; | |
gameState.enemies = []; | |
showWaveMessage(`Wave ${gameState.wave} Starting!`); | |
if(gameState.spawnInterval) clearInterval(gameState.spawnInterval); | |
setTimeout(() => { | |
gameState.spawnInterval = setInterval(() => { | |
if(gameState.enemiesSpawned < gameState.totalEnemiesInWave) { | |
const enemy = new Enemy(); | |
enemy.health = 100 + (gameState.wave - 1) * 20; | |
enemy.maxHealth = enemy.health; | |
enemy.value = 20 + Math.floor(gameState.wave / 2) * 10; | |
gameState.enemies.push(enemy); | |
gameState.enemiesSpawned++; | |
} else { | |
clearInterval(gameState.spawnInterval); | |
} | |
}, 1000); | |
}, 2000); | |
} | |
function checkWaveStatus() { | |
if (!gameState.waveInProgress) return; | |
if (gameState.enemiesSpawned >= gameState.totalEnemiesInWave && | |
gameState.enemies.length === 0) { | |
gameState.waveInProgress = false; | |
const bonus = 100 + gameState.wave * 20; | |
gameState.gold += bonus; | |
setTimeout(startNewWave, gameState.timeBetweenWaves); | |
} | |
} | |
function gameLoop(timestamp) { | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
drawPath(); | |
gameState.enemies = gameState.enemies.filter(enemy => !enemy.update()); | |
gameState.enemies.forEach(enemy => enemy.draw()); | |
gameState.towers.forEach(tower => { | |
tower.update(timestamp); | |
tower.draw(); | |
}); | |
gameState.projectiles = gameState.projectiles.filter(proj => !proj.update()); | |
gameState.projectiles.forEach(proj => proj.draw()); | |
checkWaveStatus(); | |
document.getElementById('lives').textContent = gameState.lives; | |
document.getElementById('gold').textContent = gameState.gold; | |
document.getElementById('wave').textContent = gameState.wave; | |
if (gameState.lives <= 0) { | |
alert('Game Over!'); | |
return; | |
} | |
requestAnimationFrame(gameLoop); | |
} | |
canvas.addEventListener('click', (e) => { | |
const rect = canvas.getBoundingClientRect(); | |
const x = e.clientX - rect.left; | |
const y = e.clientY - rect.top; | |
if (gameState.selectedTower) { | |
const tower = new Tower(x, y, gameState.selectedTower); | |
if (gameState.gold >= tower.cost) { | |
gameState.towers.push(tower); | |
gameState.gold -= tower.cost; | |
gameState.selectedTower = null; | |
} | |
} | |
}); | |
document.querySelectorAll('.tower-btn').forEach(btn => { | |
btn.addEventListener('click', () => { | |
const type = btn.dataset.type; | |
if (gameState.gold >= towerTypes[type].cost) { | |
gameState.selectedTower = type; | |
} | |
}); | |
}); | |
startNewWave(); | |
requestAnimationFrame(gameLoop); | |
</script> | |
</body> | |
</html> |