about summary refs log tree commit diff stats
path: root/html/tower/js
diff options
context:
space:
mode:
Diffstat (limited to 'html/tower/js')
-rw-r--r--html/tower/js/game.js487
-rw-r--r--html/tower/js/gameState.js288
-rw-r--r--html/tower/js/mechanics.js430
-rw-r--r--html/tower/js/path.js176
-rw-r--r--html/tower/js/renderer.js381
-rw-r--r--html/tower/js/uiHandlers.js96
6 files changed, 1858 insertions, 0 deletions
diff --git a/html/tower/js/game.js b/html/tower/js/game.js
new file mode 100644
index 0000000..4d8ed39
--- /dev/null
+++ b/html/tower/js/game.js
@@ -0,0 +1,487 @@
+// generate updated docs
+// jsdoc js -d docs
+
+/**
+ * Main game entry point
+ * Initializes the game state and starts the game loop
+ * 
+ * @module game
+ */
+
+/** Canvas elements for rendering the game */
+const canvas = document.getElementById('gameCanvas');
+const ctx = canvas.getContext('2d');
+
+/** Game timing variables */
+let lastTimestamp = 0;
+const ENEMY_SPAWN_INTERVAL = 1000; // 1 second between enemy spawns
+let lastEnemySpawn = 0;
+let enemiesRemaining = 0;
+
+/** Drag and drop state tracking */
+let draggedTowerType = null;
+let hoverCell = null;
+
+/**
+ * Main game loop using requestAnimationFrame
+ * This is the heart of the game, running approximately 60 times per second
+ * 
+ * @param {number} timestamp - Current time in milliseconds, provided by requestAnimationFrame
+ * 
+ * Key concepts:
+ * - RequestAnimationFrame for smooth animation
+ * - Delta time for consistent motion regardless of frame rate
+ * - Game state management
+ */
+function gameLoop(timestamp) {
+    const deltaTime = timestamp - lastTimestamp;
+    lastTimestamp = timestamp;
+    
+    ctx.clearRect(0, 0, canvas.width, canvas.height);
+    
+    if (!gameState.isGameOver) {
+        if (gameState.phase === GamePhase.COMBAT) {
+            handleCombatPhase(timestamp, deltaTime);
+            
+            // Check for level completion
+            if (gameState.checkLevelComplete()) {
+                handleLevelComplete();
+            }
+        }
+    }
+    
+    renderGame();
+    requestAnimationFrame(gameLoop);
+}
+
+/**
+ * Handles all combat phase updates including enemy movement, attacks, and collisions
+ * 
+ * @param {number} timestamp - Current game time in milliseconds
+ * @param {number} deltaTime - Time elapsed since last frame
+ * 
+ * Key concepts:
+ * - Game state updates
+ * - Entity management (enemies, towers, projectiles)
+ * - Particle effects
+ * - Combat mechanics
+ */
+function handleCombatPhase(timestamp, deltaTime) {
+    spawnEnemies(timestamp);
+    updateEnemies();
+    // Update particle effects with time-based animation
+    gameState.particles = updateParticles(gameState.particles, timestamp, deltaTime);
+    // Remove expired projectiles
+    gameState.projectiles = gameState.projectiles.filter(p => timestamp - p.createdAt < p.lifetime);
+    
+    const cellSize = canvas.width / 20;
+    
+    // Process combat interactions
+    processTowerAttacks(
+        gameState.towers,
+        gameState.enemies,
+        gameState.projectiles,
+        gameState.particles,
+        timestamp,
+        cellSize
+    );
+    
+    processEnemyAttacks(
+        gameState.enemies,
+        gameState.towers,
+        gameState.particles,
+        timestamp,
+        cellSize
+    );
+    
+    // Remove defeated enemies and destroyed towers
+    // Uses array filter with a callback that has side effects (awarding currency)
+    gameState.enemies = gameState.enemies.filter(enemy => {
+        if (enemy.currentHealth <= 0) {
+            gameState.awardEnemyDestroyed();
+            return false;
+        }
+        return true;
+    });
+    gameState.towers = gameState.towers.filter(tower => tower.currentHealth > 0);
+}
+
+/**
+ * Spawns new enemies at regular intervals during combat
+ * 
+ * @param {number} timestamp - Current game time in milliseconds
+ * 
+ * Key concepts:
+ * - Time-based game events
+ * - Enemy creation and management
+ * - Game balance through spawn timing
+ */
+function spawnEnemies(timestamp) {
+    if (enemiesRemaining > 0 && timestamp - lastEnemySpawn > ENEMY_SPAWN_INTERVAL) {
+        gameState.enemies.push(createEnemy({ x: 0, y: gameState.path[0].y }));
+        lastEnemySpawn = timestamp;
+        enemiesRemaining--;
+    }
+}
+
+/**
+ * Renders all game elements to the canvas using a layered approach.
+ * This function demonstrates several key game development patterns:
+ * 
+ * 1. Canvas State Management:
+ *    - Uses save()/restore() to isolate rendering contexts
+ *    - Resets transform matrix to prevent state leaks
+ *    - Maintains clean state between rendering phases
+ * 
+ * 2. Layered Rendering Pattern:
+ *    - Renders in specific order (background → entities → UI)
+ *    - Each layer builds on top of previous layers
+ *    - Separates rendering concerns for easier maintenance
+ * 
+ * 3. Separation of Concerns:
+ *    - Each render function handles one specific type of game element
+ *    - UI rendering is isolated from game element rendering
+ *    - Clear boundaries between different rendering responsibilities
+ * 
+ * The rendering order is important:
+ * 1. Grid (background)
+ * 2. Particles (effects under entities)
+ * 3. Projectiles (dynamic game elements)
+ * 4. Towers (static game entities)
+ * 5. Enemies (moving game entities)
+ * 6. UI (top layer)
+ */
+function renderGame() {
+    // Reset the canvas transform matrix to identity
+    // This prevents any previous transformations from affecting new renders
+    ctx.setTransform(1, 0, 0, 1, 0, 0);
+    
+    // Clear the entire canvas to prevent ghosting
+    // This is crucial for animation smoothness
+    ctx.clearRect(0, 0, canvas.width, canvas.height);
+    
+    // Save the initial clean state
+    // This is part of the state stack pattern used in canvas rendering
+    ctx.save();
+    
+    // Render game world elements in specific order
+    // This creates the layered effect common in 2D games
+    renderGrid(ctx, gameState.grid);          // Background layer
+    renderParticles(ctx, gameState.particles); // Effect layer
+    renderProjectiles(ctx, gameState.projectiles); // Dynamic elements
+    renderTowers(ctx, gameState.towers);       // Static entities
+    renderEnemies(ctx, gameState.enemies);     // Moving entities
+    
+    // Restore to clean state before UI rendering
+    // This ensures UI rendering isn't affected by game world rendering
+    ctx.restore();
+    ctx.save();
+    
+    // Render UI elements last so they appear on top
+    // UI is rendered with its own clean state to prevent interference
+    renderUI(ctx, gameState);
+    
+    // Final state restoration
+    // Ensures clean state for next frame
+    ctx.restore();
+}
+
+/**
+ * Initializes the game by:
+ * 1. Generating the path for enemies to follow
+ * 2. Setting up initial enemy count
+ * 3. Binding event listeners
+ * 4. Starting the game loop
+ * 
+ * Uses Promise-based path generation to handle async initialization
+ */
+generatePath(gameState.grid).then(path => {
+    gameState.path = path;
+    // Random enemy count between 5-30 for variety
+    enemiesRemaining = Math.floor(Math.random() * 26) + 5;
+    initializeEventListeners();
+    // Start the game loop using requestAnimationFrame for smooth animation
+    requestAnimationFrame(gameLoop);
+});
+
+/**
+ * Transitions the game from placement to combat phase.
+ * Demonstrates state machine pattern commonly used in games.
+ * 
+ * Side effects:
+ * - Updates game phase
+ * - Disables UI elements
+ * - Updates visual feedback
+ */
+function startCombat() {
+    if (gameState.phase === GamePhase.PLACEMENT && gameState.towers.length > 0) {
+        // State transition
+        gameState.phase = GamePhase.COMBAT;
+        
+        // UI updates
+        document.getElementById('startCombat').disabled = true;
+        
+        // Visual feedback for disabled state
+        document.querySelectorAll('.tower-option').forEach(option => {
+            option.draggable = false;
+            option.style.cursor = 'not-allowed';
+            option.style.opacity = '0.5';
+        });
+    }
+}
+
+/**
+ * Sets up all event listeners for user interaction
+ * 
+ * Key concepts:
+ * - Event-driven programming
+ * - HTML5 Drag and Drop API
+ * - DOM manipulation
+ * - Method decoration (towers.push)
+ */
+function initializeEventListeners() {
+    // Add this at the beginning of the function
+    populateTowerPalette();
+    
+    // Set up tower palette drag events
+    document.querySelectorAll('.tower-option').forEach(option => {
+        option.addEventListener('dragstart', (e) => {
+            draggedTowerType = e.target.dataset.towerType;
+            // Required for Firefox - must set data for drag operation
+            e.dataTransfer.setData('text/plain', '');
+        });
+        
+        option.addEventListener('dragend', () => {
+            draggedTowerType = null;
+            hoverCell = null;
+        });
+    });
+
+    // Set up canvas drag and drop handling
+    canvas.addEventListener('dragover', (e) => {
+        e.preventDefault(); // Required for drop to work
+        const rect = canvas.getBoundingClientRect();
+        // Convert mouse coordinates to grid coordinates
+        const x = Math.floor((e.clientX - rect.left) / (canvas.width / 20));
+        const y = Math.floor((e.clientY - rect.top) / (canvas.height / 20));
+        
+        // Validate grid boundaries
+        if (x >= 0 && x < 20 && y >= 0 && y < 20) {
+            hoverCell = { x, y };
+        } else {
+            hoverCell = null;
+        }
+    });
+
+    canvas.addEventListener('dragleave', () => {
+        hoverCell = null;
+    });
+
+    // Handle tower placement on drop
+    canvas.addEventListener('drop', (e) => {
+        e.preventDefault();
+        if (!draggedTowerType || !hoverCell) return;
+
+        const tower = TowerTypes[draggedTowerType];
+        // Validate placement and currency
+        if (
+            gameState.grid[hoverCell.y][hoverCell.x] === 'empty' &&
+            gameState.currency >= tower.cost
+        ) {
+            gameState.grid[hoverCell.y][hoverCell.x] = 'tower';
+            gameState.towers.push(createTower(draggedTowerType, { ...hoverCell }));
+            gameState.currency -= tower.cost;
+        }
+        
+        // Reset drag state
+        draggedTowerType = null;
+        hoverCell = null;
+    });
+
+    // Combat phase transition
+    document.getElementById('startCombat').addEventListener('click', startCombat);
+    
+    // Dynamic button state management
+    const updateStartButton = () => {
+        const button = document.getElementById('startCombat');
+        button.disabled = gameState.towers.length === 0;
+    };
+    
+    // Decorator pattern: Enhance towers.push to update UI
+    const originalPush = gameState.towers.push;
+    gameState.towers.push = function(...args) {
+        const result = originalPush.apply(this, args);
+        updateStartButton();
+        return result;
+    };
+    
+    updateStartButton();
+}
+
+/**
+ * Handles the transition between levels
+ * Shows completion message and sets up next level
+ */
+function handleLevelComplete() {
+    // Pause the game briefly
+    gameState.phase = GamePhase.TRANSITION;
+    
+    // Calculate ammo bonus
+    let ammoBonus = 0;
+    gameState.towers.forEach(tower => {
+        ammoBonus += tower.ammo * 0.25;
+    });
+    ammoBonus = Math.floor(ammoBonus);
+    
+    // Show level complete message with modal
+    const message = `
+        Level ${gameState.level} Complete!
+        
+        Stats:
+        - Enemies Destroyed: ${gameState.enemiesDestroyed}
+        - Enemies Escaped: ${gameState.enemiesEscaped}
+        
+        Bonuses:
+        - Current Money: $${gameState.currency}
+        - Remaining Ammo Bonus: +$${ammoBonus}
+        
+        Total After Bonuses: $${gameState.currency + ammoBonus + 10}
+        
+        Ready for Level ${gameState.level + 1}?
+    `;
+    
+    // Use setTimeout to allow the final frame to render
+    setTimeout(() => {
+        if (confirm(message)) {
+            startNextLevel();
+        }
+    }, 100);
+}
+
+/**
+ * Sets up the next level
+ * Increases difficulty and resets the game state while preserving currency
+ */
+function startNextLevel() {
+    gameState.advanceToNextLevel();
+    
+    // Generate new path
+    generatePath(gameState.grid).then(path => {
+        gameState.path = path;
+        
+        // Exponential enemy scaling
+        const baseEnemies = 5;
+        const scalingFactor = 1.5;  // Each level increases by 50%
+        enemiesRemaining = Math.floor(baseEnemies * Math.pow(scalingFactor, gameState.level - 1));
+        
+        // Re-enable tower palette
+        document.querySelectorAll('.tower-option').forEach(option => {
+            option.draggable = true;
+            option.style.cursor = 'grab';
+            option.style.opacity = '1';
+        });
+        
+        // Reset start button
+        const startButton = document.getElementById('startCombat');
+        startButton.disabled = false;
+        startButton.textContent = `Start Level ${gameState.level}`;
+    });
+}
+
+// Update the renderUI function to show current level
+function renderUI(ctx, gameState) {
+    ctx.fillStyle = 'black';
+    ctx.font = '20px Arial';
+    ctx.fillText(`Level: ${gameState.level}`, 10, 30);
+    ctx.fillText(`Currency: $${gameState.currency}`, 10, 60);
+    ctx.fillText(`Phase: ${gameState.phase}`, 10, 90);
+    ctx.fillText(`Destroyed: ${gameState.enemiesDestroyed}`, 10, 120);
+    ctx.fillText(`Escaped: ${gameState.enemiesEscaped}`, 10, 150);
+}
+
+/**
+ * Dynamically populates the tower palette based on TowerTypes
+ */
+function populateTowerPalette() {
+    const palette = document.querySelector('.tower-palette');
+    // Clear existing tower options
+    palette.innerHTML = '';
+    
+    // Create tower options dynamically
+    Object.entries(TowerTypes).forEach(([type, tower]) => {
+        const towerOption = document.createElement('div');
+        towerOption.className = 'tower-option';
+        towerOption.draggable = true;
+        towerOption.dataset.towerType = type;
+        
+        towerOption.innerHTML = `
+            <div class="tower-preview" style="background: ${tower.color};"></div>
+            <div class="tower-info">
+                <div class="tower-name">${tower.name}</div>
+                <div class="tower-cost">Cost: $${tower.cost}</div>
+                <div class="tower-ammo">Ammo: ${tower.maxAmmo}</div>
+            </div>
+        `;
+        
+        palette.appendChild(towerOption);
+    });
+    
+    // Add start combat button
+    const startButton = document.createElement('button');
+    startButton.id = 'startCombat';
+    startButton.className = 'start-button';
+    startButton.textContent = 'Start Run';
+    palette.appendChild(startButton);
+}
+
+/**
+ * Handles game over state and prompts for restart
+ */
+function handleGameOver() {
+    gameState.phase = GamePhase.TRANSITION;
+    gameState.isGameOver = true;
+    
+    const message = `
+        Game Over!
+        
+        Final Stats:
+        Level Reached: ${gameState.level}
+        Enemies Destroyed: ${gameState.enemiesDestroyed}
+        Enemies Escaped: ${gameState.enemiesEscaped}
+        
+        Would you like to restart from Level 1?
+    `;
+    
+    setTimeout(() => {
+        if (confirm(message)) {
+            restartGame();
+        }
+    }, 100);
+}
+
+/**
+ * Restarts the game from level 1 with fresh state
+ */
+function restartGame() {
+    gameState.resetGame();
+    
+    // Generate new path
+    generatePath(gameState.grid).then(path => {
+        gameState.path = path;
+        
+        // Reset enemy count to level 1
+        enemiesRemaining = 5;
+        
+        // Re-enable tower palette
+        document.querySelectorAll('.tower-option').forEach(option => {
+            option.draggable = true;
+            option.style.cursor = 'grab';
+            option.style.opacity = '1';
+        });
+        
+        // Reset start button
+        const startButton = document.getElementById('startCombat');
+        startButton.disabled = false;
+        startButton.textContent = 'Start Level 1';
+    });
+} 
\ No newline at end of file
diff --git a/html/tower/js/gameState.js b/html/tower/js/gameState.js
new file mode 100644
index 0000000..ac7a968
--- /dev/null
+++ b/html/tower/js/gameState.js
@@ -0,0 +1,288 @@
+/**
+ * Game State Module
+ * 
+ * This module defines the game state and game phases
+ * 
+ * @module gameState
+ */
+
+
+/**
+ * Game phases
+ * 
+ * @enum {string}
+ * @readonly
+ */
+const GamePhase = {
+    PLACEMENT: 'place',
+    COMBAT: 'run'
+};
+
+/**
+ * Tower types
+ * 
+ * @enum {string}
+ * @readonly
+ */
+const TowerTypes = {
+    BASIC: {
+        name: 'Basic',
+        cost: 5,
+        range: 3,
+        damage: 1,
+        attackSpeed: 1,
+        color: '#3498db',
+        maxAmmo: 75
+    },
+    RAPID: {
+        name: 'Fast',
+        cost: 10,
+        range: 2,
+        damage: 1,
+        attackSpeed: 3,
+        color: '#16a085',
+        maxAmmo: 50
+    },
+    SNIPER: {
+        name: 'Distance',
+        cost: 20,
+        range: 6,
+        damage: 2,
+        attackSpeed: 0.5,
+        color: '#8e44ad',
+        maxAmmo: 50
+    },
+    GOOP: {
+        name: 'Goop',
+        cost: 20,
+        range: 3,
+        damage: 0,
+        attackSpeed: 1,
+        color: '#27ae60',
+        special: 'slow',
+        slowAmount: 0.75,
+        maxAmmo: 25
+    },
+    AOE: {
+        name: 'AOE',
+        cost: 25,
+        range: 2,
+        damage: 3,
+        attackSpeed: 0.25,
+        color: '#d35400',
+        special: 'aoe',
+        aoeRadius: 2,
+        maxAmmo: 25
+    }
+};
+
+/**
+ * Particle types
+ * 
+ * @enum {string}
+ * @readonly
+ */
+const ParticleTypes = {
+    DEATH_PARTICLE: {
+        lifetime: 1000, // milliseconds
+        speed: 0.1,
+        colors: ['#e74c3c', '#c0392b', '#d35400', '#e67e22']
+    },
+    PROJECTILE: {
+        lifetime: 300,
+        speed: 0.3,
+        color: '#ecf0f1'
+    },
+    AOE_EXPLOSION: {
+        lifetime: 500,
+        initialRadius: 10,
+        finalRadius: 60,
+        color: '#d35400',
+        ringWidth: 3
+    },
+    SLIME_TRAIL: {
+        lifetime: 800,
+        color: '#27ae60',  // Same as Goop tower
+        size: 12,
+        fadeStart: 0.2     // When the fade should begin (percentage of lifetime)
+    }
+};
+
+/**
+ * Enemy types
+ * 
+ * @enum {string}
+ * @readonly
+ */
+const EnemyTypes = {
+    BASIC: {
+        color: '#c0392b',
+        baseHealth: { min: 2, max: 6 },
+        speed: { min: 1, max: 1.5 },
+        damage: 0,
+        isRanged: false
+    },
+    RANGED: {
+        color: '#2c3e50',
+        baseHealth: { min: 1, max: 4 },
+        speed: { min: 0.7, max: 1.2 },
+        damage: 0.3,
+        attackRange: 3,
+        attackSpeed: 1, // attacks per second
+        isRanged: true
+    }
+};
+
+/**
+ * Creates a tower
+ * 
+ * @param {string} type - Tower type
+ * @param {Object} position - Position of the tower
+ */
+function createTower(type, position) {
+    const towerType = TowerTypes[type];
+    return {
+        ...towerType,
+        type,
+        position,
+        lastAttackTime: 0,
+        currentHealth: 10,
+        maxHealth: 10,
+        ammo: towerType.maxAmmo  // Initialize ammo
+    };
+}
+
+/**
+ * Creates an enemy
+ * 
+ * @param {Object} startPosition - Starting position of the enemy
+ */
+function createEnemy(startPosition) {
+    // 20% chance for ranged enemy
+    const type = Math.random() < 0.2 ? 'RANGED' : 'BASIC';
+    const enemyType = EnemyTypes[type];
+    
+    // Scale health ranges with level
+    const levelScaling = 1 + (gameState.level - 1) * 0.25; // increase health by 25% per level
+    const minHealth = Math.floor(enemyType.baseHealth.min * levelScaling);
+    const maxHealth = Math.floor(enemyType.baseHealth.max * levelScaling);
+    
+    const health = Math.floor(Math.random() * 
+        (maxHealth - minHealth + 1)) + minHealth;
+    
+    return {
+        position: { ...startPosition },
+        currentHealth: health,
+        maxHealth: health,
+        speed: enemyType.speed.min + Math.random() * (enemyType.speed.max - enemyType.speed.min),
+        pathIndex: 0,
+        type,
+        lastAttackTime: 0,
+        damage: enemyType.damage
+    };
+}
+
+/**
+ * Creates a particle
+ * 
+ * @param {string} type - Particle type
+ * @param {Object} position - Position of the particle
+ */
+function createParticle(type, position, angle) {
+    return {
+        position: { ...position },
+        velocity: {
+            x: Math.cos(angle) * type.speed,
+            y: Math.sin(angle) * type.speed
+        },
+        color: Array.isArray(type.colors) 
+            ? type.colors[Math.floor(Math.random() * type.colors.length)]
+            : type.color,
+        createdAt: performance.now(),
+        lifetime: type.lifetime,
+        size: 3 + Math.random() * 2
+    };
+}
+
+
+/**
+ * Game state
+ * 
+ * @type {Object}
+ */
+const gameState = {
+    grid: Array(20).fill().map(() => Array(20).fill('empty')),
+    path: [],
+    towers: [],
+    enemies: [],
+    currency: 100,
+    phase: GamePhase.PLACEMENT,
+    isGameOver: false,
+    particles: [],
+    projectiles: [],
+    enemiesDestroyed: 0,
+    enemiesEscaped: 0,
+    level: 1,
+    
+    /**
+     * Resets the game state
+     */
+    resetGame() {
+        this.grid = Array(20).fill().map(() => Array(20).fill('empty'));
+        this.path = [];
+        this.towers = [];
+        this.enemies = [];
+        this.currency = 100;
+        this.phase = GamePhase.PLACEMENT;
+        this.isGameOver = false;
+        this.particles = [];
+        this.projectiles = [];
+        this.enemiesDestroyed = 0;
+        this.enemiesEscaped = 0;
+        this.level = 1;
+    },
+    
+
+    /**
+     * Awards the enemy destroyed
+     */
+    awardEnemyDestroyed() {
+        this.enemiesDestroyed++;
+        // Random reward between 1 and 3
+        const reward = Math.floor(Math.random() * 3) + 1;
+        this.currency += reward;
+    },
+    
+
+    /**
+     * Checks if the level is complete
+     * 
+     * @returns {boolean}
+     */
+    checkLevelComplete() {
+        return this.enemies.length === 0 && 
+               enemiesRemaining === 0 && 
+               this.phase === GamePhase.COMBAT;
+    },
+    
+
+    /**
+     * Advances to the next level
+     */
+    advanceToNextLevel() {
+
+        let ammoBonus = 0;
+        this.towers.forEach(tower => {
+            ammoBonus += tower.ammo * 0.25;
+        });
+        this.currency += Math.floor(ammoBonus);  // Round down to nearest whole number
+        
+        this.level++;
+        this.phase = GamePhase.PLACEMENT;
+        this.towers = [];
+        this.enemies = [];
+        this.projectiles = [];
+        this.particles = [];
+        this.grid = Array(20).fill().map(() => Array(20).fill('empty'));
+    }
+}; 
\ No newline at end of file
diff --git a/html/tower/js/mechanics.js b/html/tower/js/mechanics.js
new file mode 100644
index 0000000..bc73fff
--- /dev/null
+++ b/html/tower/js/mechanics.js
@@ -0,0 +1,430 @@
+/**
+ * Combat Mechanics Module
+ *  
+ * This module handles all combat-related game mechanics including:
+ * 1. Enemy movement and behavior
+ * 2. Tower attacks and targeting
+ * 3. Projectile and particle systems
+ * 4. Status effects and special abilities
+ * 
+ * @module mechanics
+ */
+
+/**
+ * Updates all enemy states including movement, health, and status effects
+ * Implements core game loop mechanics for enemy behavior
+ * 
+ * Key features:
+ * - Path following
+ * - Health management
+ * - Status effect processing
+ * - Collision detection
+ * 
+ * @returns {void}
+ */
+function updateEnemies() {
+    const cellSize = canvas.width / 20;
+    
+    gameState.enemies = gameState.enemies.filter(enemy => {
+        // Initialize progress tracking for new enemies
+        if (typeof enemy.progress === 'undefined') {
+            enemy.progress = 0;
+        }
+        
+        // Update movement progress
+        enemy.progress += enemy.speed * 0.001;
+        
+        // Handle path completion
+        if (enemy.progress >= 1) {
+            gameState.enemiesEscaped++;
+            // Deduct currency when enemy escapes
+            gameState.currency = Math.max(0, gameState.currency - 10);
+            // Check for game over
+            if (gameState.currency <= 0) {
+                handleGameOver();
+            }
+            return false;
+        }
+        
+        // Projectile collision detection and damage application
+        const hitByProjectile = gameState.projectiles.some(projectile => {
+            // Calculate current projectile position based on lifetime
+            const age = performance.now() - projectile.createdAt;
+            const progress = Math.min(age / projectile.lifetime, 1);
+            
+            const currentX = projectile.startPos.x + (projectile.targetPos.x - projectile.startPos.x) * progress;
+            const currentY = projectile.startPos.y + (projectile.targetPos.y - projectile.startPos.y) * progress;
+            
+            const distance = Math.hypot(
+                enemy.position.x - currentX,
+                enemy.position.y - currentY
+            );
+            
+            if (distance < 0.5) {
+                // Apply damage when projectile hits
+                enemy.currentHealth -= projectile.damage;
+                return true;
+            }
+            return false;
+        });
+        
+        if (enemy.currentHealth <= 0) {
+            gameState.awardEnemyDestroyed();
+            // Create death particles
+            gameState.particles.push(...createDeathParticles(enemy, cellSize));
+            return false;
+        }
+        
+        // Update position based on path progress
+        const pathPosition = getPathPosition(enemy.progress, gameState.path);
+        enemy.position.x = pathPosition.x;
+        enemy.position.y = pathPosition.y;
+        
+        // Process slow effect expiration
+        if (enemy.slowed && performance.now() > enemy.slowExpiry) {
+            enemy.slowed = false;
+            enemy.slowStacks = 0;
+            enemy.currentSlowAmount = 0;
+            enemy.speed = enemy.originalSpeed;
+        }
+        
+        // Visual feedback for slowed status
+        if (enemy.slowed && Math.random() < 0.2 + (enemy.slowStacks * 0.05)) {
+            gameState.particles.push(createSlimeTrail(enemy, cellSize));
+        }
+        
+        return true;
+    });
+    
+    // Remove projectiles that hit enemies
+    gameState.projectiles = gameState.projectiles.filter(projectile => {
+        const age = performance.now() - projectile.createdAt;
+        if (age >= projectile.lifetime) return false;
+        
+        const progress = age / projectile.lifetime;
+        const currentX = projectile.startPos.x + (projectile.targetPos.x - projectile.startPos.x) * progress;
+        const currentY = projectile.startPos.y + (projectile.targetPos.y - projectile.startPos.y) * progress;
+        
+        const hitEnemy = gameState.enemies.some(enemy => {
+            const distance = Math.hypot(
+                enemy.position.x - currentX,
+                enemy.position.y - currentY
+            );
+            return distance < 0.5;
+        });
+        
+        return !hitEnemy;
+    });
+}
+
+/**
+ * Updates particle effects with time-based animation
+ * Implements particle system lifecycle management
+ * 
+ * @param {Array<Object>} particles - Array of particle objects
+ * @param {number} timestamp - Current game timestamp
+ * @param {number} deltaTime - Time elapsed since last frame
+ * @returns {Array<Object>} Updated particles array
+ */
+function updateParticles(particles, timestamp, deltaTime) {
+    return particles.filter(particle => {
+        const age = timestamp - particle.createdAt;
+        if (age > particle.lifetime) return false;
+        
+        if (particle.velocity) {
+            particle.position.x += particle.velocity.x * deltaTime;
+            particle.position.y += particle.velocity.y * deltaTime;
+        }
+        
+        return true;
+    });
+}
+
+/**
+ * Creates death effect particles for defeated entities
+ * Implements visual feedback system
+ * 
+ * @param {Object} target - The defeated entity
+ * @param {number} cellSize - Size of grid cell for scaling
+ * @returns {Array<Object>} Array of particle objects
+ */
+function createDeathParticles(target, cellSize) {
+    const particles = [];
+    const centerX = (target.position.x + 0.5) * cellSize;
+    const centerY = (target.position.y + 0.5) * cellSize;
+    
+    const particleCount = 8 + Math.floor(Math.random() * 8);
+    for (let i = 0; i < particleCount; i++) {
+        const baseAngle = (Math.PI * 2 * i) / particleCount;
+        const randomAngle = baseAngle + (Math.random() - 0.5) * 1.5;
+        const speedMultiplier = 0.7 + Math.random() * 0.6;
+        const startOffset = Math.random() * 5;
+        
+        particles.push(createParticle(
+            {
+                ...ParticleTypes.DEATH_PARTICLE,
+                speed: ParticleTypes.DEATH_PARTICLE.speed * speedMultiplier,
+                lifetime: ParticleTypes.DEATH_PARTICLE.lifetime * (0.8 + Math.random() * 0.4)
+            },
+            {
+                x: centerX + Math.cos(randomAngle) * startOffset,
+                y: centerY + Math.sin(randomAngle) * startOffset
+            },
+            randomAngle
+        ));
+    }
+    return particles;
+}
+
+/**
+ * Processes tower attacks and targeting
+ * Implements combat mechanics and special abilities
+ * 
+ * @param {Array<Object>} towers - Array of tower objects
+ * @param {Array<Object>} enemies - Array of enemy objects
+ * @param {Array<Object>} projectiles - Array of projectile objects
+ * @param {Array<Object>} particles - Array of particle objects
+ * @param {number} timestamp - Current game timestamp
+ * @param {number} cellSize - Size of grid cell for scaling
+ */
+function processTowerAttacks(towers, enemies, projectiles, particles, timestamp, cellSize) {
+    towers.forEach(tower => {
+        if (timestamp - tower.lastAttackTime > 1000 / tower.attackSpeed) {
+            const enemiesInRange = findEnemiesInRange(tower, enemies);
+            
+            if (enemiesInRange.length > 0 && tower.ammo > 0) {
+                const target = enemiesInRange[0];
+                handleTowerAttack(tower, target, projectiles, particles, timestamp, cellSize);
+            }
+        }
+    });
+}
+
+function findEnemiesInRange(tower, enemies) {
+    return enemies.filter(enemy => {
+        const dx = enemy.position.x - tower.position.x;
+        const dy = enemy.position.y - tower.position.y;
+        return Math.sqrt(dx * dx + dy * dy) <= tower.range;
+    });
+}
+
+function createAOEExplosion(position, cellSize) {
+    return {
+        position: {
+            x: (position.x + 0.5) * cellSize,
+            y: (position.y + 0.5) * cellSize
+        },
+        createdAt: performance.now(),
+        type: 'AOE_EXPLOSION',
+        ...ParticleTypes.AOE_EXPLOSION
+    };
+}
+
+function createSlimeTrail(enemy, cellSize) {
+    return {
+        position: {
+            x: (enemy.position.x + 0.5) * cellSize,
+            y: (enemy.position.y + 0.5) * cellSize
+        },
+        createdAt: performance.now(),
+        type: 'SLIME_TRAIL',
+        ...ParticleTypes.SLIME_TRAIL
+    };
+}
+
+/**
+ * Handles individual tower attack logic including special effects
+ * Implements tower ability system
+ * 
+ * @param {Object} tower - Attacking tower
+ * @param {Object} target - Target enemy
+ * @param {Array<Object>} projectiles - Projectile array
+ * @param {Array<Object>} particles - Particle array
+ * @param {number} timestamp - Current game timestamp
+ * @param {number} cellSize - Grid cell size
+ */
+function handleTowerAttack(tower, target, projectiles, particles, timestamp, cellSize) {
+    // Only attack if we have ammo
+    if (tower.ammo <= 0) return;
+    
+    // Decrease ammo (already checked it's > 0)
+    tower.ammo--;
+    
+    // Create projectile
+    projectiles.push({
+        startPos: { ...tower.position },
+        targetPos: { ...target.position },
+        createdAt: timestamp,
+        lifetime: 300,
+        towerType: tower.type,
+        damage: tower.damage
+    });
+    
+    // Process special abilities
+    if (tower.special === 'slow') {
+        handleSlowEffect(target, tower, timestamp, particles, cellSize);
+    } else if (tower.special === 'aoe') {
+        handleAOEEffect(target, tower, gameState.enemies, particles, cellSize);
+    }
+    
+    tower.lastAttackTime = timestamp;
+}
+
+/**
+ * Processes enemy attack behaviors and targeting
+ * Implements enemy combat AI
+ * 
+ * @param {Array<Object>} enemies - Array of enemy objects
+ * @param {Array<Object>} towers - Array of tower objects
+ * @param {Array<Object>} particles - Particle effect array
+ * @param {number} timestamp - Current game timestamp
+ * @param {number} cellSize - Grid cell size
+ */
+function processEnemyAttacks(enemies, towers, particles, timestamp, cellSize) {
+    enemies.forEach(enemy => {
+        if (!EnemyTypes[enemy.type].isRanged) return;
+        
+        if (timestamp - enemy.lastAttackTime > 1000 / EnemyTypes[enemy.type].attackSpeed) {
+            const towersInRange = findTowersInRange(enemy, towers);
+            
+            if (towersInRange.length > 0) {
+                const target = towersInRange[0];
+                handleEnemyAttack(enemy, target, particles, timestamp, cellSize);
+            }
+        }
+    });
+}
+
+/**
+ * Finds towers within enemy attack range
+ * Implements targeting system for enemies
+ * 
+ * @param {Object} enemy - Enemy doing the targeting
+ * @param {Array<Object>} towers - Array of potential tower targets
+ * @returns {Array<Object>} Array of towers in range
+ */
+function findTowersInRange(enemy, towers) {
+    return towers.filter(tower => {
+        const dx = tower.position.x - enemy.position.x;
+        const dy = tower.position.y - enemy.position.y;
+        return Math.sqrt(dx * dx + dy * dy) <= EnemyTypes[enemy.type].attackRange;
+    });
+}
+
+/**
+ * Handles enemy attack execution and effects
+ * Implements enemy combat mechanics
+ * 
+ * @param {Object} enemy - Attacking enemy
+ * @param {Object} tower - Target tower
+ * @param {Array<Object>} particles - Particle array for effects
+ * @param {number} timestamp - Current game timestamp
+ * @param {number} cellSize - Grid cell size
+ */
+function handleEnemyAttack(enemy, tower, particles, timestamp, cellSize) {
+    // Create projectile effect
+    particles.push(createParticle(
+        {
+            ...ParticleTypes.PROJECTILE,
+            color: '#8e44ad80', // Semi-transparent purple
+            lifetime: 500
+        },
+        {
+            x: (enemy.position.x + 0.5) * cellSize,
+            y: (enemy.position.y + 0.5) * cellSize
+        },
+        Math.atan2(
+            tower.position.y - enemy.position.y,
+            tower.position.x - enemy.position.x
+        )
+    ));
+    
+    // Apply damage and update tower state
+    tower.currentHealth -= enemy.damage;
+    enemy.lastAttackTime = timestamp;
+    
+    // Dynamic damage reduction based on tower health
+    tower.damage = TowerTypes[tower.type].damage * (tower.currentHealth / tower.maxHealth);
+}
+
+/**
+ * Handles slow effect application and stacking
+ * Implements status effect system
+ * 
+ * @param {Object} target - Enemy to apply slow to
+ * @param {Object} tower - Tower applying the effect
+ * @param {number} timestamp - Current game timestamp
+ * @param {Array<Object>} particles - Particle array for effects
+ * @param {number} cellSize - Grid cell size
+ */
+function handleSlowEffect(target, tower, timestamp, particles, cellSize) {
+    // Initialize slow effect tracking
+    if (!target.slowStacks) {
+        target.slowStacks = 0;
+    }
+    
+    const maxStacks = 5;  // Maximum 5 stacks
+    if (target.slowStacks < maxStacks) {
+        target.slowStacks++;
+        // Each stack slows by an additional 10% (multiplicative)
+        const newSlowAmount = 1 - Math.pow(0.9, target.slowStacks);
+        
+        // Only update if new slow is stronger
+        if (!target.slowed || newSlowAmount > target.currentSlowAmount) {
+            const originalSpeed = target.originalSpeed || target.speed;
+            target.originalSpeed = originalSpeed;
+            target.speed = originalSpeed * (1 - newSlowAmount);
+            target.currentSlowAmount = newSlowAmount;
+            target.slowed = true;
+        }
+        
+        // Visual feedback particles
+        for (let i = 0; i < 4 + target.slowStacks; i++) {
+            particles.push(createSlimeTrail(target, cellSize));
+        }
+    }
+    
+    // Refresh effect duration
+    target.slowExpiry = timestamp + 2000;  // 2 second duration
+}
+
+/**
+ * Handles AOE (Area of Effect) damage and visual effects
+ * Implements area damage system
+ * 
+ * @param {Object} target - Primary target
+ * @param {Object} tower - Tower dealing AOE damage
+ * @param {Array<Object>} enemies - All enemies for AOE calculation
+ * @param {Array<Object>} particles - Particle array for effects
+ * @param {number} cellSize - Grid cell size
+ */
+function handleAOEEffect(target, tower, enemies, particles, cellSize) {
+    // Find all enemies in AOE radius
+    const enemiesInAOE = enemies.filter(enemy => {
+        const dx = enemy.position.x - target.position.x;
+        const dy = enemy.position.y - target.position.y;
+        return Math.sqrt(dx * dx + dy * dy) <= tower.aoeRadius;
+    });
+    
+    // Create explosion effect
+    particles.push(createAOEExplosion(target.position, cellSize));
+    
+    // Apply AOE damage
+    enemiesInAOE.forEach(enemy => {
+        enemy.currentHealth -= tower.damage;
+        if (enemy.currentHealth <= 0) {
+            particles.push(...createDeathParticles(enemy, cellSize));
+        }
+    });
+}
+
+// Update createEnemy to track original speed
+function createEnemy(startPosition) {
+    const enemy = {
+        // ... existing enemy properties ...
+        slowStacks: 0,
+        currentSlowAmount: 0
+    };
+    enemy.originalSpeed = enemy.speed;  // Store original speed
+    return enemy;
+} 
\ No newline at end of file
diff --git a/html/tower/js/path.js b/html/tower/js/path.js
new file mode 100644
index 0000000..8b840ce
--- /dev/null
+++ b/html/tower/js/path.js
@@ -0,0 +1,176 @@
+/**
+ * Path Generation Module
+ * 
+ * This module demonstrates several advanced game development concepts:
+ * 1. Procedural Content Generation (PCG)
+ * 2. Pathfinding algorithms
+ * 3. Constraint-based generation
+ * 
+ * @module path
+ */
+
+/**
+ * Generates a valid path through the game grid using a modified depth-first search.
+ * This algorithm ensures:
+ * - Path always moves from left to right
+ * - No diagonal movements
+ * - No path segments touch each other (except at turns)
+ * - Path is always completable
+ * 
+ * @param {Array<Array<string>>} grid - 2D array representing the game grid
+ * @returns {Promise<Array<{x: number, y: number}>>} Promise resolving to array of path coordinates
+ * 
+ * Implementation uses:
+ * - Backtracking algorithm pattern
+ * - Constraint satisfaction
+ * - Random selection for variety
+ */
+function generatePath(grid) {
+    const width = grid[0].length;
+    const height = grid.length;
+    
+    // Initialize with random start point on left edge
+    const startY = Math.floor(Math.random() * height);
+    let currentPos = { x: 0, y: startY };
+    
+    const path = [currentPos];
+    grid[startY][0] = 'path';
+    
+    /**
+     * Determines valid moves from current position based on game rules
+     * Uses constraint checking to ensure path validity
+     * 
+     * @param {Object} pos - Current position {x, y}
+     * @returns {Array<{x: number, y: number}>} Array of valid next positions
+     */
+    function getValidMoves(pos) {
+        const moves = [];
+        // Prioritize right movement for path progression
+        const directions = [
+            { x: 1, y: 0 },  // right
+            { x: 0, y: -1 }, // up
+            { x: 0, y: 1 }   // down
+        ];
+        
+        for (const dir of directions) {
+            const newX = pos.x + dir.x;
+            const newY = pos.y + dir.y;
+            
+            // Enforce boundary constraints
+            if (newX < 0 || newX >= width || newY < 0 || newY >= height) {
+                continue;
+            }
+            
+            // Check path isolation constraint
+            if (grid[newY][newX] === 'empty' && !hasAdjacentPath(newX, newY, grid)) {
+                moves.push({ x: newX, y: newY });
+            }
+        }
+        
+        return moves;
+    }
+    
+    /**
+     * Checks if a position has adjacent path tiles (excluding previous path tile)
+     * Implements path isolation constraint
+     * 
+     * @param {number} x - X coordinate to check
+     * @param {number} y - Y coordinate to check
+     * @param {Array<Array<string>>} grid - Current grid state
+     * @returns {boolean} True if position has adjacent path tiles
+     */
+    function hasAdjacentPath(x, y, grid) {
+        const adjacentCells = [
+            { x: x, y: y - 1 },     // up
+            { x: x, y: y + 1 },     // down
+            { x: x - 1, y: y },     // left
+            { x: x + 1, y: y },     // right
+        ];
+        
+        return adjacentCells.some(cell => {
+            if (cell.x < 0 || cell.x >= width || cell.y < 0 || cell.y >= height) {
+                return false;
+            }
+            return grid[cell.y][cell.x] === 'path' && 
+                   !path.some(p => p.x === cell.x && p.y === cell.y);
+        });
+    }
+    
+    // Main path generation loop with backtracking
+    while (currentPos.x < width - 1) {
+        const moves = getValidMoves(currentPos);
+        
+        if (moves.length === 0) {
+            // Backtrack when no valid moves exist
+            if (path.length <= 1) {
+                // Restart if backtracking fails
+                return generatePath(grid);
+            }
+            
+            path.pop();
+            const lastPos = path[path.length - 1];
+            grid[currentPos.y][currentPos.x] = 'empty';
+            currentPos = lastPos;
+            continue;
+        }
+        
+        // Random selection for path variety
+        const nextMove = moves[Math.floor(Math.random() * moves.length)];
+        currentPos = nextMove;
+        path.push(currentPos);
+        grid[currentPos.y][currentPos.x] = 'path';
+    }
+    
+    return Promise.resolve(path);
+}
+
+/**
+ * Calculates a position along the path based on a progress value
+ * Implements smooth entity movement along path segments
+ * 
+ * @param {number} progress - Progress along path (0-1)
+ * @param {Array<{x: number, y: number}>} path - Array of path coordinates
+ * @returns {{x: number, y: number}} Interpolated position along path
+ * 
+ * Uses:
+ * - Linear interpolation (lerp)
+ * - Path segment traversal
+ * - Normalized progress tracking
+ */
+function getPathPosition(progress, path) {
+    // Normalize progress to valid range
+    progress = Math.max(0, Math.min(1, progress));
+    
+    // Calculate total path length for normalization
+    let totalLength = 0;
+    for (let i = 1; i < path.length; i++) {
+        const dx = path[i].x - path[i-1].x;
+        const dy = path[i].y - path[i-1].y;
+        totalLength += Math.sqrt(dx * dx + dy * dy);
+    }
+    
+    // Convert progress to distance along path
+    const targetDistance = progress * totalLength;
+    
+    // Find appropriate path segment
+    let currentDistance = 0;
+    for (let i = 1; i < path.length; i++) {
+        const dx = path[i].x - path[i-1].x;
+        const dy = path[i].y - path[i-1].y;
+        const segmentLength = Math.sqrt(dx * dx + dy * dy);
+        
+        if (currentDistance + segmentLength >= targetDistance) {
+            // Linear interpolation within segment
+            const segmentProgress = (targetDistance - currentDistance) / segmentLength;
+            return {
+                x: path[i-1].x + dx * segmentProgress,
+                y: path[i-1].y + dy * segmentProgress
+            };
+        }
+        
+        currentDistance += segmentLength;
+    }
+    
+    // Fallback to end of path
+    return { ...path[path.length - 1] };
+} 
\ No newline at end of file
diff --git a/html/tower/js/renderer.js b/html/tower/js/renderer.js
new file mode 100644
index 0000000..fce4b88
--- /dev/null
+++ b/html/tower/js/renderer.js
@@ -0,0 +1,381 @@
+/**
+ * Rendering Module
+ * 
+ * This module handles all game rendering operations using HTML5 Canvas.
+ * Demonstrates key game development patterns:
+ * 1. Layer-based rendering
+ * 2. Particle systems
+ * 3. Visual state feedback
+ * 4. Canvas state management
+ * 
+ * @module renderer
+ */
+
+/**
+ * Renders the game grid with path and hover previews
+ * Implements visual feedback for player actions
+ * 
+ * @param {CanvasRenderingContext2D} ctx - Canvas rendering context
+ * @param {Array<Array<string>>} grid - Game grid state
+ */
+function renderGrid(ctx, grid) {
+    const cellSize = canvas.width / 20;
+    
+    // Draw grid lines for visual reference
+    ctx.strokeStyle = '#ccc';
+    ctx.lineWidth = 1;
+    
+    for (let i = 0; i <= 20; i++) {
+        // Vertical lines
+        ctx.beginPath();
+        ctx.moveTo(i * cellSize, 0);
+        ctx.lineTo(i * cellSize, canvas.height);
+        ctx.stroke();
+        
+        // Horizontal lines
+        ctx.beginPath();
+        ctx.moveTo(0, i * cellSize);
+        ctx.lineTo(canvas.width, i * cellSize);
+        ctx.stroke();
+    }
+    
+    // Render grid cells with path highlighting
+    grid.forEach((row, y) => {
+        row.forEach((cell, x) => {
+            if (cell === 'path') {
+                ctx.fillStyle = '#f4a460';
+                ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize);
+            }
+        });
+    });
+
+    // Render tower placement preview
+    if (gameState.phase === GamePhase.PLACEMENT && draggedTowerType && hoverCell) {
+        const tower = TowerTypes[draggedTowerType];
+        const canPlace = grid[hoverCell.y][hoverCell.x] === 'empty' &&
+                        gameState.playerCurrency >= tower.cost;
+        
+        // Visual feedback for placement validity
+        ctx.fillStyle = canPlace ? tower.color + '80' : 'rgba(255, 0, 0, 0.3)';
+        ctx.fillRect(
+            hoverCell.x * cellSize,
+            hoverCell.y * cellSize,
+            cellSize,
+            cellSize
+        );
+        
+        // Range indicator preview
+        ctx.beginPath();
+        ctx.arc(
+            (hoverCell.x + 0.5) * cellSize,
+            (hoverCell.y + 0.5) * cellSize,
+            tower.range * cellSize,
+            0,
+            Math.PI * 2
+        );
+        ctx.strokeStyle = canPlace ? tower.color + '40' : 'rgba(255, 0, 0, 0.2)';
+        ctx.stroke();
+    }
+}
+
+/**
+ * Renders all enemies with health indicators and effects
+ * Implements visual state representation
+ * 
+ * @param {CanvasRenderingContext2D} ctx - Canvas rendering context
+ * @param {Array<Object>} enemies - Array of enemy objects
+ */
+function renderEnemies(ctx, enemies) {
+    const cellSize = canvas.width / 20;
+    
+    enemies.forEach(enemy => {
+        // Health-based opacity for visual feedback
+        const healthPercent = enemy.currentHealth / enemy.maxHealth;
+        const opacity = 0.3 + (healthPercent * 0.7);
+        
+        // Dynamic color based on enemy state
+        const color = EnemyTypes[enemy.type].color;
+        const hexOpacity = Math.floor(opacity * 255).toString(16).padStart(2, '0');
+        
+        // Draw enemy body with solid black border
+        ctx.beginPath();
+        ctx.arc(
+            (enemy.position.x + 0.5) * cellSize,
+            (enemy.position.y + 0.5) * cellSize,
+            cellSize / 3,
+            0,
+            Math.PI * 2
+        );
+        
+        // Fill with dynamic opacity
+        ctx.fillStyle = `${color}${hexOpacity}`;
+        ctx.fill();
+        
+        // Add solid black border
+        ctx.strokeStyle = 'black';
+        ctx.lineWidth = 2;
+        ctx.stroke();
+        
+        // Range indicator for special enemy types
+        if (EnemyTypes[enemy.type].isRanged) {
+            ctx.beginPath();
+            ctx.arc(
+                (enemy.position.x + 0.5) * cellSize,
+                (enemy.position.y + 0.5) * cellSize,
+                EnemyTypes[enemy.type].attackRange * cellSize,
+                0,
+                Math.PI * 2
+            );
+            ctx.strokeStyle = `${EnemyTypes[enemy.type].color}40`;
+            ctx.stroke();
+        }
+    });
+}
+
+/**
+ * Renders game UI elements with clean state management
+ * Implements heads-up display (HUD) pattern
+ * 
+ * @param {CanvasRenderingContext2D} ctx - Canvas rendering context
+ * @param {Object} gameState - Current game state
+ */
+function renderUI(ctx, gameState) {
+    const padding = 20;
+    const lineHeight = 30;
+    const startY = padding;
+    const width = 200;
+    const height = lineHeight * 5;
+    
+    // Save the current canvas state
+    ctx.save();
+    
+    // Reset any transformations
+    ctx.setTransform(1, 0, 0, 1, 0, 0);
+    
+    // Semi-transparent background for readability
+    ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
+    ctx.fillRect(0, 0, width, height + padding);
+    
+    // Text rendering setup
+    ctx.fillStyle = 'black';
+    ctx.font = '20px Arial';
+    ctx.textAlign = 'left';
+    ctx.textBaseline = 'top';
+    
+    // Game state information
+    ctx.fillText(`Level: ${gameState.level}`, padding, startY);
+    ctx.fillText(`Currency: $${gameState.currency}`, padding, startY + lineHeight);
+    ctx.fillText(`Phase: ${gameState.phase}`, padding, startY + lineHeight * 2);
+    ctx.fillText(`Destroyed: ${gameState.enemiesDestroyed}`, padding, startY + lineHeight * 3);
+    ctx.fillText(`Escaped: ${gameState.enemiesEscaped}`, padding, startY + lineHeight * 4);
+    
+    // Restore the canvas state
+    ctx.restore();
+}
+
+function renderTowers(ctx, towers) {
+    const cellSize = canvas.width / 20;
+    
+    towers.forEach(tower => {
+        const healthPercent = tower.currentHealth / tower.maxHealth;
+        
+        // Draw tower body
+        ctx.fillStyle = tower.color + Math.floor(healthPercent * 255).toString(16).padStart(2, '0');
+        ctx.fillRect(
+            tower.position.x * cellSize + cellSize * 0.1,
+            tower.position.y * cellSize + cellSize * 0.1,
+            cellSize * 0.8,
+            cellSize * 0.8
+        );
+        
+        // Draw ammo count
+        ctx.fillStyle = 'white';
+        ctx.font = '12px Arial';
+        ctx.textAlign = 'center';
+        ctx.fillText(
+            tower.ammo,
+            (tower.position.x + 0.5) * cellSize,
+            (tower.position.y + 0.7) * cellSize
+        );
+        
+        // Draw range indicator
+        if (gameState.phase === GamePhase.PLACEMENT) {
+            ctx.beginPath();
+            ctx.arc(
+                (tower.position.x + 0.5) * cellSize,
+                (tower.position.y + 0.5) * cellSize,
+                tower.range * cellSize,
+                0,
+                Math.PI * 2
+            );
+            ctx.strokeStyle = tower.color + '40';
+            ctx.stroke();
+        }
+    });
+}
+
+// Add new render function for particles
+function renderParticles(ctx, particles) {
+    particles.forEach(particle => {
+        const age = performance.now() - particle.createdAt;
+        const lifePercent = age / particle.lifetime;
+        
+        if (lifePercent <= 1) {
+            if (particle.type === 'SLIME_TRAIL') {
+                // Calculate opacity based on lifetime and fade start
+                let opacity = 1;
+                if (lifePercent > particle.fadeStart) {
+                    opacity = 1 - ((lifePercent - particle.fadeStart) / (1 - particle.fadeStart));
+                }
+                opacity *= 0.3; // Make it translucent
+                
+                ctx.globalAlpha = opacity;
+                ctx.fillStyle = particle.color;
+                
+                // Draw a circular slime splat
+                ctx.beginPath();
+                ctx.arc(
+                    particle.position.x,
+                    particle.position.y,
+                    particle.size * (1 - lifePercent * 0.3), // Slightly shrink over time
+                    0,
+                    Math.PI * 2
+                );
+                ctx.fill();
+                
+                // Add some variation to the splat
+                for (let i = 0; i < 3; i++) {
+                    const angle = (Math.PI * 2 * i) / 3;
+                    const distance = particle.size * 0.4;
+                    ctx.beginPath();
+                    ctx.arc(
+                        particle.position.x + Math.cos(angle) * distance,
+                        particle.position.y + Math.sin(angle) * distance,
+                        particle.size * 0.4 * (1 - lifePercent * 0.3),
+                        0,
+                        Math.PI * 2
+                    );
+                    ctx.fill();
+                }
+            } else if (particle.type === 'AOE_EXPLOSION') {
+                // Draw expanding circle
+                const radius = particle.initialRadius + 
+                    (particle.finalRadius - particle.initialRadius) * lifePercent;
+                
+                // Draw multiple rings for better effect
+                const numRings = 3;
+                for (let i = 0; i < numRings; i++) {
+                    const ringRadius = radius * (1 - (i * 0.2));
+                    const ringAlpha = (1 - lifePercent) * (1 - (i * 0.3));
+                    
+                    ctx.beginPath();
+                    ctx.arc(
+                        particle.position.x,
+                        particle.position.y,
+                        ringRadius,
+                        0,
+                        Math.PI * 2
+                    );
+                    ctx.strokeStyle = particle.color;
+                    ctx.lineWidth = particle.ringWidth * (1 - (i * 0.2));
+                    ctx.globalAlpha = ringAlpha;
+                    ctx.stroke();
+                }
+                
+                // Draw affected area
+                ctx.beginPath();
+                ctx.arc(
+                    particle.position.x,
+                    particle.position.y,
+                    radius,
+                    0,
+                    Math.PI * 2
+                );
+                ctx.fillStyle = particle.color + '20'; // Very transparent fill
+                ctx.fill();
+            } else {
+                // Original particle rendering
+                ctx.fillStyle = particle.color;
+                ctx.beginPath();
+                ctx.arc(
+                    particle.position.x,
+                    particle.position.y,
+                    particle.size * (1 - lifePercent),
+                    0,
+                    Math.PI * 2
+                );
+                ctx.fill();
+            }
+        }
+    });
+    ctx.globalAlpha = 1;
+}
+
+// Add new render function for projectiles
+function renderProjectiles(ctx, projectiles) {
+    const cellSize = canvas.width / 20;
+    
+    projectiles.forEach(projectile => {
+        const age = performance.now() - projectile.createdAt;
+        const progress = age / projectile.lifetime;
+        
+        if (progress <= 1) {
+            // Draw projectile trail
+            ctx.beginPath();
+            ctx.moveTo(
+                projectile.startPos.x * cellSize + cellSize / 2,
+                projectile.startPos.y * cellSize + cellSize / 2
+            );
+            
+            const currentX = projectile.startPos.x + (projectile.targetPos.x - projectile.startPos.x) * progress;
+            const currentY = projectile.startPos.y + (projectile.targetPos.y - projectile.startPos.y) * progress;
+            
+            ctx.lineTo(
+                currentX * cellSize + cellSize / 2,
+                currentY * cellSize + cellSize / 2
+            );
+            
+            ctx.strokeStyle = '#fff';
+            ctx.lineWidth = 2;
+            ctx.stroke();
+            
+            // Draw projectile head
+            ctx.beginPath();
+            ctx.arc(
+                currentX * cellSize + cellSize / 2,
+                currentY * cellSize + cellSize / 2,
+                4,
+                0,
+                Math.PI * 2
+            );
+            ctx.fillStyle = '#fff';
+            ctx.fill();
+        }
+    });
+}
+
+// Update level complete message in game.js
+function handleLevelComplete() {
+    gameState.phase = GamePhase.TRANSITION;
+    
+    // Calculate ammo bonus
+    let ammoBonus = 0;
+    gameState.towers.forEach(tower => {
+        ammoBonus += tower.ammo * 2;
+    });
+    
+    const message = `
+        Level ${gameState.level} Complete!
+        Current Money: $${gameState.currency}
+        Ammo Bonus: +$${ammoBonus}
+        Level Bonus: +$10
+        
+        Ready for Level ${gameState.level + 1}?
+    `;
+    
+    setTimeout(() => {
+        if (confirm(message)) {
+            startNextLevel();
+        }
+    }, 100);
+} 
\ No newline at end of file
diff --git a/html/tower/js/uiHandlers.js b/html/tower/js/uiHandlers.js
new file mode 100644
index 0000000..00651ca
--- /dev/null
+++ b/html/tower/js/uiHandlers.js
@@ -0,0 +1,96 @@
+/**
+ * UI Handlers Module
+ * 
+ * This module manages user interactions and UI state.
+ * Implements:
+ * 1. Drag and Drop system
+ * 2. Event handling
+ * 3. UI state management
+ * 4. Input validation
+ * 
+ * @module uiHandlers
+ */
+
+/**
+ * Initializes drag and drop functionality for tower placement
+ * Implements HTML5 Drag and Drop API
+ * 
+ * @param {HTMLCanvasElement} canvas - Game canvas element
+ * @param {Object} gameState - Current game state
+ * @returns {Object} Drag handlers and state information
+ */
+function initializeDragAndDrop(canvas, gameState) {
+    let draggedTowerType = null;
+    let hoverCell = null;
+
+    const dragHandlers = {
+        /**
+         * Handles start of tower drag operation
+         * Sets up drag data and visual feedback
+         */
+        onDragStart: (e) => {
+            draggedTowerType = e.target.dataset.towerType;
+            e.dataTransfer.setData('text/plain', '');
+        },
+        
+        /**
+         * Handles end of drag operation
+         * Cleans up drag state
+         */
+        onDragEnd: () => {
+            draggedTowerType = null;
+            hoverCell = null;
+        },
+        
+        /**
+         * Handles drag over canvas
+         * Updates hover position and preview
+         */
+        onDragOver: (e) => {
+            e.preventDefault();
+            const rect = canvas.getBoundingClientRect();
+            const x = Math.floor((e.clientX - rect.left) / (canvas.width / 20));
+            const y = Math.floor((e.clientY - rect.top) / (canvas.height / 20));
+            
+            hoverCell = (x >= 0 && x < 20 && y >= 0 && y < 20) ? { x, y } : null;
+        },
+        
+        /**
+         * Handles tower placement on drop
+         * Validates placement and updates game state
+         */
+        onDrop: (e) => {
+            e.preventDefault();
+            if (!draggedTowerType || !hoverCell) return;
+            
+            placeTower(gameState, draggedTowerType, hoverCell);
+            draggedTowerType = null;
+            hoverCell = null;
+        }
+    };
+
+    return { 
+        dragHandlers, 
+        getHoverInfo: () => ({ draggedTowerType, hoverCell }) 
+    };
+}
+
+/**
+ * Places a tower in the game grid
+ * Implements tower placement validation and state updates
+ * 
+ * @param {Object} gameState - Current game state
+ * @param {string} towerType - Type of tower to place
+ * @param {Object} position - Grid position for placement
+ */
+function placeTower(gameState, towerType, position) {
+    const tower = TowerTypes[towerType];
+    if (
+        gameState.grid[position.y][position.x] === 'empty' &&
+        gameState.currency >= tower.cost
+    ) {
+        gameState.grid[position.y][position.x] = 'tower';
+        gameState.towers.push(createTower(towerType, { ...position }));
+        gameState.currency -= tower.cost;
+    }
+} 
\ No newline at end of file