💰 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>
