/****
* 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