💰 Money: 100
❤️ Lives: 10
🌊 Wave: 1
👾 Enemies Left: 0
🧱 Build Tower:
Tower Info
Click a tower to view stats
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>16-Bit Tower Defense</title> <style> body { font-family: monospace; background: #111; color: #eee; margin: 20px; user-select: none; } #container { display: flex; gap: 20px; align-items: flex-start; } canvas { border: 5px solid darkgray; background-color: white; vertical-align: top; } #tower-panel { font-family: monospace; border: 1px solid #ccc; padding: 10px; width: 220px; background: #222; color: #eee; flex-shrink: 0; } #hud { margin-bottom: 10px; } #tooltip { position: absolute; background: #222; color: #fff; font-family: monospace; padding: 4px 8px; font-size: 12px; border-radius: 4px; pointer-events: none; display: none; z-index: 1000; } #tower-shop { margin-bottom: 10px; font-family: monospace; } button:disabled { opacity: 0.5; cursor: not-allowed; } #tower-art { border: 1px solid #ccc; display: block; margin-bottom: 10px; } #tower-panel button { margin: 4px 4px 8px 0; padding: 6px 12px; font-family: monospace; cursor: pointer; border: none; border-radius: 3px; background: #444; color: #eee; } #tower-panel button:hover:not(:disabled) { background: #666; } </style> </head> <body> <div id="hud"> 💰 Money: <span id="money">100</span> ❤️ Lives: <span id="lives">10</span> 🌊 Wave: <span id="wave">1</span> 👾 Enemies Left: <span id="enemy-count">0</span> </div> <div id="tower-shop"> <strong>🧱 Build Tower:</strong> <select id="tower-type"> <option value="basic">Basic</option> <option value="sniper">Sniper</option> <option value="splash">Splash</option> <option value="slow">Slow</option> </select> </div> <div id="container"> <div id="tower-panel"> <h3>Tower Info</h3> <canvas id="tower-art" width="80" height="80"></canvas> <div id="panel-content">Click a tower to view stats</div> <div id="tower-buttons" style="margin-top: 10px; display:none;"> <button id="upgrade-btn">Upgrade ($0)</button> <button id="sell-btn">Sell ($0)</button> </div> </div> <canvas id="game_window_static" width="700" height="500"></canvas> </div> <div id="tooltip"></div> <script> const canvas = document.getElementById("game_window_static"); const ctx = canvas.getContext("2d"); const squareSize = 16; const gap = 1; const cols = Math.floor(canvas.width / (squareSize + gap)); const rows = Math.floor(canvas.height / (squareSize + gap)); const GridMap = []; let selectedTower = null; let hoverTower = null; let money = 100; let lives = 10; let wave = 1; let enemies = []; let projectiles = []; let pathDashOffset = 0; const TowerTypes = { basic: { range: 48, damage: 25, upgradeCost: 20, cost: 30, maxLevel: 3 }, sniper: { range: 120, damage: 50, upgradeCost: 40, cost: 50, maxLevel: 3 }, splash: { range: 48, damage: 15, splashRadius: 24, upgradeCost: 30, cost: 40, maxLevel: 3 }, slow: { range: 64, damage: 10, slows: true, upgradeCost: 25, cost: 35, maxLevel: 3 } }; const TowerArt = { basic: [["", "gray", "gray", "gray", ""], ["gray", "darkgray", "darkgray", "darkgray", "gray"], ["gray", "black", "black", "black", "gray"], ["", "black", "gray", "black", ""], ["", "", "black", "", ""]], sniper: [["", "", "blue", "", ""], ["", "blue", "lightblue", "blue", ""], ["blue", "black", "black", "black", "blue"], ["", "black", "blue", "black", ""], ["", "", "black", "", ""]], splash: [["", "orange", "orange", "orange", ""], ["orange", "red", "red", "red", "orange"], ["orange", "black", "black", "black", "orange"], ["", "black", "orange", "black", ""], ["", "", "black", "", ""]], slow: [["", "purple", "purple", "purple", ""], ["purple", "indigo", "indigo", "indigo", "purple"], ["purple", "black", "black", "black", "purple"], ["", "black", "purple", "black", ""], ["", "", "black", "", ""]] }; const EnemyTypes = { ghost: { name: "Ghost", speed: 1.2, health: 50, color: "aqua", reward: 5 }, zombie: { name: "Zombie", speed: 0.6, health: 100, color: "green", reward: 10 }, shadow: { name: "Shadow", speed: 0.3, health: 250, color: "purple", reward: 25 } }; function calculateGrid() { for (let col = 0; col < cols; col++) { GridMap[col] = []; for (let row = 0; row < rows; row++) { GridMap[col][row] = { col, row, x: col * (squareSize + gap), y: row * (squareSize + gap), hasTower: false, type: null, level: 0, range: 48, damage: 25, upgradeCost: 20, cooldown: 0, isPath: false }; } } } function drawGrid() { for (let col = 0; col < cols; col++) { for (let row = 0; row < rows; row++) { const square = GridMap[col][row]; if (square.isPath) ctx.fillStyle = "#ccc"; else ctx.fillStyle = (col + row) % 2 === 0 ? "#999" : "#666"; ctx.fillRect(square.x, square.y, squareSize, squareSize); if (square.hasTower) { drawPixelTower(square); ctx.fillStyle = "white"; ctx.font = "10px monospace"; ctx.fillText(square.level, square.x + 4, square.y + 12); } } } } function drawPixelTower(square) { const baseX = square.x; const baseY = square.y; const pixels = TowerArt[square.type || "basic"]; const pixelSize = squareSize / 5; for (let y = 0; y < pixels.length; y++) { for (let x = 0; x < pixels[y].length; x++) { const color = pixels[y][x]; if (color) { ctx.fillStyle = color; ctx.fillRect(baseX + x * pixelSize, baseY + y * pixelSize, pixelSize, pixelSize); } } } } const towerArtCanvas = document.getElementById("tower-art"); const towerArtCtx = towerArtCanvas.getContext("2d"); function drawTowerIcon(type) { towerArtCtx.clearRect(0, 0, towerArtCanvas.width, towerArtCanvas.height); if (!type || !TowerArt[type]) return; const pixels = TowerArt[type]; const pixelSize = towerArtCanvas.width / pixels.length; for (let y = 0; y < pixels.length; y++) { for (let x = 0; x < pixels[y].length; x++) { const color = pixels[y][x]; if (color) { towerArtCtx.fillStyle = color; towerArtCtx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize); } } } } function generatePath(level) { for (let col = 0; col < cols; col++) { for (let row = 0; row < rows; row++) { GridMap[col][row].isPath = false; } } const path = []; let currentCol = 0; let currentRow = Math.floor(rows / 2); path.push({ col: currentCol, row: currentRow }); GridMap[currentCol][currentRow].isPath = true; while (currentCol < cols - 1) { currentCol++; path.push({ col: currentCol, row: currentRow }); GridMap[currentCol][currentRow].isPath = true; const maxVerticalMoves = Math.min(level, 5); if (maxVerticalMoves > 0 && Math.random() < 0.3) { const direction = Math.random() < 0.5 ? -1 : 1; const newRow = currentRow + direction; if (newRow >= 0 && newRow < rows) { currentRow = newRow; path.push({ col: currentCol, row: currentRow }); GridMap[currentCol][currentRow].isPath = true; } } } return path.map(cell => ({ x: cell.col * (squareSize + gap) + squareSize / 2, y: cell.row * (squareSize + gap) + squareSize / 2 })); } function drawPathLine(path) { if (!path || path.length < 2) return; ctx.save(); ctx.strokeStyle = "#8800cc"; ctx.lineWidth = 3; ctx.setLineDash([10, 10]); ctx.lineDashOffset = -pathDashOffset; ctx.beginPath(); ctx.moveTo(path[0].x, path[0].y); for (let i = 1; i < path.length; i++) { ctx.lineTo(path[i].x, path[i].y); } ctx.stroke(); ctx.restore(); } function distance(x1, y1, x2, y2) { return Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2); } let currentPath = []; let spawnCooldown = 0; let enemySpawnIndex = 0; const enemyTypesOrder = ["ghost", "zombie", "shadow"]; let waveEnemyCount = 0; function gameLoop() { update(); render(); requestAnimationFrame(gameLoop); pathDashOffset += 2; } function update() { // Spawn enemies over time if (spawnCooldown <= 0 && enemySpawnIndex < waveEnemyCount) { const typeKey = enemySpawnIndex % enemyTypesOrder.length; const type = EnemyTypes[enemyTypesOrder[typeKey]]; enemies.push({ ...type, x: currentPath[0].x, y: currentPath[0].y, path: currentPath, pathIndex: 0, health: type.health, alive: true }); enemySpawnIndex++; spawnCooldown = 30; } else { spawnCooldown--; } // Update enemies enemies.forEach(enemy => { if (!enemy.alive) return; const target = enemy.path[enemy.pathIndex]; if (!target) { lives--; enemy.alive = false; updateHUD(); checkWaveComplete(); return; } const dx = target.x - enemy.x; const dy = target.y - enemy.y; const speed = enemy.speed; if (Math.abs(dx) > 0) { const stepX = Math.sign(dx) * speed; if (Math.abs(dx) <= speed) { enemy.x = target.x; } else { enemy.x += stepX; } } else if (Math.abs(dy) > 0) { const stepY = Math.sign(dy) * speed; if (Math.abs(dy) <= speed) { enemy.y = target.y; } else { enemy.y += stepY; } } if (enemy.x === target.x && enemy.y === target.y) { enemy.pathIndex++; } }); // Tower attack logic GridMap.flat().forEach(cell => { if (cell.hasTower) { if (cell.cooldown > 0) cell.cooldown--; else { const inRange = enemies.filter(e => e.alive && distance(cell.x + squareSize/2, cell.y + squareSize/2, e.x, e.y) <= cell.range); if (inRange.length > 0) { const target = inRange[0]; projectiles.push({ x: cell.x + squareSize/2, y: cell.y + squareSize/2, tx: target, damage: cell.damage }); cell.cooldown = 30; } } } }); // Update projectiles projectiles.forEach(p => { const dx = p.tx.x - p.x; const dy = p.tx.y - p.y; const dist = Math.sqrt(dx * dx + dy * dy); const speed = 6; if (dist < speed) { p.tx.health -= p.damage; if (p.tx.health <= 0) { p.tx.alive = false; money += p.tx.reward; updateHUD(); checkWaveComplete(); } p.hit = true; } else { p.x += (dx / dist) * speed; p.y += (dy / dist) * speed; } }); projectiles = projectiles.filter(p => !p.hit); updateHUD(); } function checkWaveComplete() { // When no alive enemies and no enemies left to spawn => next wave if (enemies.filter(e => e.alive).length === 0 && enemySpawnIndex >= waveEnemyCount) { setTimeout(() => { spawnWave(); }, 1500); } } function render() { ctx.clearRect(0, 0, canvas.width, canvas.height); drawGrid(); drawPathLine(currentPath); enemies.forEach(e => { if (!e.alive) return; ctx.fillStyle = e.color; ctx.beginPath(); ctx.arc(e.x, e.y, 6, 0, Math.PI * 2); ctx.fill(); }); projectiles.forEach(p => { ctx.fillStyle = "black"; ctx.beginPath(); ctx.arc(p.x, p.y, 2, 0, Math.PI * 2); ctx.fill(); }); if (hoverTower) { ctx.save(); ctx.strokeStyle = "yellow"; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(hoverTower.x + squareSize/2, hoverTower.y + squareSize/2, hoverTower.range, 0, Math.PI * 2); ctx.stroke(); ctx.restore(); } } function updateHUD() { document.getElementById("money").textContent = money; document.getElementById("lives").textContent = lives; document.getElementById("wave").textContent = wave; document.getElementById("enemy-count").textContent = enemies.filter(e => e.alive).length; } function updateTowerPanel() { const panel = document.getElementById("panel-content"); const buttonsDiv = document.getElementById("tower-buttons"); const upgradeBtn = document.getElementById("upgrade-btn"); const sellBtn = document.getElementById("sell-btn"); if (!selectedTower) { panel.textContent = "Click a tower to view stats"; drawTowerIcon(null); buttonsDiv.style.display = "none"; return; } panel.innerHTML = ` <strong>${selectedTower.type.toUpperCase()} Tower</strong><br> Level: ${selectedTower.level}<br> Range: ${selectedTower.range}<br> Damage: ${selectedTower.damage}<br> Upgrade Cost: $${selectedTower.upgradeCost} `; drawTowerIcon(selectedTower.type); buttonsDiv.style.display = "block"; // Disable upgrade if no money or max level reached const maxLevel = TowerTypes[selectedTower.type].maxLevel || 3; upgradeBtn.disabled = (money < selectedTower.upgradeCost || selectedTower.level >= maxLevel); // Sell button always enabled sellBtn.disabled = false; sellBtn.textContent = `Sell ($${Math.floor(getTowerSellValue(selectedTower))})`; upgradeBtn.textContent = `Upgrade ($${selectedTower.upgradeCost})`; } function getTowerSellValue(tower) { // Return half cost including upgrades let baseCost = TowerTypes[tower.type].cost; let total = baseCost; for(let i=1; i < tower.level; i++){ total += TowerTypes[tower.type].upgradeCost; } return total * 0.5; } // UI Events canvas.addEventListener("click", e => { const col = Math.floor(e.offsetX / (squareSize + gap)); const row = Math.floor(e.offsetY / (squareSize + gap)); const square = GridMap[col]?.[row]; if (!square) return; if (!square.hasTower && !square.isPath) { const towerType = document.getElementById("tower-type").value; const towerDef = TowerTypes[towerType]; if (money >= towerDef.cost) { square.hasTower = true; square.type = towerType; square.level = 1; square.range = towerDef.range; square.damage = towerDef.damage; square.upgradeCost = towerDef.upgradeCost; square.cooldown = 0; money -= towerDef.cost; updateHUD(); selectedTower = square; updateTowerPanel(); } } else if (square.hasTower) { selectedTower = square; updateTowerPanel(); } else { selectedTower = null; updateTowerPanel(); } }); document.getElementById("upgrade-btn").addEventListener("click", () => { if (!selectedTower) return; if (money < selectedTower.upgradeCost) return; if (selectedTower.level >= (TowerTypes[selectedTower.type].maxLevel || 3)) return; money -= selectedTower.upgradeCost; selectedTower.level++; selectedTower.damage += 10; // Increase damage on upgrade selectedTower.range += 5; // Increase range on upgrade selectedTower.upgradeCost = Math.floor(selectedTower.upgradeCost * 1.5); updateHUD(); updateTowerPanel(); }); document.getElementById("sell-btn").addEventListener("click", () => { if (!selectedTower) return; money += Math.floor(getTowerSellValue(selectedTower)); selectedTower.hasTower = false; selectedTower.type = null; selectedTower.level = 0; selectedTower.range = 0; selectedTower.damage = 0; selectedTower.upgradeCost = 0; selectedTower.cooldown = 0; selectedTower = null; updateHUD(); updateTowerPanel(); }); canvas.addEventListener("mousemove", e => { const col = Math.floor(e.offsetX / (squareSize + gap)); const row = Math.floor(e.offsetY / (squareSize + gap)); hoverTower = GridMap[col]?.[row]; }); function spawnWave() { enemies = []; projectiles = []; enemySpawnIndex = 0; spawnCooldown = 0; waveEnemyCount = 5 + wave * 2; currentPath = generatePath(wave); wave++; updateHUD(); selectedTower = null; updateTowerPanel(); } calculateGrid(); spawnWave(); updateHUD(); updateTowerPanel(); requestAnimationFrame(gameLoop); </script> </body> </html>