tower / index.html
Phoenixoni's picture
Update index.html
88a4a00 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fantasy Tower Defense</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@keyframes moveEnemy {
to {
background-position: -512px 0;
}
}
.enemy {
width: 32px;
height: 32px;
background-image: url('https://i.imgur.com/Jj7Zq9G.png');
animation: moveEnemy 0.8s steps(16) infinite;
position: absolute;
z-index: 10;
transition: left 0.1s linear, top 0.1s linear;
}
.tower {
width: 48px;
height: 48px;
background-image: url('https://i.imgur.com/Q9ZvZLQ.png');
background-position: 0 0;
position: absolute;
z-index: 5;
}
.projectile {
width: 16px;
height: 16px;
background-color: #f00;
border-radius: 50%;
position: absolute;
z-index: 8;
}
.path {
background-color: #8B4513;
background-image: url('https://i.imgur.com/5XQ3y9j.png');
background-size: cover;
}
.grass {
background-color: #7CFC00;
background-image: url('https://i.imgur.com/4ZQ3y9j.png');
background-size: cover;
}
.buildable {
background-color: #3a7d44;
background-image: url('https://i.imgur.com/6XQ3y9j.png');
background-size: cover;
cursor: pointer;
}
.buildable:hover {
filter: brightness(1.2);
box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.5);
}
.game-grid {
display: grid;
grid-template-columns: repeat(20, 32px);
grid-template-rows: repeat(15, 32px);
gap: 1px;
background-color: #333;
}
.tower-range {
position: absolute;
border-radius: 50%;
background-color: rgba(0, 100, 255, 0.2);
transform: translate(-50%, -50%);
pointer-events: none;
z-index: 2;
}
.health-bar {
height: 4px;
background-color: red;
position: absolute;
top: -6px;
left: 0;
width: 100%;
}
.upgrade-btn {
transition: all 0.2s;
}
.upgrade-btn:hover {
transform: scale(1.05);
box-shadow: 0 0 10px gold;
}
#game-container {
perspective: 1000px;
}
#game-board {
transform-style: preserve-3d;
box-shadow: 0 0 30px rgba(0, 0, 0, 0.5);
}
.start-point {
background-color: #ff0000;
background-image: url('https://i.imgur.com/7XQ3y9j.png');
background-size: cover;
}
.end-point {
background-color: #0000ff;
background-image: url('https://i.imgur.com/8XQ3y9j.png');
background-size: cover;
}
</style>
</head>
<body class="bg-gray-900 text-white font-mono">
<div class="container mx-auto px-4 py-8">
<h1 class="text-4xl font-bold text-center mb-6 text-transparent bg-clip-text bg-gradient-to-r from-yellow-400 to-red-500">
<i class="fas fa-fort-awesome-alt mr-2"></i> Fantasy Tower Defense
</h1>
<div class="flex flex-col lg:flex-row gap-6">
<!-- Game Board -->
<div id="game-container" class="flex-1">
<div id="game-board" class="game-grid relative overflow-hidden border-4 border-yellow-700 rounded-lg bg-gray-800">
<!-- Grid will be generated by JavaScript -->
</div>
</div>
<!-- Game Controls -->
<div class="w-full lg:w-64 bg-gray-800 p-4 rounded-lg border-2 border-gray-700">
<div class="flex justify-between items-center mb-4">
<div>
<div class="text-xl font-bold text-yellow-400">Wave: <span id="wave">1</span></div>
<div class="text-sm text-gray-300">Enemies: <span id="enemies-left">0</span></div>
</div>
<div class="text-right">
<div class="text-xl font-bold text-green-400">$<span id="money">150</span></div>
<div class="text-sm text-gray-300">Lives: <span id="lives">20</span></div>
</div>
</div>
<div class="mb-4">
<div class="h-2 bg-gray-700 rounded-full">
<div id="wave-progress" class="h-2 bg-blue-500 rounded-full" style="width: 0%"></div>
</div>
<div class="flex justify-between text-xs mt-1">
<span>Wave Progress</span>
<span id="wave-timer">30s</span>
</div>
</div>
<button id="start-wave" class="w-full bg-green-600 hover:bg-green-700 text-white py-2 px-4 rounded-lg font-bold mb-4 transition-all">
<i class="fas fa-play mr-2"></i> Start Wave
</button>
<div class="mb-4">
<h3 class="font-bold text-lg border-b border-gray-700 pb-2 mb-2">Towers</h3>
<div class="grid grid-cols-2 gap-2">
<button class="tower-btn bg-blue-800 hover:bg-blue-700 p-2 rounded-lg" data-type="arrow" data-cost="100">
<div class="text-center">
<i class="fas fa-arrow-up text-xl mb-1"></i>
<div>Arrow Tower</div>
<div class="text-xs">$100</div>
</div>
</button>
<button class="tower-btn bg-red-800 hover:bg-red-700 p-2 rounded-lg" data-type="cannon" data-cost="150">
<div class="text-center">
<i class="fas fa-bomb text-xl mb-1"></i>
<div>Cannon Tower</div>
<div class="text-xs">$150</div>
</div>
</button>
<button class="tower-btn bg-purple-800 hover:bg-purple-700 p-2 rounded-lg" data-type="magic" data-cost="200">
<div class="text-center">
<i class="fas fa-magic text-xl mb-1"></i>
<div>Magic Tower</div>
<div class="text-xs">$200</div>
</div>
</button>
<button class="tower-btn bg-yellow-800 hover:bg-yellow-700 p-2 rounded-lg" data-type="gold" data-cost="300">
<div class="text-center">
<i class="fas fa-coins text-xl mb-1"></i>
<div>Gold Tower</div>
<div class="text-xs">$300</div>
</div>
</button>
</div>
</div>
<div id="tower-info" class="hidden bg-gray-700 p-3 rounded-lg border border-gray-600">
<h4 class="font-bold mb-2 flex justify-between">
<span id="tower-name">Tower</span>
<button id="sell-tower" class="text-xs bg-red-600 hover:bg-red-700 px-2 py-1 rounded">Sell ($<span id="sell-price">0</span>)</button>
</h4>
<div class="text-sm mb-2">
<div>Level: <span id="tower-level">1</span></div>
<div>Damage: <span id="tower-damage">10</span></div>
<div>Range: <span id="tower-range">3</span></div>
<div>Speed: <span id="tower-speed">1.5</span>s</div>
</div>
<button id="upgrade-tower" class="upgrade-btn w-full bg-yellow-600 hover:bg-yellow-700 text-white py-1 px-3 rounded text-sm">
Upgrade ($<span id="upgrade-cost">50</span>)
</button>
</div>
<div class="mt-4 text-sm text-gray-400">
<p><i class="fas fa-info-circle mr-1"></i> Click on green buildable tiles to place towers</p>
<p><i class="fas fa-info-circle mr-1"></i> Click on a tower to upgrade it</p>
</div>
</div>
</div>
</div>
<script>
// Game State
const gameState = {
money: 150,
lives: 20,
wave: 1,
waveActive: false,
waveTimer: 30,
selectedTowerType: null,
selectedTower: null,
towers: [],
enemies: [],
projectiles: [],
path: [],
waypoints: [],
buildableTiles: []
};
// Tower Types
const towerTypes = {
arrow: {
name: "Arrow Tower",
damage: 10,
range: 3,
speed: 1.5,
cost: 100,
color: "blue",
upgradeCost: 50,
damageIncrement: 5,
rangeIncrement: 0.5,
speedIncrement: -0.2
},
cannon: {
name: "Cannon Tower",
damage: 25,
range: 2.5,
speed: 2.5,
cost: 150,
color: "red",
upgradeCost: 75,
damageIncrement: 10,
rangeIncrement: 0.3,
speedIncrement: -0.3
},
magic: {
name: "Magic Tower",
damage: 15,
range: 3.5,
speed: 1.8,
cost: 200,
color: "purple",
upgradeCost: 100,
damageIncrement: 7,
rangeIncrement: 0.4,
speedIncrement: -0.15
},
gold: {
name: "Gold Tower",
damage: 8,
range: 2,
speed: 1,
cost: 300,
color: "yellow",
upgradeCost: 150,
damageIncrement: 4,
rangeIncrement: 0.2,
speedIncrement: -0.05,
moneyPerShot: 1
}
};
// Enemy Types
const enemyTypes = [
{ name: "Goblin", health: 50, speed: 1, reward: 10, spriteY: 0 },
{ name: "Orc", health: 100, speed: 0.8, reward: 20, spriteY: -32 },
{ name: "Troll", health: 200, speed: 0.6, reward: 40, spriteY: -64 },
{ name: "Dragon", health: 500, speed: 0.5, reward: 100, spriteY: -96 }
];
// Initialize Game Board
const gameBoard = document.getElementById('game-board');
const gridWidth = 20;
const gridHeight = 15;
const cellSize = 32;
// Generate path and buildable areas
function generatePath() {
const path = [];
const waypoints = [];
const buildableTiles = [];
// Start at top middle (red)
let x = 9, y = 0;
path.push({x, y});
waypoints.push({x, y});
// Go down
for (y = 1; y < 5; y++) {
path.push({x, y});
}
waypoints.push({x: x, y: y - 1});
// Go right
for (x = 10; x < 15; x++) {
path.push({x: x, y: y-1});
}
waypoints.push({x: x-1, y: y-1});
// Go down
for (y = 5; y < 10; y++) {
path.push({x: x-1, y: y});
}
waypoints.push({x: x-1, y: y-1});
// Go left
for (x = 14; x > 5; x--) {
path.push({x: x, y: y-1});
}
waypoints.push({x: x+1, y: y-1});
// Go down to exit (blue)
for (y = 10; y < 15; y++) {
path.push({x: x+1, y: y});
}
waypoints.push({x: x+1, y: y-1});
// Generate buildable areas (not on path)
for (let y = 0; y < gridHeight; y++) {
for (let x = 0; x < gridWidth; x++) {
const isPath = path.some(point => point.x === x && point.y === y);
if (!isPath) {
// Check if tile is adjacent to path (for better gameplay)
const adjacentToPath =
path.some(point =>
(point.x === x && Math.abs(point.y - y) === 1) ||
(point.y === y && Math.abs(point.x - x) === 1)
);
if (adjacentToPath) {
buildableTiles.push({x, y});
}
}
}
}
gameState.path = path;
gameState.waypoints = waypoints;
gameState.buildableTiles = buildableTiles;
renderGrid();
}
// Render grid
function renderGrid() {
gameBoard.innerHTML = '';
// Create grid cells
for (let y = 0; y < gridHeight; y++) {
for (let x = 0; x < gridWidth; x++) {
const cell = document.createElement('div');
cell.className = 'w-8 h-8';
cell.dataset.x = x;
cell.dataset.y = y;
// Check if this cell is part of the path
const isPath = gameState.path.some(point => point.x === x && point.y === y);
const isBuildable = gameState.buildableTiles.some(point => point.x === x && point.y === y);
if (isPath) {
// Check if start or end point
if (x === 9 && y === 0) {
cell.classList.add('start-point');
} else if (x === 6 && y === 14) {
cell.classList.add('end-point');
} else {
cell.classList.add('path');
}
} else if (isBuildable) {
cell.classList.add('buildable');
cell.addEventListener('click', () => placeTower(x, y));
} else {
cell.classList.add('grass');
}
gameBoard.appendChild(cell);
}
}
}
// Place tower
function placeTower(x, y) {
if (gameState.selectedTowerType && !gameState.waveActive) {
const towerType = towerTypes[gameState.selectedTowerType];
if (gameState.money >= towerType.cost) {
gameState.money -= towerType.cost;
updateMoney();
const tower = {
type: gameState.selectedTowerType,
x: x,
y: y,
level: 1,
damage: towerType.damage,
range: towerType.range,
speed: towerType.speed,
lastShot: 0,
element: null,
rangeElement: null
};
gameState.towers.push(tower);
renderTower(tower);
// Deselect tower type after placement
gameState.selectedTowerType = null;
document.querySelectorAll('.tower-btn').forEach(btn => {
btn.classList.remove('ring-2', 'ring-yellow-400');
});
} else {
showMessage("Not enough money!");
}
}
}
// Render tower
function renderTower(tower) {
const towerElement = document.createElement('div');
towerElement.className = 'tower';
towerElement.style.left = `${tower.x * cellSize}px`;
towerElement.style.top = `${tower.y * cellSize}px`;
// Add color based on tower type
towerElement.style.filter = `hue-rotate(${getTowerHue(tower.type)}deg)`;
towerElement.addEventListener('click', (e) => {
e.stopPropagation();
selectTower(tower);
});
// Create range indicator
const rangeElement = document.createElement('div');
rangeElement.className = 'tower-range';
rangeElement.style.width = `${tower.range * cellSize * 2}px`;
rangeElement.style.height = `${tower.range * cellSize * 2}px`;
rangeElement.style.left = `${(tower.x + 0.5) * cellSize}px`;
rangeElement.style.top = `${(tower.y + 0.5) * cellSize}px`;
rangeElement.style.display = 'none';
gameBoard.appendChild(towerElement);
gameBoard.appendChild(rangeElement);
tower.element = towerElement;
tower.rangeElement = rangeElement;
}
function getTowerHue(type) {
switch(type) {
case 'arrow': return 200; // blue
case 'cannon': return 0; // red
case 'magic': return 270; // purple
case 'gold': return 50; // yellow
default: return 0;
}
}
// Select tower
function selectTower(tower) {
if (gameState.waveActive) return;
// Deselect previous tower
if (gameState.selectedTower) {
gameState.selectedTower.rangeElement.style.display = 'none';
}
gameState.selectedTower = tower;
tower.rangeElement.style.display = 'block';
// Update tower info panel
const towerType = towerTypes[tower.type];
document.getElementById('tower-info').classList.remove('hidden');
document.getElementById('tower-name').textContent = towerType.name;
document.getElementById('tower-level').textContent = tower.level;
document.getElementById('tower-damage').textContent = tower.damage;
document.getElementById('tower-range').textContent = tower.range;
document.getElementById('tower-speed').textContent = tower.speed;
// Calculate upgrade cost
const upgradeCost = towerType.upgradeCost * tower.level;
document.getElementById('upgrade-cost').textContent = upgradeCost;
// Calculate sell price (50% of total invested)
const totalInvested = towerType.cost + (tower.level - 1) * upgradeCost;
const sellPrice = Math.floor(totalInvested * 0.7);
document.getElementById('sell-price').textContent = sellPrice;
}
// Upgrade tower
document.getElementById('upgrade-tower').addEventListener('click', function() {
if (!gameState.selectedTower) return;
const tower = gameState.selectedTower;
const towerType = towerTypes[tower.type];
const upgradeCost = towerType.upgradeCost * tower.level;
if (gameState.money >= upgradeCost) {
gameState.money -= upgradeCost;
updateMoney();
tower.level++;
tower.damage += towerType.damageIncrement;
tower.range += towerType.rangeIncrement;
tower.speed += towerType.speedIncrement;
// Update tower info
document.getElementById('tower-level').textContent = tower.level;
document.getElementById('tower-damage').textContent = tower.damage;
document.getElementById('tower-range').textContent = tower.range;
document.getElementById('tower-speed').textContent = tower.speed;
// Update range indicator
tower.rangeElement.style.width = `${tower.range * cellSize * 2}px`;
tower.rangeElement.style.height = `${tower.range * cellSize * 2}px`;
// Update upgrade cost for next level
const newUpgradeCost = towerType.upgradeCost * tower.level;
document.getElementById('upgrade-cost').textContent = newUpgradeCost;
// Update sell price
const totalInvested = towerType.cost + (tower.level - 1) * upgradeCost;
const sellPrice = Math.floor(totalInvested * 0.7);
document.getElementById('sell-price').textContent = sellPrice;
showMessage(`Tower upgraded to level ${tower.level}!`);
} else {
showMessage("Not enough money for upgrade!");
}
});
// Sell tower
document.getElementById('sell-tower').addEventListener('click', function() {
if (!gameState.selectedTower) return;
const tower = gameState.selectedTower;
const towerType = towerTypes[tower.type];
const totalInvested = towerType.cost + (tower.level - 1) * towerType.upgradeCost * tower.level;
const sellPrice = Math.floor(totalInvested * 0.7);
gameState.money += sellPrice;
updateMoney();
// Remove tower elements
gameBoard.removeChild(tower.element);
gameBoard.removeChild(tower.rangeElement);
// Remove from towers array
gameState.towers = gameState.towers.filter(t => t !== tower);
// Hide tower info
document.getElementById('tower-info').classList.add('hidden');
gameState.selectedTower = null;
showMessage(`Tower sold for $${sellPrice}!`);
});
// Tower buttons
document.querySelectorAll('.tower-btn').forEach(btn => {
btn.addEventListener('click', function() {
if (gameState.waveActive) return;
gameState.selectedTowerType = this.dataset.type;
// Update UI
document.querySelectorAll('.tower-btn').forEach(b => {
b.classList.remove('ring-2', 'ring-yellow-400');
});
this.classList.add('ring-2', 'ring-yellow-400');
// Hide tower info if showing
document.getElementById('tower-info').classList.add('hidden');
if (gameState.selectedTower) {
gameState.selectedTower.rangeElement.style.display = 'none';
gameState.selectedTower = null;
}
});
});
// Start wave
document.getElementById('start-wave').addEventListener('click', startWave);
function startWave() {
if (gameState.waveActive) return;
gameState.waveActive = true;
document.getElementById('start-wave').disabled = true;
// Calculate number of enemies based on wave
const baseEnemies = 5;
const waveMultiplier = 1 + (gameState.wave - 1) * 0.3;
const totalEnemies = Math.floor(baseEnemies * waveMultiplier);
// Spawn enemies
let spawned = 0;
const spawnInterval = setInterval(() => {
if (spawned >= totalEnemies) {
clearInterval(spawnInterval);
return;
}
// Random enemy type (higher waves get stronger enemies)
let enemyTypeIndex = 0;
const random = Math.random();
if (gameState.wave > 10 && random < 0.1) {
enemyTypeIndex = 3; // Dragon
} else if (gameState.wave > 5 && random < 0.3) {
enemyTypeIndex = 2; // Troll
} else if (gameState.wave > 3 && random < 0.6) {
enemyTypeIndex = 1; // Orc
}
spawnEnemy(enemyTypes[enemyTypeIndex]);
spawned++;
// Update enemies left
document.getElementById('enemies-left').textContent = gameState.enemies.length;
}, 1000);
// Wave timer
let timeLeft = 30;
const waveTimer = setInterval(() => {
timeLeft--;
document.getElementById('wave-timer').textContent = `${timeLeft}s`;
document.getElementById('wave-progress').style.width = `${(1 - timeLeft/30) * 100}%`;
if (timeLeft <= 0 || gameState.enemies.length === 0) {
clearInterval(waveTimer);
// Check if all enemies are dead
if (gameState.enemies.length === 0) {
endWave(true);
}
}
}, 1000);
}
function endWave(success) {
gameState.waveActive = false;
document.getElementById('start-wave').disabled = false;
if (success) {
// Wave completed successfully
gameState.money += 100 + (gameState.wave * 20);
gameState.wave++;
updateMoney();
updateWave();
showMessage(`Wave ${gameState.wave-1} completed! Bonus: $${100 + (gameState.wave-1) * 20}`);
}
// Reset wave timer UI
document.getElementById('wave-timer').textContent = '30s';
document.getElementById('wave-progress').style.width = '0%';
}
function spawnEnemy(type) {
const enemy = {
type: type.name,
health: type.health,
maxHealth: type.health,
speed: type.speed,
reward: type.reward,
x: gameState.path[0].x,
y: gameState.path[0].y,
pathIndex: 0,
element: null,
healthBar: null
};
gameState.enemies.push(enemy);
renderEnemy(enemy);
}
function renderEnemy(enemy) {
const enemyElement = document.createElement('div');
enemyElement.className = 'enemy';
enemyElement.style.left = `${enemy.x * cellSize}px`;
enemyElement.style.top = `${enemy.y * cellSize}px`;
enemyElement.style.backgroundPositionY = `${enemyTypes.find(e => e.name === enemy.type).spriteY}px`;
// Health bar
const healthBar = document.createElement('div');
healthBar.className = 'health-bar';
enemyElement.appendChild(healthBar);
gameBoard.appendChild(enemyElement);
enemy.element = enemyElement;
enemy.healthBar = healthBar;
}
// Game loop
function gameLoop(timestamp) {
// Move enemies
gameState.enemies.forEach(enemy => {
if (enemy.pathIndex < gameState.path.length - 1) {
const nextPoint = gameState.path[enemy.pathIndex + 1];
const dx = nextPoint.x - enemy.x;
const dy = nextPoint.y - enemy.y;
// Normalize direction
const distance = Math.sqrt(dx * dx + dy * dy);
const speed = 0.02 * enemy.speed;
if (distance < speed) {
// Reached next point
enemy.x = nextPoint.x;
enemy.y = nextPoint.y;
enemy.pathIndex++;
// Check if reached end
if (enemy.pathIndex === gameState.path.length - 1) {
// Enemy reached the end
gameState.lives--;
updateLives();
// Remove enemy
gameBoard.removeChild(enemy.element);
gameState.enemies = gameState.enemies.filter(e => e !== enemy);
if (gameState.lives <= 0) {
gameOver();
}
return;
}
} else {
// Move towards next point
enemy.x += (dx / distance) * speed;
enemy.y += (dy / distance) * speed;
}
// Update position
enemy.element.style.left = `${enemy.x * cellSize}px`;
enemy.element.style.top = `${enemy.y * cellSize}px`;
}
});
// Tower attacks
gameState.towers.forEach(tower => {
if (timestamp - tower.lastShot > tower.speed * 1000) {
// Find closest enemy in range
let closestEnemy = null;
let minDistance = Infinity;
gameState.enemies.forEach(enemy => {
const dx = enemy.x - (tower.x + 0.5);
const dy = enemy.y - (tower.y + 0.5);
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= tower.range && distance < minDistance) {
closestEnemy = enemy;
minDistance = distance;
}
});
if (closestEnemy) {
// Shoot at enemy
shoot(tower, closestEnemy, timestamp);
tower.lastShot = timestamp;
}
}
});
// Move projectiles
gameState.projectiles.forEach((proj, index) => {
const dx = proj.targetX - proj.x;
const dy = proj.targetY - proj.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 0.2) {
// Hit target
proj.target.health -= proj.damage;
// Update health bar
if (proj.target.healthBar) {
const healthPercent = (proj.target.health / proj.target.maxHealth) * 100;
proj.target.healthBar.style.width = `${healthPercent}%`;
if (healthPercent > 50) {
proj.target.healthBar.style.backgroundColor = 'green';
} else if (healthPercent > 25) {
proj.target.healthBar.style.backgroundColor = 'orange';
} else {
proj.target.healthBar.style.backgroundColor = 'red';
}
}
// Check if enemy died
if (proj.target.health <= 0) {
gameState.money += proj.target.reward;
updateMoney();
gameBoard.removeChild(proj.target.element);
gameState.enemies = gameState.enemies.filter(e => e !== proj.target);
// Check if wave completed
if (gameState.waveActive && gameState.enemies.length === 0) {
endWave(true);
}
}
// Remove projectile
gameBoard.removeChild(proj.element);
gameState.projectiles.splice(index, 1);
} else {
// Move projectile
const speed = 0.2;
proj.x += (dx / distance) * speed;
proj.y += (dy / distance) * speed;
proj.element.style.left = `${proj.x * cellSize}px`;
proj.element.style.top = `${proj.y * cellSize}px`;
}
});
requestAnimationFrame(gameLoop);
}
function shoot(tower, enemy, timestamp) {
const towerType = towerTypes[tower.type];
// Create projectile
const projectile = {
x: tower.x + 0.5,
y: tower.y + 0.5,
targetX: enemy.x + 0.5,
targetY: enemy.y + 0.5,
damage: tower.damage,
target: enemy,
element: null
};
// Special effects for tower types
if (tower.type === 'gold') {
// Gold tower earns money per shot
gameState.money += towerType.moneyPerShot;
updateMoney();
}
// Create projectile element
const projElement = document.createElement('div');
projElement.className = 'projectile';
// Color based on tower type
switch(tower.type) {
case 'arrow': projElement.style.backgroundColor = '#3498db'; break;
case 'cannon': projElement.style.backgroundColor = '#e74c3c'; break;
case 'magic': projElement.style.backgroundColor = '#9b59b6'; break;
case 'gold': projElement.style.backgroundColor = '#f1c40f'; break;
}
projElement.style.left = `${projectile.x * cellSize}px`;
projElement.style.top = `${projectile.y * cellSize}px`;
gameBoard.appendChild(projElement);
projectile.element = projElement;
gameState.projectiles.push(projectile);
}
function gameOver() {
showMessage("Game Over! Refresh to play again.");
gameState.waveActive = false;
document.getElementById('start-wave').disabled = true;
}
// UI Updates
function updateMoney() {
document.getElementById('money').textContent = gameState.money;
}
function updateLives() {
document.getElementById('lives').textContent = gameState.lives;
// Flash lives when low
if (gameState.lives <= 5) {
const livesEl = document.getElementById('lives');
livesEl.classList.add('text-red-500', 'animate-pulse');
} else {
document.getElementById('lives').classList.remove('text-red-500', 'animate-pulse');
}
}
function updateWave() {
document.getElementById('wave').textContent = gameState.wave;
}
function showMessage(msg) {
const message = document.createElement('div');
message.className = 'fixed top-4 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white px-4 py-2 rounded-lg shadow-lg z-50 animate-bounce';
message.textContent = msg;
document.body.appendChild(message);
setTimeout(() => {
message.classList.remove('animate-bounce');
message.classList.add('opacity-0', 'transition-opacity', 'duration-500');
setTimeout(() => document.body.removeChild(message), 500);
}, 2000);
}
// Initialize game
generatePath();
updateMoney();
updateLives();
updateWave();
// Start game loop
requestAnimationFrame(gameLoop);
// Helpers
document.addEventListener('click', function(e) {
// Click outside to deselect tower
if (!e.target.closest('#game-board') && !e.target.closest('.tower-btn') && !e.target.closest('#tower-info')) {
if (gameState.selectedTower) {
gameState.selectedTower.rangeElement.style.display = 'none';
gameState.selectedTower = null;
document.getElementById('tower-info').classList.add('hidden');
}
if (gameState.selectedTowerType) {
gameState.selectedTowerType = null;
document.querySelectorAll('.tower-btn').forEach(btn => {
btn.classList.remove('ring-2', 'ring-yellow-400');
});
}
}
});
</script>
</body>
</html>