/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Bullet = Container.expand(function (type, damage, speed, tower) { var self = Container.call(this); self.type = type || 'normal'; self.damage = damage || 10; self.speed = speed || 8; self.tower = tower; self.target = null; self.destroyed = false; var bulletGraphics; if (self.type === 'ice') { bulletGraphics = self.attachAsset('iceBullet', { anchorX: 0.5, anchorY: 0.5 }); } else if (self.type === 'cannon') { bulletGraphics = self.attachAsset('cannonBall', { anchorX: 0.5, anchorY: 0.5 }); } else { bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); } self.update = function () { // Don't update if bullet is already destroyed if (self.destroyed) { return; } if (self.target && !self.target.destroyed) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 20) { // Hit target - mark bullet as destroyed first to prevent multiple hits self.destroyed = true; // Apply damage to target self.target.takeDamage(self.damage); // Apply special effects based on bullet type if (self.type === 'ice') { self.target.slow(0.5, 2000); // 50% speed for 2 seconds self.target.applyIceEffect(); // Apply blue visual effect } else if (self.type === 'cannon') { // Splash damage for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var splashDx = enemy.x - self.x; var splashDy = enemy.y - self.y; var splashDistance = Math.sqrt(splashDx * splashDx + splashDy * splashDy); if (splashDistance < 60 && enemy !== self.target) { enemy.takeDamage(self.damage * 0.5); } } } LK.getSound('enemyHit').play(); self.destroy(); return; } var angle = Math.atan2(dy, dx); self.x += Math.cos(angle) * self.speed; self.y += Math.sin(angle) * self.speed; } else { self.destroyed = true; self.destroy(); } }; return self; }); var Enemy = Container.expand(function (type, path) { var self = Container.call(this); self.type = type || 'basic'; self.path = path; self.pathIndex = 0; self.health = 0; self.maxHealth = 0; self.speed = 0; self.baseSpeed = 0; self.reward = 0; self.slowFactor = 1; self.slowEndTime = 0; self.destroyed = false; self.healAmount = 0; self.hasHealed = false; // Enemy stats based on type var stats = { basic: { health: 60, speed: 1.5, reward: 10 }, fast: { health: 40, speed: 2.5, reward: 15 }, tank: { health: 160, speed: 1, reward: 25 }, boss: { health: 500, speed: 0.8, reward: 100 } }; var enemyStats = stats[self.type]; // Double health after wave 5 var healthMultiplier = currentWave > 5 ? 2 : 1; self.health = enemyStats.health * healthMultiplier; self.maxHealth = enemyStats.health * healthMultiplier; self.speed = enemyStats.speed; self.baseSpeed = enemyStats.speed; self.reward = enemyStats.reward; if (self.type === 'boss') { if (currentWave === 5) { self.healAmount = 3000; } else if (currentWave === 10) { self.healAmount = 10000; } } var enemyGraphics; if (self.type === 'fast') { enemyGraphics = self.attachAsset('fastEnemy', { anchorX: 0.5, anchorY: 0.5 }); } else if (self.type === 'tank') { enemyGraphics = self.attachAsset('tankEnemy', { anchorX: 0.5, anchorY: 0.5 }); } else if (self.type === 'boss') { if (currentWave === 5) { enemyGraphics = self.attachAsset('Bos1', { anchorX: 0.5, anchorY: 0.5 }); } else if (currentWave === 10) { enemyGraphics = self.attachAsset('Bos2', { anchorX: 0.5, anchorY: 0.5 }); } enemyGraphics.scaleX = 1.5; enemyGraphics.scaleY = 1.5; } else { enemyGraphics = self.attachAsset('basicEnemy', { anchorX: 0.5, anchorY: 0.5 }); } // Health bar self.healthBarBg = LK.getAsset('gridCell', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.6, scaleY: 0.1 }); self.healthBarBg.tint = 0x000000; self.healthBarBg.y = -30; self.addChild(self.healthBarBg); self.healthBar = LK.getAsset('gridCell', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.6, scaleY: 0.1 }); self.healthBar.tint = 0x00FF00; self.healthBar.y = -30; self.addChild(self.healthBar); self.takeDamage = function (damage) { self.health -= damage; self.updateHealthBar(); if (self.health <= 0) { self.die(); } }; self.updateHealthBar = function () { var healthPercent = self.health / self.maxHealth; self.healthBar.scaleX = 0.6 * healthPercent; if (healthPercent > 0.6) { self.healthBar.tint = 0x00FF00; } else if (healthPercent > 0.3) { self.healthBar.tint = 0xFFFF00; } else { self.healthBar.tint = 0xFF0000; } }; self.slow = function (factor, duration) { self.slowFactor = factor; self.slowEndTime = Date.now() + duration; }; self.applyIceEffect = function () { // Stop any existing ice effect tween tween.stop(enemyGraphics, { tint: true }); // Apply blue tint immediately enemyGraphics.tint = 0x4169E1; // Royal blue color // Tween back to normal color after 2 seconds tween(enemyGraphics, { tint: 0xFFFFFF }, { duration: 2000, easing: tween.easeOut }); }; self.die = function () { self.destroyed = true; // Stop any ongoing tween effects tween.stop(enemyGraphics, { tint: true }); playerMoney += self.reward; LK.getSound('enemyDestroyed').play(); updateUI(); self.destroy(); }; self.reachBase = function () { self.destroyed = true; playerLives--; updateUI(); if (playerLives <= 0) { gameState = 'gameOver'; LK.showGameOver(); } self.destroy(); }; self.update = function () { // Boss healing logic if (self.type === 'boss' && !self.hasHealed && self.health > 0 && self.health / self.maxHealth <= 0.5) { self.health += self.healAmount; if (self.health > self.maxHealth) { self.health = self.maxHealth; } self.hasHealed = true; self.updateHealthBar(); // Visual effect for healing - flash green tween.stop(enemyGraphics, { tint: true }); enemyGraphics.tint = 0x00FF00; tween(enemyGraphics, { tint: 0xFFFFFF }, { duration: 500 }); } // Handle slow effect if (Date.now() > self.slowEndTime) { self.slowFactor = 1; } var currentSpeed = self.baseSpeed * self.slowFactor; if (self.pathIndex < self.path.length - 1) { var target = self.path[self.pathIndex + 1]; var dx = target.x - self.x; var dy = target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 5) { self.pathIndex++; if (self.pathIndex >= self.path.length - 1) { self.reachBase(); return; } } else { var angle = Math.atan2(dy, dx); self.x += Math.cos(angle) * currentSpeed; self.y += Math.sin(angle) * currentSpeed; } } else { self.reachBase(); } }; return self; }); var MoneyCoin = Container.expand(function (value) { var self = Container.call(this); self.value = value || 25; self.collected = false; var coinGraphics = self.attachAsset('moneyCoin', { anchorX: 0.5, anchorY: 0.5 }); coinGraphics.tint = 0xFFD700; // Gold color // Add value text self.valueText = new Text2('$' + self.value, { size: 20, fill: 0xFFFFFF }); self.valueText.anchor.set(0.5, 0.5); self.valueText.x = 0; self.valueText.y = -25; self.addChild(self.valueText); self.collect = function () { if (self.collected) return; self.collected = true; // Add money to player playerMoney += self.value; // If this coin was from a money tree, decrement its counter if (self.sourceTree) { self.sourceTree.currentMoney--; } // Play collection sound LK.getSound('coinCollect').play(); // Collection animation - fly up and fade out tween(self, { y: self.y - 100, alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { self.destroy(); } }); // Update UI updateUI(); }; self.down = function (x, y, obj) { self.collect(); }; return self; }); var MoneyTree = Container.expand(function (gridX, gridY) { var self = Container.call(this); self.type = 'moneyTree'; self.gridX = gridX; self.gridY = gridY; self.lastDropTime = 0; self.dropInterval = 5000; // Drop money every 5 seconds self.moneyAmount = 25; // Amount of money to drop self.maxMoney = 2; // Maximum number of money coins that can be accumulated self.currentMoney = 0; // Current number of money coins from this tree var treeGraphics = self.attachAsset('moneyTree', { anchorX: 0.5, anchorY: 0.5 }); // Add glow effect to distinguish money trees treeGraphics.tint = 0xFFD700; // Gold tint self.dropMoney = function () { // Only drop money if under the limit if (self.currentMoney >= self.maxMoney) { return; // Don't drop more money if at maximum } // Create money coin var coin = new MoneyCoin(self.moneyAmount); coin.x = self.x; coin.y = self.y - 30; // Start above the tree coin.sourceTree = self; // Reference to the tree that created this coin // Increment the tree's money count self.currentMoney++; // Add to game moneyCoins.push(coin); game.addChild(coin); // Animate coin dropping with bounce var targetY = self.y + 50; tween(coin, { y: targetY }, { duration: 800, easing: tween.bounceOut }); // Add floating effect after landing LK.setTimeout(function () { tween(coin, { y: targetY - 10 }, { duration: 1000, repeat: -1, yoyo: true, easing: tween.easeInOut }); }, 800); }; self.getTotalCost = function () { return 200; // Cost of money tree is fixed at 200 }; self.sell = function () { var sellValue = Math.floor(self.getTotalCost() * 0.75); playerMoney += sellValue; // Mark grid cell as unoccupied grid[self.gridX][self.gridY].occupied = false; // Remove from moneyTrees array for (var i = moneyTrees.length - 1; i >= 0; i--) { if (moneyTrees[i] === self) { moneyTrees.splice(i, 1); break; } } // Clear selection selectedTower = null; showUpgradeUI = false; // Update UI updateUI(); // Destroy tree self.destroy(); }; self.down = function (x, y, obj) { if (gameState === 'playing') { selectedTower = self; showUpgradeUI = true; } }; self.update = function () { var currentTime = Date.now(); if (currentTime - self.lastDropTime >= self.dropInterval) { self.dropMoney(); self.lastDropTime = currentTime; } }; return self; }); var Tower = Container.expand(function (type, gridX, gridY) { var self = Container.call(this); self.type = type || 'archer'; self.gridX = gridX; self.gridY = gridY; self.level = 1; self.lastShotTime = 0; // Tower stats based on type and level self.getStats = function () { var stats = { archer: { damage: 15 * self.level, range: 240 + self.level * 20, fireRate: 800 - self.level * 100, // ms between shots cost: 50, upgradeCost: self.level * 60 }, ice: { damage: 8 * self.level, range: 135 + self.level * 15, fireRate: 1200 - self.level * 150, cost: 75, upgradeCost: self.level * 90 }, cannon: { damage: 40 * self.level, range: 90 + self.level * 10, fireRate: 1500 - self.level * 200, cost: 100, upgradeCost: self.level * 120 } }; return stats[self.type]; }; var towerGraphics; if (self.type === 'ice') { towerGraphics = self.attachAsset('iceTower', { anchorX: 0.5, anchorY: 0.5 }); } else if (self.type === 'cannon') { towerGraphics = self.attachAsset('cannonTower', { anchorX: 0.5, anchorY: 0.5 }); } else { towerGraphics = self.attachAsset('archerTower', { anchorX: 0.5, anchorY: 0.5 }); } // Level indicator self.levelText = new Text2(self.level.toString(), { size: 20, fill: 0xFFFFFF }); self.levelText.anchor.set(0.5, 0.5); self.levelText.x = 0; self.levelText.y = -30; self.addChild(self.levelText); self.findTarget = function () { var stats = self.getStats(); var closestEnemy = null; var closestDistance = Infinity; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= stats.range && distance < closestDistance) { closestDistance = distance; closestEnemy = enemy; } } return closestEnemy; }; self.shoot = function (target) { var stats = self.getStats(); var bullet = new Bullet(self.type, stats.damage, 8, self); bullet.target = target; bullet.x = self.x; bullet.y = self.y; bullets.push(bullet); game.addChild(bullet); LK.getSound('shoot').play(); }; self.getTotalCost = function () { var stats = self.getStats(); var totalCost = stats.cost; // Add upgrade costs for each level beyond 1 for (var i = 1; i < self.level; i++) { totalCost += i * (stats.cost * 0.6); // Upgrade cost formula } return totalCost; }; self.upgrade = function () { var stats = self.getStats(); if (playerMoney >= stats.upgradeCost) { playerMoney -= stats.upgradeCost; self.level++; self.levelText.setText(self.level.toString()); updateUI(); } }; self.sell = function () { var sellValue = Math.floor(self.getTotalCost() * 0.75); playerMoney += sellValue; // Mark grid cell as unoccupied grid[self.gridX][self.gridY].occupied = false; // Remove from towers array for (var i = towers.length - 1; i >= 0; i--) { if (towers[i] === self) { towers.splice(i, 1); break; } } // Clear selection selectedTower = null; showUpgradeUI = false; // Update UI updateUI(); // Destroy tower self.destroy(); }; self.down = function (x, y, obj) { if (gameState === 'playing') { selectedTower = self; showUpgradeUI = true; } }; self.update = function () { var stats = self.getStats(); var currentTime = Date.now(); if (currentTime - self.lastShotTime >= stats.fireRate) { var target = self.findTarget(); if (target) { self.shoot(target); self.lastShotTime = currentTime; } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x228B22 }); /**** * Game Code ****/ // Game state var gameState = 'playing'; // 'playing', 'gameOver', 'victory' var playerMoney = 200; var playerLives = 20; var currentWave = 1; var maxWaves = 10; var waveInProgress = false; var nextWaveTimer = 0; var enemySpawnTimer = 0; var enemiesInWave = 0; var enemiesSpawned = 0; // Game objects var towers = []; var enemies = []; var bullets = []; var moneyTrees = []; var moneyCoins = []; var selectedTower = null; var showUpgradeUI = false; // Grid system var gridSize = 80; var gridWidth = Math.floor(2048 / gridSize); var gridHeight = Math.floor(2732 / gridSize); var grid = []; // Path definition (from top to bottom with some turns) var pathPoints = [{ x: 0, y: 5 }, { x: 3, y: 5 }, { x: 3, y: 10 }, { x: 8, y: 10 }, { x: 8, y: 15 }, { x: 15, y: 15 }, { x: 15, y: 20 }, { x: 20, y: 20 }, { x: 20, y: 25 }, { x: 25, y: 25 }]; // Convert grid coordinates to world coordinates function gridToWorld(gridX, gridY) { return { x: gridX * gridSize + gridSize / 2, y: gridY * gridSize + gridSize / 2 }; } // Convert world coordinates to grid coordinates function worldToGrid(worldX, worldY) { return { x: Math.floor(worldX / gridSize), y: Math.floor(worldY / gridSize) }; } // Initialize grid function initializeGrid() { for (var x = 0; x < gridWidth; x++) { grid[x] = []; for (var y = 0; y < gridHeight; y++) { grid[x][y] = { occupied: false, isPath: false }; } } // Mark path cells - create continuous path between all points for (var i = 0; i < pathPoints.length; i++) { var point = pathPoints[i]; if (point.x < gridWidth && point.y < gridHeight) { grid[point.x][point.y].isPath = true; } // If not the last point, draw line to next point if (i < pathPoints.length - 1) { var nextPoint = pathPoints[i + 1]; var startX = point.x; var startY = point.y; var endX = nextPoint.x; var endY = nextPoint.y; // Draw horizontal line first, then vertical if (startX !== endX) { var minX = Math.min(startX, endX); var maxX = Math.max(startX, endX); for (var x = minX; x <= maxX; x++) { if (x < gridWidth && startY < gridHeight) { grid[x][startY].isPath = true; } } } if (startY !== endY) { var minY = Math.min(startY, endY); var maxY = Math.max(startY, endY); for (var y = minY; y <= maxY; y++) { if (endX < gridWidth && y < gridHeight) { grid[endX][y].isPath = true; } } } } } } // Draw grid function drawGrid() { for (var x = 0; x < gridWidth; x++) { for (var y = 0; y < gridHeight; y++) { var worldPos = gridToWorld(x, y); var cell; if (grid[x][y].isPath) { cell = LK.getAsset('pathCell', { anchorX: 0.5, anchorY: 0.5, x: worldPos.x, y: worldPos.y, alpha: 0.8 }); } else { cell = LK.getAsset('gridCell', { anchorX: 0.5, anchorY: 0.5, x: worldPos.x, y: worldPos.y, alpha: 0.3 }); } game.addChild(cell); } } } // Create path for enemies function createPath() { var path = []; for (var i = 0; i < pathPoints.length; i++) { var point = pathPoints[i]; var worldPos = gridToWorld(point.x, point.y); path.push(worldPos); } return path; } // Place base at the end of path function placeBase() { var lastPoint = pathPoints[pathPoints.length - 1]; var worldPos = gridToWorld(lastPoint.x, lastPoint.y); var base = LK.getAsset('base', { anchorX: 0.5, anchorY: 0.5, x: worldPos.x, y: worldPos.y }); game.addChild(base); } // Place money trees at strategic locations - removed automatic placement for player purchase function placeMoneyTrees() { // Money trees are now purchased by player, no automatic placement } // UI elements var moneyText = new Text2('Money: $' + playerMoney, { size: 40, fill: 0xFFFFFF }); moneyText.anchor.set(0, 0); moneyText.x = 120; moneyText.y = 20; LK.gui.topLeft.addChild(moneyText); var livesText = new Text2('Lives: ' + playerLives, { size: 40, fill: 0xFFFFFF }); livesText.anchor.set(1, 0); livesText.x = -20; livesText.y = 20; LK.gui.topRight.addChild(livesText); var waveText = new Text2('Wave: ' + currentWave + '/' + maxWaves, { size: 40, fill: 0xFFFFFF }); waveText.anchor.set(0.5, 0); waveText.x = 0; waveText.y = 20; LK.gui.top.addChild(waveText); // Tower shop with button assets positioned at bottom corner var shopY = -300; // Position from bottom var buttonSize = 80; var buttonSpacing = 100; // Archer button (Buton1) var archerButton = LK.getAsset('Buton1', { anchorX: 0.5, anchorY: 0.5, x: buttonSpacing, y: shopY }); // Add direct click handler to archer button archerButton.down = function (x, y, obj) { selectedTowerType = 'archer'; showRangeCircle = true; console.log('Archer button clicked directly'); // Add click animation tween(archerButton, { scaleX: 1.3, scaleY: 1.3 }, { duration: 100, onFinish: function onFinish() { updateTowerSelection(); } }); updateTowerSelection(); }; LK.gui.bottomLeft.addChild(archerButton); var archerText = new Text2('Archer', { size: 25, fill: 0xFFFFFF }); archerText.anchor.set(0.5, 0.5); archerText.x = buttonSpacing; archerText.y = shopY; LK.gui.bottomLeft.addChild(archerText); // Ice button (Buton2) var iceButton = LK.getAsset('Buton2', { anchorX: 0.5, anchorY: 0.5, x: buttonSpacing * 2, y: shopY }); // Add direct click handler to ice button iceButton.down = function (x, y, obj) { selectedTowerType = 'ice'; showRangeCircle = true; console.log('Ice button clicked directly'); // Add click animation tween(iceButton, { scaleX: 1.3, scaleY: 1.3 }, { duration: 100, onFinish: function onFinish() { updateTowerSelection(); } }); updateTowerSelection(); }; LK.gui.bottomLeft.addChild(iceButton); var iceText = new Text2('Ice', { size: 25, fill: 0xFFFFFF }); iceText.anchor.set(0.5, 0.5); iceText.x = buttonSpacing * 2; iceText.y = shopY; LK.gui.bottomLeft.addChild(iceText); // Cannon button (Buton3) var cannonButton = LK.getAsset('Buton3', { anchorX: 0.5, anchorY: 0.5, x: buttonSpacing * 3, y: shopY }); // Add direct click handler to cannon button cannonButton.down = function (x, y, obj) { selectedTowerType = 'cannon'; showRangeCircle = true; console.log('Cannon button clicked directly'); // Add click animation tween(cannonButton, { scaleX: 1.3, scaleY: 1.3 }, { duration: 100, onFinish: function onFinish() { updateTowerSelection(); } }); updateTowerSelection(); }; LK.gui.bottomLeft.addChild(cannonButton); var cannonText = new Text2('Cannon', { size: 25, fill: 0xFFFFFF }); cannonText.anchor.set(0.5, 0.5); cannonText.x = buttonSpacing * 3; cannonText.y = shopY; LK.gui.bottomLeft.addChild(cannonText); // Money Tree button (fourth button) var moneyTreeButton = LK.getAsset('Buton1', { anchorX: 0.5, anchorY: 0.5, x: buttonSpacing * 4, y: shopY }); moneyTreeButton.tint = 0xFFD700; // Gold color for money tree // Add direct click handler to money tree button moneyTreeButton.down = function (x, y, obj) { selectedTowerType = 'moneyTree'; showRangeCircle = false; // No range circle for money trees console.log('Money Tree button clicked directly'); // Add click animation tween(moneyTreeButton, { scaleX: 1.3, scaleY: 1.3 }, { duration: 100, onFinish: function onFinish() { updateTowerSelection(); } }); updateTowerSelection(); }; LK.gui.bottomLeft.addChild(moneyTreeButton); var moneyTreeText = new Text2('Money Tree', { size: 20, fill: 0xFFFFFF }); moneyTreeText.anchor.set(0.5, 0.5); moneyTreeText.x = buttonSpacing * 4; moneyTreeText.y = shopY; LK.gui.bottomLeft.addChild(moneyTreeText); // Selected tower info var selectedTowerType = 'archer'; var rangeCircle = null; var showRangeCircle = false; // Add status text to show selected tower type var selectedTowerText = new Text2('Selected: Archer Tower ($50)', { size: 30, fill: 0x00FF00 }); selectedTowerText.anchor.set(1, 0); selectedTowerText.x = -20; selectedTowerText.y = 80; LK.gui.topRight.addChild(selectedTowerText); function showTowerRange(x, y) { if (!showRangeCircle) return; // Get range for selected tower type var towerRanges = { archer: 240, ice: 135, cannon: 90 }; var range = towerRanges[selectedTowerType]; // Remove existing range circle if (rangeCircle) { rangeCircle.destroy(); rangeCircle = null; } // Create new range circle rangeCircle = LK.getAsset('gridCell', { anchorX: 0.5, anchorY: 0.5, scaleX: range / 40, // Scale to match range (gridCell is 80px, so 40 is radius) scaleY: range / 40, alpha: 0.3, x: x, y: y }); rangeCircle.tint = 0x0080FF; // Semi-transparent blue game.addChild(rangeCircle); } function hideRangeCircle() { showRangeCircle = false; if (rangeCircle) { rangeCircle.destroy(); rangeCircle = null; } } function updateTowerSelection() { // Stop any existing tweens on buttons tween.stop(archerButton, { tint: true, scaleX: true, scaleY: true }); tween.stop(iceButton, { tint: true, scaleX: true, scaleY: true }); tween.stop(cannonButton, { tint: true, scaleX: true, scaleY: true }); tween.stop(moneyTreeButton, { tint: true, scaleX: true, scaleY: true }); // Reset all button colors and scales archerButton.tint = 0xFFFFFF; iceButton.tint = 0xFFFFFF; cannonButton.tint = 0xFFFFFF; moneyTreeButton.tint = 0xFFD700; // Gold base color for money tree archerButton.scaleX = archerButton.scaleY = 1; iceButton.scaleX = iceButton.scaleY = 1; cannonButton.scaleX = cannonButton.scaleY = 1; moneyTreeButton.scaleX = moneyTreeButton.scaleY = 1; // Highlight selected button with bright green glow and scale if (selectedTowerType === 'archer') { archerButton.tint = 0x00FF00; // Bright green for selected archerButton.scaleX = archerButton.scaleY = 1.2; // Add pulsing effect tween(archerButton, { scaleX: 1.1, scaleY: 1.1 }, { duration: 500, repeat: -1, yoyo: true, easing: tween.easeInOut }); } else if (selectedTowerType === 'ice') { iceButton.tint = 0x00FF00; // Bright green for selected iceButton.scaleX = iceButton.scaleY = 1.2; // Add pulsing effect tween(iceButton, { scaleX: 1.1, scaleY: 1.1 }, { duration: 500, repeat: -1, yoyo: true, easing: tween.easeInOut }); } else if (selectedTowerType === 'cannon') { cannonButton.tint = 0x00FF00; // Bright green for selected cannonButton.scaleX = cannonButton.scaleY = 1.2; // Add pulsing effect tween(cannonButton, { scaleX: 1.1, scaleY: 1.1 }, { duration: 500, repeat: -1, yoyo: true, easing: tween.easeInOut }); } else if (selectedTowerType === 'moneyTree') { moneyTreeButton.tint = 0x00FF00; // Bright green for selected moneyTreeButton.scaleX = moneyTreeButton.scaleY = 1.2; // Add pulsing effect tween(moneyTreeButton, { scaleX: 1.1, scaleY: 1.1 }, { duration: 500, repeat: -1, yoyo: true, easing: tween.easeInOut }); } // Update status text var towerCosts = { archer: 50, ice: 75, cannon: 100, moneyTree: 200 }; var cost = towerCosts[selectedTowerType]; var towerName = selectedTowerType === 'moneyTree' ? 'Money Tree' : selectedTowerType.charAt(0).toUpperCase() + selectedTowerType.slice(1); var canAfford = playerMoney >= cost; var statusColor = canAfford ? 0x00FF00 : 0xFF0000; selectedTowerText.tint = statusColor; var itemType = selectedTowerType === 'moneyTree' ? '' : ' Tower'; selectedTowerText.setText('Selected: ' + towerName + itemType + ' ($' + cost + ') - Click grid to place'); } // Upgrade UI Canvas Background var upgradeCanvas = LK.getAsset('gridCell', { anchorX: 0.5, anchorY: 0.5, scaleX: 6, scaleY: 4, alpha: 0.7 }); upgradeCanvas.tint = 0x333333; // Semi-transparent gray upgradeCanvas.x = 0; upgradeCanvas.y = 280; LK.gui.center.addChild(upgradeCanvas); // Upgrade UI Title var upgradeTitle = new Text2('TOWER INFO', { size: 32, fill: 0xFFFFFF }); upgradeTitle.anchor.set(0.5, 0.5); upgradeTitle.x = 0; upgradeTitle.y = 180; LK.gui.center.addChild(upgradeTitle); // Tower stats display var upgradeText = new Text2('', { size: 28, fill: 0xFFFFFF }); upgradeText.anchor.set(0.5, 0.5); upgradeText.x = 0; upgradeText.y = 250; LK.gui.center.addChild(upgradeText); // Upgrade button with background var upgradeButtonBg = LK.getAsset('gridCell', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.5, scaleY: 0.8, alpha: 0.8 }); upgradeButtonBg.tint = 0xFFD700; upgradeButtonBg.x = 0; upgradeButtonBg.y = 350; LK.gui.center.addChild(upgradeButtonBg); var upgradeButton = new Text2('', { size: 30, fill: 0x000000 }); upgradeButton.anchor.set(0.5, 0.5); upgradeButton.x = 0; upgradeButton.y = 350; upgradeButton.scaleX = 1; upgradeButton.scaleY = 1; LK.gui.center.addChild(upgradeButton); // Sell button with background var sellButtonBg = LK.getAsset('gridCell', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.5, scaleY: 0.8, alpha: 0.8 }); sellButtonBg.tint = 0xFF4444; sellButtonBg.x = 0; sellButtonBg.y = 410; LK.gui.center.addChild(sellButtonBg); var sellButton = new Text2('', { size: 30, fill: 0xFFFFFF }); sellButton.anchor.set(0.5, 0.5); sellButton.x = 0; sellButton.y = 410; sellButton.scaleX = 1; sellButton.scaleY = 1; LK.gui.center.addChild(sellButton); upgradeButton.down = function () { if (showUpgradeUI && selectedTower) { var stats = selectedTower.getStats(); if (playerMoney >= stats.upgradeCost) { // Animate both button and background tween(upgradeButton, { scaleX: 1.1, scaleY: 1.1 }, { duration: 100, onFinish: function onFinish() { tween(upgradeButton, { scaleX: 1, scaleY: 1 }, { duration: 100 }); } }); tween(upgradeButtonBg, { scaleX: 2.7, scaleY: 0.9 }, { duration: 100, onFinish: function onFinish() { tween(upgradeButtonBg, { scaleX: 2.5, scaleY: 0.8 }, { duration: 100 }); } }); selectedTower.upgrade(); } } }; sellButton.down = function () { if (showUpgradeUI && selectedTower) { // Animate both button and background tween(sellButton, { scaleX: 1.1, scaleY: 1.1 }, { duration: 100, onFinish: function onFinish() { tween(sellButton, { scaleX: 1, scaleY: 1 }, { duration: 100 }); } }); tween(sellButtonBg, { scaleX: 2.7, scaleY: 0.9 }, { duration: 100, onFinish: function onFinish() { tween(sellButtonBg, { scaleX: 2.5, scaleY: 0.8 }, { duration: 100 }); } }); selectedTower.sell(); } }; function updateUI() { moneyText.setText('Money: $' + playerMoney); livesText.setText('Lives: ' + playerLives); waveText.setText('Wave: ' + currentWave + '/' + maxWaves); updateTowerSelection(); // Update selection display when money changes if (showUpgradeUI && selectedTower) { var isMoneyTree = selectedTower.type === 'moneyTree'; var sellValue = Math.floor(selectedTower.getTotalCost() * 0.75); // Show canvas and UI elements upgradeCanvas.alpha = 0.7; upgradeTitle.alpha = 1; upgradeText.alpha = 1; upgradeButton.alpha = isMoneyTree ? 0 : 1; sellButton.alpha = 1; upgradeButtonBg.alpha = isMoneyTree ? 0 : 0.8; sellButtonBg.alpha = 0.8; sellButton.setText('SELL $' + sellValue); if (isMoneyTree) { // Update text content for Money Tree upgradeTitle.setText('MONEY TREE'); var infoText = 'Generates $' + selectedTower.moneyAmount + ' every ' + selectedTower.dropInterval / 1000 + 's.\n'; infoText += 'Max ' + selectedTower.maxMoney + ' coins can accumulate.'; upgradeText.setText(infoText); } else { // Update text content for normal towers var stats = selectedTower.getStats(); var towerName = selectedTower.type.charAt(0).toUpperCase() + selectedTower.type.slice(1); upgradeTitle.setText(towerName.toUpperCase() + ' TOWER'); upgradeText.setText('Damage: ' + stats.damage + '\nRange: ' + stats.range + '\nFire Rate: ' + Math.round(1000 / stats.fireRate * 60) + '/sec'); upgradeButton.setText('UPGRADE $' + stats.upgradeCost); // Visual feedback for upgrade affordability if (playerMoney >= stats.upgradeCost) { upgradeButtonBg.tint = 0xFFD700; // Gold when affordable upgradeButton.tint = 0x000000; // Black text on gold background } else { upgradeButtonBg.tint = 0x666666; // Gray when not affordable upgradeButton.tint = 0xFFFFFF; // White text on gray background } } // Sell button is always available sellButtonBg.tint = 0xFF4444; sellButton.tint = 0xFFFFFF; } else { upgradeCanvas.alpha = 0; upgradeTitle.alpha = 0; upgradeText.alpha = 0; upgradeButton.alpha = 0; sellButton.alpha = 0; upgradeButtonBg.alpha = 0; sellButtonBg.alpha = 0; } } // Wave management function getWaveData(waveNumber) { if (waveNumber === 5 || waveNumber === 10) { var waveEnemies = ['boss']; var escortTypes = ['basic', 'fast', 'tank']; var numEscorts = waveNumber === 5 ? 8 : 12; for (var i = 0; i < numEscorts; i++) { var type = escortTypes[Math.floor(Math.random() * escortTypes.length)]; waveEnemies.push(type); } // Shuffle the wave so boss doesn't always appear first for (var i = waveEnemies.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = waveEnemies[i]; waveEnemies[i] = waveEnemies[j]; waveEnemies[j] = temp; } return waveEnemies; } var baseEnemies = 5 + waveNumber * 2; var enemyTypes = ['basic']; if (waveNumber >= 3) enemyTypes.push('fast'); if (waveNumber >= 5) enemyTypes.push('tank'); var waveEnemies = []; for (var i = 0; i < baseEnemies; i++) { var type = enemyTypes[Math.floor(Math.random() * enemyTypes.length)]; waveEnemies.push(type); } return waveEnemies; } function startWave() { if (currentWave > maxWaves) { gameState = 'victory'; LK.showYouWin(); return; } waveInProgress = true; var waveData = getWaveData(currentWave); enemiesInWave = waveData.length; enemiesSpawned = 0; enemySpawnTimer = 0; // Store wave data for spawning game.currentWaveData = waveData; } function spawnEnemy() { if (!waveInProgress || enemiesSpawned >= enemiesInWave) return; var enemyType = game.currentWaveData[enemiesSpawned]; var path = createPath(); var enemy = new Enemy(enemyType, path); enemy.x = path[0].x; enemy.y = path[0].y; enemies.push(enemy); game.addChild(enemy); enemiesSpawned++; } function checkWaveComplete() { if (waveInProgress && enemies.length === 0 && enemiesSpawned >= enemiesInWave) { waveInProgress = false; currentWave++; nextWaveTimer = LK.ticks + 180; // 3 seconds delay updateUI(); } } // Mouse move handler for range circle game.move = function (x, y, obj) { if (showRangeCircle && gameState === 'playing') { showTowerRange(x, y); } }; // Game input handling game.down = function (x, y, obj) { if (gameState !== 'playing') return; console.log('Click detected at raw coordinates:', x, y); // Use the click coordinates directly as they're already in game space var gameX = x; var gameY = y; console.log('Game coordinates:', gameX, gameY); // Check tower shop buttons - convert screen coordinates to GUI coordinates var screenPoint = { x: x, y: y }; var guiPoint = LK.gui.bottomLeft.toLocal(screenPoint); console.log('GUI click coordinates:', guiPoint.x, guiPoint.y); // Check archer button (Buton1) - expanded click area for better touch detection if (guiPoint.x >= 50 && guiPoint.x <= 150 && guiPoint.y >= shopY - 50 && guiPoint.y <= shopY + 50) { selectedTowerType = 'archer'; showRangeCircle = true; console.log('Selected tower type: archer'); // Add click animation tween(archerButton, { scaleX: 1.3, scaleY: 1.3 }, { duration: 100, onFinish: function onFinish() { updateTowerSelection(); // This will reset scale based on selection } }); updateTowerSelection(); return; } // Check ice button (Buton2) - expanded click area for better touch detection else if (guiPoint.x >= 150 && guiPoint.x <= 250 && guiPoint.y >= shopY - 50 && guiPoint.y <= shopY + 50) { selectedTowerType = 'ice'; showRangeCircle = true; console.log('Selected tower type: ice'); // Add click animation tween(iceButton, { scaleX: 1.3, scaleY: 1.3 }, { duration: 100, onFinish: function onFinish() { updateTowerSelection(); // This will reset scale based on selection } }); updateTowerSelection(); return; } // Check cannon button (Buton3) - expanded click area for better touch detection else if (guiPoint.x >= 250 && guiPoint.x <= 350 && guiPoint.y >= shopY - 50 && guiPoint.y <= shopY + 50) { selectedTowerType = 'cannon'; showRangeCircle = true; console.log('Selected tower type: cannon'); // Add click animation tween(cannonButton, { scaleX: 1.3, scaleY: 1.3 }, { duration: 100, onFinish: function onFinish() { updateTowerSelection(); // This will reset scale based on selection } }); updateTowerSelection(); return; } // Check money tree button - expanded click area for better touch detection else if (guiPoint.x >= 350 && guiPoint.x <= 450 && guiPoint.y >= shopY - 50 && guiPoint.y <= shopY + 50) { selectedTowerType = 'moneyTree'; showRangeCircle = false; // No range circle for money trees console.log('Selected tower type: money tree'); // Add click animation tween(moneyTreeButton, { scaleX: 1.3, scaleY: 1.3 }, { duration: 100, onFinish: function onFinish() { updateTowerSelection(); // This will reset scale based on selection } }); updateTowerSelection(); return; } // Upgrade and Sell button clicks are now handled by their own .down methods. var centerPos = LK.gui.center.toLocal({ x: x, y: y }); // Hide upgrade UI if clicking elsewhere if (showUpgradeUI) { showUpgradeUI = false; selectedTower = null; updateUI(); } // Hide range circle when clicking elsewhere (cancelling purchase) hideRangeCircle(); // Try to place tower using game coordinates var gridPos = worldToGrid(gameX, gameY); console.log('Grid position:', gridPos.x, gridPos.y); console.log('Grid bounds check:', gridPos.x >= 0, gridPos.x < gridWidth, gridPos.y >= 0, gridPos.y < gridHeight); if (gridPos.x >= 0 && gridPos.x < gridWidth && gridPos.y >= 0 && gridPos.y < gridHeight) { console.log('Grid cell occupied:', grid[gridPos.x][gridPos.y].occupied); console.log('Grid cell is path:', grid[gridPos.x][gridPos.y].isPath); if (!grid[gridPos.x][gridPos.y].occupied && !grid[gridPos.x][gridPos.y].isPath) { var towerCosts = { archer: 50, ice: 75, cannon: 100, moneyTree: 200 }; var cost = towerCosts[selectedTowerType]; console.log('Tower cost:', cost, 'Player money:', playerMoney); if (playerMoney >= cost) { console.log('Placing', selectedTowerType, 'at grid:', gridPos.x, gridPos.y); var worldPos = gridToWorld(gridPos.x, gridPos.y); console.log('World position:', worldPos.x, worldPos.y); if (selectedTowerType === 'moneyTree') { // Place money tree var tree = new MoneyTree(gridPos.x, gridPos.y); tree.x = worldPos.x; tree.y = worldPos.y; // Add placement animation tree.scaleX = 0; tree.scaleY = 0; tween(tree, { scaleX: 1, scaleY: 1 }, { duration: 300, easing: tween.bounceOut }); moneyTrees.push(tree); game.addChild(tree); } else { // Place tower var tower = new Tower(selectedTowerType, gridPos.x, gridPos.y); tower.x = worldPos.x; tower.y = worldPos.y; // Add placement animation tower.scaleX = 0; tower.scaleY = 0; tween(tower, { scaleX: 1, scaleY: 1 }, { duration: 300, easing: tween.bounceOut }); towers.push(tower); game.addChild(tower); } grid[gridPos.x][gridPos.y].occupied = true; playerMoney -= cost; hideRangeCircle(); // Hide range circle after placing tower updateUI(); console.log(selectedTowerType, 'placed successfully!'); } else { console.log('Not enough money to place', selectedTowerType); } } else { console.log('Grid cell is occupied or on path'); } } else { console.log('Click outside grid bounds'); } }; // Initialize game initializeGrid(); drawGrid(); placeBase(); placeMoneyTrees(); updateUI(); updateTowerSelection(); startWave(); // Main game loop game.update = function () { if (gameState !== 'playing') return; // Update all towers (this enables shooting!) for (var i = 0; i < towers.length; i++) { towers[i].update(); } // Update all bullets (this enables bullet movement and collision detection!) for (var i = 0; i < bullets.length; i++) { bullets[i].update(); } // Update all enemies for (var i = 0; i < enemies.length; i++) { enemies[i].update(); } // Update all money trees for (var i = 0; i < moneyTrees.length; i++) { moneyTrees[i].update(); } // Clean up collected money coins for (var i = moneyCoins.length - 1; i >= 0; i--) { if (moneyCoins[i].collected) { moneyCoins.splice(i, 1); } } // Spawn enemies if (waveInProgress && LK.ticks % 60 === 0) { // Every second spawnEnemy(); } // Start next wave if (!waveInProgress && LK.ticks >= nextWaveTimer) { startWave(); } // Clean up destroyed enemies for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i].destroyed) { enemies.splice(i, 1); } } // Clean up destroyed bullets for (var i = bullets.length - 1; i >= 0; i--) { if (bullets[i].destroyed) { bullets.splice(i, 1); } } checkWaveComplete(); };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Bullet = Container.expand(function (type, damage, speed, tower) {
var self = Container.call(this);
self.type = type || 'normal';
self.damage = damage || 10;
self.speed = speed || 8;
self.tower = tower;
self.target = null;
self.destroyed = false;
var bulletGraphics;
if (self.type === 'ice') {
bulletGraphics = self.attachAsset('iceBullet', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.type === 'cannon') {
bulletGraphics = self.attachAsset('cannonBall', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
}
self.update = function () {
// Don't update if bullet is already destroyed
if (self.destroyed) {
return;
}
if (self.target && !self.target.destroyed) {
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 20) {
// Hit target - mark bullet as destroyed first to prevent multiple hits
self.destroyed = true;
// Apply damage to target
self.target.takeDamage(self.damage);
// Apply special effects based on bullet type
if (self.type === 'ice') {
self.target.slow(0.5, 2000); // 50% speed for 2 seconds
self.target.applyIceEffect(); // Apply blue visual effect
} else if (self.type === 'cannon') {
// Splash damage
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var splashDx = enemy.x - self.x;
var splashDy = enemy.y - self.y;
var splashDistance = Math.sqrt(splashDx * splashDx + splashDy * splashDy);
if (splashDistance < 60 && enemy !== self.target) {
enemy.takeDamage(self.damage * 0.5);
}
}
}
LK.getSound('enemyHit').play();
self.destroy();
return;
}
var angle = Math.atan2(dy, dx);
self.x += Math.cos(angle) * self.speed;
self.y += Math.sin(angle) * self.speed;
} else {
self.destroyed = true;
self.destroy();
}
};
return self;
});
var Enemy = Container.expand(function (type, path) {
var self = Container.call(this);
self.type = type || 'basic';
self.path = path;
self.pathIndex = 0;
self.health = 0;
self.maxHealth = 0;
self.speed = 0;
self.baseSpeed = 0;
self.reward = 0;
self.slowFactor = 1;
self.slowEndTime = 0;
self.destroyed = false;
self.healAmount = 0;
self.hasHealed = false;
// Enemy stats based on type
var stats = {
basic: {
health: 60,
speed: 1.5,
reward: 10
},
fast: {
health: 40,
speed: 2.5,
reward: 15
},
tank: {
health: 160,
speed: 1,
reward: 25
},
boss: {
health: 500,
speed: 0.8,
reward: 100
}
};
var enemyStats = stats[self.type];
// Double health after wave 5
var healthMultiplier = currentWave > 5 ? 2 : 1;
self.health = enemyStats.health * healthMultiplier;
self.maxHealth = enemyStats.health * healthMultiplier;
self.speed = enemyStats.speed;
self.baseSpeed = enemyStats.speed;
self.reward = enemyStats.reward;
if (self.type === 'boss') {
if (currentWave === 5) {
self.healAmount = 3000;
} else if (currentWave === 10) {
self.healAmount = 10000;
}
}
var enemyGraphics;
if (self.type === 'fast') {
enemyGraphics = self.attachAsset('fastEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.type === 'tank') {
enemyGraphics = self.attachAsset('tankEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.type === 'boss') {
if (currentWave === 5) {
enemyGraphics = self.attachAsset('Bos1', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (currentWave === 10) {
enemyGraphics = self.attachAsset('Bos2', {
anchorX: 0.5,
anchorY: 0.5
});
}
enemyGraphics.scaleX = 1.5;
enemyGraphics.scaleY = 1.5;
} else {
enemyGraphics = self.attachAsset('basicEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Health bar
self.healthBarBg = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.1
});
self.healthBarBg.tint = 0x000000;
self.healthBarBg.y = -30;
self.addChild(self.healthBarBg);
self.healthBar = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.1
});
self.healthBar.tint = 0x00FF00;
self.healthBar.y = -30;
self.addChild(self.healthBar);
self.takeDamage = function (damage) {
self.health -= damage;
self.updateHealthBar();
if (self.health <= 0) {
self.die();
}
};
self.updateHealthBar = function () {
var healthPercent = self.health / self.maxHealth;
self.healthBar.scaleX = 0.6 * healthPercent;
if (healthPercent > 0.6) {
self.healthBar.tint = 0x00FF00;
} else if (healthPercent > 0.3) {
self.healthBar.tint = 0xFFFF00;
} else {
self.healthBar.tint = 0xFF0000;
}
};
self.slow = function (factor, duration) {
self.slowFactor = factor;
self.slowEndTime = Date.now() + duration;
};
self.applyIceEffect = function () {
// Stop any existing ice effect tween
tween.stop(enemyGraphics, {
tint: true
});
// Apply blue tint immediately
enemyGraphics.tint = 0x4169E1; // Royal blue color
// Tween back to normal color after 2 seconds
tween(enemyGraphics, {
tint: 0xFFFFFF
}, {
duration: 2000,
easing: tween.easeOut
});
};
self.die = function () {
self.destroyed = true;
// Stop any ongoing tween effects
tween.stop(enemyGraphics, {
tint: true
});
playerMoney += self.reward;
LK.getSound('enemyDestroyed').play();
updateUI();
self.destroy();
};
self.reachBase = function () {
self.destroyed = true;
playerLives--;
updateUI();
if (playerLives <= 0) {
gameState = 'gameOver';
LK.showGameOver();
}
self.destroy();
};
self.update = function () {
// Boss healing logic
if (self.type === 'boss' && !self.hasHealed && self.health > 0 && self.health / self.maxHealth <= 0.5) {
self.health += self.healAmount;
if (self.health > self.maxHealth) {
self.health = self.maxHealth;
}
self.hasHealed = true;
self.updateHealthBar();
// Visual effect for healing - flash green
tween.stop(enemyGraphics, {
tint: true
});
enemyGraphics.tint = 0x00FF00;
tween(enemyGraphics, {
tint: 0xFFFFFF
}, {
duration: 500
});
}
// Handle slow effect
if (Date.now() > self.slowEndTime) {
self.slowFactor = 1;
}
var currentSpeed = self.baseSpeed * self.slowFactor;
if (self.pathIndex < self.path.length - 1) {
var target = self.path[self.pathIndex + 1];
var dx = target.x - self.x;
var dy = target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 5) {
self.pathIndex++;
if (self.pathIndex >= self.path.length - 1) {
self.reachBase();
return;
}
} else {
var angle = Math.atan2(dy, dx);
self.x += Math.cos(angle) * currentSpeed;
self.y += Math.sin(angle) * currentSpeed;
}
} else {
self.reachBase();
}
};
return self;
});
var MoneyCoin = Container.expand(function (value) {
var self = Container.call(this);
self.value = value || 25;
self.collected = false;
var coinGraphics = self.attachAsset('moneyCoin', {
anchorX: 0.5,
anchorY: 0.5
});
coinGraphics.tint = 0xFFD700; // Gold color
// Add value text
self.valueText = new Text2('$' + self.value, {
size: 20,
fill: 0xFFFFFF
});
self.valueText.anchor.set(0.5, 0.5);
self.valueText.x = 0;
self.valueText.y = -25;
self.addChild(self.valueText);
self.collect = function () {
if (self.collected) return;
self.collected = true;
// Add money to player
playerMoney += self.value;
// If this coin was from a money tree, decrement its counter
if (self.sourceTree) {
self.sourceTree.currentMoney--;
}
// Play collection sound
LK.getSound('coinCollect').play();
// Collection animation - fly up and fade out
tween(self, {
y: self.y - 100,
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
}
});
// Update UI
updateUI();
};
self.down = function (x, y, obj) {
self.collect();
};
return self;
});
var MoneyTree = Container.expand(function (gridX, gridY) {
var self = Container.call(this);
self.type = 'moneyTree';
self.gridX = gridX;
self.gridY = gridY;
self.lastDropTime = 0;
self.dropInterval = 5000; // Drop money every 5 seconds
self.moneyAmount = 25; // Amount of money to drop
self.maxMoney = 2; // Maximum number of money coins that can be accumulated
self.currentMoney = 0; // Current number of money coins from this tree
var treeGraphics = self.attachAsset('moneyTree', {
anchorX: 0.5,
anchorY: 0.5
});
// Add glow effect to distinguish money trees
treeGraphics.tint = 0xFFD700; // Gold tint
self.dropMoney = function () {
// Only drop money if under the limit
if (self.currentMoney >= self.maxMoney) {
return; // Don't drop more money if at maximum
}
// Create money coin
var coin = new MoneyCoin(self.moneyAmount);
coin.x = self.x;
coin.y = self.y - 30; // Start above the tree
coin.sourceTree = self; // Reference to the tree that created this coin
// Increment the tree's money count
self.currentMoney++;
// Add to game
moneyCoins.push(coin);
game.addChild(coin);
// Animate coin dropping with bounce
var targetY = self.y + 50;
tween(coin, {
y: targetY
}, {
duration: 800,
easing: tween.bounceOut
});
// Add floating effect after landing
LK.setTimeout(function () {
tween(coin, {
y: targetY - 10
}, {
duration: 1000,
repeat: -1,
yoyo: true,
easing: tween.easeInOut
});
}, 800);
};
self.getTotalCost = function () {
return 200; // Cost of money tree is fixed at 200
};
self.sell = function () {
var sellValue = Math.floor(self.getTotalCost() * 0.75);
playerMoney += sellValue;
// Mark grid cell as unoccupied
grid[self.gridX][self.gridY].occupied = false;
// Remove from moneyTrees array
for (var i = moneyTrees.length - 1; i >= 0; i--) {
if (moneyTrees[i] === self) {
moneyTrees.splice(i, 1);
break;
}
}
// Clear selection
selectedTower = null;
showUpgradeUI = false;
// Update UI
updateUI();
// Destroy tree
self.destroy();
};
self.down = function (x, y, obj) {
if (gameState === 'playing') {
selectedTower = self;
showUpgradeUI = true;
}
};
self.update = function () {
var currentTime = Date.now();
if (currentTime - self.lastDropTime >= self.dropInterval) {
self.dropMoney();
self.lastDropTime = currentTime;
}
};
return self;
});
var Tower = Container.expand(function (type, gridX, gridY) {
var self = Container.call(this);
self.type = type || 'archer';
self.gridX = gridX;
self.gridY = gridY;
self.level = 1;
self.lastShotTime = 0;
// Tower stats based on type and level
self.getStats = function () {
var stats = {
archer: {
damage: 15 * self.level,
range: 240 + self.level * 20,
fireRate: 800 - self.level * 100,
// ms between shots
cost: 50,
upgradeCost: self.level * 60
},
ice: {
damage: 8 * self.level,
range: 135 + self.level * 15,
fireRate: 1200 - self.level * 150,
cost: 75,
upgradeCost: self.level * 90
},
cannon: {
damage: 40 * self.level,
range: 90 + self.level * 10,
fireRate: 1500 - self.level * 200,
cost: 100,
upgradeCost: self.level * 120
}
};
return stats[self.type];
};
var towerGraphics;
if (self.type === 'ice') {
towerGraphics = self.attachAsset('iceTower', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.type === 'cannon') {
towerGraphics = self.attachAsset('cannonTower', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
towerGraphics = self.attachAsset('archerTower', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Level indicator
self.levelText = new Text2(self.level.toString(), {
size: 20,
fill: 0xFFFFFF
});
self.levelText.anchor.set(0.5, 0.5);
self.levelText.x = 0;
self.levelText.y = -30;
self.addChild(self.levelText);
self.findTarget = function () {
var stats = self.getStats();
var closestEnemy = null;
var closestDistance = Infinity;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= stats.range && distance < closestDistance) {
closestDistance = distance;
closestEnemy = enemy;
}
}
return closestEnemy;
};
self.shoot = function (target) {
var stats = self.getStats();
var bullet = new Bullet(self.type, stats.damage, 8, self);
bullet.target = target;
bullet.x = self.x;
bullet.y = self.y;
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
};
self.getTotalCost = function () {
var stats = self.getStats();
var totalCost = stats.cost;
// Add upgrade costs for each level beyond 1
for (var i = 1; i < self.level; i++) {
totalCost += i * (stats.cost * 0.6); // Upgrade cost formula
}
return totalCost;
};
self.upgrade = function () {
var stats = self.getStats();
if (playerMoney >= stats.upgradeCost) {
playerMoney -= stats.upgradeCost;
self.level++;
self.levelText.setText(self.level.toString());
updateUI();
}
};
self.sell = function () {
var sellValue = Math.floor(self.getTotalCost() * 0.75);
playerMoney += sellValue;
// Mark grid cell as unoccupied
grid[self.gridX][self.gridY].occupied = false;
// Remove from towers array
for (var i = towers.length - 1; i >= 0; i--) {
if (towers[i] === self) {
towers.splice(i, 1);
break;
}
}
// Clear selection
selectedTower = null;
showUpgradeUI = false;
// Update UI
updateUI();
// Destroy tower
self.destroy();
};
self.down = function (x, y, obj) {
if (gameState === 'playing') {
selectedTower = self;
showUpgradeUI = true;
}
};
self.update = function () {
var stats = self.getStats();
var currentTime = Date.now();
if (currentTime - self.lastShotTime >= stats.fireRate) {
var target = self.findTarget();
if (target) {
self.shoot(target);
self.lastShotTime = currentTime;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x228B22
});
/****
* Game Code
****/
// Game state
var gameState = 'playing'; // 'playing', 'gameOver', 'victory'
var playerMoney = 200;
var playerLives = 20;
var currentWave = 1;
var maxWaves = 10;
var waveInProgress = false;
var nextWaveTimer = 0;
var enemySpawnTimer = 0;
var enemiesInWave = 0;
var enemiesSpawned = 0;
// Game objects
var towers = [];
var enemies = [];
var bullets = [];
var moneyTrees = [];
var moneyCoins = [];
var selectedTower = null;
var showUpgradeUI = false;
// Grid system
var gridSize = 80;
var gridWidth = Math.floor(2048 / gridSize);
var gridHeight = Math.floor(2732 / gridSize);
var grid = [];
// Path definition (from top to bottom with some turns)
var pathPoints = [{
x: 0,
y: 5
}, {
x: 3,
y: 5
}, {
x: 3,
y: 10
}, {
x: 8,
y: 10
}, {
x: 8,
y: 15
}, {
x: 15,
y: 15
}, {
x: 15,
y: 20
}, {
x: 20,
y: 20
}, {
x: 20,
y: 25
}, {
x: 25,
y: 25
}];
// Convert grid coordinates to world coordinates
function gridToWorld(gridX, gridY) {
return {
x: gridX * gridSize + gridSize / 2,
y: gridY * gridSize + gridSize / 2
};
}
// Convert world coordinates to grid coordinates
function worldToGrid(worldX, worldY) {
return {
x: Math.floor(worldX / gridSize),
y: Math.floor(worldY / gridSize)
};
}
// Initialize grid
function initializeGrid() {
for (var x = 0; x < gridWidth; x++) {
grid[x] = [];
for (var y = 0; y < gridHeight; y++) {
grid[x][y] = {
occupied: false,
isPath: false
};
}
}
// Mark path cells - create continuous path between all points
for (var i = 0; i < pathPoints.length; i++) {
var point = pathPoints[i];
if (point.x < gridWidth && point.y < gridHeight) {
grid[point.x][point.y].isPath = true;
}
// If not the last point, draw line to next point
if (i < pathPoints.length - 1) {
var nextPoint = pathPoints[i + 1];
var startX = point.x;
var startY = point.y;
var endX = nextPoint.x;
var endY = nextPoint.y;
// Draw horizontal line first, then vertical
if (startX !== endX) {
var minX = Math.min(startX, endX);
var maxX = Math.max(startX, endX);
for (var x = minX; x <= maxX; x++) {
if (x < gridWidth && startY < gridHeight) {
grid[x][startY].isPath = true;
}
}
}
if (startY !== endY) {
var minY = Math.min(startY, endY);
var maxY = Math.max(startY, endY);
for (var y = minY; y <= maxY; y++) {
if (endX < gridWidth && y < gridHeight) {
grid[endX][y].isPath = true;
}
}
}
}
}
}
// Draw grid
function drawGrid() {
for (var x = 0; x < gridWidth; x++) {
for (var y = 0; y < gridHeight; y++) {
var worldPos = gridToWorld(x, y);
var cell;
if (grid[x][y].isPath) {
cell = LK.getAsset('pathCell', {
anchorX: 0.5,
anchorY: 0.5,
x: worldPos.x,
y: worldPos.y,
alpha: 0.8
});
} else {
cell = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
x: worldPos.x,
y: worldPos.y,
alpha: 0.3
});
}
game.addChild(cell);
}
}
}
// Create path for enemies
function createPath() {
var path = [];
for (var i = 0; i < pathPoints.length; i++) {
var point = pathPoints[i];
var worldPos = gridToWorld(point.x, point.y);
path.push(worldPos);
}
return path;
}
// Place base at the end of path
function placeBase() {
var lastPoint = pathPoints[pathPoints.length - 1];
var worldPos = gridToWorld(lastPoint.x, lastPoint.y);
var base = LK.getAsset('base', {
anchorX: 0.5,
anchorY: 0.5,
x: worldPos.x,
y: worldPos.y
});
game.addChild(base);
}
// Place money trees at strategic locations - removed automatic placement for player purchase
function placeMoneyTrees() {
// Money trees are now purchased by player, no automatic placement
}
// UI elements
var moneyText = new Text2('Money: $' + playerMoney, {
size: 40,
fill: 0xFFFFFF
});
moneyText.anchor.set(0, 0);
moneyText.x = 120;
moneyText.y = 20;
LK.gui.topLeft.addChild(moneyText);
var livesText = new Text2('Lives: ' + playerLives, {
size: 40,
fill: 0xFFFFFF
});
livesText.anchor.set(1, 0);
livesText.x = -20;
livesText.y = 20;
LK.gui.topRight.addChild(livesText);
var waveText = new Text2('Wave: ' + currentWave + '/' + maxWaves, {
size: 40,
fill: 0xFFFFFF
});
waveText.anchor.set(0.5, 0);
waveText.x = 0;
waveText.y = 20;
LK.gui.top.addChild(waveText);
// Tower shop with button assets positioned at bottom corner
var shopY = -300; // Position from bottom
var buttonSize = 80;
var buttonSpacing = 100;
// Archer button (Buton1)
var archerButton = LK.getAsset('Buton1', {
anchorX: 0.5,
anchorY: 0.5,
x: buttonSpacing,
y: shopY
});
// Add direct click handler to archer button
archerButton.down = function (x, y, obj) {
selectedTowerType = 'archer';
showRangeCircle = true;
console.log('Archer button clicked directly');
// Add click animation
tween(archerButton, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 100,
onFinish: function onFinish() {
updateTowerSelection();
}
});
updateTowerSelection();
};
LK.gui.bottomLeft.addChild(archerButton);
var archerText = new Text2('Archer', {
size: 25,
fill: 0xFFFFFF
});
archerText.anchor.set(0.5, 0.5);
archerText.x = buttonSpacing;
archerText.y = shopY;
LK.gui.bottomLeft.addChild(archerText);
// Ice button (Buton2)
var iceButton = LK.getAsset('Buton2', {
anchorX: 0.5,
anchorY: 0.5,
x: buttonSpacing * 2,
y: shopY
});
// Add direct click handler to ice button
iceButton.down = function (x, y, obj) {
selectedTowerType = 'ice';
showRangeCircle = true;
console.log('Ice button clicked directly');
// Add click animation
tween(iceButton, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 100,
onFinish: function onFinish() {
updateTowerSelection();
}
});
updateTowerSelection();
};
LK.gui.bottomLeft.addChild(iceButton);
var iceText = new Text2('Ice', {
size: 25,
fill: 0xFFFFFF
});
iceText.anchor.set(0.5, 0.5);
iceText.x = buttonSpacing * 2;
iceText.y = shopY;
LK.gui.bottomLeft.addChild(iceText);
// Cannon button (Buton3)
var cannonButton = LK.getAsset('Buton3', {
anchorX: 0.5,
anchorY: 0.5,
x: buttonSpacing * 3,
y: shopY
});
// Add direct click handler to cannon button
cannonButton.down = function (x, y, obj) {
selectedTowerType = 'cannon';
showRangeCircle = true;
console.log('Cannon button clicked directly');
// Add click animation
tween(cannonButton, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 100,
onFinish: function onFinish() {
updateTowerSelection();
}
});
updateTowerSelection();
};
LK.gui.bottomLeft.addChild(cannonButton);
var cannonText = new Text2('Cannon', {
size: 25,
fill: 0xFFFFFF
});
cannonText.anchor.set(0.5, 0.5);
cannonText.x = buttonSpacing * 3;
cannonText.y = shopY;
LK.gui.bottomLeft.addChild(cannonText);
// Money Tree button (fourth button)
var moneyTreeButton = LK.getAsset('Buton1', {
anchorX: 0.5,
anchorY: 0.5,
x: buttonSpacing * 4,
y: shopY
});
moneyTreeButton.tint = 0xFFD700; // Gold color for money tree
// Add direct click handler to money tree button
moneyTreeButton.down = function (x, y, obj) {
selectedTowerType = 'moneyTree';
showRangeCircle = false; // No range circle for money trees
console.log('Money Tree button clicked directly');
// Add click animation
tween(moneyTreeButton, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 100,
onFinish: function onFinish() {
updateTowerSelection();
}
});
updateTowerSelection();
};
LK.gui.bottomLeft.addChild(moneyTreeButton);
var moneyTreeText = new Text2('Money Tree', {
size: 20,
fill: 0xFFFFFF
});
moneyTreeText.anchor.set(0.5, 0.5);
moneyTreeText.x = buttonSpacing * 4;
moneyTreeText.y = shopY;
LK.gui.bottomLeft.addChild(moneyTreeText);
// Selected tower info
var selectedTowerType = 'archer';
var rangeCircle = null;
var showRangeCircle = false;
// Add status text to show selected tower type
var selectedTowerText = new Text2('Selected: Archer Tower ($50)', {
size: 30,
fill: 0x00FF00
});
selectedTowerText.anchor.set(1, 0);
selectedTowerText.x = -20;
selectedTowerText.y = 80;
LK.gui.topRight.addChild(selectedTowerText);
function showTowerRange(x, y) {
if (!showRangeCircle) return;
// Get range for selected tower type
var towerRanges = {
archer: 240,
ice: 135,
cannon: 90
};
var range = towerRanges[selectedTowerType];
// Remove existing range circle
if (rangeCircle) {
rangeCircle.destroy();
rangeCircle = null;
}
// Create new range circle
rangeCircle = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: range / 40,
// Scale to match range (gridCell is 80px, so 40 is radius)
scaleY: range / 40,
alpha: 0.3,
x: x,
y: y
});
rangeCircle.tint = 0x0080FF; // Semi-transparent blue
game.addChild(rangeCircle);
}
function hideRangeCircle() {
showRangeCircle = false;
if (rangeCircle) {
rangeCircle.destroy();
rangeCircle = null;
}
}
function updateTowerSelection() {
// Stop any existing tweens on buttons
tween.stop(archerButton, {
tint: true,
scaleX: true,
scaleY: true
});
tween.stop(iceButton, {
tint: true,
scaleX: true,
scaleY: true
});
tween.stop(cannonButton, {
tint: true,
scaleX: true,
scaleY: true
});
tween.stop(moneyTreeButton, {
tint: true,
scaleX: true,
scaleY: true
});
// Reset all button colors and scales
archerButton.tint = 0xFFFFFF;
iceButton.tint = 0xFFFFFF;
cannonButton.tint = 0xFFFFFF;
moneyTreeButton.tint = 0xFFD700; // Gold base color for money tree
archerButton.scaleX = archerButton.scaleY = 1;
iceButton.scaleX = iceButton.scaleY = 1;
cannonButton.scaleX = cannonButton.scaleY = 1;
moneyTreeButton.scaleX = moneyTreeButton.scaleY = 1;
// Highlight selected button with bright green glow and scale
if (selectedTowerType === 'archer') {
archerButton.tint = 0x00FF00; // Bright green for selected
archerButton.scaleX = archerButton.scaleY = 1.2;
// Add pulsing effect
tween(archerButton, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 500,
repeat: -1,
yoyo: true,
easing: tween.easeInOut
});
} else if (selectedTowerType === 'ice') {
iceButton.tint = 0x00FF00; // Bright green for selected
iceButton.scaleX = iceButton.scaleY = 1.2;
// Add pulsing effect
tween(iceButton, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 500,
repeat: -1,
yoyo: true,
easing: tween.easeInOut
});
} else if (selectedTowerType === 'cannon') {
cannonButton.tint = 0x00FF00; // Bright green for selected
cannonButton.scaleX = cannonButton.scaleY = 1.2;
// Add pulsing effect
tween(cannonButton, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 500,
repeat: -1,
yoyo: true,
easing: tween.easeInOut
});
} else if (selectedTowerType === 'moneyTree') {
moneyTreeButton.tint = 0x00FF00; // Bright green for selected
moneyTreeButton.scaleX = moneyTreeButton.scaleY = 1.2;
// Add pulsing effect
tween(moneyTreeButton, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 500,
repeat: -1,
yoyo: true,
easing: tween.easeInOut
});
}
// Update status text
var towerCosts = {
archer: 50,
ice: 75,
cannon: 100,
moneyTree: 200
};
var cost = towerCosts[selectedTowerType];
var towerName = selectedTowerType === 'moneyTree' ? 'Money Tree' : selectedTowerType.charAt(0).toUpperCase() + selectedTowerType.slice(1);
var canAfford = playerMoney >= cost;
var statusColor = canAfford ? 0x00FF00 : 0xFF0000;
selectedTowerText.tint = statusColor;
var itemType = selectedTowerType === 'moneyTree' ? '' : ' Tower';
selectedTowerText.setText('Selected: ' + towerName + itemType + ' ($' + cost + ') - Click grid to place');
}
// Upgrade UI Canvas Background
var upgradeCanvas = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 6,
scaleY: 4,
alpha: 0.7
});
upgradeCanvas.tint = 0x333333; // Semi-transparent gray
upgradeCanvas.x = 0;
upgradeCanvas.y = 280;
LK.gui.center.addChild(upgradeCanvas);
// Upgrade UI Title
var upgradeTitle = new Text2('TOWER INFO', {
size: 32,
fill: 0xFFFFFF
});
upgradeTitle.anchor.set(0.5, 0.5);
upgradeTitle.x = 0;
upgradeTitle.y = 180;
LK.gui.center.addChild(upgradeTitle);
// Tower stats display
var upgradeText = new Text2('', {
size: 28,
fill: 0xFFFFFF
});
upgradeText.anchor.set(0.5, 0.5);
upgradeText.x = 0;
upgradeText.y = 250;
LK.gui.center.addChild(upgradeText);
// Upgrade button with background
var upgradeButtonBg = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 0.8,
alpha: 0.8
});
upgradeButtonBg.tint = 0xFFD700;
upgradeButtonBg.x = 0;
upgradeButtonBg.y = 350;
LK.gui.center.addChild(upgradeButtonBg);
var upgradeButton = new Text2('', {
size: 30,
fill: 0x000000
});
upgradeButton.anchor.set(0.5, 0.5);
upgradeButton.x = 0;
upgradeButton.y = 350;
upgradeButton.scaleX = 1;
upgradeButton.scaleY = 1;
LK.gui.center.addChild(upgradeButton);
// Sell button with background
var sellButtonBg = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 0.8,
alpha: 0.8
});
sellButtonBg.tint = 0xFF4444;
sellButtonBg.x = 0;
sellButtonBg.y = 410;
LK.gui.center.addChild(sellButtonBg);
var sellButton = new Text2('', {
size: 30,
fill: 0xFFFFFF
});
sellButton.anchor.set(0.5, 0.5);
sellButton.x = 0;
sellButton.y = 410;
sellButton.scaleX = 1;
sellButton.scaleY = 1;
LK.gui.center.addChild(sellButton);
upgradeButton.down = function () {
if (showUpgradeUI && selectedTower) {
var stats = selectedTower.getStats();
if (playerMoney >= stats.upgradeCost) {
// Animate both button and background
tween(upgradeButton, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 100,
onFinish: function onFinish() {
tween(upgradeButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
}
});
tween(upgradeButtonBg, {
scaleX: 2.7,
scaleY: 0.9
}, {
duration: 100,
onFinish: function onFinish() {
tween(upgradeButtonBg, {
scaleX: 2.5,
scaleY: 0.8
}, {
duration: 100
});
}
});
selectedTower.upgrade();
}
}
};
sellButton.down = function () {
if (showUpgradeUI && selectedTower) {
// Animate both button and background
tween(sellButton, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 100,
onFinish: function onFinish() {
tween(sellButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
}
});
tween(sellButtonBg, {
scaleX: 2.7,
scaleY: 0.9
}, {
duration: 100,
onFinish: function onFinish() {
tween(sellButtonBg, {
scaleX: 2.5,
scaleY: 0.8
}, {
duration: 100
});
}
});
selectedTower.sell();
}
};
function updateUI() {
moneyText.setText('Money: $' + playerMoney);
livesText.setText('Lives: ' + playerLives);
waveText.setText('Wave: ' + currentWave + '/' + maxWaves);
updateTowerSelection(); // Update selection display when money changes
if (showUpgradeUI && selectedTower) {
var isMoneyTree = selectedTower.type === 'moneyTree';
var sellValue = Math.floor(selectedTower.getTotalCost() * 0.75);
// Show canvas and UI elements
upgradeCanvas.alpha = 0.7;
upgradeTitle.alpha = 1;
upgradeText.alpha = 1;
upgradeButton.alpha = isMoneyTree ? 0 : 1;
sellButton.alpha = 1;
upgradeButtonBg.alpha = isMoneyTree ? 0 : 0.8;
sellButtonBg.alpha = 0.8;
sellButton.setText('SELL $' + sellValue);
if (isMoneyTree) {
// Update text content for Money Tree
upgradeTitle.setText('MONEY TREE');
var infoText = 'Generates $' + selectedTower.moneyAmount + ' every ' + selectedTower.dropInterval / 1000 + 's.\n';
infoText += 'Max ' + selectedTower.maxMoney + ' coins can accumulate.';
upgradeText.setText(infoText);
} else {
// Update text content for normal towers
var stats = selectedTower.getStats();
var towerName = selectedTower.type.charAt(0).toUpperCase() + selectedTower.type.slice(1);
upgradeTitle.setText(towerName.toUpperCase() + ' TOWER');
upgradeText.setText('Damage: ' + stats.damage + '\nRange: ' + stats.range + '\nFire Rate: ' + Math.round(1000 / stats.fireRate * 60) + '/sec');
upgradeButton.setText('UPGRADE $' + stats.upgradeCost);
// Visual feedback for upgrade affordability
if (playerMoney >= stats.upgradeCost) {
upgradeButtonBg.tint = 0xFFD700; // Gold when affordable
upgradeButton.tint = 0x000000; // Black text on gold background
} else {
upgradeButtonBg.tint = 0x666666; // Gray when not affordable
upgradeButton.tint = 0xFFFFFF; // White text on gray background
}
}
// Sell button is always available
sellButtonBg.tint = 0xFF4444;
sellButton.tint = 0xFFFFFF;
} else {
upgradeCanvas.alpha = 0;
upgradeTitle.alpha = 0;
upgradeText.alpha = 0;
upgradeButton.alpha = 0;
sellButton.alpha = 0;
upgradeButtonBg.alpha = 0;
sellButtonBg.alpha = 0;
}
}
// Wave management
function getWaveData(waveNumber) {
if (waveNumber === 5 || waveNumber === 10) {
var waveEnemies = ['boss'];
var escortTypes = ['basic', 'fast', 'tank'];
var numEscorts = waveNumber === 5 ? 8 : 12;
for (var i = 0; i < numEscorts; i++) {
var type = escortTypes[Math.floor(Math.random() * escortTypes.length)];
waveEnemies.push(type);
}
// Shuffle the wave so boss doesn't always appear first
for (var i = waveEnemies.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = waveEnemies[i];
waveEnemies[i] = waveEnemies[j];
waveEnemies[j] = temp;
}
return waveEnemies;
}
var baseEnemies = 5 + waveNumber * 2;
var enemyTypes = ['basic'];
if (waveNumber >= 3) enemyTypes.push('fast');
if (waveNumber >= 5) enemyTypes.push('tank');
var waveEnemies = [];
for (var i = 0; i < baseEnemies; i++) {
var type = enemyTypes[Math.floor(Math.random() * enemyTypes.length)];
waveEnemies.push(type);
}
return waveEnemies;
}
function startWave() {
if (currentWave > maxWaves) {
gameState = 'victory';
LK.showYouWin();
return;
}
waveInProgress = true;
var waveData = getWaveData(currentWave);
enemiesInWave = waveData.length;
enemiesSpawned = 0;
enemySpawnTimer = 0;
// Store wave data for spawning
game.currentWaveData = waveData;
}
function spawnEnemy() {
if (!waveInProgress || enemiesSpawned >= enemiesInWave) return;
var enemyType = game.currentWaveData[enemiesSpawned];
var path = createPath();
var enemy = new Enemy(enemyType, path);
enemy.x = path[0].x;
enemy.y = path[0].y;
enemies.push(enemy);
game.addChild(enemy);
enemiesSpawned++;
}
function checkWaveComplete() {
if (waveInProgress && enemies.length === 0 && enemiesSpawned >= enemiesInWave) {
waveInProgress = false;
currentWave++;
nextWaveTimer = LK.ticks + 180; // 3 seconds delay
updateUI();
}
}
// Mouse move handler for range circle
game.move = function (x, y, obj) {
if (showRangeCircle && gameState === 'playing') {
showTowerRange(x, y);
}
};
// Game input handling
game.down = function (x, y, obj) {
if (gameState !== 'playing') return;
console.log('Click detected at raw coordinates:', x, y);
// Use the click coordinates directly as they're already in game space
var gameX = x;
var gameY = y;
console.log('Game coordinates:', gameX, gameY);
// Check tower shop buttons - convert screen coordinates to GUI coordinates
var screenPoint = {
x: x,
y: y
};
var guiPoint = LK.gui.bottomLeft.toLocal(screenPoint);
console.log('GUI click coordinates:', guiPoint.x, guiPoint.y);
// Check archer button (Buton1) - expanded click area for better touch detection
if (guiPoint.x >= 50 && guiPoint.x <= 150 && guiPoint.y >= shopY - 50 && guiPoint.y <= shopY + 50) {
selectedTowerType = 'archer';
showRangeCircle = true;
console.log('Selected tower type: archer');
// Add click animation
tween(archerButton, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 100,
onFinish: function onFinish() {
updateTowerSelection(); // This will reset scale based on selection
}
});
updateTowerSelection();
return;
}
// Check ice button (Buton2) - expanded click area for better touch detection
else if (guiPoint.x >= 150 && guiPoint.x <= 250 && guiPoint.y >= shopY - 50 && guiPoint.y <= shopY + 50) {
selectedTowerType = 'ice';
showRangeCircle = true;
console.log('Selected tower type: ice');
// Add click animation
tween(iceButton, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 100,
onFinish: function onFinish() {
updateTowerSelection(); // This will reset scale based on selection
}
});
updateTowerSelection();
return;
}
// Check cannon button (Buton3) - expanded click area for better touch detection
else if (guiPoint.x >= 250 && guiPoint.x <= 350 && guiPoint.y >= shopY - 50 && guiPoint.y <= shopY + 50) {
selectedTowerType = 'cannon';
showRangeCircle = true;
console.log('Selected tower type: cannon');
// Add click animation
tween(cannonButton, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 100,
onFinish: function onFinish() {
updateTowerSelection(); // This will reset scale based on selection
}
});
updateTowerSelection();
return;
}
// Check money tree button - expanded click area for better touch detection
else if (guiPoint.x >= 350 && guiPoint.x <= 450 && guiPoint.y >= shopY - 50 && guiPoint.y <= shopY + 50) {
selectedTowerType = 'moneyTree';
showRangeCircle = false; // No range circle for money trees
console.log('Selected tower type: money tree');
// Add click animation
tween(moneyTreeButton, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 100,
onFinish: function onFinish() {
updateTowerSelection(); // This will reset scale based on selection
}
});
updateTowerSelection();
return;
}
// Upgrade and Sell button clicks are now handled by their own .down methods.
var centerPos = LK.gui.center.toLocal({
x: x,
y: y
});
// Hide upgrade UI if clicking elsewhere
if (showUpgradeUI) {
showUpgradeUI = false;
selectedTower = null;
updateUI();
}
// Hide range circle when clicking elsewhere (cancelling purchase)
hideRangeCircle();
// Try to place tower using game coordinates
var gridPos = worldToGrid(gameX, gameY);
console.log('Grid position:', gridPos.x, gridPos.y);
console.log('Grid bounds check:', gridPos.x >= 0, gridPos.x < gridWidth, gridPos.y >= 0, gridPos.y < gridHeight);
if (gridPos.x >= 0 && gridPos.x < gridWidth && gridPos.y >= 0 && gridPos.y < gridHeight) {
console.log('Grid cell occupied:', grid[gridPos.x][gridPos.y].occupied);
console.log('Grid cell is path:', grid[gridPos.x][gridPos.y].isPath);
if (!grid[gridPos.x][gridPos.y].occupied && !grid[gridPos.x][gridPos.y].isPath) {
var towerCosts = {
archer: 50,
ice: 75,
cannon: 100,
moneyTree: 200
};
var cost = towerCosts[selectedTowerType];
console.log('Tower cost:', cost, 'Player money:', playerMoney);
if (playerMoney >= cost) {
console.log('Placing', selectedTowerType, 'at grid:', gridPos.x, gridPos.y);
var worldPos = gridToWorld(gridPos.x, gridPos.y);
console.log('World position:', worldPos.x, worldPos.y);
if (selectedTowerType === 'moneyTree') {
// Place money tree
var tree = new MoneyTree(gridPos.x, gridPos.y);
tree.x = worldPos.x;
tree.y = worldPos.y;
// Add placement animation
tree.scaleX = 0;
tree.scaleY = 0;
tween(tree, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.bounceOut
});
moneyTrees.push(tree);
game.addChild(tree);
} else {
// Place tower
var tower = new Tower(selectedTowerType, gridPos.x, gridPos.y);
tower.x = worldPos.x;
tower.y = worldPos.y;
// Add placement animation
tower.scaleX = 0;
tower.scaleY = 0;
tween(tower, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.bounceOut
});
towers.push(tower);
game.addChild(tower);
}
grid[gridPos.x][gridPos.y].occupied = true;
playerMoney -= cost;
hideRangeCircle(); // Hide range circle after placing tower
updateUI();
console.log(selectedTowerType, 'placed successfully!');
} else {
console.log('Not enough money to place', selectedTowerType);
}
} else {
console.log('Grid cell is occupied or on path');
}
} else {
console.log('Click outside grid bounds');
}
};
// Initialize game
initializeGrid();
drawGrid();
placeBase();
placeMoneyTrees();
updateUI();
updateTowerSelection();
startWave();
// Main game loop
game.update = function () {
if (gameState !== 'playing') return;
// Update all towers (this enables shooting!)
for (var i = 0; i < towers.length; i++) {
towers[i].update();
}
// Update all bullets (this enables bullet movement and collision detection!)
for (var i = 0; i < bullets.length; i++) {
bullets[i].update();
}
// Update all enemies
for (var i = 0; i < enemies.length; i++) {
enemies[i].update();
}
// Update all money trees
for (var i = 0; i < moneyTrees.length; i++) {
moneyTrees[i].update();
}
// Clean up collected money coins
for (var i = moneyCoins.length - 1; i >= 0; i--) {
if (moneyCoins[i].collected) {
moneyCoins.splice(i, 1);
}
}
// Spawn enemies
if (waveInProgress && LK.ticks % 60 === 0) {
// Every second
spawnEnemy();
}
// Start next wave
if (!waveInProgress && LK.ticks >= nextWaveTimer) {
startWave();
}
// Clean up destroyed enemies
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i].destroyed) {
enemies.splice(i, 1);
}
}
// Clean up destroyed bullets
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i].destroyed) {
bullets.splice(i, 1);
}
}
checkWaveComplete();
};
bow with arrow. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
ice tower. In-Game asset. 2d. High contrast. No shadows
medieval soldier. In-Game asset. 2d. High contrast. No shadows
cannon. In-Game asset. 2d. High contrast. No shadows
knight with horse. In-Game asset. 2d. High contrast. No shadows
cannonball. In-Game asset. 2d. High contrast. No shadows
arrow. In-Game asset. 2d. High contrast. No shadows
ice bullet. In-Game asset. 2d. High contrast. No shadows
build a tower from a bird's eye view. In-Game asset. 2d. High contrast. No shadows
giant. In-Game asset. 2d. High contrast. No shadows
bird's eye view of grass. In-Game asset. 2d. High contrast. No shadows
soil bird's eye view. In-Game asset. 2d. High contrast. No shadows
money. In-Game asset. 2d. High contrast. No shadows
money tree. In-Game asset. 2d. High contrast. No shadows
giand mosnter. In-Game asset. 2d. High contrast. No shadows
bad wizard. In-Game asset. 2d. High contrast. No shadows