16-Bit Tower Defense
💰 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>