/****
* Plugins
****/
var storage = LK.import("@upit/storage.v1");
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Arrow = Container.expand(function (team, startX, startY, targetX, targetY, damage) {
var self = Container.call(this);
var graphics = self.attachAsset('Arrow', {
anchorX: 0.5,
anchorY: 0.5
});
self.team = team;
self.damage = damage;
self.speed = 8;
self.target = null;
// Calculate direction
var dx = targetX - startX;
var dy = targetY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
self.directionX = dx / distance;
self.directionY = dy / distance;
// Set rotation to point toward target
self.rotation = Math.atan2(dy, dx);
// Position arrow at start
self.x = startX;
self.y = startY;
self.update = function () {
// Move arrow
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
// Check if arrow is off screen
if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) {
self.destroy();
// Remove from arrows array
for (var i = arrows.length - 1; i >= 0; i--) {
if (arrows[i] === self) {
arrows.splice(i, 1);
break;
}
}
}
// Check collision with enemy units
var enemyUnits = self.team === 'player' ? aiUnits : playerUnits;
for (var i = 0; i < enemyUnits.length; i++) {
var enemy = enemyUnits[i];
if (self.intersects(enemy)) {
enemy.health -= self.damage;
if (enemy.health <= 0) {
enemyUnits.splice(i, 1);
enemy.destroy();
}
self.destroy();
// Remove from arrows array
for (var j = arrows.length - 1; j >= 0; j--) {
if (arrows[j] === self) {
arrows.splice(j, 1);
break;
}
}
return;
}
}
// Check collision with enemy towers
var enemyTowers = self.team === 'player' ? aiTowers : playerTowers;
for (var i = 0; i < enemyTowers.length; i++) {
var tower = enemyTowers[i];
if (self.intersects(tower)) {
tower.health -= self.damage;
if (tower.health <= 0) {
enemyTowers.splice(i, 1);
tower.destroy();
LK.getSound('towerDestroyed').play();
}
self.destroy();
// Remove from arrows array
for (var j = arrows.length - 1; j >= 0; j--) {
if (arrows[j] === self) {
arrows.splice(j, 1);
break;
}
}
return;
}
}
};
return self;
});
var Coin = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
self.value = 1;
self.collected = false;
return self;
});
var CombatUnit = Container.expand(function (team, unitType) {
var self = Container.call(this);
var assetName = team === 'player' ? unitType : 'ai' + unitType.charAt(0).toUpperCase() + unitType.slice(1);
var graphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.team = team;
self.unitType = unitType;
self.speed = unitType === 'dragon' ? 3 : unitType === 'wizard' ? 1.5 : unitType === 'archer' ? 2.5 : 2;
self.damage = unitType === 'dragon' ? 40 : unitType === 'wizard' ? 30 : unitType === 'archer' ? 20 : 10;
self.health = unitType === 'dragon' ? 50 : unitType === 'wizard' ? 40 : unitType === 'archer' ? 30 : unitType === 'warrior' ? 20 : 10;
self.maxHealth = self.health;
self.attackRange = unitType === 'wizard' ? 100 : unitType === 'archer' ? 200 : 50;
self.attackCooldown = 0;
self.target = null;
self.isWaiting = !zombieMode && team === 'player'; // Only player units wait in normal mode
// Create health bar background (black)
self.healthBarBg = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.75
});
self.healthBarBg.x = 0;
self.healthBarBg.y = -120;
self.healthBarBg.tint = team === 'player' ? 0x0000ff : 0x000000;
self.addChild(self.healthBarBg);
// Create health bar (green for player, red for AI)
self.healthBar = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.75
});
self.healthBar.x = 0;
self.healthBar.y = -120;
self.healthBar.tint = team === 'player' ? 0x00ff00 : 0xff0000;
self.addChild(self.healthBar);
self.healthText = new Text2(self.health.toString(), {
size: 50,
fill: '#ffffff'
});
self.healthText.anchor.set(0.5, 0.5);
self.addChild(self.healthText);
self.healthText.x = 0;
self.healthText.y = -120;
self.update = function () {
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
// In zombie mode, player units prioritize attacking zombies
if (zombieMode && self.team === 'player' && self.unitType !== 'worker') {
self.attackZombies();
} else if (!zombieMode && self.team === 'player' && self.isWaiting) {
// In normal mode, player units wait unless attack is triggered
// Stay in position and don't move
} else {
if (!self.target) {
self.findNearestEnemyTower();
} else {
self.moveToTarget();
}
// Combat with nearby enemy units
self.combatNearbyEnemies();
}
self.healthText.setText(self.health.toString());
// Update health bar scale based on health percentage
var healthPercentage = self.health / self.maxHealth;
tween(self.healthBar, {
scaleX: 1.5 * healthPercentage
}, {
duration: 200
});
};
self.findNearestEnemyTower = function () {
// In zombie mode, player units should prioritize attacking zombies
if (zombieMode && self.team === 'player' && self.unitType !== 'worker') {
var nearestZombie = null;
var nearestDistance = Infinity;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var distance = Math.sqrt(Math.pow(zombie.x - self.x, 2) + Math.pow(zombie.y - self.y, 2));
if (distance < nearestDistance) {
nearestDistance = distance;
nearestZombie = zombie;
}
}
// If we found a zombie, target it
if (nearestZombie) {
self.target = nearestZombie;
return;
}
}
// Find nearest target - prioritize ALL enemy units first (including workers), then towers
var nearestTarget = null;
var nearestDistance = Infinity;
// First check all enemy units (including workers)
var enemyUnits = self.team === 'player' ? aiUnits : playerUnits;
for (var i = 0; i < enemyUnits.length; i++) {
var enemy = enemyUnits[i];
// Target all enemy units now, including workers
if (self.unitType !== 'worker') {
var distance = Math.sqrt(Math.pow(enemy.x - self.x, 2) + Math.pow(enemy.y - self.y, 2));
if (distance < nearestDistance) {
nearestDistance = distance;
nearestTarget = enemy;
}
}
}
// Only target towers if no enemy units remain
if (!nearestTarget) {
var enemyTowers = self.team === 'player' ? aiTowers : playerTowers;
for (var i = 0; i < enemyTowers.length; i++) {
var tower = enemyTowers[i];
var distance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2));
if (distance < nearestDistance) {
nearestDistance = distance;
nearestTarget = tower;
}
}
}
self.target = nearestTarget;
};
self.moveToTarget = function () {
if (!self.target) {
return;
}
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < self.attackRange) {
self.attackTarget();
} else {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.attackTarget = function () {
if (self.attackCooldown <= 0 && self.target) {
if (self.unitType === 'archer') {
// Archer shoots arrow
var arrow = new Arrow(self.team, self.x, self.y, self.target.x, self.target.y, self.damage);
arrows.push(arrow);
game.addChild(arrow);
} else if (self.unitType === 'wizard') {
// Wizard shoots magic
var magic = new Magic(self.team, self.x, self.y, self.target.x, self.target.y, self.damage);
magics.push(magic);
game.addChild(magic);
} else if (self.unitType === 'dragon') {
// Change to attack asset for all dragon attacks
self.removeChild(graphics);
var assetName = 'DragonAttack';
graphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
// Dragon shoots fire with 1 damage
var fire = new Fire(self.team, self.x, self.y, self.target.x, self.target.y, 1);
fires.push(fire);
game.addChild(fire);
} else {
// Melee attack
self.target.health -= self.damage;
if (self.target.health <= 0) {
// Check if target is a unit or tower
if (self.target.unitType) {
// Target is a unit
self.destroyUnit();
} else {
// Target is a tower
self.destroyTower();
}
}
}
// Set dragon attack cooldown to 3 frames (0.05 seconds)
if (self.unitType === 'dragon') {
self.attackCooldown = 3;
} else {
self.attackCooldown = 60;
}
LK.getSound('attack').play();
}
};
self.destroyUnit = function () {
var units = self.team === 'player' ? aiUnits : playerUnits;
var index = units.indexOf(self.target);
if (index > -1) {
units.splice(index, 1);
self.target.destroy();
self.target = null;
}
};
self.destroyTower = function () {
var towers = self.team === 'player' ? aiTowers : playerTowers;
var index = towers.indexOf(self.target);
if (index > -1) {
towers.splice(index, 1);
self.target.destroy();
LK.getSound('towerDestroyed').play();
self.target = null;
}
};
self.attackZombies = function () {
// Find nearest zombie to target
var nearestZombie = null;
var nearestDistance = Infinity;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestZombie = zombie;
}
}
// Move toward and attack nearest zombie
if (nearestZombie) {
var dx = nearestZombie.x - self.x;
var dy = nearestZombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Move toward zombie if not in attack range
if (distance > self.attackRange) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
} else if (self.attackCooldown <= 0) {
// Attack zombie based on unit type
if (self.unitType === 'archer') {
// Archer shoots arrow at zombie
var arrow = new Arrow(self.team, self.x, self.y, nearestZombie.x, nearestZombie.y, self.damage);
arrows.push(arrow);
game.addChild(arrow);
} else if (self.unitType === 'wizard') {
// Wizard shoots magic at zombie
var magic = new Magic(self.team, self.x, self.y, nearestZombie.x, nearestZombie.y, self.damage);
magics.push(magic);
game.addChild(magic);
} else if (self.unitType === 'dragon') {
// Change to attack asset for dragon zombie attacks
self.removeChild(graphics);
var assetName = 'DragonAttack';
graphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
// Dragon shoots fire at zombie
var fire = new Fire(self.team, self.x, self.y, nearestZombie.x, nearestZombie.y, self.damage);
fires.push(fire);
game.addChild(fire);
} else {
// Melee attack (warrior)
nearestZombie.health -= self.damage;
if (nearestZombie.health <= 0) {
for (var j = 0; j < zombies.length; j++) {
if (zombies[j] === nearestZombie) {
zombies.splice(j, 1);
nearestZombie.destroy();
break;
}
}
}
}
self.attackCooldown = 60; // 1 second cooldown
LK.getSound('attack').play();
}
}
};
self.combatNearbyEnemies = function () {
if (self.attackCooldown <= 0) {
var enemyUnits = self.team === 'player' ? aiUnits : playerUnits;
var meleeRange = 60; // Close combat range
for (var i = 0; i < enemyUnits.length; i++) {
var enemy = enemyUnits[i];
// Skip workers in combat
if (enemy.unitType === 'worker') {
continue;
}
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If enemy unit is within melee range, attack it
if (distance <= meleeRange) {
enemy.health -= self.damage;
self.attackCooldown = 60; // 1 second cooldown
LK.getSound('attack').play();
// Check if enemy is destroyed
if (enemy.health <= 0) {
enemyUnits.splice(i, 1);
enemy.destroy();
}
break; // Only attack one enemy per cycle
}
}
}
};
return self;
});
var Fire = Container.expand(function (team, startX, startY, targetX, targetY, damage) {
var self = Container.call(this);
var graphics = self.attachAsset('Magic', {
anchorX: 0.5,
anchorY: 0.5
});
self.team = team;
self.damage = damage;
self.speed = 4;
self.target = null;
// Calculate direction
var dx = targetX - startX;
var dy = targetY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
self.directionX = dx / distance;
self.directionY = dy / distance;
// Position fire at start
self.x = startX;
self.y = startY;
// Add fire effect - orange/red tint and scaling
graphics.tint = 0xff4500; // Orange-red fire color
tween(graphics, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeOut
});
self.update = function () {
// Move fire
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
// Add fire flicker rotation
graphics.rotation += 0.15;
// Check if fire is off screen
if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) {
self.destroy();
// Remove from fires array
for (var i = fires.length - 1; i >= 0; i--) {
if (fires[i] === self) {
fires.splice(i, 1);
break;
}
}
}
// Check collision with enemy units
var enemyUnits = self.team === 'player' ? aiUnits : playerUnits;
for (var i = 0; i < enemyUnits.length; i++) {
var enemy = enemyUnits[i];
if (self.intersects(enemy)) {
enemy.health -= self.damage;
if (enemy.health <= 0) {
enemyUnits.splice(i, 1);
enemy.destroy();
}
self.destroy();
// Remove from fires array
for (var j = fires.length - 1; j >= 0; j--) {
if (fires[j] === self) {
fires.splice(j, 1);
break;
}
}
return;
}
}
// Check collision with enemy towers
var enemyTowers = self.team === 'player' ? aiTowers : playerTowers;
for (var i = 0; i < enemyTowers.length; i++) {
var tower = enemyTowers[i];
if (self.intersects(tower)) {
tower.health -= self.damage;
if (tower.health <= 0) {
enemyTowers.splice(i, 1);
tower.destroy();
LK.getSound('towerDestroyed').play();
}
self.destroy();
// Remove from fires array
for (var j = fires.length - 1; j >= 0; j--) {
if (fires[j] === self) {
fires.splice(j, 1);
break;
}
}
return;
}
}
};
return self;
});
var Magic = Container.expand(function (team, startX, startY, targetX, targetY, damage) {
var self = Container.call(this);
var graphics = self.attachAsset('Magic', {
anchorX: 0.5,
anchorY: 0.5
});
self.team = team;
self.damage = damage;
self.speed = 6;
self.target = null;
// Calculate direction
var dx = targetX - startX;
var dy = targetY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
self.directionX = dx / distance;
self.directionY = dy / distance;
// Position magic at start
self.x = startX;
self.y = startY;
// Add magical glow effect
tween(graphics, {
tint: 0x9966ff
}, {
duration: 500,
easing: tween.easeInOut
});
tween(graphics, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
easing: tween.easeOut
});
self.update = function () {
// Move magic
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
// Add sparkle rotation
graphics.rotation += 0.1;
// Check if magic is off screen
if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) {
self.destroy();
// Remove from magics array
for (var i = magics.length - 1; i >= 0; i--) {
if (magics[i] === self) {
magics.splice(i, 1);
break;
}
}
}
// Check collision with enemy units
var enemyUnits = self.team === 'player' ? aiUnits : playerUnits;
for (var i = 0; i < enemyUnits.length; i++) {
var enemy = enemyUnits[i];
if (self.intersects(enemy)) {
enemy.health -= self.damage;
if (enemy.health <= 0) {
enemyUnits.splice(i, 1);
enemy.destroy();
}
self.destroy();
// Remove from magics array
for (var j = magics.length - 1; j >= 0; j--) {
if (magics[j] === self) {
magics.splice(j, 1);
break;
}
}
return;
}
}
// Check collision with enemy towers
var enemyTowers = self.team === 'player' ? aiTowers : playerTowers;
for (var i = 0; i < enemyTowers.length; i++) {
var tower = enemyTowers[i];
if (self.intersects(tower)) {
tower.health -= self.damage;
if (tower.health <= 0) {
enemyTowers.splice(i, 1);
tower.destroy();
LK.getSound('towerDestroyed').play();
}
self.destroy();
// Remove from magics array
for (var j = magics.length - 1; j >= 0; j--) {
if (magics[j] === self) {
magics.splice(j, 1);
break;
}
}
return;
}
}
};
return self;
});
var Tower = Container.expand(function (team) {
var self = Container.call(this);
var assetName = team === 'player' ? 'playerTower' : 'aiTower';
var graphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 1.0
});
self.team = team;
self.health = 500;
self.maxHealth = 500;
// Create health bar background (black)
self.healthBarBg = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.08,
scaleY: 0.625
});
self.healthBarBg.x = 0;
self.healthBarBg.y = -262.5;
self.healthBarBg.tint = 0x0000ff;
self.addChild(self.healthBarBg);
// Create health bar (green for player, red for AI)
self.healthBar = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.08,
scaleY: 0.625
});
self.healthBar.x = 0;
self.healthBar.y = -262.5;
self.healthBar.tint = team === 'player' ? 0x00ff00 : 0xff0000;
self.addChild(self.healthBar);
self.healthText = new Text2(self.health.toString(), {
size: 42,
fill: '#ffffff'
});
self.healthText.anchor.set(0.5, 0.5);
self.addChild(self.healthText);
self.healthText.x = 0;
self.healthText.y = -262.5;
self.attackCooldown = 0;
self.attackRange = 400;
// Damage area outline removed as requested
self.update = function () {
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
self.attackNearbyEnemies();
self.healthText.setText(self.health.toString());
// Update health bar scale based on health percentage
var healthPercentage = self.health / self.maxHealth;
tween(self.healthBar, {
scaleX: 2.08 * healthPercentage
}, {
duration: 200
});
};
self.attackNearbyEnemies = function () {
if (self.attackCooldown <= 0) {
var enemyUnits = self.team === 'player' ? aiUnits : playerUnits;
var nearestEnemy = null;
var nearestDistance = Infinity;
// Find nearest enemy unit within attack range
for (var i = 0; i < enemyUnits.length; i++) {
var enemy = enemyUnits[i];
var dx = enemy.x - self.x;
var dy = enemy.y - (self.y - 131.25); // Adjust for tower center position
var distance = Math.sqrt(dx * dx + dy * dy);
// Only consider units within the attack range
if (distance <= self.attackRange && distance < nearestDistance) {
nearestDistance = distance;
nearestEnemy = enemy;
}
}
// Attack only the nearest enemy if found
if (nearestEnemy) {
nearestEnemy.health -= 1; // Deal 1 damage as specified
self.attackCooldown = 3; // 0.05 seconds at 60 FPS
LK.getSound('attack').play();
if (nearestEnemy.health <= 0) {
var index = enemyUnits.indexOf(nearestEnemy);
if (index > -1) {
self.destroyUnit(nearestEnemy, enemyUnits, index);
}
}
}
// In zombie mode, player towers also attack zombies (find nearest zombie)
if (zombieMode && self.team === 'player') {
var nearestZombie = null;
var nearestZombieDistance = Infinity;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - (self.y - 131.25);
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.attackRange && distance < nearestZombieDistance) {
nearestZombieDistance = distance;
nearestZombie = zombie;
}
}
// Attack only the nearest zombie if found
if (nearestZombie && !nearestEnemy) {
// Only attack zombie if no enemy unit was attacked
nearestZombie.health -= 1;
self.attackCooldown = 3;
LK.getSound('attack').play();
if (nearestZombie.health <= 0) {
var index = zombies.indexOf(nearestZombie);
if (index > -1) {
zombies.splice(index, 1);
nearestZombie.destroy();
}
}
}
}
}
};
self.showDamageArea = function () {
// No visual effects when towers attack
};
self.destroyUnit = function (unit, unitsArray, index) {
unitsArray.splice(index, 1);
unit.destroy();
};
return self;
});
var Worker = Container.expand(function (team) {
var self = Container.call(this);
var assetName = team === 'player' ? 'worker' : 'aiWorker';
var graphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.team = team;
self.speed = 4;
self.target = null;
self.collectRange = 40;
self.health = 10;
self.maxHealth = 10;
// Create health bar background (black)
self.healthBarBg = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.75
});
self.healthBarBg.x = 0;
self.healthBarBg.y = -120;
self.healthBarBg.tint = 0x000000;
self.addChild(self.healthBarBg);
// Create health bar (green for player, red for AI)
self.healthBar = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.75
});
self.healthBar.x = 0;
self.healthBar.y = -120;
self.healthBar.tint = team === 'player' ? 0x00ff00 : 0xff0000;
self.addChild(self.healthBar);
self.healthText = new Text2(self.health.toString(), {
size: 50,
fill: '#ffffff'
});
self.healthText.anchor.set(0.5, 0.5);
self.addChild(self.healthText);
self.healthText.x = 0;
self.healthText.y = -120;
self.update = function () {
if (!self.target) {
self.findNearestCoin();
} else {
self.moveToTarget();
}
// Workers can defend themselves
self.defendSelf();
self.healthText.setText(self.health.toString());
// Update health bar scale based on health percentage
var healthPercentage = self.health / self.maxHealth;
tween(self.healthBar, {
scaleX: 1.5 * healthPercentage
}, {
duration: 200
});
};
self.findNearestCoin = function () {
var targetCoins = self.team === 'player' ? playerCoins_array : aiCoins_array;
var teamWorkers = self.team === 'player' ? playerUnits : aiUnits;
var availableCoins = [];
// First, collect all untargeted coins
for (var i = 0; i < targetCoins.length; i++) {
var coin = targetCoins[i];
if (!coin.collected) {
// Check if another worker is already targeting this coin
var isTargeted = false;
for (var j = 0; j < teamWorkers.length; j++) {
var worker = teamWorkers[j];
if (worker.unitType === 'worker' && worker !== self && worker.target === coin) {
isTargeted = true;
break;
}
}
if (!isTargeted) {
availableCoins.push(coin);
}
}
}
// If no available coins, clear target
if (availableCoins.length === 0) {
self.target = null;
return;
}
// Get worker index to create unique targeting preference
var workerIndex = -1;
for (var i = 0; i < teamWorkers.length; i++) {
if (teamWorkers[i] === self && teamWorkers[i].unitType === 'worker') {
workerIndex = i;
break;
}
}
// Use worker index to create different targeting preferences
var targetCoin = null;
if (workerIndex >= 0 && workerIndex < availableCoins.length) {
// Each worker gets a different coin based on their index
targetCoin = availableCoins[workerIndex];
} else {
// If more workers than coins, use modulo to cycle through available coins
var coinIndex = workerIndex % availableCoins.length;
targetCoin = availableCoins[coinIndex];
}
// If targeted approach fails, fall back to nearest coin
if (!targetCoin) {
var nearestCoin = null;
var nearestDistance = Infinity;
for (var i = 0; i < availableCoins.length; i++) {
var coin = availableCoins[i];
var distance = Math.sqrt(Math.pow(coin.x - self.x, 2) + Math.pow(coin.y - self.y, 2));
if (distance < nearestDistance) {
nearestDistance = distance;
nearestCoin = coin;
}
}
targetCoin = nearestCoin;
}
self.target = targetCoin;
};
self.moveToTarget = function () {
if (!self.target || self.target.collected) {
self.target = null;
return;
}
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < self.collectRange) {
self.collectCoin();
} else {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.collectCoin = function () {
if (self.target && !self.target.collected) {
self.target.collected = true;
if (self.team === 'player') {
playerCoins += self.target.value;
} else {
aiCoins += self.target.value;
}
LK.getSound('coinCollect').play();
self.target = null;
}
};
self.defendSelf = function () {
var enemyUnits = self.team === 'player' ? aiUnits : playerUnits;
var defenseRange = 50;
for (var i = 0; i < enemyUnits.length; i++) {
var enemy = enemyUnits[i];
// Workers only defend against other workers
if (enemy.unitType === 'worker') {
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= defenseRange) {
enemy.health -= 5; // Workers do less damage
LK.getSound('attack').play();
if (enemy.health <= 0) {
enemyUnits.splice(i, 1);
enemy.destroy();
}
break;
}
}
}
};
return self;
});
var Zombie = Container.expand(function (waveNumber) {
var self = Container.call(this);
var graphics = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
// Stats increase with wave number
var waveMultiplier = waveNumber || 1;
self.speed = 1.5 + (waveMultiplier - 1) * 0.2; // Speed increases slightly each wave
self.health = 30 + (waveMultiplier - 1) * 15; // Health increases by 15 each wave
self.maxHealth = self.health;
self.damage = 10 + (waveMultiplier - 1) * 5; // Damage increases by 5 each wave
self.waveNumber = waveNumber;
self.target = null;
self.attackCooldown = 0;
// Create health bar background (black)
self.healthBarBg = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.75
});
self.healthBarBg.x = 0;
self.healthBarBg.y = -120;
self.healthBarBg.tint = 0x000000;
self.addChild(self.healthBarBg);
// Create health bar (red for zombies)
self.healthBar = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.75
});
self.healthBar.x = 0;
self.healthBar.y = -120;
self.healthBar.tint = 0xff0000;
self.addChild(self.healthBar);
self.healthText = new Text2(self.health.toString(), {
size: 50,
fill: '#ffffff'
});
self.healthText.anchor.set(0.5, 0.5);
self.addChild(self.healthText);
self.healthText.x = 0;
self.healthText.y = -120;
self.update = function () {
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
if (!self.target) {
self.findNearestPlayerTower();
} else {
self.moveToTarget();
}
// Attack nearby player units
self.attackNearbyPlayerUnits();
self.healthText.setText(self.health.toString());
// Update health bar scale based on health percentage
var healthPercentage = self.health / self.maxHealth;
tween(self.healthBar, {
scaleX: 1.5 * healthPercentage
}, {
duration: 200
});
};
self.findNearestPlayerTower = function () {
// Target player tower
if (playerTowers.length > 0) {
self.target = playerTowers[0];
}
};
self.moveToTarget = function () {
if (!self.target) {
return;
}
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 60) {
self.attackTarget();
} else {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.attackTarget = function () {
if (self.attackCooldown <= 0 && self.target) {
self.target.health -= self.damage;
self.attackCooldown = 60;
LK.getSound('attack').play();
if (self.target.health <= 0) {
// Tower destroyed
var index = playerTowers.indexOf(self.target);
if (index > -1) {
playerTowers.splice(index, 1);
self.target.destroy();
LK.getSound('towerDestroyed').play();
self.target = null;
}
}
}
};
self.attackNearbyPlayerUnits = function () {
if (self.attackCooldown <= 0) {
var meleeRange = 60;
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
var dx = unit.x - self.x;
var dy = unit.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= meleeRange) {
unit.health -= self.damage;
self.attackCooldown = 60;
LK.getSound('attack').play();
if (unit.health <= 0) {
playerUnits.splice(i, 1);
unit.destroy();
}
break;
}
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x228B22
});
/****
* Game Code
****/
// Game state management
var gameState = 'menu'; // 'menu' or 'playing'
// Add menu background
var menuBackground = LK.getAsset('menuBg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(menuBackground);
// Create menu overlay
var menuOverlay = new Container();
game.addChild(menuOverlay);
// Add towers asset at the top of the menu
var towersAsset = LK.getAsset('Towers', {
anchorX: 0.5,
anchorY: 0,
scaleX: 4.0,
scaleY: 4.0
});
towersAsset.x = 1024;
towersAsset.y = 50;
menuOverlay.addChild(towersAsset);
// Add app banner at top of menu
var appBanner = LK.getAsset('Appbanner', {
anchorX: 0.5,
anchorY: 0,
x: 1024,
y: 20
});
menuOverlay.addChild(appBanner);
// Start button background
var startButtonBg = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4.0,
scaleY: 1.5
});
startButtonBg.x = 1024;
startButtonBg.y = 1000;
startButtonBg.tint = 0x228b22;
menuOverlay.addChild(startButtonBg);
// Start button image
var startButton = LK.getAsset('StartGameButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4.0,
scaleY: 2.0
});
startButton.x = 1024;
startButton.y = 1000;
menuOverlay.addChild(startButton);
// How to Play button
var howToPlayButton = LK.getAsset('Howtoplay22', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4.0,
scaleY: 4.0
});
howToPlayButton.x = 1024;
howToPlayButton.y = 1615; // 1.3x higher (2100 - 485 = 1615)
menuOverlay.addChild(howToPlayButton);
// Play menu music when game starts
LK.playMusic('Menusong');
// How to Play instructions overlay
var howToPlayOverlay = new Container();
howToPlayOverlay.visible = false;
game.addChild(howToPlayOverlay);
// How to Play background
var howToPlayBg = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 18.0,
scaleY: 24.0
});
howToPlayBg.x = 1024;
howToPlayBg.y = 1366;
howToPlayBg.tint = 0x000000;
howToPlayBg.alpha = 0.9;
howToPlayOverlay.addChild(howToPlayBg);
// How to Play title
var howToPlayTitle = new Text2('HOW TO PLAY', {
size: 100,
fill: '#ffffff'
});
howToPlayTitle.anchor.set(0.5, 0.5);
howToPlayTitle.x = 1024;
howToPlayTitle.y = 400;
howToPlayOverlay.addChild(howToPlayTitle);
// How to Play instructions
var instructionsText = new Text2('NORMAL MODE:\n• Collect gold coins with workers\n• Spend gold to recruit units\n• Press Attack to command units to advance\n• Destroy enemy tower to win\n\nUNIT TYPES & STATS:\n• Worker: 5 gold, 10 HP, 5 damage, collects coins\n• Warrior: 10 gold, 20 HP, 10 damage, melee combat\n• Archer: 15 gold, 30 HP, 20 damage, ranged arrows\n• Wizard: 20 gold, 40 HP, 30 damage, magic attacks\n• Dragon: 25 gold, 50 HP, 40 damage, fire breath\n\nTOWERS:\n• Player Tower: 500 HP, 5 damage per second, attacks nearest enemy\n• AI Tower: 500 HP, 5 damage per second, attacks nearest enemy\n• Attack range: 400 pixels\n\nZOMBIE MODE:\n• Survive endless zombie waves\n• Units automatically attack zombies first\n• Zombies spawn 30 seconds after start\n• Wave 1: 5 zombies (30 HP, 10 damage)\n• Each wave: +2 zombies, +15 HP, +5 damage\n• Defend your tower at all costs!', {
size: 42,
fill: '#ffffff'
});
instructionsText.anchor.set(0.5, 0.5);
instructionsText.x = 1024;
instructionsText.y = 1300;
howToPlayOverlay.addChild(instructionsText);
// Back button
var backButton = new Text2('BACK TO MENU', {
size: 80,
fill: '#ffff00'
});
backButton.anchor.set(0.5, 0.5);
backButton.x = 1024;
backButton.y = 2100;
howToPlayOverlay.addChild(backButton);
// Mode selection overlay
var modeSelectionOverlay = new Container();
modeSelectionOverlay.visible = false;
game.addChild(modeSelectionOverlay);
// Mode selection background
var modeSelectionBg = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 18.0,
scaleY: 24.0
});
modeSelectionBg.x = 1024;
modeSelectionBg.y = 1366;
modeSelectionBg.tint = 0x000000;
modeSelectionBg.alpha = 0.9;
modeSelectionOverlay.addChild(modeSelectionBg);
// Mode selection title
var modeSelectionTitle = new Text2('SELECT MODE', {
size: 100,
fill: '#ffffff'
});
modeSelectionTitle.anchor.set(0.5, 0.5);
modeSelectionTitle.x = 1024;
modeSelectionTitle.y = 600;
modeSelectionOverlay.addChild(modeSelectionTitle);
// Normal mode button
var normalModeButton = LK.getAsset('Normalmode', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
});
normalModeButton.x = 1024;
normalModeButton.y = 1000;
modeSelectionOverlay.addChild(normalModeButton);
// Zombie mode button in mode selection
var zombieModeSelectButton = LK.getAsset('Zombiemode', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
});
zombieModeSelectButton.x = 1024;
zombieModeSelectButton.y = 1400;
modeSelectionOverlay.addChild(zombieModeSelectButton);
// Back to menu button from mode selection
var backToMenuButton = new Text2('BACK TO MENU', {
size: 80,
fill: '#ffff00'
});
backToMenuButton.anchor.set(0.5, 0.5);
backToMenuButton.x = 1024;
backToMenuButton.y = 2000;
modeSelectionOverlay.addChild(backToMenuButton);
// Start button event handler - now shows mode selection
startButton.down = function () {
menuOverlay.visible = false;
modeSelectionOverlay.visible = true;
};
startButtonBg.down = function () {
menuOverlay.visible = false;
modeSelectionOverlay.visible = true;
};
// Normal mode button event handler
normalModeButton.down = function () {
zombieMode = false;
gameState = 'playing';
modeSelectionOverlay.visible = false;
LK.stopMusic(); // Stop menu music when starting game
initializeGame();
};
// Zombie mode button event handler in mode selection
zombieModeSelectButton.down = function () {
zombieMode = true;
gameState = 'playing';
modeSelectionOverlay.visible = false;
LK.stopMusic();
initializeGame();
};
// Back to menu button event handler
backToMenuButton.down = function () {
modeSelectionOverlay.visible = false;
menuOverlay.visible = true;
};
// How to Play button event handler
howToPlayButton.down = function () {
menuOverlay.visible = false;
howToPlayOverlay.visible = true;
};
// Back button event handler
backButton.down = function () {
howToPlayOverlay.visible = false;
menuOverlay.visible = true;
};
// Add game background (initially hidden)
var background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
background.visible = false;
game.addChild(background);
var playerCoins = 0;
var aiCoins = 0;
var playerTowers = [];
var aiTowers = [];
var coins = [];
var playerCoins_array = [];
var aiCoins_array = [];
var playerUnits = [];
var aiUnits = [];
var arrows = [];
var magics = [];
var fires = [];
var coinSpawnTimer = 0;
var aiActionTimer = 0;
var zombies = [];
var zombieSpawnTimer = 0;
var zombieMode = false;
var zombieWaveNumber = 1;
var zombieWaveSpawnTimer = 0;
var zombieWaveInProgress = false;
var zombiesInCurrentWave = 0;
var zombiesToSpawnInWave = 0;
// UI Elements
var coinDisplay = new Text2('Golds: 0', {
size: 80,
fill: '#ffff00'
});
coinDisplay.anchor.set(0.5, 0.5);
coinDisplay.x = 200;
coinDisplay.y = -273.75;
coinDisplay.visible = false;
LK.gui.bottom.addChild(coinDisplay);
// Attack button
var attackButton = new Text2('Attack', {
size: 80,
fill: '#ff0000'
});
attackButton.anchor.set(0.5, 0.5);
attackButton.x = -200;
attackButton.y = -273.75;
attackButton.visible = false;
LK.gui.bottom.addChild(attackButton);
var workerButton = new Text2('5', {
size: 32,
fill: '#ffffff'
});
workerButton.anchor.set(0.5, 0.5);
workerButton.x = -400;
workerButton.y = -45;
var warriorButton = new Text2('10', {
size: 32,
fill: '#ffffff'
});
warriorButton.anchor.set(0.5, 0.5);
warriorButton.x = -200;
warriorButton.y = -45;
var wizardButton = new Text2('20', {
size: 32,
fill: '#ffffff'
});
wizardButton.anchor.set(0.5, 0.5);
wizardButton.x = 200;
wizardButton.y = -45;
var archerButton = new Text2('15', {
size: 32,
fill: '#ffffff'
});
archerButton.anchor.set(0.5, 0.5);
archerButton.x = 0;
archerButton.y = -45;
var dragonButton = new Text2('25', {
size: 32,
fill: '#ffffff'
});
dragonButton.anchor.set(0.5, 0.5);
dragonButton.x = 400;
dragonButton.y = -45;
// Add unified black background behind all recruitment buttons and unit images
var unifiedBackground = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 22.0,
scaleY: 2.8
});
unifiedBackground.x = 0;
unifiedBackground.y = -78;
unifiedBackground.tint = 0x000000;
unifiedBackground.alpha = 0.5;
unifiedBackground.visible = false;
LK.gui.bottom.addChild(unifiedBackground);
// Add unit images above recruitment buttons
var workerImage = LK.getAsset('worker', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0
});
workerImage.x = -400;
workerImage.y = -110;
LK.gui.bottom.addChild(workerImage);
var warriorImage = LK.getAsset('warrior', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0
});
warriorImage.x = -200;
warriorImage.y = -110;
LK.gui.bottom.addChild(warriorImage);
var wizardImage = LK.getAsset('wizard', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0
});
wizardImage.x = 200;
wizardImage.y = -110;
LK.gui.bottom.addChild(wizardImage);
var archerImage = LK.getAsset('archer', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0
});
archerImage.x = 0;
archerImage.y = -110;
LK.gui.bottom.addChild(archerImage);
var dragonImage = LK.getAsset('dragon', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0
});
dragonImage.x = 400;
dragonImage.y = -110;
LK.gui.bottom.addChild(dragonImage);
// Wave display for zombie mode
var waveDisplay = new Text2('Wave: 1', {
size: 80,
fill: '#ff0000'
});
waveDisplay.anchor.set(0.5, 0.5);
waveDisplay.x = 0;
waveDisplay.y = 100;
waveDisplay.visible = false;
LK.gui.top.addChild(waveDisplay);
// Hide all UI elements initially (menu state)
workerButton.visible = false;
warriorButton.visible = false;
wizardButton.visible = false;
archerButton.visible = false;
dragonButton.visible = false;
workerImage.visible = false;
warriorImage.visible = false;
wizardImage.visible = false;
archerImage.visible = false;
dragonImage.visible = false;
// Add texts after backgrounds so they appear on top
LK.gui.bottom.addChild(workerButton);
LK.gui.bottom.addChild(warriorButton);
LK.gui.bottom.addChild(wizardButton);
LK.gui.bottom.addChild(archerButton);
LK.gui.bottom.addChild(dragonButton);
// Initialize towers
function initializeTowers() {
// Player tower
var playerTower = new Tower('player');
playerTower.x = 1024;
playerTower.y = 2300;
playerTowers.push(playerTower);
game.addChild(playerTower);
// AI tower (only in normal mode)
if (!zombieMode) {
var aiTower = new Tower('ai');
aiTower.x = 1024;
aiTower.y = 400;
aiTowers.push(aiTower);
game.addChild(aiTower);
}
}
;
function spawnCoin() {
// Spawn player coin
var playerCoin = new Coin();
playerCoin.x = Math.random() * 1800 + 124;
playerCoin.y = Math.random() * 1000 + 1200; // Lower half for player
playerCoins_array.push(playerCoin);
coins.push(playerCoin);
game.addChild(playerCoin);
// Spawn AI coin
var aiCoin = new Coin();
aiCoin.x = Math.random() * 1800 + 124;
aiCoin.y = Math.random() * 1000 + 400; // Upper half for AI
aiCoins_array.push(aiCoin);
coins.push(aiCoin);
game.addChild(aiCoin);
}
function spawnUnit(team, unitType) {
var unit;
var cost = unitType === 'worker' ? 5 : unitType === 'warrior' ? 10 : unitType === 'wizard' ? 20 : unitType === 'archer' ? 15 : 25;
if (team === 'player' && playerCoins >= cost) {
playerCoins -= cost;
if (unitType === 'worker') {
unit = new Worker('player');
} else {
unit = new CombatUnit('player', unitType);
}
if (!zombieMode && unitType !== 'worker') {
// Position combat units in front of tower in normal mode with better spacing
var unitCount = playerUnits.length;
var spreadRadius = 300;
var angle = unitCount * 0.5 % (Math.PI * 2);
unit.x = playerTowers[0].x + Math.cos(angle) * spreadRadius;
unit.y = playerTowers[0].y - 507 + Math.sin(angle) * 100;
} else {
var unitCount = playerUnits.length;
var spreadRadius = 200;
var angle = unitCount * 0.8 % (Math.PI * 2);
unit.x = playerTowers[0].x + Math.cos(angle) * spreadRadius;
unit.y = playerTowers[0].y - 100 + Math.sin(angle) * 50;
}
playerUnits.push(unit);
game.addChild(unit);
LK.getSound('unitSpawn').play();
} else if (team === 'ai' && aiCoins >= cost) {
aiCoins -= cost;
if (unitType === 'worker') {
unit = new Worker('ai');
} else {
unit = new CombatUnit('ai', unitType);
}
var unitCount = aiUnits.length;
var spreadRadius = 200;
var angle = unitCount * 0.8 % (Math.PI * 2);
unit.x = aiTowers[0].x + Math.cos(angle) * spreadRadius;
unit.y = aiTowers[0].y + 100 + Math.sin(angle) * 50;
aiUnits.push(unit);
game.addChild(unit);
}
}
function spawnZombie() {
var zombie = new Zombie(zombieWaveNumber);
zombie.x = Math.random() * 1800 + 124; // Random X position across screen width
zombie.y = 50; // Spawn at top of screen
zombies.push(zombie);
game.addChild(zombie);
}
function startZombieWave() {
zombieWaveInProgress = true;
zombiesToSpawnInWave = 3 + zombieWaveNumber * 2; // Wave 1: 5 zombies, Wave 2: 7 zombies, etc.
zombiesInCurrentWave = 0;
zombieWaveSpawnTimer = 0;
// Update wave display
if (zombieMode) {
waveDisplay.setText('Wave: ' + zombieWaveNumber);
}
}
function checkWaveComplete() {
// Check if all zombies in current wave are spawned and destroyed
if (zombieWaveInProgress && zombiesInCurrentWave >= zombiesToSpawnInWave && zombies.length === 0) {
zombieWaveInProgress = false;
zombieWaveNumber++;
// Start next wave after 10 seconds (doubled from 5 seconds)
zombieWaveSpawnTimer = -600; // 10 seconds delay before next wave
}
}
function aiLogic() {
if (aiActionTimer <= 0) {
if (aiCoins >= 25 && Math.random() < 0.1) {
spawnUnit('ai', 'dragon');
} else if (aiCoins >= 15 && Math.random() < 0.15) {
spawnUnit('ai', 'archer');
} else if (aiCoins >= 20 && Math.random() < 0.2) {
spawnUnit('ai', 'wizard');
} else if (aiCoins >= 10 && Math.random() < 0.3) {
spawnUnit('ai', 'warrior');
} else if (aiCoins >= 5 && Math.random() < 0.4) {
spawnUnit('ai', 'worker');
}
aiActionTimer = 120;
}
aiActionTimer--;
}
function checkGameEnd() {
if (playerTowers.length === 0) {
LK.showGameOver();
} else if (!zombieMode && aiTowers.length === 0) {
LK.showYouWin();
}
}
function cleanupCollectedCoins() {
// Clean up from main coins array
for (var i = coins.length - 1; i >= 0; i--) {
if (coins[i].collected) {
coins[i].destroy();
coins.splice(i, 1);
}
}
// Clean up from player coins array
for (var i = playerCoins_array.length - 1; i >= 0; i--) {
if (playerCoins_array[i].collected) {
playerCoins_array.splice(i, 1);
}
}
// Clean up from AI coins array
for (var i = aiCoins_array.length - 1; i >= 0; i--) {
if (aiCoins_array[i].collected) {
aiCoins_array.splice(i, 1);
}
}
}
// Button event handlers
workerButton.down = function () {
spawnUnit('player', 'worker');
};
warriorButton.down = function () {
spawnUnit('player', 'warrior');
};
wizardButton.down = function () {
spawnUnit('player', 'wizard');
};
archerButton.down = function () {
spawnUnit('player', 'archer');
};
dragonButton.down = function () {
spawnUnit('player', 'dragon');
};
// Character image event handlers
workerImage.down = function () {
spawnUnit('player', 'worker');
};
warriorImage.down = function () {
spawnUnit('player', 'warrior');
};
wizardImage.down = function () {
spawnUnit('player', 'wizard');
};
archerImage.down = function () {
spawnUnit('player', 'archer');
};
dragonImage.down = function () {
spawnUnit('player', 'dragon');
};
// Attack button event handler
attackButton.down = function () {
if (!zombieMode) {
// Make all waiting player units start attacking
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
if (unit.unitType !== 'worker') {
unit.isWaiting = false;
}
}
}
};
// Initialize game function
function initializeGame() {
background.visible = true;
// Show UI elements when game starts
coinDisplay.visible = true;
unifiedBackground.visible = true;
workerButton.visible = true;
warriorButton.visible = true;
wizardButton.visible = true;
archerButton.visible = true;
dragonButton.visible = true;
workerImage.visible = true;
warriorImage.visible = true;
wizardImage.visible = true;
archerImage.visible = true;
dragonImage.visible = true;
// Show attack button only in normal mode
if (!zombieMode) {
attackButton.visible = true;
} else {
// Show wave display in zombie mode
waveDisplay.visible = true;
waveDisplay.setText('Wave: ' + zombieWaveNumber);
}
// Reset zombie wave system
zombieWaveNumber = 1;
zombieWaveSpawnTimer = -1800; // 30 second delay (30 * 60 FPS = 1800 frames)
zombieWaveInProgress = false;
zombiesInCurrentWave = 0;
zombiesToSpawnInWave = 0;
// Play game music when match starts
LK.playMusic('Song');
initializeTowers();
// Spawn initial workers for both teams
var playerWorker = new Worker('player');
playerWorker.x = playerTowers[0].x + 50;
playerWorker.y = playerTowers[0].y - 100;
playerUnits.push(playerWorker);
game.addChild(playerWorker);
// AI worker (only in normal mode)
if (!zombieMode) {
var aiWorker = new Worker('ai');
aiWorker.x = aiTowers[0].x + 50;
aiWorker.y = aiTowers[0].y + 100;
aiUnits.push(aiWorker);
game.addChild(aiWorker);
}
}
// Main game loop
game.update = function () {
if (gameState === 'playing') {
// Update coin display
coinDisplay.setText('Golds: ' + playerCoins);
// Spawn coins
coinSpawnTimer++;
if (coinSpawnTimer >= 36) {
// 5x more frequent (180/5 = 36)
spawnCoin();
coinSpawnTimer = 0;
}
// AI logic (only in normal mode)
if (!zombieMode) {
aiLogic();
}
// Clean up collected coins
cleanupCollectedCoins();
// Zombie mode logic
if (zombieMode) {
zombieWaveSpawnTimer++;
// Start first wave or next wave after delay
if (!zombieWaveInProgress && zombieWaveSpawnTimer >= 0) {
startZombieWave();
}
// Spawn zombies in current wave
if (zombieWaveInProgress && zombiesInCurrentWave < zombiesToSpawnInWave) {
if (zombieWaveSpawnTimer % 60 === 0) {
// Spawn one zombie every second during wave
spawnZombie();
zombiesInCurrentWave++;
}
}
// Check if current wave is complete
checkWaveComplete();
// Clean up off-screen zombies
for (var i = zombies.length - 1; i >= 0; i--) {
if (zombies[i].y > 2800) {
zombies[i].destroy();
zombies.splice(i, 1);
}
}
}
// Check game end conditions
checkGameEnd();
}
}; /****
* Plugins
****/
var storage = LK.import("@upit/storage.v1");
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Arrow = Container.expand(function (team, startX, startY, targetX, targetY, damage) {
var self = Container.call(this);
var graphics = self.attachAsset('Arrow', {
anchorX: 0.5,
anchorY: 0.5
});
self.team = team;
self.damage = damage;
self.speed = 8;
self.target = null;
// Calculate direction
var dx = targetX - startX;
var dy = targetY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
self.directionX = dx / distance;
self.directionY = dy / distance;
// Set rotation to point toward target
self.rotation = Math.atan2(dy, dx);
// Position arrow at start
self.x = startX;
self.y = startY;
self.update = function () {
// Move arrow
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
// Check if arrow is off screen
if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) {
self.destroy();
// Remove from arrows array
for (var i = arrows.length - 1; i >= 0; i--) {
if (arrows[i] === self) {
arrows.splice(i, 1);
break;
}
}
}
// Check collision with enemy units
var enemyUnits = self.team === 'player' ? aiUnits : playerUnits;
for (var i = 0; i < enemyUnits.length; i++) {
var enemy = enemyUnits[i];
if (self.intersects(enemy)) {
enemy.health -= self.damage;
if (enemy.health <= 0) {
enemyUnits.splice(i, 1);
enemy.destroy();
}
self.destroy();
// Remove from arrows array
for (var j = arrows.length - 1; j >= 0; j--) {
if (arrows[j] === self) {
arrows.splice(j, 1);
break;
}
}
return;
}
}
// Check collision with enemy towers
var enemyTowers = self.team === 'player' ? aiTowers : playerTowers;
for (var i = 0; i < enemyTowers.length; i++) {
var tower = enemyTowers[i];
if (self.intersects(tower)) {
tower.health -= self.damage;
if (tower.health <= 0) {
enemyTowers.splice(i, 1);
tower.destroy();
LK.getSound('towerDestroyed').play();
}
self.destroy();
// Remove from arrows array
for (var j = arrows.length - 1; j >= 0; j--) {
if (arrows[j] === self) {
arrows.splice(j, 1);
break;
}
}
return;
}
}
};
return self;
});
var Coin = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
self.value = 1;
self.collected = false;
return self;
});
var CombatUnit = Container.expand(function (team, unitType) {
var self = Container.call(this);
var assetName = team === 'player' ? unitType : 'ai' + unitType.charAt(0).toUpperCase() + unitType.slice(1);
var graphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.team = team;
self.unitType = unitType;
self.speed = unitType === 'dragon' ? 3 : unitType === 'wizard' ? 1.5 : unitType === 'archer' ? 2.5 : 2;
self.damage = unitType === 'dragon' ? 40 : unitType === 'wizard' ? 30 : unitType === 'archer' ? 20 : 10;
self.health = unitType === 'dragon' ? 50 : unitType === 'wizard' ? 40 : unitType === 'archer' ? 30 : unitType === 'warrior' ? 20 : 10;
self.maxHealth = self.health;
self.attackRange = unitType === 'wizard' ? 100 : unitType === 'archer' ? 200 : 50;
self.attackCooldown = 0;
self.target = null;
self.isWaiting = !zombieMode && team === 'player'; // Only player units wait in normal mode
// Create health bar background (black)
self.healthBarBg = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.75
});
self.healthBarBg.x = 0;
self.healthBarBg.y = -120;
self.healthBarBg.tint = team === 'player' ? 0x0000ff : 0x000000;
self.addChild(self.healthBarBg);
// Create health bar (green for player, red for AI)
self.healthBar = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.75
});
self.healthBar.x = 0;
self.healthBar.y = -120;
self.healthBar.tint = team === 'player' ? 0x00ff00 : 0xff0000;
self.addChild(self.healthBar);
self.healthText = new Text2(self.health.toString(), {
size: 50,
fill: '#ffffff'
});
self.healthText.anchor.set(0.5, 0.5);
self.addChild(self.healthText);
self.healthText.x = 0;
self.healthText.y = -120;
self.update = function () {
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
// In zombie mode, player units prioritize attacking zombies
if (zombieMode && self.team === 'player' && self.unitType !== 'worker') {
self.attackZombies();
} else if (!zombieMode && self.team === 'player' && self.isWaiting) {
// In normal mode, player units wait unless attack is triggered
// Stay in position and don't move
} else {
if (!self.target) {
self.findNearestEnemyTower();
} else {
self.moveToTarget();
}
// Combat with nearby enemy units
self.combatNearbyEnemies();
}
self.healthText.setText(self.health.toString());
// Update health bar scale based on health percentage
var healthPercentage = self.health / self.maxHealth;
tween(self.healthBar, {
scaleX: 1.5 * healthPercentage
}, {
duration: 200
});
};
self.findNearestEnemyTower = function () {
// In zombie mode, player units should prioritize attacking zombies
if (zombieMode && self.team === 'player' && self.unitType !== 'worker') {
var nearestZombie = null;
var nearestDistance = Infinity;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var distance = Math.sqrt(Math.pow(zombie.x - self.x, 2) + Math.pow(zombie.y - self.y, 2));
if (distance < nearestDistance) {
nearestDistance = distance;
nearestZombie = zombie;
}
}
// If we found a zombie, target it
if (nearestZombie) {
self.target = nearestZombie;
return;
}
}
// Find nearest target - prioritize ALL enemy units first (including workers), then towers
var nearestTarget = null;
var nearestDistance = Infinity;
// First check all enemy units (including workers)
var enemyUnits = self.team === 'player' ? aiUnits : playerUnits;
for (var i = 0; i < enemyUnits.length; i++) {
var enemy = enemyUnits[i];
// Target all enemy units now, including workers
if (self.unitType !== 'worker') {
var distance = Math.sqrt(Math.pow(enemy.x - self.x, 2) + Math.pow(enemy.y - self.y, 2));
if (distance < nearestDistance) {
nearestDistance = distance;
nearestTarget = enemy;
}
}
}
// Only target towers if no enemy units remain
if (!nearestTarget) {
var enemyTowers = self.team === 'player' ? aiTowers : playerTowers;
for (var i = 0; i < enemyTowers.length; i++) {
var tower = enemyTowers[i];
var distance = Math.sqrt(Math.pow(tower.x - self.x, 2) + Math.pow(tower.y - self.y, 2));
if (distance < nearestDistance) {
nearestDistance = distance;
nearestTarget = tower;
}
}
}
self.target = nearestTarget;
};
self.moveToTarget = function () {
if (!self.target) {
return;
}
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < self.attackRange) {
self.attackTarget();
} else {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.attackTarget = function () {
if (self.attackCooldown <= 0 && self.target) {
if (self.unitType === 'archer') {
// Archer shoots arrow
var arrow = new Arrow(self.team, self.x, self.y, self.target.x, self.target.y, self.damage);
arrows.push(arrow);
game.addChild(arrow);
} else if (self.unitType === 'wizard') {
// Wizard shoots magic
var magic = new Magic(self.team, self.x, self.y, self.target.x, self.target.y, self.damage);
magics.push(magic);
game.addChild(magic);
} else if (self.unitType === 'dragon') {
// Change to attack asset for all dragon attacks
self.removeChild(graphics);
var assetName = 'DragonAttack';
graphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
// Dragon shoots fire with 1 damage
var fire = new Fire(self.team, self.x, self.y, self.target.x, self.target.y, 1);
fires.push(fire);
game.addChild(fire);
} else {
// Melee attack
self.target.health -= self.damage;
if (self.target.health <= 0) {
// Check if target is a unit or tower
if (self.target.unitType) {
// Target is a unit
self.destroyUnit();
} else {
// Target is a tower
self.destroyTower();
}
}
}
// Set dragon attack cooldown to 3 frames (0.05 seconds)
if (self.unitType === 'dragon') {
self.attackCooldown = 3;
} else {
self.attackCooldown = 60;
}
LK.getSound('attack').play();
}
};
self.destroyUnit = function () {
var units = self.team === 'player' ? aiUnits : playerUnits;
var index = units.indexOf(self.target);
if (index > -1) {
units.splice(index, 1);
self.target.destroy();
self.target = null;
}
};
self.destroyTower = function () {
var towers = self.team === 'player' ? aiTowers : playerTowers;
var index = towers.indexOf(self.target);
if (index > -1) {
towers.splice(index, 1);
self.target.destroy();
LK.getSound('towerDestroyed').play();
self.target = null;
}
};
self.attackZombies = function () {
// Find nearest zombie to target
var nearestZombie = null;
var nearestDistance = Infinity;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestZombie = zombie;
}
}
// Move toward and attack nearest zombie
if (nearestZombie) {
var dx = nearestZombie.x - self.x;
var dy = nearestZombie.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Move toward zombie if not in attack range
if (distance > self.attackRange) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
} else if (self.attackCooldown <= 0) {
// Attack zombie based on unit type
if (self.unitType === 'archer') {
// Archer shoots arrow at zombie
var arrow = new Arrow(self.team, self.x, self.y, nearestZombie.x, nearestZombie.y, self.damage);
arrows.push(arrow);
game.addChild(arrow);
} else if (self.unitType === 'wizard') {
// Wizard shoots magic at zombie
var magic = new Magic(self.team, self.x, self.y, nearestZombie.x, nearestZombie.y, self.damage);
magics.push(magic);
game.addChild(magic);
} else if (self.unitType === 'dragon') {
// Change to attack asset for dragon zombie attacks
self.removeChild(graphics);
var assetName = 'DragonAttack';
graphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
// Dragon shoots fire at zombie
var fire = new Fire(self.team, self.x, self.y, nearestZombie.x, nearestZombie.y, self.damage);
fires.push(fire);
game.addChild(fire);
} else {
// Melee attack (warrior)
nearestZombie.health -= self.damage;
if (nearestZombie.health <= 0) {
for (var j = 0; j < zombies.length; j++) {
if (zombies[j] === nearestZombie) {
zombies.splice(j, 1);
nearestZombie.destroy();
break;
}
}
}
}
self.attackCooldown = 60; // 1 second cooldown
LK.getSound('attack').play();
}
}
};
self.combatNearbyEnemies = function () {
if (self.attackCooldown <= 0) {
var enemyUnits = self.team === 'player' ? aiUnits : playerUnits;
var meleeRange = 60; // Close combat range
for (var i = 0; i < enemyUnits.length; i++) {
var enemy = enemyUnits[i];
// Skip workers in combat
if (enemy.unitType === 'worker') {
continue;
}
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If enemy unit is within melee range, attack it
if (distance <= meleeRange) {
enemy.health -= self.damage;
self.attackCooldown = 60; // 1 second cooldown
LK.getSound('attack').play();
// Check if enemy is destroyed
if (enemy.health <= 0) {
enemyUnits.splice(i, 1);
enemy.destroy();
}
break; // Only attack one enemy per cycle
}
}
}
};
return self;
});
var Fire = Container.expand(function (team, startX, startY, targetX, targetY, damage) {
var self = Container.call(this);
var graphics = self.attachAsset('Magic', {
anchorX: 0.5,
anchorY: 0.5
});
self.team = team;
self.damage = damage;
self.speed = 4;
self.target = null;
// Calculate direction
var dx = targetX - startX;
var dy = targetY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
self.directionX = dx / distance;
self.directionY = dy / distance;
// Position fire at start
self.x = startX;
self.y = startY;
// Add fire effect - orange/red tint and scaling
graphics.tint = 0xff4500; // Orange-red fire color
tween(graphics, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeOut
});
self.update = function () {
// Move fire
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
// Add fire flicker rotation
graphics.rotation += 0.15;
// Check if fire is off screen
if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) {
self.destroy();
// Remove from fires array
for (var i = fires.length - 1; i >= 0; i--) {
if (fires[i] === self) {
fires.splice(i, 1);
break;
}
}
}
// Check collision with enemy units
var enemyUnits = self.team === 'player' ? aiUnits : playerUnits;
for (var i = 0; i < enemyUnits.length; i++) {
var enemy = enemyUnits[i];
if (self.intersects(enemy)) {
enemy.health -= self.damage;
if (enemy.health <= 0) {
enemyUnits.splice(i, 1);
enemy.destroy();
}
self.destroy();
// Remove from fires array
for (var j = fires.length - 1; j >= 0; j--) {
if (fires[j] === self) {
fires.splice(j, 1);
break;
}
}
return;
}
}
// Check collision with enemy towers
var enemyTowers = self.team === 'player' ? aiTowers : playerTowers;
for (var i = 0; i < enemyTowers.length; i++) {
var tower = enemyTowers[i];
if (self.intersects(tower)) {
tower.health -= self.damage;
if (tower.health <= 0) {
enemyTowers.splice(i, 1);
tower.destroy();
LK.getSound('towerDestroyed').play();
}
self.destroy();
// Remove from fires array
for (var j = fires.length - 1; j >= 0; j--) {
if (fires[j] === self) {
fires.splice(j, 1);
break;
}
}
return;
}
}
};
return self;
});
var Magic = Container.expand(function (team, startX, startY, targetX, targetY, damage) {
var self = Container.call(this);
var graphics = self.attachAsset('Magic', {
anchorX: 0.5,
anchorY: 0.5
});
self.team = team;
self.damage = damage;
self.speed = 6;
self.target = null;
// Calculate direction
var dx = targetX - startX;
var dy = targetY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
self.directionX = dx / distance;
self.directionY = dy / distance;
// Position magic at start
self.x = startX;
self.y = startY;
// Add magical glow effect
tween(graphics, {
tint: 0x9966ff
}, {
duration: 500,
easing: tween.easeInOut
});
tween(graphics, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
easing: tween.easeOut
});
self.update = function () {
// Move magic
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
// Add sparkle rotation
graphics.rotation += 0.1;
// Check if magic is off screen
if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) {
self.destroy();
// Remove from magics array
for (var i = magics.length - 1; i >= 0; i--) {
if (magics[i] === self) {
magics.splice(i, 1);
break;
}
}
}
// Check collision with enemy units
var enemyUnits = self.team === 'player' ? aiUnits : playerUnits;
for (var i = 0; i < enemyUnits.length; i++) {
var enemy = enemyUnits[i];
if (self.intersects(enemy)) {
enemy.health -= self.damage;
if (enemy.health <= 0) {
enemyUnits.splice(i, 1);
enemy.destroy();
}
self.destroy();
// Remove from magics array
for (var j = magics.length - 1; j >= 0; j--) {
if (magics[j] === self) {
magics.splice(j, 1);
break;
}
}
return;
}
}
// Check collision with enemy towers
var enemyTowers = self.team === 'player' ? aiTowers : playerTowers;
for (var i = 0; i < enemyTowers.length; i++) {
var tower = enemyTowers[i];
if (self.intersects(tower)) {
tower.health -= self.damage;
if (tower.health <= 0) {
enemyTowers.splice(i, 1);
tower.destroy();
LK.getSound('towerDestroyed').play();
}
self.destroy();
// Remove from magics array
for (var j = magics.length - 1; j >= 0; j--) {
if (magics[j] === self) {
magics.splice(j, 1);
break;
}
}
return;
}
}
};
return self;
});
var Tower = Container.expand(function (team) {
var self = Container.call(this);
var assetName = team === 'player' ? 'playerTower' : 'aiTower';
var graphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 1.0
});
self.team = team;
self.health = 500;
self.maxHealth = 500;
// Create health bar background (black)
self.healthBarBg = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.08,
scaleY: 0.625
});
self.healthBarBg.x = 0;
self.healthBarBg.y = -262.5;
self.healthBarBg.tint = 0x0000ff;
self.addChild(self.healthBarBg);
// Create health bar (green for player, red for AI)
self.healthBar = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.08,
scaleY: 0.625
});
self.healthBar.x = 0;
self.healthBar.y = -262.5;
self.healthBar.tint = team === 'player' ? 0x00ff00 : 0xff0000;
self.addChild(self.healthBar);
self.healthText = new Text2(self.health.toString(), {
size: 42,
fill: '#ffffff'
});
self.healthText.anchor.set(0.5, 0.5);
self.addChild(self.healthText);
self.healthText.x = 0;
self.healthText.y = -262.5;
self.attackCooldown = 0;
self.attackRange = 400;
// Damage area outline removed as requested
self.update = function () {
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
self.attackNearbyEnemies();
self.healthText.setText(self.health.toString());
// Update health bar scale based on health percentage
var healthPercentage = self.health / self.maxHealth;
tween(self.healthBar, {
scaleX: 2.08 * healthPercentage
}, {
duration: 200
});
};
self.attackNearbyEnemies = function () {
if (self.attackCooldown <= 0) {
var enemyUnits = self.team === 'player' ? aiUnits : playerUnits;
var nearestEnemy = null;
var nearestDistance = Infinity;
// Find nearest enemy unit within attack range
for (var i = 0; i < enemyUnits.length; i++) {
var enemy = enemyUnits[i];
var dx = enemy.x - self.x;
var dy = enemy.y - (self.y - 131.25); // Adjust for tower center position
var distance = Math.sqrt(dx * dx + dy * dy);
// Only consider units within the attack range
if (distance <= self.attackRange && distance < nearestDistance) {
nearestDistance = distance;
nearestEnemy = enemy;
}
}
// Attack only the nearest enemy if found
if (nearestEnemy) {
nearestEnemy.health -= 1; // Deal 1 damage as specified
self.attackCooldown = 3; // 0.05 seconds at 60 FPS
LK.getSound('attack').play();
if (nearestEnemy.health <= 0) {
var index = enemyUnits.indexOf(nearestEnemy);
if (index > -1) {
self.destroyUnit(nearestEnemy, enemyUnits, index);
}
}
}
// In zombie mode, player towers also attack zombies (find nearest zombie)
if (zombieMode && self.team === 'player') {
var nearestZombie = null;
var nearestZombieDistance = Infinity;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var dx = zombie.x - self.x;
var dy = zombie.y - (self.y - 131.25);
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.attackRange && distance < nearestZombieDistance) {
nearestZombieDistance = distance;
nearestZombie = zombie;
}
}
// Attack only the nearest zombie if found
if (nearestZombie && !nearestEnemy) {
// Only attack zombie if no enemy unit was attacked
nearestZombie.health -= 1;
self.attackCooldown = 3;
LK.getSound('attack').play();
if (nearestZombie.health <= 0) {
var index = zombies.indexOf(nearestZombie);
if (index > -1) {
zombies.splice(index, 1);
nearestZombie.destroy();
}
}
}
}
}
};
self.showDamageArea = function () {
// No visual effects when towers attack
};
self.destroyUnit = function (unit, unitsArray, index) {
unitsArray.splice(index, 1);
unit.destroy();
};
return self;
});
var Worker = Container.expand(function (team) {
var self = Container.call(this);
var assetName = team === 'player' ? 'worker' : 'aiWorker';
var graphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.team = team;
self.speed = 4;
self.target = null;
self.collectRange = 40;
self.health = 10;
self.maxHealth = 10;
// Create health bar background (black)
self.healthBarBg = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.75
});
self.healthBarBg.x = 0;
self.healthBarBg.y = -120;
self.healthBarBg.tint = 0x000000;
self.addChild(self.healthBarBg);
// Create health bar (green for player, red for AI)
self.healthBar = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.75
});
self.healthBar.x = 0;
self.healthBar.y = -120;
self.healthBar.tint = team === 'player' ? 0x00ff00 : 0xff0000;
self.addChild(self.healthBar);
self.healthText = new Text2(self.health.toString(), {
size: 50,
fill: '#ffffff'
});
self.healthText.anchor.set(0.5, 0.5);
self.addChild(self.healthText);
self.healthText.x = 0;
self.healthText.y = -120;
self.update = function () {
if (!self.target) {
self.findNearestCoin();
} else {
self.moveToTarget();
}
// Workers can defend themselves
self.defendSelf();
self.healthText.setText(self.health.toString());
// Update health bar scale based on health percentage
var healthPercentage = self.health / self.maxHealth;
tween(self.healthBar, {
scaleX: 1.5 * healthPercentage
}, {
duration: 200
});
};
self.findNearestCoin = function () {
var targetCoins = self.team === 'player' ? playerCoins_array : aiCoins_array;
var teamWorkers = self.team === 'player' ? playerUnits : aiUnits;
var availableCoins = [];
// First, collect all untargeted coins
for (var i = 0; i < targetCoins.length; i++) {
var coin = targetCoins[i];
if (!coin.collected) {
// Check if another worker is already targeting this coin
var isTargeted = false;
for (var j = 0; j < teamWorkers.length; j++) {
var worker = teamWorkers[j];
if (worker.unitType === 'worker' && worker !== self && worker.target === coin) {
isTargeted = true;
break;
}
}
if (!isTargeted) {
availableCoins.push(coin);
}
}
}
// If no available coins, clear target
if (availableCoins.length === 0) {
self.target = null;
return;
}
// Get worker index to create unique targeting preference
var workerIndex = -1;
for (var i = 0; i < teamWorkers.length; i++) {
if (teamWorkers[i] === self && teamWorkers[i].unitType === 'worker') {
workerIndex = i;
break;
}
}
// Use worker index to create different targeting preferences
var targetCoin = null;
if (workerIndex >= 0 && workerIndex < availableCoins.length) {
// Each worker gets a different coin based on their index
targetCoin = availableCoins[workerIndex];
} else {
// If more workers than coins, use modulo to cycle through available coins
var coinIndex = workerIndex % availableCoins.length;
targetCoin = availableCoins[coinIndex];
}
// If targeted approach fails, fall back to nearest coin
if (!targetCoin) {
var nearestCoin = null;
var nearestDistance = Infinity;
for (var i = 0; i < availableCoins.length; i++) {
var coin = availableCoins[i];
var distance = Math.sqrt(Math.pow(coin.x - self.x, 2) + Math.pow(coin.y - self.y, 2));
if (distance < nearestDistance) {
nearestDistance = distance;
nearestCoin = coin;
}
}
targetCoin = nearestCoin;
}
self.target = targetCoin;
};
self.moveToTarget = function () {
if (!self.target || self.target.collected) {
self.target = null;
return;
}
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < self.collectRange) {
self.collectCoin();
} else {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.collectCoin = function () {
if (self.target && !self.target.collected) {
self.target.collected = true;
if (self.team === 'player') {
playerCoins += self.target.value;
} else {
aiCoins += self.target.value;
}
LK.getSound('coinCollect').play();
self.target = null;
}
};
self.defendSelf = function () {
var enemyUnits = self.team === 'player' ? aiUnits : playerUnits;
var defenseRange = 50;
for (var i = 0; i < enemyUnits.length; i++) {
var enemy = enemyUnits[i];
// Workers only defend against other workers
if (enemy.unitType === 'worker') {
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= defenseRange) {
enemy.health -= 5; // Workers do less damage
LK.getSound('attack').play();
if (enemy.health <= 0) {
enemyUnits.splice(i, 1);
enemy.destroy();
}
break;
}
}
}
};
return self;
});
var Zombie = Container.expand(function (waveNumber) {
var self = Container.call(this);
var graphics = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
// Stats increase with wave number
var waveMultiplier = waveNumber || 1;
self.speed = 1.5 + (waveMultiplier - 1) * 0.2; // Speed increases slightly each wave
self.health = 30 + (waveMultiplier - 1) * 15; // Health increases by 15 each wave
self.maxHealth = self.health;
self.damage = 10 + (waveMultiplier - 1) * 5; // Damage increases by 5 each wave
self.waveNumber = waveNumber;
self.target = null;
self.attackCooldown = 0;
// Create health bar background (black)
self.healthBarBg = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.75
});
self.healthBarBg.x = 0;
self.healthBarBg.y = -120;
self.healthBarBg.tint = 0x000000;
self.addChild(self.healthBarBg);
// Create health bar (red for zombies)
self.healthBar = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.75
});
self.healthBar.x = 0;
self.healthBar.y = -120;
self.healthBar.tint = 0xff0000;
self.addChild(self.healthBar);
self.healthText = new Text2(self.health.toString(), {
size: 50,
fill: '#ffffff'
});
self.healthText.anchor.set(0.5, 0.5);
self.addChild(self.healthText);
self.healthText.x = 0;
self.healthText.y = -120;
self.update = function () {
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
if (!self.target) {
self.findNearestPlayerTower();
} else {
self.moveToTarget();
}
// Attack nearby player units
self.attackNearbyPlayerUnits();
self.healthText.setText(self.health.toString());
// Update health bar scale based on health percentage
var healthPercentage = self.health / self.maxHealth;
tween(self.healthBar, {
scaleX: 1.5 * healthPercentage
}, {
duration: 200
});
};
self.findNearestPlayerTower = function () {
// Target player tower
if (playerTowers.length > 0) {
self.target = playerTowers[0];
}
};
self.moveToTarget = function () {
if (!self.target) {
return;
}
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 60) {
self.attackTarget();
} else {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.attackTarget = function () {
if (self.attackCooldown <= 0 && self.target) {
self.target.health -= self.damage;
self.attackCooldown = 60;
LK.getSound('attack').play();
if (self.target.health <= 0) {
// Tower destroyed
var index = playerTowers.indexOf(self.target);
if (index > -1) {
playerTowers.splice(index, 1);
self.target.destroy();
LK.getSound('towerDestroyed').play();
self.target = null;
}
}
}
};
self.attackNearbyPlayerUnits = function () {
if (self.attackCooldown <= 0) {
var meleeRange = 60;
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
var dx = unit.x - self.x;
var dy = unit.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= meleeRange) {
unit.health -= self.damage;
self.attackCooldown = 60;
LK.getSound('attack').play();
if (unit.health <= 0) {
playerUnits.splice(i, 1);
unit.destroy();
}
break;
}
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x228B22
});
/****
* Game Code
****/
// Game state management
var gameState = 'menu'; // 'menu' or 'playing'
// Add menu background
var menuBackground = LK.getAsset('menuBg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(menuBackground);
// Create menu overlay
var menuOverlay = new Container();
game.addChild(menuOverlay);
// Add towers asset at the top of the menu
var towersAsset = LK.getAsset('Towers', {
anchorX: 0.5,
anchorY: 0,
scaleX: 4.0,
scaleY: 4.0
});
towersAsset.x = 1024;
towersAsset.y = 50;
menuOverlay.addChild(towersAsset);
// Add app banner at top of menu
var appBanner = LK.getAsset('Appbanner', {
anchorX: 0.5,
anchorY: 0,
x: 1024,
y: 20
});
menuOverlay.addChild(appBanner);
// Start button background
var startButtonBg = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4.0,
scaleY: 1.5
});
startButtonBg.x = 1024;
startButtonBg.y = 1000;
startButtonBg.tint = 0x228b22;
menuOverlay.addChild(startButtonBg);
// Start button image
var startButton = LK.getAsset('StartGameButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4.0,
scaleY: 2.0
});
startButton.x = 1024;
startButton.y = 1000;
menuOverlay.addChild(startButton);
// How to Play button
var howToPlayButton = LK.getAsset('Howtoplay22', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4.0,
scaleY: 4.0
});
howToPlayButton.x = 1024;
howToPlayButton.y = 1615; // 1.3x higher (2100 - 485 = 1615)
menuOverlay.addChild(howToPlayButton);
// Play menu music when game starts
LK.playMusic('Menusong');
// How to Play instructions overlay
var howToPlayOverlay = new Container();
howToPlayOverlay.visible = false;
game.addChild(howToPlayOverlay);
// How to Play background
var howToPlayBg = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 18.0,
scaleY: 24.0
});
howToPlayBg.x = 1024;
howToPlayBg.y = 1366;
howToPlayBg.tint = 0x000000;
howToPlayBg.alpha = 0.9;
howToPlayOverlay.addChild(howToPlayBg);
// How to Play title
var howToPlayTitle = new Text2('HOW TO PLAY', {
size: 100,
fill: '#ffffff'
});
howToPlayTitle.anchor.set(0.5, 0.5);
howToPlayTitle.x = 1024;
howToPlayTitle.y = 400;
howToPlayOverlay.addChild(howToPlayTitle);
// How to Play instructions
var instructionsText = new Text2('NORMAL MODE:\n• Collect gold coins with workers\n• Spend gold to recruit units\n• Press Attack to command units to advance\n• Destroy enemy tower to win\n\nUNIT TYPES & STATS:\n• Worker: 5 gold, 10 HP, 5 damage, collects coins\n• Warrior: 10 gold, 20 HP, 10 damage, melee combat\n• Archer: 15 gold, 30 HP, 20 damage, ranged arrows\n• Wizard: 20 gold, 40 HP, 30 damage, magic attacks\n• Dragon: 25 gold, 50 HP, 40 damage, fire breath\n\nTOWERS:\n• Player Tower: 500 HP, 5 damage per second, attacks nearest enemy\n• AI Tower: 500 HP, 5 damage per second, attacks nearest enemy\n• Attack range: 400 pixels\n\nZOMBIE MODE:\n• Survive endless zombie waves\n• Units automatically attack zombies first\n• Zombies spawn 30 seconds after start\n• Wave 1: 5 zombies (30 HP, 10 damage)\n• Each wave: +2 zombies, +15 HP, +5 damage\n• Defend your tower at all costs!', {
size: 42,
fill: '#ffffff'
});
instructionsText.anchor.set(0.5, 0.5);
instructionsText.x = 1024;
instructionsText.y = 1300;
howToPlayOverlay.addChild(instructionsText);
// Back button
var backButton = new Text2('BACK TO MENU', {
size: 80,
fill: '#ffff00'
});
backButton.anchor.set(0.5, 0.5);
backButton.x = 1024;
backButton.y = 2100;
howToPlayOverlay.addChild(backButton);
// Mode selection overlay
var modeSelectionOverlay = new Container();
modeSelectionOverlay.visible = false;
game.addChild(modeSelectionOverlay);
// Mode selection background
var modeSelectionBg = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 18.0,
scaleY: 24.0
});
modeSelectionBg.x = 1024;
modeSelectionBg.y = 1366;
modeSelectionBg.tint = 0x000000;
modeSelectionBg.alpha = 0.9;
modeSelectionOverlay.addChild(modeSelectionBg);
// Mode selection title
var modeSelectionTitle = new Text2('SELECT MODE', {
size: 100,
fill: '#ffffff'
});
modeSelectionTitle.anchor.set(0.5, 0.5);
modeSelectionTitle.x = 1024;
modeSelectionTitle.y = 600;
modeSelectionOverlay.addChild(modeSelectionTitle);
// Normal mode button
var normalModeButton = LK.getAsset('Normalmode', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
});
normalModeButton.x = 1024;
normalModeButton.y = 1000;
modeSelectionOverlay.addChild(normalModeButton);
// Zombie mode button in mode selection
var zombieModeSelectButton = LK.getAsset('Zombiemode', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
});
zombieModeSelectButton.x = 1024;
zombieModeSelectButton.y = 1400;
modeSelectionOverlay.addChild(zombieModeSelectButton);
// Back to menu button from mode selection
var backToMenuButton = new Text2('BACK TO MENU', {
size: 80,
fill: '#ffff00'
});
backToMenuButton.anchor.set(0.5, 0.5);
backToMenuButton.x = 1024;
backToMenuButton.y = 2000;
modeSelectionOverlay.addChild(backToMenuButton);
// Start button event handler - now shows mode selection
startButton.down = function () {
menuOverlay.visible = false;
modeSelectionOverlay.visible = true;
};
startButtonBg.down = function () {
menuOverlay.visible = false;
modeSelectionOverlay.visible = true;
};
// Normal mode button event handler
normalModeButton.down = function () {
zombieMode = false;
gameState = 'playing';
modeSelectionOverlay.visible = false;
LK.stopMusic(); // Stop menu music when starting game
initializeGame();
};
// Zombie mode button event handler in mode selection
zombieModeSelectButton.down = function () {
zombieMode = true;
gameState = 'playing';
modeSelectionOverlay.visible = false;
LK.stopMusic();
initializeGame();
};
// Back to menu button event handler
backToMenuButton.down = function () {
modeSelectionOverlay.visible = false;
menuOverlay.visible = true;
};
// How to Play button event handler
howToPlayButton.down = function () {
menuOverlay.visible = false;
howToPlayOverlay.visible = true;
};
// Back button event handler
backButton.down = function () {
howToPlayOverlay.visible = false;
menuOverlay.visible = true;
};
// Add game background (initially hidden)
var background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
background.visible = false;
game.addChild(background);
var playerCoins = 0;
var aiCoins = 0;
var playerTowers = [];
var aiTowers = [];
var coins = [];
var playerCoins_array = [];
var aiCoins_array = [];
var playerUnits = [];
var aiUnits = [];
var arrows = [];
var magics = [];
var fires = [];
var coinSpawnTimer = 0;
var aiActionTimer = 0;
var zombies = [];
var zombieSpawnTimer = 0;
var zombieMode = false;
var zombieWaveNumber = 1;
var zombieWaveSpawnTimer = 0;
var zombieWaveInProgress = false;
var zombiesInCurrentWave = 0;
var zombiesToSpawnInWave = 0;
// UI Elements
var coinDisplay = new Text2('Golds: 0', {
size: 80,
fill: '#ffff00'
});
coinDisplay.anchor.set(0.5, 0.5);
coinDisplay.x = 200;
coinDisplay.y = -273.75;
coinDisplay.visible = false;
LK.gui.bottom.addChild(coinDisplay);
// Attack button
var attackButton = new Text2('Attack', {
size: 80,
fill: '#ff0000'
});
attackButton.anchor.set(0.5, 0.5);
attackButton.x = -200;
attackButton.y = -273.75;
attackButton.visible = false;
LK.gui.bottom.addChild(attackButton);
var workerButton = new Text2('5', {
size: 32,
fill: '#ffffff'
});
workerButton.anchor.set(0.5, 0.5);
workerButton.x = -400;
workerButton.y = -45;
var warriorButton = new Text2('10', {
size: 32,
fill: '#ffffff'
});
warriorButton.anchor.set(0.5, 0.5);
warriorButton.x = -200;
warriorButton.y = -45;
var wizardButton = new Text2('20', {
size: 32,
fill: '#ffffff'
});
wizardButton.anchor.set(0.5, 0.5);
wizardButton.x = 200;
wizardButton.y = -45;
var archerButton = new Text2('15', {
size: 32,
fill: '#ffffff'
});
archerButton.anchor.set(0.5, 0.5);
archerButton.x = 0;
archerButton.y = -45;
var dragonButton = new Text2('25', {
size: 32,
fill: '#ffffff'
});
dragonButton.anchor.set(0.5, 0.5);
dragonButton.x = 400;
dragonButton.y = -45;
// Add unified black background behind all recruitment buttons and unit images
var unifiedBackground = LK.getAsset('blackBackground', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 22.0,
scaleY: 2.8
});
unifiedBackground.x = 0;
unifiedBackground.y = -78;
unifiedBackground.tint = 0x000000;
unifiedBackground.alpha = 0.5;
unifiedBackground.visible = false;
LK.gui.bottom.addChild(unifiedBackground);
// Add unit images above recruitment buttons
var workerImage = LK.getAsset('worker', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0
});
workerImage.x = -400;
workerImage.y = -110;
LK.gui.bottom.addChild(workerImage);
var warriorImage = LK.getAsset('warrior', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0
});
warriorImage.x = -200;
warriorImage.y = -110;
LK.gui.bottom.addChild(warriorImage);
var wizardImage = LK.getAsset('wizard', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0
});
wizardImage.x = 200;
wizardImage.y = -110;
LK.gui.bottom.addChild(wizardImage);
var archerImage = LK.getAsset('archer', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0
});
archerImage.x = 0;
archerImage.y = -110;
LK.gui.bottom.addChild(archerImage);
var dragonImage = LK.getAsset('dragon', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0
});
dragonImage.x = 400;
dragonImage.y = -110;
LK.gui.bottom.addChild(dragonImage);
// Wave display for zombie mode
var waveDisplay = new Text2('Wave: 1', {
size: 80,
fill: '#ff0000'
});
waveDisplay.anchor.set(0.5, 0.5);
waveDisplay.x = 0;
waveDisplay.y = 100;
waveDisplay.visible = false;
LK.gui.top.addChild(waveDisplay);
// Hide all UI elements initially (menu state)
workerButton.visible = false;
warriorButton.visible = false;
wizardButton.visible = false;
archerButton.visible = false;
dragonButton.visible = false;
workerImage.visible = false;
warriorImage.visible = false;
wizardImage.visible = false;
archerImage.visible = false;
dragonImage.visible = false;
// Add texts after backgrounds so they appear on top
LK.gui.bottom.addChild(workerButton);
LK.gui.bottom.addChild(warriorButton);
LK.gui.bottom.addChild(wizardButton);
LK.gui.bottom.addChild(archerButton);
LK.gui.bottom.addChild(dragonButton);
// Initialize towers
function initializeTowers() {
// Player tower
var playerTower = new Tower('player');
playerTower.x = 1024;
playerTower.y = 2300;
playerTowers.push(playerTower);
game.addChild(playerTower);
// AI tower (only in normal mode)
if (!zombieMode) {
var aiTower = new Tower('ai');
aiTower.x = 1024;
aiTower.y = 400;
aiTowers.push(aiTower);
game.addChild(aiTower);
}
}
;
function spawnCoin() {
// Spawn player coin
var playerCoin = new Coin();
playerCoin.x = Math.random() * 1800 + 124;
playerCoin.y = Math.random() * 1000 + 1200; // Lower half for player
playerCoins_array.push(playerCoin);
coins.push(playerCoin);
game.addChild(playerCoin);
// Spawn AI coin
var aiCoin = new Coin();
aiCoin.x = Math.random() * 1800 + 124;
aiCoin.y = Math.random() * 1000 + 400; // Upper half for AI
aiCoins_array.push(aiCoin);
coins.push(aiCoin);
game.addChild(aiCoin);
}
function spawnUnit(team, unitType) {
var unit;
var cost = unitType === 'worker' ? 5 : unitType === 'warrior' ? 10 : unitType === 'wizard' ? 20 : unitType === 'archer' ? 15 : 25;
if (team === 'player' && playerCoins >= cost) {
playerCoins -= cost;
if (unitType === 'worker') {
unit = new Worker('player');
} else {
unit = new CombatUnit('player', unitType);
}
if (!zombieMode && unitType !== 'worker') {
// Position combat units in front of tower in normal mode with better spacing
var unitCount = playerUnits.length;
var spreadRadius = 300;
var angle = unitCount * 0.5 % (Math.PI * 2);
unit.x = playerTowers[0].x + Math.cos(angle) * spreadRadius;
unit.y = playerTowers[0].y - 507 + Math.sin(angle) * 100;
} else {
var unitCount = playerUnits.length;
var spreadRadius = 200;
var angle = unitCount * 0.8 % (Math.PI * 2);
unit.x = playerTowers[0].x + Math.cos(angle) * spreadRadius;
unit.y = playerTowers[0].y - 100 + Math.sin(angle) * 50;
}
playerUnits.push(unit);
game.addChild(unit);
LK.getSound('unitSpawn').play();
} else if (team === 'ai' && aiCoins >= cost) {
aiCoins -= cost;
if (unitType === 'worker') {
unit = new Worker('ai');
} else {
unit = new CombatUnit('ai', unitType);
}
var unitCount = aiUnits.length;
var spreadRadius = 200;
var angle = unitCount * 0.8 % (Math.PI * 2);
unit.x = aiTowers[0].x + Math.cos(angle) * spreadRadius;
unit.y = aiTowers[0].y + 100 + Math.sin(angle) * 50;
aiUnits.push(unit);
game.addChild(unit);
}
}
function spawnZombie() {
var zombie = new Zombie(zombieWaveNumber);
zombie.x = Math.random() * 1800 + 124; // Random X position across screen width
zombie.y = 50; // Spawn at top of screen
zombies.push(zombie);
game.addChild(zombie);
}
function startZombieWave() {
zombieWaveInProgress = true;
zombiesToSpawnInWave = 3 + zombieWaveNumber * 2; // Wave 1: 5 zombies, Wave 2: 7 zombies, etc.
zombiesInCurrentWave = 0;
zombieWaveSpawnTimer = 0;
// Update wave display
if (zombieMode) {
waveDisplay.setText('Wave: ' + zombieWaveNumber);
}
}
function checkWaveComplete() {
// Check if all zombies in current wave are spawned and destroyed
if (zombieWaveInProgress && zombiesInCurrentWave >= zombiesToSpawnInWave && zombies.length === 0) {
zombieWaveInProgress = false;
zombieWaveNumber++;
// Start next wave after 10 seconds (doubled from 5 seconds)
zombieWaveSpawnTimer = -600; // 10 seconds delay before next wave
}
}
function aiLogic() {
if (aiActionTimer <= 0) {
if (aiCoins >= 25 && Math.random() < 0.1) {
spawnUnit('ai', 'dragon');
} else if (aiCoins >= 15 && Math.random() < 0.15) {
spawnUnit('ai', 'archer');
} else if (aiCoins >= 20 && Math.random() < 0.2) {
spawnUnit('ai', 'wizard');
} else if (aiCoins >= 10 && Math.random() < 0.3) {
spawnUnit('ai', 'warrior');
} else if (aiCoins >= 5 && Math.random() < 0.4) {
spawnUnit('ai', 'worker');
}
aiActionTimer = 120;
}
aiActionTimer--;
}
function checkGameEnd() {
if (playerTowers.length === 0) {
LK.showGameOver();
} else if (!zombieMode && aiTowers.length === 0) {
LK.showYouWin();
}
}
function cleanupCollectedCoins() {
// Clean up from main coins array
for (var i = coins.length - 1; i >= 0; i--) {
if (coins[i].collected) {
coins[i].destroy();
coins.splice(i, 1);
}
}
// Clean up from player coins array
for (var i = playerCoins_array.length - 1; i >= 0; i--) {
if (playerCoins_array[i].collected) {
playerCoins_array.splice(i, 1);
}
}
// Clean up from AI coins array
for (var i = aiCoins_array.length - 1; i >= 0; i--) {
if (aiCoins_array[i].collected) {
aiCoins_array.splice(i, 1);
}
}
}
// Button event handlers
workerButton.down = function () {
spawnUnit('player', 'worker');
};
warriorButton.down = function () {
spawnUnit('player', 'warrior');
};
wizardButton.down = function () {
spawnUnit('player', 'wizard');
};
archerButton.down = function () {
spawnUnit('player', 'archer');
};
dragonButton.down = function () {
spawnUnit('player', 'dragon');
};
// Character image event handlers
workerImage.down = function () {
spawnUnit('player', 'worker');
};
warriorImage.down = function () {
spawnUnit('player', 'warrior');
};
wizardImage.down = function () {
spawnUnit('player', 'wizard');
};
archerImage.down = function () {
spawnUnit('player', 'archer');
};
dragonImage.down = function () {
spawnUnit('player', 'dragon');
};
// Attack button event handler
attackButton.down = function () {
if (!zombieMode) {
// Make all waiting player units start attacking
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
if (unit.unitType !== 'worker') {
unit.isWaiting = false;
}
}
}
};
// Initialize game function
function initializeGame() {
background.visible = true;
// Show UI elements when game starts
coinDisplay.visible = true;
unifiedBackground.visible = true;
workerButton.visible = true;
warriorButton.visible = true;
wizardButton.visible = true;
archerButton.visible = true;
dragonButton.visible = true;
workerImage.visible = true;
warriorImage.visible = true;
wizardImage.visible = true;
archerImage.visible = true;
dragonImage.visible = true;
// Show attack button only in normal mode
if (!zombieMode) {
attackButton.visible = true;
} else {
// Show wave display in zombie mode
waveDisplay.visible = true;
waveDisplay.setText('Wave: ' + zombieWaveNumber);
}
// Reset zombie wave system
zombieWaveNumber = 1;
zombieWaveSpawnTimer = -1800; // 30 second delay (30 * 60 FPS = 1800 frames)
zombieWaveInProgress = false;
zombiesInCurrentWave = 0;
zombiesToSpawnInWave = 0;
// Play game music when match starts
LK.playMusic('Song');
initializeTowers();
// Spawn initial workers for both teams
var playerWorker = new Worker('player');
playerWorker.x = playerTowers[0].x + 50;
playerWorker.y = playerTowers[0].y - 100;
playerUnits.push(playerWorker);
game.addChild(playerWorker);
// AI worker (only in normal mode)
if (!zombieMode) {
var aiWorker = new Worker('ai');
aiWorker.x = aiTowers[0].x + 50;
aiWorker.y = aiTowers[0].y + 100;
aiUnits.push(aiWorker);
game.addChild(aiWorker);
}
}
// Main game loop
game.update = function () {
if (gameState === 'playing') {
// Update coin display
coinDisplay.setText('Golds: ' + playerCoins);
// Spawn coins
coinSpawnTimer++;
if (coinSpawnTimer >= 36) {
// 5x more frequent (180/5 = 36)
spawnCoin();
coinSpawnTimer = 0;
}
// AI logic (only in normal mode)
if (!zombieMode) {
aiLogic();
}
// Clean up collected coins
cleanupCollectedCoins();
// Zombie mode logic
if (zombieMode) {
zombieWaveSpawnTimer++;
// Start first wave or next wave after delay
if (!zombieWaveInProgress && zombieWaveSpawnTimer >= 0) {
startZombieWave();
}
// Spawn zombies in current wave
if (zombieWaveInProgress && zombiesInCurrentWave < zombiesToSpawnInWave) {
if (zombieWaveSpawnTimer % 60 === 0) {
// Spawn one zombie every second during wave
spawnZombie();
zombiesInCurrentWave++;
}
}
// Check if current wave is complete
checkWaveComplete();
// Clean up off-screen zombies
for (var i = zombies.length - 1; i >= 0; i--) {
if (zombies[i].y > 2800) {
zombies[i].destroy();
zombies.splice(i, 1);
}
}
}
// Check game end conditions
checkGameEnd();
}
};
Worker. In-Game asset. 2d. High contrast. No shadows
Wizard. In-Game asset. 2d. High contrast. No shadows
Dragon. In-Game asset. 2d. High contrast. No shadows
Warrior. In-Game asset. 2d. High contrast. No shadows
etrafı kırmızı işçi. In-Game asset. 2d. High contrast. No shadows
Archer. In-Game asset. 2d. High contrast. No shadows
Magic. In-Game asset. 2d. High contrast. No shadows
Sadece 1 Altın parçacığı. In-Game asset. 2d. High contrast. No shadows
Köşelerdeki taş yerleri kaldır
Çözünürlüğü artır ve kırmızı şeyleri kaldır. In-Game asset. 2d. High contrast. No shadows
Ağzından ateş püskürtsün
Tam yukarıdan bakılan bir kule clash royaledeki gibi. In-Game asset. 2d. High contrast. No shadows
Mavi yerler kırmızı olsun
Sol tarafında kılıçlar olan start buton. In-Game asset. 2d. High contrast. No shadows
How to play button. In-Game asset. 2d. High contrast. No shadows
Tower defence menü background. In-Game asset. 2d. High contrast. No shadows
Towers On Attack yazısı. In-Game asset. 2d. High contrast. No shadows
Zombie. In-Game asset. 2d. High contrast. No shadows
Normal Mode button. In-Game asset. 2d. High contrast. No shadows
Zombie Mode buton. In-Game asset. 2d. High contrast. No shadows