/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Enemy = Container.expand(function (type, pathIndex) { var self = Container.call(this); self.type = type; self.pathIndex = pathIndex || 0; self.speed = 2; self.maxHealth = 100; self.health = self.maxHealth; self.resources = 10; self.isDead = false; // Set enemy properties based on type if (type === 'goblin') { self.speed = 3; self.maxHealth = 50; self.health = 50; self.resources = 5; self.enemyGraphics = self.attachAsset('goblin', { anchorX: 0.5, anchorY: 0.5 }); } else if (type === 'orc') { self.speed = 2; self.maxHealth = 100; self.health = 100; self.resources = 10; self.enemyGraphics = self.attachAsset('orc', { anchorX: 0.5, anchorY: 0.5 }); } else if (type === 'troll') { self.speed = 1; self.maxHealth = 200; self.health = 200; self.resources = 20; self.enemyGraphics = self.attachAsset('troll', { anchorX: 0.5, anchorY: 0.5 }); } // Health bar self.healthBar = self.addChild(LK.getAsset('pathTile', { width: 40, height: 6, anchorX: 0.5, anchorY: 0.5, tint: 0x00FF00 })); self.healthBar.y = -40; self.takeDamage = function (damage) { self.health -= damage; var healthPercent = self.health / self.maxHealth; self.healthBar.width = 40 * healthPercent; if (healthPercent > 0.6) { self.healthBar.tint = 0x00FF00; } else if (healthPercent > 0.3) { self.healthBar.tint = 0xFFFF00; } else { self.healthBar.tint = 0xFF0000; } if (self.health <= 0) { self.isDead = true; LK.getSound('enemyDeath').play(); return true; } LK.getSound('enemyHit').play(); return false; }; self.update = function () { if (self.isDead) { return; } // Move along path if (self.pathIndex < gamePath.length - 1) { var currentTarget = gamePath[self.pathIndex + 1]; var dx = currentTarget.x - self.x; var dy = currentTarget.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 10) { self.pathIndex++; if (self.pathIndex >= gamePath.length - 1) { // Reached base self.reachedBase = true; } } else { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } } }; return self; }); var Projectile = Container.expand(function (type, startX, startY, targetX, targetY, damage) { var self = Container.call(this); self.type = type; self.damage = damage; self.speed = 8; self.targetX = targetX; self.targetY = targetY; self.hasHit = false; self.x = startX; self.y = startY; if (type === 'arrow') { self.projectileGraphics = self.attachAsset('arrow', { anchorX: 0.5, anchorY: 0.5 }); } else if (type === 'fireball') { self.projectileGraphics = self.attachAsset('fireball', { anchorX: 0.5, anchorY: 0.5 }); } else if (type === 'sword') { self.projectileGraphics = self.attachAsset('sword', { anchorX: 0.5, anchorY: 0.5 }); } // Calculate direction var dx = targetX - startX; var dy = targetY - startY; var distance = Math.sqrt(dx * dx + dy * dy); self.dirX = dx / distance; self.dirY = dy / distance; self.update = function () { if (self.hasHit) { return; } self.x += self.dirX * self.speed; self.y += self.dirY * self.speed; // Check if reached target area var distanceToTarget = Math.sqrt(Math.pow(self.x - self.targetX, 2) + Math.pow(self.y - self.targetY, 2)); if (distanceToTarget < 20) { self.hasHit = true; } // Remove if off screen if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) { self.hasHit = true; } }; return self; }); var Tower = Container.expand(function (type, slotX, slotY) { var self = Container.call(this); self.type = type; self.level = 1; self.range = 150; self.damage = 25; self.attackSpeed = 60; // frames between attacks self.lastAttack = 0; self.cost = 50; self.x = slotX; self.y = slotY; if (type === 'archer') { self.towerGraphics = self.attachAsset('archerTower', { anchorX: 0.5, anchorY: 0.5 }); self.projectileType = 'arrow'; self.range = 180; self.damage = 20; self.attackSpeed = 40; self.cost = 30; } else if (type === 'mage') { self.towerGraphics = self.attachAsset('mageTower', { anchorX: 0.5, anchorY: 0.5 }); self.projectileType = 'fireball'; self.range = 120; self.damage = 40; self.attackSpeed = 80; self.cost = 60; } else if (type === 'warrior') { self.towerGraphics = self.attachAsset('warriorTower', { anchorX: 0.5, anchorY: 0.5 }); self.projectileType = 'sword'; self.range = 100; self.damage = 60; self.attackSpeed = 100; self.cost = 80; self.armyCooldown = 60 * 60; // 60 seconds at 60 FPS self.lastArmyRelease = 0; } // Range indicator (invisible by default) self.rangeIndicator = self.addChild(LK.getAsset('pathTile', { width: self.range * 2, height: self.range * 2, anchorX: 0.5, anchorY: 0.5, alpha: 0, tint: 0xFFFFFF })); self.findTarget = function () { var closestEnemy = null; var closestDistance = Infinity; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy.isDead) { continue; } var distance = Math.sqrt(Math.pow(enemy.x - self.x, 2) + Math.pow(enemy.y - self.y, 2)); if (distance <= self.range && distance < closestDistance) { closestDistance = distance; closestEnemy = enemy; } } return closestEnemy; }; self.releaseArmy = function () { // Clear all enemies on the path for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; if (!enemy.isDead) { // Add visual effect before destroying tween(enemy, { alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { if (enemy && !enemy.isDead) { gameResources += enemy.resources; gameResources += 10; // Bonus 10 coins for warrior tower kills enemy.isDead = true; enemy.destroy(); enemies.splice(enemies.indexOf(enemy), 1); } } }); } } // Visual effect on tower tween(self.towerGraphics, { tint: 0xFFD700 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(self.towerGraphics, { tint: 0xFFFFFF }, { duration: 800, easing: tween.easeIn }); } }); LK.getSound('shoot').play(); }; self.attack = function (target) { if (self.type === 'warrior') { // Check if army cooldown is ready if (LK.ticks - self.lastArmyRelease >= self.armyCooldown) { self.releaseArmy(); self.lastArmyRelease = LK.ticks; } return; } if (LK.ticks - self.lastAttack < self.attackSpeed) { return; } var projectile = new Projectile(self.projectileType, self.x, self.y, target.x, target.y, self.damage); projectiles.push(projectile); game.addChild(projectile); self.lastAttack = LK.ticks; LK.getSound('shoot').play(); }; self.update = function () { var target = self.findTarget(); if (target) { self.attack(target); } // Update warrior tower cooldown indicator if (self.type === 'warrior') { var cooldownRemaining = self.armyCooldown - (LK.ticks - self.lastArmyRelease); if (cooldownRemaining > 0) { var cooldownPercent = cooldownRemaining / self.armyCooldown; self.towerGraphics.tint = 0x888888 + Math.floor((1 - cooldownPercent) * 0x777777); } else { self.towerGraphics.tint = 0xFFFFFF; } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2F4F2F }); /**** * Game Code ****/ // Game state variables // Tower Assets // Enemy Assets // Projectile Assets // UI Assets // Sound Assets var gameResources = 100; var baseHealth = 100; var currentWave = 1; var enemiesInWave = 5; var enemiesSpawned = 0; var waveInProgress = false; var gameOver = false; var selectedTowerType = 'archer'; // Game arrays var enemies = []; var towers = []; var projectiles = []; var towerSlots = []; // Different paths for different waves var allPaths = [ // Path 1 - Straight horizontal then vertical [{ x: 100, y: 100 }, { x: 300, y: 100 }, { x: 300, y: 300 }, { x: 600, y: 300 }, { x: 600, y: 500 }, { x: 900, y: 500 }, { x: 900, y: 700 }, { x: 1200, y: 700 }, { x: 1200, y: 900 }, { x: 1500, y: 900 }, { x: 1500, y: 1100 }, { x: 1800, y: 1100 }], // Path 2 - Zigzag pattern [{ x: 100, y: 200 }, { x: 500, y: 200 }, { x: 500, y: 400 }, { x: 200, y: 400 }, { x: 200, y: 600 }, { x: 800, y: 600 }, { x: 800, y: 800 }, { x: 400, y: 800 }, { x: 400, y: 1000 }, { x: 1200, y: 1000 }, { x: 1200, y: 1200 }, { x: 1800, y: 1200 }], // Path 3 - Curved path [{ x: 100, y: 300 }, { x: 400, y: 300 }, { x: 400, y: 150 }, { x: 700, y: 150 }, { x: 700, y: 450 }, { x: 1000, y: 450 }, { x: 1000, y: 200 }, { x: 1300, y: 200 }, { x: 1300, y: 700 }, { x: 1600, y: 700 }, { x: 1600, y: 1000 }, { x: 1800, y: 1000 }], // Path 4 - Long winding path [{ x: 100, y: 400 }, { x: 200, y: 400 }, { x: 200, y: 200 }, { x: 600, y: 200 }, { x: 600, y: 600 }, { x: 400, y: 600 }, { x: 400, y: 800 }, { x: 1000, y: 800 }, { x: 1000, y: 400 }, { x: 1400, y: 400 }, { x: 1400, y: 1200 }, { x: 1800, y: 1200 }]]; // Current game path - changes each wave var gamePath = allPaths[0]; // Path tiles array to track them var pathTiles = []; // Create path tiles function createPathTiles() { for (var i = 0; i < gamePath.length; i++) { var pathTile = game.addChild(LK.getAsset('pathTile', { anchorX: 0.5, anchorY: 0.5, alpha: 0.3 })); pathTile.x = gamePath[i].x; pathTile.y = gamePath[i].y; pathTiles.push(pathTile); } } // Function to redraw path for new waves function redrawPath() { // Remove old path tiles for (var i = 0; i < pathTiles.length; i++) { pathTiles[i].destroy(); } pathTiles = []; // Create new path tiles createPathTiles(); // Update base position to end of new path base.x = gamePath[gamePath.length - 1].x; base.y = gamePath[gamePath.length - 1].y; } // Initialize path tiles createPathTiles(); // Create base at end of path var base = game.addChild(LK.getAsset('base', { anchorX: 0.5, anchorY: 0.5 })); base.x = gamePath[gamePath.length - 1].x; base.y = gamePath[gamePath.length - 1].y; // Create tower slots around the path var baseSlotPositions = [{ x: 200, y: 200 }, { x: 400, y: 200 }, { x: 500, y: 400 }, { x: 700, y: 400 }, { x: 800, y: 600 }, { x: 1000, y: 600 }, { x: 1100, y: 800 }, { x: 1300, y: 800 }, { x: 1400, y: 1000 }, { x: 1600, y: 1000 }, { x: 1700, y: 1200 }]; // Additional slots for wave 2 and higher var extraSlotPositions = [{ x: 150, y: 350 }, { x: 350, y: 550 }, { x: 650, y: 350 }, { x: 850, y: 450 }, { x: 1050, y: 350 }, { x: 1250, y: 550 }, { x: 1450, y: 650 }, { x: 1550, y: 850 }]; function createTowerSlots() { // Clear existing slots for (var i = 0; i < towerSlots.length; i++) { towerSlots[i].destroy(); } towerSlots = []; // Always create base slots var slotsToCreate = baseSlotPositions.slice(); // Add extra slots for wave 2 and higher if (currentWave >= 2) { slotsToCreate = slotsToCreate.concat(extraSlotPositions); } for (var i = 0; i < slotsToCreate.length; i++) { var slot = game.addChild(LK.getAsset('towerSlot', { anchorX: 0.5, anchorY: 0.5, alpha: 0.7 })); slot.x = slotsToCreate[i].x; slot.y = slotsToCreate[i].y; slot.isEmpty = true; slot.slotIndex = i; towerSlots.push(slot); } } // Initialize tower slots createTowerSlots(); // Create decorative trees and bushes var decorativeElements = []; var treePositions = [{ x: 50, y: 50 }, { x: 150, y: 80 }, { x: 250, y: 120 }, { x: 1850, y: 100 }, { x: 1750, y: 150 }, { x: 1650, y: 80 }, { x: 80, y: 800 }, { x: 180, y: 950 }, { x: 120, y: 1100 }, { x: 1880, y: 1400 }, { x: 1780, y: 1500 }, { x: 1820, y: 1600 }, { x: 950, y: 50 }, { x: 1050, y: 80 }, { x: 850, y: 120 }, { x: 50, y: 1800 }, { x: 150, y: 1900 }, { x: 250, y: 2000 }]; var bushPositions = [{ x: 350, y: 80 }, { x: 450, y: 120 }, { x: 550, y: 90 }, { x: 1550, y: 200 }, { x: 1650, y: 180 }, { x: 1750, y: 220 }, { x: 80, y: 1200 }, { x: 180, y: 1300 }, { x: 120, y: 1400 }, { x: 1880, y: 1800 }, { x: 1780, y: 1900 }, { x: 1820, y: 2000 }, { x: 750, y: 100 }, { x: 850, y: 180 }, { x: 950, y: 140 }, { x: 1100, y: 1800 }, { x: 1200, y: 1900 }, { x: 1300, y: 2000 }, { x: 300, y: 1600 }, { x: 400, y: 1700 }, { x: 500, y: 1800 }]; // Create trees for (var i = 0; i < treePositions.length; i++) { var tree = game.addChild(LK.getAsset('tree', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8 })); tree.x = treePositions[i].x; tree.y = treePositions[i].y; decorativeElements.push(tree); } // Create bushes for (var i = 0; i < bushPositions.length; i++) { var bush = game.addChild(LK.getAsset('bush', { anchorX: 0.5, anchorY: 0.5, alpha: 0.7 })); bush.x = bushPositions[i].x; bush.y = bushPositions[i].y; decorativeElements.push(bush); } // UI Elements var resourcesText = new Text2('Resources: ' + gameResources, { size: 60, fill: 0xFFD700 }); resourcesText.anchor.set(0, 0); LK.gui.top.addChild(resourcesText); resourcesText.x = 200; resourcesText.y = 20; var healthText = new Text2('Base Health: ' + baseHealth, { size: 60, fill: 0xFF0000 }); healthText.anchor.set(0, 0); LK.gui.top.addChild(healthText); healthText.x = 200; healthText.y = 90; var waveText = new Text2('Wave: ' + currentWave, { size: 60, fill: 0xFFFFFF }); waveText.anchor.set(0, 0); LK.gui.top.addChild(waveText); waveText.x = 200; waveText.y = 160; // Tower selection buttons var archerButton = LK.gui.bottomLeft.addChild(LK.getAsset('archerTower', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 })); archerButton.x = 80; archerButton.y = -80; var mageButton = LK.gui.bottomLeft.addChild(LK.getAsset('mageTower', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 })); mageButton.x = 200; mageButton.y = -80; var warriorButton = LK.gui.bottomLeft.addChild(LK.getAsset('warriorTower', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 })); warriorButton.x = 320; warriorButton.y = -80; // Tower cost labels var archerCost = new Text2('30', { size: 30, fill: 0xFFFFFF }); archerCost.anchor.set(0.5, 0); LK.gui.bottomLeft.addChild(archerCost); archerCost.x = 80; archerCost.y = -40; var mageCost = new Text2('60', { size: 30, fill: 0xFFFFFF }); mageCost.anchor.set(0.5, 0); LK.gui.bottomLeft.addChild(mageCost); mageCost.x = 200; mageCost.y = -40; var warriorCost = new Text2('80', { size: 30, fill: 0xFFFFFF }); warriorCost.anchor.set(0.5, 0); LK.gui.bottomLeft.addChild(warriorCost); warriorCost.x = 320; warriorCost.y = -40; // Start wave button var startWaveButton = new Text2('START WAVE', { size: 50, fill: 0x00FF00 }); startWaveButton.anchor.set(0.5, 0); LK.gui.bottomRight.addChild(startWaveButton); startWaveButton.x = -150; startWaveButton.y = -80; // Game functions function spawnEnemy() { var enemyTypes = ['goblin', 'orc', 'troll']; var randomType = enemyTypes[Math.floor(Math.random() * enemyTypes.length)]; // Increase difficulty with wave number if (currentWave > 3) { randomType = enemyTypes[Math.floor(Math.random() * enemyTypes.length)]; } else if (currentWave > 1) { randomType = enemyTypes[Math.floor(Math.random() * 2)]; // No trolls in early waves } else { randomType = 'goblin'; // Only goblins in first wave } var enemy = new Enemy(randomType, 0); enemy.x = gamePath[0].x; enemy.y = gamePath[0].y; enemies.push(enemy); game.addChild(enemy); enemiesSpawned++; } function updateUI() { resourcesText.setText('Resources: ' + gameResources); healthText.setText('Base Health: ' + baseHealth); waveText.setText('Wave: ' + currentWave); // Update tower selection UI updateTowerSelectionUI(); } function getTowerCost(type) { if (type === 'archer') { return 30; } if (type === 'mage') { return 60; } if (type === 'warrior') { return 80; } return 50; } function placeTower(slotIndex, towerType) { var slot = towerSlots[slotIndex]; if (!slot.isEmpty) { return false; } var cost = getTowerCost(towerType); if (gameResources < cost) { return false; } var tower = new Tower(towerType, slot.x, slot.y); towers.push(tower); game.addChild(tower); gameResources -= cost; slot.isEmpty = false; slot.alpha = 0.3; LK.getSound('towerPlace').play(); updateUI(); return true; } function startWave() { if (waveInProgress) { return; } waveInProgress = true; enemiesSpawned = 0; enemiesInWave = 3 + currentWave * 2; // Increase enemies per wave startWaveButton.setText('WAVE ' + currentWave); startWaveButton.tint = 0x888888; // Change path for each wave var pathIndex = (currentWave - 1) % allPaths.length; gamePath = allPaths[pathIndex]; redrawPath(); // Recreate tower slots for new wave createTowerSlots(); } function endWave() { waveInProgress = false; currentWave++; gameResources += 20 + currentWave * 5; // Bonus resources between waves startWaveButton.setText('START WAVE'); startWaveButton.tint = 0x00FF00; updateUI(); } // Event handlers game.down = function (x, y, obj) { if (gameOver) { return; } // Check tower slot clicks for (var i = 0; i < towerSlots.length; i++) { var slot = towerSlots[i]; var distance = Math.sqrt(Math.pow(slot.x - x, 2) + Math.pow(slot.y - y, 2)); if (distance < 50 && slot.isEmpty) { placeTower(i, selectedTowerType); return; } } }; // Button click handlers archerButton.down = function () { if (gameResources >= 30) { selectedTowerType = 'archer'; updateTowerSelectionUI(); } }; mageButton.down = function () { if (gameResources >= 60) { selectedTowerType = 'mage'; updateTowerSelectionUI(); } }; warriorButton.down = function () { if (gameResources >= 80) { selectedTowerType = 'warrior'; updateTowerSelectionUI(); } }; // Function to update tower selection UI function updateTowerSelectionUI() { // Reset all button colors archerButton.tint = gameResources >= 30 ? 0xFFFFFF : 0x888888; mageButton.tint = gameResources >= 60 ? 0xFFFFFF : 0x888888; warriorButton.tint = gameResources >= 80 ? 0xFFFFFF : 0x888888; // Highlight selected tower if (selectedTowerType === 'archer') { archerButton.tint = 0x00FF00; } else if (selectedTowerType === 'mage') { mageButton.tint = 0x00FF00; } else if (selectedTowerType === 'warrior') { warriorButton.tint = 0x00FF00; } } startWaveButton.down = function () { if (!waveInProgress) { startWave(); } }; // Main game loop game.update = function () { if (gameOver) { return; } // Spawn enemies during wave if (waveInProgress && enemiesSpawned < enemiesInWave) { if (LK.ticks % 60 === 0) { // Spawn every second spawnEnemy(); } } // Update enemies for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; if (enemy.isDead) { gameResources += enemy.resources; enemy.destroy(); enemies.splice(i, 1); continue; } if (enemy.reachedBase) { baseHealth -= 10; enemy.destroy(); enemies.splice(i, 1); if (baseHealth <= 0) { gameOver = true; LK.showGameOver(); return; } continue; } } // Update projectiles for (var i = projectiles.length - 1; i >= 0; i--) { var projectile = projectiles[i]; if (projectile.hasHit) { // Check for enemy hits for (var j = 0; j < enemies.length; j++) { var enemy = enemies[j]; if (!enemy.isDead && enemy.intersects(projectile)) { enemy.takeDamage(projectile.damage); break; } } projectile.destroy(); projectiles.splice(i, 1); } } // Check if wave is complete if (waveInProgress && enemiesSpawned >= enemiesInWave && enemies.length === 0) { endWave(); } // Win condition if (currentWave > 10) { LK.showYouWin(); return; } updateUI(); }; // Selected tower preview var selectedTowerPreview = null; // Initialize selected tower archerButton.tint = 0x00FF00; updateUI(); // Show preview of selected tower when hovering over valid slots game.move = function (x, y, obj) { if (gameOver) { return; } // Remove existing preview if (selectedTowerPreview) { selectedTowerPreview.destroy(); selectedTowerPreview = null; } // Check if hovering over a valid tower slot for (var i = 0; i < towerSlots.length; i++) { var slot = towerSlots[i]; var distance = Math.sqrt(Math.pow(slot.x - x, 2) + Math.pow(slot.y - y, 2)); if (slot.isEmpty && distance < 50) { var cost = getTowerCost(selectedTowerType); if (gameResources >= cost) { // Create preview of selected tower if (selectedTowerType === 'archer') { selectedTowerPreview = game.addChild(LK.getAsset('archerTower', { anchorX: 0.5, anchorY: 0.5, alpha: 0.6 })); } else if (selectedTowerType === 'mage') { selectedTowerPreview = game.addChild(LK.getAsset('mageTower', { anchorX: 0.5, anchorY: 0.5, alpha: 0.6 })); } else if (selectedTowerType === 'warrior') { selectedTowerPreview = game.addChild(LK.getAsset('warriorTower', { anchorX: 0.5, anchorY: 0.5, alpha: 0.6 })); } if (selectedTowerPreview) { selectedTowerPreview.x = slot.x; selectedTowerPreview.y = slot.y; selectedTowerPreview.tint = 0x00FF00; // Green tint for valid placement } // Highlight the slot slot.tint = 0x00FF00; slot.alpha = 1.0; } break; } else { // Reset slot appearance slot.tint = 0xFFFFFF; slot.alpha = 0.7; } } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Enemy = Container.expand(function (type, pathIndex) {
var self = Container.call(this);
self.type = type;
self.pathIndex = pathIndex || 0;
self.speed = 2;
self.maxHealth = 100;
self.health = self.maxHealth;
self.resources = 10;
self.isDead = false;
// Set enemy properties based on type
if (type === 'goblin') {
self.speed = 3;
self.maxHealth = 50;
self.health = 50;
self.resources = 5;
self.enemyGraphics = self.attachAsset('goblin', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'orc') {
self.speed = 2;
self.maxHealth = 100;
self.health = 100;
self.resources = 10;
self.enemyGraphics = self.attachAsset('orc', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'troll') {
self.speed = 1;
self.maxHealth = 200;
self.health = 200;
self.resources = 20;
self.enemyGraphics = self.attachAsset('troll', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Health bar
self.healthBar = self.addChild(LK.getAsset('pathTile', {
width: 40,
height: 6,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x00FF00
}));
self.healthBar.y = -40;
self.takeDamage = function (damage) {
self.health -= damage;
var healthPercent = self.health / self.maxHealth;
self.healthBar.width = 40 * healthPercent;
if (healthPercent > 0.6) {
self.healthBar.tint = 0x00FF00;
} else if (healthPercent > 0.3) {
self.healthBar.tint = 0xFFFF00;
} else {
self.healthBar.tint = 0xFF0000;
}
if (self.health <= 0) {
self.isDead = true;
LK.getSound('enemyDeath').play();
return true;
}
LK.getSound('enemyHit').play();
return false;
};
self.update = function () {
if (self.isDead) {
return;
}
// Move along path
if (self.pathIndex < gamePath.length - 1) {
var currentTarget = gamePath[self.pathIndex + 1];
var dx = currentTarget.x - self.x;
var dy = currentTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 10) {
self.pathIndex++;
if (self.pathIndex >= gamePath.length - 1) {
// Reached base
self.reachedBase = true;
}
} else {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
}
};
return self;
});
var Projectile = Container.expand(function (type, startX, startY, targetX, targetY, damage) {
var self = Container.call(this);
self.type = type;
self.damage = damage;
self.speed = 8;
self.targetX = targetX;
self.targetY = targetY;
self.hasHit = false;
self.x = startX;
self.y = startY;
if (type === 'arrow') {
self.projectileGraphics = self.attachAsset('arrow', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'fireball') {
self.projectileGraphics = self.attachAsset('fireball', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'sword') {
self.projectileGraphics = self.attachAsset('sword', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Calculate direction
var dx = targetX - startX;
var dy = targetY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
self.dirX = dx / distance;
self.dirY = dy / distance;
self.update = function () {
if (self.hasHit) {
return;
}
self.x += self.dirX * self.speed;
self.y += self.dirY * self.speed;
// Check if reached target area
var distanceToTarget = Math.sqrt(Math.pow(self.x - self.targetX, 2) + Math.pow(self.y - self.targetY, 2));
if (distanceToTarget < 20) {
self.hasHit = true;
}
// Remove if off screen
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.hasHit = true;
}
};
return self;
});
var Tower = Container.expand(function (type, slotX, slotY) {
var self = Container.call(this);
self.type = type;
self.level = 1;
self.range = 150;
self.damage = 25;
self.attackSpeed = 60; // frames between attacks
self.lastAttack = 0;
self.cost = 50;
self.x = slotX;
self.y = slotY;
if (type === 'archer') {
self.towerGraphics = self.attachAsset('archerTower', {
anchorX: 0.5,
anchorY: 0.5
});
self.projectileType = 'arrow';
self.range = 180;
self.damage = 20;
self.attackSpeed = 40;
self.cost = 30;
} else if (type === 'mage') {
self.towerGraphics = self.attachAsset('mageTower', {
anchorX: 0.5,
anchorY: 0.5
});
self.projectileType = 'fireball';
self.range = 120;
self.damage = 40;
self.attackSpeed = 80;
self.cost = 60;
} else if (type === 'warrior') {
self.towerGraphics = self.attachAsset('warriorTower', {
anchorX: 0.5,
anchorY: 0.5
});
self.projectileType = 'sword';
self.range = 100;
self.damage = 60;
self.attackSpeed = 100;
self.cost = 80;
self.armyCooldown = 60 * 60; // 60 seconds at 60 FPS
self.lastArmyRelease = 0;
}
// Range indicator (invisible by default)
self.rangeIndicator = self.addChild(LK.getAsset('pathTile', {
width: self.range * 2,
height: self.range * 2,
anchorX: 0.5,
anchorY: 0.5,
alpha: 0,
tint: 0xFFFFFF
}));
self.findTarget = function () {
var closestEnemy = null;
var closestDistance = Infinity;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.isDead) {
continue;
}
var distance = Math.sqrt(Math.pow(enemy.x - self.x, 2) + Math.pow(enemy.y - self.y, 2));
if (distance <= self.range && distance < closestDistance) {
closestDistance = distance;
closestEnemy = enemy;
}
}
return closestEnemy;
};
self.releaseArmy = function () {
// Clear all enemies on the path
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
if (!enemy.isDead) {
// Add visual effect before destroying
tween(enemy, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (enemy && !enemy.isDead) {
gameResources += enemy.resources;
gameResources += 10; // Bonus 10 coins for warrior tower kills
enemy.isDead = true;
enemy.destroy();
enemies.splice(enemies.indexOf(enemy), 1);
}
}
});
}
}
// Visual effect on tower
tween(self.towerGraphics, {
tint: 0xFFD700
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self.towerGraphics, {
tint: 0xFFFFFF
}, {
duration: 800,
easing: tween.easeIn
});
}
});
LK.getSound('shoot').play();
};
self.attack = function (target) {
if (self.type === 'warrior') {
// Check if army cooldown is ready
if (LK.ticks - self.lastArmyRelease >= self.armyCooldown) {
self.releaseArmy();
self.lastArmyRelease = LK.ticks;
}
return;
}
if (LK.ticks - self.lastAttack < self.attackSpeed) {
return;
}
var projectile = new Projectile(self.projectileType, self.x, self.y, target.x, target.y, self.damage);
projectiles.push(projectile);
game.addChild(projectile);
self.lastAttack = LK.ticks;
LK.getSound('shoot').play();
};
self.update = function () {
var target = self.findTarget();
if (target) {
self.attack(target);
}
// Update warrior tower cooldown indicator
if (self.type === 'warrior') {
var cooldownRemaining = self.armyCooldown - (LK.ticks - self.lastArmyRelease);
if (cooldownRemaining > 0) {
var cooldownPercent = cooldownRemaining / self.armyCooldown;
self.towerGraphics.tint = 0x888888 + Math.floor((1 - cooldownPercent) * 0x777777);
} else {
self.towerGraphics.tint = 0xFFFFFF;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2F4F2F
});
/****
* Game Code
****/
// Game state variables
// Tower Assets
// Enemy Assets
// Projectile Assets
// UI Assets
// Sound Assets
var gameResources = 100;
var baseHealth = 100;
var currentWave = 1;
var enemiesInWave = 5;
var enemiesSpawned = 0;
var waveInProgress = false;
var gameOver = false;
var selectedTowerType = 'archer';
// Game arrays
var enemies = [];
var towers = [];
var projectiles = [];
var towerSlots = [];
// Different paths for different waves
var allPaths = [
// Path 1 - Straight horizontal then vertical
[{
x: 100,
y: 100
}, {
x: 300,
y: 100
}, {
x: 300,
y: 300
}, {
x: 600,
y: 300
}, {
x: 600,
y: 500
}, {
x: 900,
y: 500
}, {
x: 900,
y: 700
}, {
x: 1200,
y: 700
}, {
x: 1200,
y: 900
}, {
x: 1500,
y: 900
}, {
x: 1500,
y: 1100
}, {
x: 1800,
y: 1100
}],
// Path 2 - Zigzag pattern
[{
x: 100,
y: 200
}, {
x: 500,
y: 200
}, {
x: 500,
y: 400
}, {
x: 200,
y: 400
}, {
x: 200,
y: 600
}, {
x: 800,
y: 600
}, {
x: 800,
y: 800
}, {
x: 400,
y: 800
}, {
x: 400,
y: 1000
}, {
x: 1200,
y: 1000
}, {
x: 1200,
y: 1200
}, {
x: 1800,
y: 1200
}],
// Path 3 - Curved path
[{
x: 100,
y: 300
}, {
x: 400,
y: 300
}, {
x: 400,
y: 150
}, {
x: 700,
y: 150
}, {
x: 700,
y: 450
}, {
x: 1000,
y: 450
}, {
x: 1000,
y: 200
}, {
x: 1300,
y: 200
}, {
x: 1300,
y: 700
}, {
x: 1600,
y: 700
}, {
x: 1600,
y: 1000
}, {
x: 1800,
y: 1000
}],
// Path 4 - Long winding path
[{
x: 100,
y: 400
}, {
x: 200,
y: 400
}, {
x: 200,
y: 200
}, {
x: 600,
y: 200
}, {
x: 600,
y: 600
}, {
x: 400,
y: 600
}, {
x: 400,
y: 800
}, {
x: 1000,
y: 800
}, {
x: 1000,
y: 400
}, {
x: 1400,
y: 400
}, {
x: 1400,
y: 1200
}, {
x: 1800,
y: 1200
}]];
// Current game path - changes each wave
var gamePath = allPaths[0];
// Path tiles array to track them
var pathTiles = [];
// Create path tiles
function createPathTiles() {
for (var i = 0; i < gamePath.length; i++) {
var pathTile = game.addChild(LK.getAsset('pathTile', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3
}));
pathTile.x = gamePath[i].x;
pathTile.y = gamePath[i].y;
pathTiles.push(pathTile);
}
}
// Function to redraw path for new waves
function redrawPath() {
// Remove old path tiles
for (var i = 0; i < pathTiles.length; i++) {
pathTiles[i].destroy();
}
pathTiles = [];
// Create new path tiles
createPathTiles();
// Update base position to end of new path
base.x = gamePath[gamePath.length - 1].x;
base.y = gamePath[gamePath.length - 1].y;
}
// Initialize path tiles
createPathTiles();
// Create base at end of path
var base = game.addChild(LK.getAsset('base', {
anchorX: 0.5,
anchorY: 0.5
}));
base.x = gamePath[gamePath.length - 1].x;
base.y = gamePath[gamePath.length - 1].y;
// Create tower slots around the path
var baseSlotPositions = [{
x: 200,
y: 200
}, {
x: 400,
y: 200
}, {
x: 500,
y: 400
}, {
x: 700,
y: 400
}, {
x: 800,
y: 600
}, {
x: 1000,
y: 600
}, {
x: 1100,
y: 800
}, {
x: 1300,
y: 800
}, {
x: 1400,
y: 1000
}, {
x: 1600,
y: 1000
}, {
x: 1700,
y: 1200
}];
// Additional slots for wave 2 and higher
var extraSlotPositions = [{
x: 150,
y: 350
}, {
x: 350,
y: 550
}, {
x: 650,
y: 350
}, {
x: 850,
y: 450
}, {
x: 1050,
y: 350
}, {
x: 1250,
y: 550
}, {
x: 1450,
y: 650
}, {
x: 1550,
y: 850
}];
function createTowerSlots() {
// Clear existing slots
for (var i = 0; i < towerSlots.length; i++) {
towerSlots[i].destroy();
}
towerSlots = [];
// Always create base slots
var slotsToCreate = baseSlotPositions.slice();
// Add extra slots for wave 2 and higher
if (currentWave >= 2) {
slotsToCreate = slotsToCreate.concat(extraSlotPositions);
}
for (var i = 0; i < slotsToCreate.length; i++) {
var slot = game.addChild(LK.getAsset('towerSlot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7
}));
slot.x = slotsToCreate[i].x;
slot.y = slotsToCreate[i].y;
slot.isEmpty = true;
slot.slotIndex = i;
towerSlots.push(slot);
}
}
// Initialize tower slots
createTowerSlots();
// Create decorative trees and bushes
var decorativeElements = [];
var treePositions = [{
x: 50,
y: 50
}, {
x: 150,
y: 80
}, {
x: 250,
y: 120
}, {
x: 1850,
y: 100
}, {
x: 1750,
y: 150
}, {
x: 1650,
y: 80
}, {
x: 80,
y: 800
}, {
x: 180,
y: 950
}, {
x: 120,
y: 1100
}, {
x: 1880,
y: 1400
}, {
x: 1780,
y: 1500
}, {
x: 1820,
y: 1600
}, {
x: 950,
y: 50
}, {
x: 1050,
y: 80
}, {
x: 850,
y: 120
}, {
x: 50,
y: 1800
}, {
x: 150,
y: 1900
}, {
x: 250,
y: 2000
}];
var bushPositions = [{
x: 350,
y: 80
}, {
x: 450,
y: 120
}, {
x: 550,
y: 90
}, {
x: 1550,
y: 200
}, {
x: 1650,
y: 180
}, {
x: 1750,
y: 220
}, {
x: 80,
y: 1200
}, {
x: 180,
y: 1300
}, {
x: 120,
y: 1400
}, {
x: 1880,
y: 1800
}, {
x: 1780,
y: 1900
}, {
x: 1820,
y: 2000
}, {
x: 750,
y: 100
}, {
x: 850,
y: 180
}, {
x: 950,
y: 140
}, {
x: 1100,
y: 1800
}, {
x: 1200,
y: 1900
}, {
x: 1300,
y: 2000
}, {
x: 300,
y: 1600
}, {
x: 400,
y: 1700
}, {
x: 500,
y: 1800
}];
// Create trees
for (var i = 0; i < treePositions.length; i++) {
var tree = game.addChild(LK.getAsset('tree', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
}));
tree.x = treePositions[i].x;
tree.y = treePositions[i].y;
decorativeElements.push(tree);
}
// Create bushes
for (var i = 0; i < bushPositions.length; i++) {
var bush = game.addChild(LK.getAsset('bush', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7
}));
bush.x = bushPositions[i].x;
bush.y = bushPositions[i].y;
decorativeElements.push(bush);
}
// UI Elements
var resourcesText = new Text2('Resources: ' + gameResources, {
size: 60,
fill: 0xFFD700
});
resourcesText.anchor.set(0, 0);
LK.gui.top.addChild(resourcesText);
resourcesText.x = 200;
resourcesText.y = 20;
var healthText = new Text2('Base Health: ' + baseHealth, {
size: 60,
fill: 0xFF0000
});
healthText.anchor.set(0, 0);
LK.gui.top.addChild(healthText);
healthText.x = 200;
healthText.y = 90;
var waveText = new Text2('Wave: ' + currentWave, {
size: 60,
fill: 0xFFFFFF
});
waveText.anchor.set(0, 0);
LK.gui.top.addChild(waveText);
waveText.x = 200;
waveText.y = 160;
// Tower selection buttons
var archerButton = LK.gui.bottomLeft.addChild(LK.getAsset('archerTower', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
}));
archerButton.x = 80;
archerButton.y = -80;
var mageButton = LK.gui.bottomLeft.addChild(LK.getAsset('mageTower', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
}));
mageButton.x = 200;
mageButton.y = -80;
var warriorButton = LK.gui.bottomLeft.addChild(LK.getAsset('warriorTower', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
}));
warriorButton.x = 320;
warriorButton.y = -80;
// Tower cost labels
var archerCost = new Text2('30', {
size: 30,
fill: 0xFFFFFF
});
archerCost.anchor.set(0.5, 0);
LK.gui.bottomLeft.addChild(archerCost);
archerCost.x = 80;
archerCost.y = -40;
var mageCost = new Text2('60', {
size: 30,
fill: 0xFFFFFF
});
mageCost.anchor.set(0.5, 0);
LK.gui.bottomLeft.addChild(mageCost);
mageCost.x = 200;
mageCost.y = -40;
var warriorCost = new Text2('80', {
size: 30,
fill: 0xFFFFFF
});
warriorCost.anchor.set(0.5, 0);
LK.gui.bottomLeft.addChild(warriorCost);
warriorCost.x = 320;
warriorCost.y = -40;
// Start wave button
var startWaveButton = new Text2('START WAVE', {
size: 50,
fill: 0x00FF00
});
startWaveButton.anchor.set(0.5, 0);
LK.gui.bottomRight.addChild(startWaveButton);
startWaveButton.x = -150;
startWaveButton.y = -80;
// Game functions
function spawnEnemy() {
var enemyTypes = ['goblin', 'orc', 'troll'];
var randomType = enemyTypes[Math.floor(Math.random() * enemyTypes.length)];
// Increase difficulty with wave number
if (currentWave > 3) {
randomType = enemyTypes[Math.floor(Math.random() * enemyTypes.length)];
} else if (currentWave > 1) {
randomType = enemyTypes[Math.floor(Math.random() * 2)]; // No trolls in early waves
} else {
randomType = 'goblin'; // Only goblins in first wave
}
var enemy = new Enemy(randomType, 0);
enemy.x = gamePath[0].x;
enemy.y = gamePath[0].y;
enemies.push(enemy);
game.addChild(enemy);
enemiesSpawned++;
}
function updateUI() {
resourcesText.setText('Resources: ' + gameResources);
healthText.setText('Base Health: ' + baseHealth);
waveText.setText('Wave: ' + currentWave);
// Update tower selection UI
updateTowerSelectionUI();
}
function getTowerCost(type) {
if (type === 'archer') {
return 30;
}
if (type === 'mage') {
return 60;
}
if (type === 'warrior') {
return 80;
}
return 50;
}
function placeTower(slotIndex, towerType) {
var slot = towerSlots[slotIndex];
if (!slot.isEmpty) {
return false;
}
var cost = getTowerCost(towerType);
if (gameResources < cost) {
return false;
}
var tower = new Tower(towerType, slot.x, slot.y);
towers.push(tower);
game.addChild(tower);
gameResources -= cost;
slot.isEmpty = false;
slot.alpha = 0.3;
LK.getSound('towerPlace').play();
updateUI();
return true;
}
function startWave() {
if (waveInProgress) {
return;
}
waveInProgress = true;
enemiesSpawned = 0;
enemiesInWave = 3 + currentWave * 2; // Increase enemies per wave
startWaveButton.setText('WAVE ' + currentWave);
startWaveButton.tint = 0x888888;
// Change path for each wave
var pathIndex = (currentWave - 1) % allPaths.length;
gamePath = allPaths[pathIndex];
redrawPath();
// Recreate tower slots for new wave
createTowerSlots();
}
function endWave() {
waveInProgress = false;
currentWave++;
gameResources += 20 + currentWave * 5; // Bonus resources between waves
startWaveButton.setText('START WAVE');
startWaveButton.tint = 0x00FF00;
updateUI();
}
// Event handlers
game.down = function (x, y, obj) {
if (gameOver) {
return;
}
// Check tower slot clicks
for (var i = 0; i < towerSlots.length; i++) {
var slot = towerSlots[i];
var distance = Math.sqrt(Math.pow(slot.x - x, 2) + Math.pow(slot.y - y, 2));
if (distance < 50 && slot.isEmpty) {
placeTower(i, selectedTowerType);
return;
}
}
};
// Button click handlers
archerButton.down = function () {
if (gameResources >= 30) {
selectedTowerType = 'archer';
updateTowerSelectionUI();
}
};
mageButton.down = function () {
if (gameResources >= 60) {
selectedTowerType = 'mage';
updateTowerSelectionUI();
}
};
warriorButton.down = function () {
if (gameResources >= 80) {
selectedTowerType = 'warrior';
updateTowerSelectionUI();
}
};
// Function to update tower selection UI
function updateTowerSelectionUI() {
// Reset all button colors
archerButton.tint = gameResources >= 30 ? 0xFFFFFF : 0x888888;
mageButton.tint = gameResources >= 60 ? 0xFFFFFF : 0x888888;
warriorButton.tint = gameResources >= 80 ? 0xFFFFFF : 0x888888;
// Highlight selected tower
if (selectedTowerType === 'archer') {
archerButton.tint = 0x00FF00;
} else if (selectedTowerType === 'mage') {
mageButton.tint = 0x00FF00;
} else if (selectedTowerType === 'warrior') {
warriorButton.tint = 0x00FF00;
}
}
startWaveButton.down = function () {
if (!waveInProgress) {
startWave();
}
};
// Main game loop
game.update = function () {
if (gameOver) {
return;
}
// Spawn enemies during wave
if (waveInProgress && enemiesSpawned < enemiesInWave) {
if (LK.ticks % 60 === 0) {
// Spawn every second
spawnEnemy();
}
}
// Update enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
if (enemy.isDead) {
gameResources += enemy.resources;
enemy.destroy();
enemies.splice(i, 1);
continue;
}
if (enemy.reachedBase) {
baseHealth -= 10;
enemy.destroy();
enemies.splice(i, 1);
if (baseHealth <= 0) {
gameOver = true;
LK.showGameOver();
return;
}
continue;
}
}
// Update projectiles
for (var i = projectiles.length - 1; i >= 0; i--) {
var projectile = projectiles[i];
if (projectile.hasHit) {
// Check for enemy hits
for (var j = 0; j < enemies.length; j++) {
var enemy = enemies[j];
if (!enemy.isDead && enemy.intersects(projectile)) {
enemy.takeDamage(projectile.damage);
break;
}
}
projectile.destroy();
projectiles.splice(i, 1);
}
}
// Check if wave is complete
if (waveInProgress && enemiesSpawned >= enemiesInWave && enemies.length === 0) {
endWave();
}
// Win condition
if (currentWave > 10) {
LK.showYouWin();
return;
}
updateUI();
};
// Selected tower preview
var selectedTowerPreview = null;
// Initialize selected tower
archerButton.tint = 0x00FF00;
updateUI();
// Show preview of selected tower when hovering over valid slots
game.move = function (x, y, obj) {
if (gameOver) {
return;
}
// Remove existing preview
if (selectedTowerPreview) {
selectedTowerPreview.destroy();
selectedTowerPreview = null;
}
// Check if hovering over a valid tower slot
for (var i = 0; i < towerSlots.length; i++) {
var slot = towerSlots[i];
var distance = Math.sqrt(Math.pow(slot.x - x, 2) + Math.pow(slot.y - y, 2));
if (slot.isEmpty && distance < 50) {
var cost = getTowerCost(selectedTowerType);
if (gameResources >= cost) {
// Create preview of selected tower
if (selectedTowerType === 'archer') {
selectedTowerPreview = game.addChild(LK.getAsset('archerTower', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6
}));
} else if (selectedTowerType === 'mage') {
selectedTowerPreview = game.addChild(LK.getAsset('mageTower', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6
}));
} else if (selectedTowerType === 'warrior') {
selectedTowerPreview = game.addChild(LK.getAsset('warriorTower', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6
}));
}
if (selectedTowerPreview) {
selectedTowerPreview.x = slot.x;
selectedTowerPreview.y = slot.y;
selectedTowerPreview.tint = 0x00FF00; // Green tint for valid placement
}
// Highlight the slot
slot.tint = 0x00FF00;
slot.alpha = 1.0;
}
break;
} else {
// Reset slot appearance
slot.tint = 0xFFFFFF;
slot.alpha = 0.7;
}
}
};