/****
* Plugins
****/
var storage = LK.import("@upit/storage.v1");
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Arrow class for castle arrow shooting
var Arrow = Container.expand(function () {
var self = Container.call(this);
// Increase arrow speed for longer distance
self.speed = 40; // px per frame (was 32)
self.damage = 2 + Math.floor(Math.random() * 3); // 2-4 damage
self.target = null;
self.team = null;
self.destroyed = false;
self.asset = null;
self.lastX = 0;
self.lastY = 0;
// Initialize arrow
self.init = function (x, y, target, damage, team) {
self.x = x;
self.y = y;
self.target = target;
// Arrow damage is randomized 2-4, ignore passed damage param for castle arrows
self.damage = 2 + Math.floor(Math.random() * 3);
self.team = team;
// Remove previous asset if any
if (self.asset) {
self.removeChild(self.asset);
}
// Use a visible arrow using a rectangle shape since centerCircle is removed
self.asset = self.attachAsset('arrow_shape', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 0.3,
tint: team === 'player' ? 0x00aaff : 0xffaa00 // more visible blue/orange
});
};
// Update arrow position and check for hit
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
if (!self.target || self.target.destroyed || self.destroyed) {
self.destroyed = true;
self.destroy();
return;
}
// Move towards target
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 1) dist = 1;
var vx = dx / dist * self.speed;
var vy = dy / dist * self.speed;
self.x += vx;
self.y += vy;
// Rotate arrow to face target
self.asset.rotation = Math.atan2(dy, dx);
// Check for collision with target (simple distance check)
if (dist < 60) {
// Hit!
if (typeof self.target.health === "number") {
self.target.health -= self.damage;
self.target.health = Math.round(self.target.health);
// If the target is a soldier and its health drops to 0 or below, destroy it and remove from array
if (self.target.health <= 0 && typeof self.target.team === "string") {
self.target.destroyed = true;
self.target.destroy();
// Remove from correct array
if (self.target.team === "enemy" && typeof enemySoldiers !== "undefined") {
for (var i = 0; i < enemySoldiers.length; i++) {
if (enemySoldiers[i] === self.target) {
enemySoldiers.splice(i, 1);
break;
}
}
} else if (self.target.team === "player" && typeof playerSoldiers !== "undefined") {
for (var i = 0; i < playerSoldiers.length; i++) {
if (playerSoldiers[i] === self.target) {
playerSoldiers.splice(i, 1);
break;
}
}
}
}
}
self.destroyed = true;
self.destroy();
}
// Remove if out of bounds (double the distance for longer arrows)
if (self.x < -600 || self.x > 3200 || self.y < -600 || self.y > 4000) {
self.destroyed = true;
self.destroy();
}
};
return self;
});
// Castle class for player and enemy castles
var Castle = Container.expand(function () {
var self = Container.call(this);
// Properties
self.team = null;
self.health = CASTLE_HEALTH_INIT;
self.hasUpgrade = false;
self.attack = 0;
self.arrowCooldown = 0;
self.arrowCooldownMax = 60; // frames between shots
self.attackRadius = 500; // px, for arrow shooting
self.asset = null;
// Initialize castle
self.init = function (team, x, y) {
self.team = team;
self.x = x;
self.y = y;
self.health = CASTLE_HEALTH_INIT;
self.hasUpgrade = false;
self.attack = 0;
self.arrowCooldown = 0;
// Remove previous asset if any
if (self.asset) {
self.removeChild(self.asset);
}
// Add correct asset
if (team === 'player') {
var assetId = 'castle_player';
if (self.hasUpgrade2) {
assetId = 'castle_player_3';
} else if (self.hasUpgrade) {
assetId = 'castle_player_2';
}
self.asset = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
} else {
var assetId = 'castle_enemy';
if (self.hasUpgrade2 || self.health >= 2500) {
assetId = 'castle_enemy_3';
} else if (self.hasUpgrade) {
assetId = 'castle_enemy_2';
}
self.asset = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
}
};
// Upgrade castle
self.upgrade = function () {
self.hasUpgrade = true;
self.health = 1500;
self.attack = 1 + Math.floor(Math.random() * 3); // 1-3
self.arrowCooldown = 0;
// Update asset for first upgrade
if (self.asset) {
self.removeChild(self.asset);
}
var assetId = self.team === 'player' ? 'castle_player_2' : 'castle_enemy_2';
self.asset = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Second upgrade handled externally (see gamecode.js)
};
// Update castle (shoot arrows if upgraded)
self.update = function () {
// Always round health to integer for display and logic
if (typeof self.health === "number") {
self.health = Math.round(self.health);
}
// Switch to castle_enemy_3 asset if health reaches 2500 and not already set
if (self.team === 'enemy' && self.health >= 2500 && (!self.asset || self.asset.assetId !== 'castle_enemy_3')) {
if (self.asset) {
self.removeChild(self.asset);
}
self.asset = self.attachAsset('castle_enemy_3', {
anchorX: 0.5,
anchorY: 0.5
});
// Ensure assetId is set for future checks
self.asset.assetId = 'castle_enemy_3';
// Immediately update the castleEnemy reference in game if this is the main enemy castle
if (typeof castleEnemy !== "undefined" && castleEnemy === self) {
castleEnemy.asset = self.asset;
}
}
if (self.hasUpgrade && self.health > 0) {
// Only shoot at enemy soldiers in range
var targets = [];
if (self.team === 'player') {
for (var i = 0; i < enemySoldiers.length; i++) {
var e = enemySoldiers[i];
var dx = e.x - self.x;
var dy = e.y - self.y;
if (dx * dx + dy * dy <= self.attackRadius * self.attackRadius) {
targets.push(e);
}
}
} else {
for (var i = 0; i < playerSoldiers.length; i++) {
var e = playerSoldiers[i];
var dx = e.x - self.x;
var dy = e.y - self.y;
if (dx * dx + dy * dy <= self.attackRadius * self.attackRadius) {
targets.push(e);
}
}
}
// Shoot at first target if cooldown is ready
if (targets.length > 0 && self.arrowCooldown <= 0) {
var target = targets[0];
var arrow = new Arrow();
arrow.init(self.x, self.y, target, self.attack, self.team);
arrows.push(arrow);
game.addChild(arrow);
self.arrowCooldown = self.arrowCooldownMax;
}
if (self.arrowCooldown > 0) self.arrowCooldown--;
}
};
return self;
});
// Soldier class for both player and enemy soldiers
var Soldier = Container.expand(function () {
var self = Container.call(this);
self.team = null;
self.health = 100;
self.attack = 10;
self.speed = SOLDIER_SPEED;
self.inCombat = false;
self.asset = null;
self.lastX = 0;
self.lastY = 0;
// Initialize soldier
self.init = function (team, health, attack) {
self.team = team;
self.health = health;
self.attack = attack;
self.inCombat = false;
// Remove previous asset if any
if (self.asset) {
self.removeChild(self.asset);
}
// Add correct asset
if (team === 'player') {
self.asset = self.attachAsset('soldier_player', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
self.asset = self.attachAsset('soldier_enemy', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Add health text label above the soldier
if (self.healthTxt && typeof self.healthTxt.destroy === "function") {
self.healthTxt.destroy();
}
self.healthTxt = new Text2(Math.round(self.health) + "", {
size: 38,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
self.healthTxt.anchor.set(0.5, 1.2);
self.healthTxt.x = 0;
self.healthTxt.y = -90;
self.addChild(self.healthTxt);
};
// Update soldier position (move forward if not in combat)
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
// Always round health to integer for display and logic
if (typeof self.health === "number") {
self.health = Math.round(self.health);
}
// Update health text label
if (self.healthTxt) {
self.healthTxt.setText(self.health + "");
}
if (!self.inCombat) {
if (self.team === 'player') {
self.x += self.speed;
} else {
self.x -= self.speed;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xBFFF00
});
/****
* Game Code
****/
// --- Add background image ---
// Ottoman-inspired church music with kanun, tambourine, zurna melodies
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
var background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: GAME_W,
height: GAME_H
});
game.addChild(background);
// Play Ottoman-inspired church music with kanun, tambourine, zurna melodies
LK.playMusic('ottoman_church_bg');
// --- Game constants ---
// Player castle (blue)
// Enemy castle (red)
// Player soldier (green)
// --- Game constants ---
var GAME_W = 2048,
GAME_H = 2732;
var CASTLE_OFFSET_X = 120,
CASTLE_OFFSET_Y = GAME_H - 520; // moved up by 200px
var CASTLE_HEALTH_INIT = 1000;
var GOLD_INIT = 200;
var GOLD_PER_TICK = 5;
// After enemy 800 gold upgrade, this will be increased for mine-like feature
var GOLD_TICK_MS = 200;
var SOLDIER_COST = 100;
var SOLDIER_HEALTH_MIN = 80,
SOLDIER_HEALTH_MAX = 160;
var SOLDIER_ATTACK_MIN = 20,
SOLDIER_ATTACK_MAX = 50;
var SOLDIER_SPEED = 14 / 5 * 0.1 * 4.0; // px per frame (now 100% faster than previous)
// --- Speed mode state ---
var speedMode = false;
// --- Speed mode toggle button (top right, not in topLeft 100x100) ---
var speedBtn = new Text2("SPEED x1", {
size: 60,
fill: "#fff",
font: "PressStart2P,Pixel,monospace"
});
speedBtn.anchor.set(1, 0);
speedBtn.x = LK.gui.width - 40;
speedBtn.y = 40;
LK.gui.top.addChild(speedBtn);
speedBtn.down = function (x, y, obj) {
speedMode = !speedMode;
speedBtn.setText(speedMode ? "SPEED x3" : "SPEED x1");
};
// --- Difficulty selection ---
var difficulty = null;
var ENEMY_SOLDIER_COST = 100; // default, will be set by difficulty
// Show difficulty selection overlay
var diffOverlay = new Container();
diffOverlay.zIndex = 10000; // ensure on top
diffOverlay.width = GAME_W;
diffOverlay.height = GAME_H;
// Use a Text2 as a fake overlay background (solid block) since centerCircle is removed
var overlayBg = new Text2(" ", {
size: 10,
fill: 0x000000
});
overlayBg.width = GAME_W;
overlayBg.height = GAME_H;
overlayBg.alpha = 0.7;
overlayBg.x = 0;
overlayBg.y = 0;
diffOverlay.addChild(overlayBg);
// Difficulty title with black background
var diffTitleBg = new Text2(" ", {
size: 10,
fill: 0x000000
});
diffTitleBg.width = 900;
diffTitleBg.height = 160;
diffTitleBg.alpha = 0.95;
diffTitleBg.anchor.set(0.5, 0.5);
diffTitleBg.x = GAME_W / 2;
diffTitleBg.y = GAME_H / 2 - 300;
diffOverlay.addChild(diffTitleBg);
var diffTitle = new Text2("Select Difficulty", {
size: 120,
fill: "#fff",
font: "PressStart2P,Pixel,monospace"
});
diffTitle.anchor.set(0.5, 0.5);
diffTitle.x = GAME_W / 2;
diffTitle.y = GAME_H / 2 - 300;
diffOverlay.addChild(diffTitle);
// Easy button with black background
var btnEasyBg = new Text2(" ", {
size: 10,
fill: 0x000000
});
btnEasyBg.width = 600;
btnEasyBg.height = 120;
btnEasyBg.alpha = 0.95;
btnEasyBg.anchor.set(0.5, 0.5);
btnEasyBg.x = GAME_W / 2;
btnEasyBg.y = GAME_H / 2 - 80;
diffOverlay.addChild(btnEasyBg);
var btnEasy = new Text2("Easy", {
size: 100,
fill: 0x00FF00,
font: "PressStart2P,Pixel,monospace"
});
btnEasy.anchor.set(0.5, 0.5);
btnEasy.x = GAME_W / 2;
btnEasy.y = GAME_H / 2 - 80;
diffOverlay.addChild(btnEasy);
// Normal button with black background
var btnNormalBg = new Text2(" ", {
size: 10,
fill: 0x000000
});
btnNormalBg.width = 600;
btnNormalBg.height = 120;
btnNormalBg.alpha = 0.95;
btnNormalBg.anchor.set(0.5, 0.5);
btnNormalBg.x = GAME_W / 2;
btnNormalBg.y = GAME_H / 2 + 80;
diffOverlay.addChild(btnNormalBg);
var btnNormal = new Text2("Normal", {
size: 100,
fill: 0xFFFF00,
font: "PressStart2P,Pixel,monospace"
});
btnNormal.anchor.set(0.5, 0.5);
btnNormal.x = GAME_W / 2;
btnNormal.y = GAME_H / 2 + 80;
diffOverlay.addChild(btnNormal);
// Hard button with black background
var btnHardBg = new Text2(" ", {
size: 10,
fill: 0x000000
});
btnHardBg.width = 600;
btnHardBg.height = 120;
btnHardBg.alpha = 0.95;
btnHardBg.anchor.set(0.5, 0.5);
btnHardBg.x = GAME_W / 2;
btnHardBg.y = GAME_H / 2 + 240;
diffOverlay.addChild(btnHardBg);
var btnHard = new Text2("Hard", {
size: 100,
fill: 0xFF0000,
font: "PressStart2P,Pixel,monospace"
});
btnHard.anchor.set(0.5, 0.5);
btnHard.x = GAME_W / 2;
btnHard.y = GAME_H / 2 + 240;
diffOverlay.addChild(btnHard);
// Speed Mode button (below hard)
var btnSpeedBg = new Text2(" ", {
size: 10,
fill: 0x000000
});
btnSpeedBg.width = 600;
btnSpeedBg.height = 120;
btnSpeedBg.alpha = 0.95;
btnSpeedBg.anchor.set(0.5, 0.5);
btnSpeedBg.x = GAME_W / 2;
btnSpeedBg.y = GAME_H / 2 + 400;
diffOverlay.addChild(btnSpeedBg);
var btnSpeed = new Text2("Speed Mode: OFF", {
size: 80,
fill: 0x00E0FF,
font: "PressStart2P,Pixel,monospace"
});
btnSpeed.anchor.set(0.5, 0.5);
btnSpeed.x = GAME_W / 2;
btnSpeed.y = GAME_H / 2 + 400;
diffOverlay.addChild(btnSpeed);
var speedModeMenuSelected = false;
btnSpeed.down = function (x, y, obj) {
speedModeMenuSelected = !speedModeMenuSelected;
btnSpeed.setText(speedModeMenuSelected ? "Speed Mode: ON" : "Speed Mode: OFF");
};
game.addChild(diffOverlay);
// Disable game input until difficulty is chosen
var gameInputEnabled = false;
// Helper to start game with selected difficulty
function selectDifficulty(level) {
difficulty = level;
if (difficulty === "easy") {
ENEMY_SOLDIER_COST = SOLDIER_COST; // same as player
} else if (difficulty === "normal") {
ENEMY_SOLDIER_COST = Math.floor(SOLDIER_COST * 0.9); // 0.9 of player
} else if (difficulty === "hard") {
ENEMY_SOLDIER_COST = Math.floor(SOLDIER_COST * 0.7); // 0.7 of player
}
// Enable speed mode if selected in menu
if (typeof speedModeMenuSelected !== "undefined" && speedModeMenuSelected) {
speedMode = true;
if (typeof speedBtn !== "undefined") speedBtn.setText("SPEED x3");
} else {
speedMode = false;
if (typeof speedBtn !== "undefined") speedBtn.setText("SPEED x1");
}
// Set enemy deploy interval based on difficulty
if (typeof enemyDeployTimer !== "undefined") {
LK.clearInterval(enemyDeployTimer);
}
var enemyDeployInterval = 600; // default
if (difficulty === "easy") {
enemyDeployInterval = 900;
} else if (difficulty === "normal") {
enemyDeployInterval = 700;
} else if (difficulty === "hard") {
enemyDeployInterval = 500;
}
enemyDeployTimer = LK.setInterval(function () {
if (!gameInputEnabled) return;
// --- Initial Phase: 0-30 seconds ---
if (LK.ticks < 1800) {
// Only send soldiers, no upgrades, no archers.
// If player sends soldiers, let enemy build walls.
// If more than 3 enemy soldiers alive, let them build walls.
// (Blowing up walls is allowed, but not archers or upgrades.)
// Only send soldiers
if (enemyGold >= ENEMY_SOLDIER_COST) {
if (deploySoldier('enemy')) {
updateGui();
enemySoldierDeployCount++;
enemyTotalSoldierDeployed++;
enemySoldierSinceLastWall++;
}
}
// If player has sent soldiers (playerSoldiers.length > 0) or more than 3 enemy soldiers alive, build wall if possible
if ((playerSoldiers.length > 0 || enemySoldiers.length > 3) && enemyGold >= 50 && wallEnemies.length === 0) {
deployWallEnemy();
}
return;
}
// --- Middle Phase: 30-60 seconds ---
if (LK.ticks >= 1800 && LK.ticks < 3600) {
// Soldier + Archer + Wall alternately, each as rolling 1-3 dice and send that many
// Do not send archers without doing the 1st upgrade
// 50% chance to upgrade if enough gold and not immediately
if (typeof enemyMiddlePhase === "undefined") {
enemyMiddlePhase = {
step: 0,
upgradeTried: false
};
}
// 0: soldier, 1: archer, 2: wall, repeat
var phaseType = enemyMiddlePhase.step % 3;
// Roll 1-3 dice for how many to send
var toSend = 1 + Math.floor(Math.random() * 3);
if (phaseType === 0) {
// Soldier
for (var i = 0; i < toSend; i++) {
if (enemyGold >= ENEMY_SOLDIER_COST) {
if (deploySoldier('enemy')) {
updateGui();
enemyTotalSoldierDeployed++;
enemySoldierSinceLastWall++;
}
}
}
} else if (phaseType === 1) {
// Archer
if (castleEnemy.hasUpgrade) {
for (var i = 0; i < toSend; i++) {
if (enemyGold >= 150) {
enemyGold -= 150;
var s = new Soldier();
var baseSoldierHealth = 25;
s.init('enemy', baseSoldierHealth * 2, 5 + Math.floor(Math.random() * 6));
s.speed = SOLDIER_SPEED * 0.8;
s.inCombat = false;
if (s.asset) {
s.removeChild(s.asset);
}
s.asset = s.attachAsset('archer_enemy', {
anchorX: 0.5,
anchorY: 0.5
});
s.y = CASTLE_OFFSET_Y + 120;
s.x = castleEnemy.x - 120;
enemySoldiers.push(s);
game.addChild(s);
LK.getSound('unit_leave_castle').play();
updateGui();
}
}
}
// If not upgraded, skip archer phase (do nothing)
} else if (phaseType === 2) {
// Wall
for (var i = 0; i < toSend; i++) {
if (enemyGold >= 50 && wallEnemies.length === 0) {
deployWallEnemy();
}
}
}
enemyMiddlePhase.step++;
// 50% chance to upgrade if enough gold and not upgraded yet, but not immediately
if (!castleEnemy.hasUpgrade && !enemyMiddlePhase.upgradeTried && enemyGold >= 350) {
if (Math.random() < 0.5) {
enemyGold -= 350;
castleEnemy.upgrade();
updateGui();
}
enemyMiddlePhase.upgradeTried = true;
}
// Second upgrade logic (after player upgrades or 10 units lost)
if (castleEnemy.hasUpgrade && !castleEnemy.hasUpgrade2 && !castleEnemy._upgrade2Started && (castlePlayer.hasUpgrade || enemyUnitsRemoved >= 10)) {
castleEnemy._upgrade2Started = true;
enemyGold -= 800;
castleEnemy.hasUpgrade2 = true;
castleEnemy.health = 2500;
castleEnemy.attack = 4 + Math.floor(Math.random() * 3); // 4-6
castleEnemy.arrowCooldownMax = 40;
GOLD_PER_TICK = GOLD_PER_TICK + 5;
updateGui();
return;
}
return;
}
// --- Advanced Phase: 60s+ ---
// Upgradeable, soldier + archer + wall alternately, dice-based, archers only after upgrade
if (typeof enemyAdvancedPhase === "undefined") {
enemyAdvancedPhase = {
step: 0
};
}
var advType = enemyAdvancedPhase.step % 3;
var advToSend = 1 + Math.floor(Math.random() * 3);
if (advType === 0) {
// Soldier
for (var i = 0; i < advToSend; i++) {
if (enemyGold >= ENEMY_SOLDIER_COST) {
if (deploySoldier('enemy')) {
updateGui();
enemyTotalSoldierDeployed++;
enemySoldierSinceLastWall++;
}
}
}
} else if (advType === 1) {
// Archer
if (castleEnemy.hasUpgrade) {
for (var i = 0; i < advToSend; i++) {
if (enemyGold >= 150) {
enemyGold -= 150;
var s = new Soldier();
var baseSoldierHealth = 25;
s.init('enemy', baseSoldierHealth * 2, 5 + Math.floor(Math.random() * 6));
s.speed = SOLDIER_SPEED * 0.8;
s.inCombat = false;
if (s.asset) {
s.removeChild(s.asset);
}
s.asset = s.attachAsset('archer_enemy', {
anchorX: 0.5,
anchorY: 0.5
});
s.y = CASTLE_OFFSET_Y + 120;
s.x = castleEnemy.x - 120;
enemySoldiers.push(s);
game.addChild(s);
LK.getSound('unit_leave_castle').play();
updateGui();
}
}
}
// If not upgraded, skip archer phase (do nothing)
} else if (advType === 2) {
// Wall
for (var i = 0; i < advToSend; i++) {
if (enemyGold >= 50 && wallEnemies.length === 0) {
deployWallEnemy();
}
}
}
enemyAdvancedPhase.step++;
// Allow upgrades at any time if enough gold and not upgraded
if (!castleEnemy.hasUpgrade && enemyGold >= 350) {
enemyGold -= 350;
castleEnemy.upgrade();
updateGui();
}
// Second upgrade logic (after player upgrades or 10 units lost)
if (castleEnemy.hasUpgrade && !castleEnemy.hasUpgrade2 && !castleEnemy._upgrade2Started && (castlePlayer.hasUpgrade || enemyUnitsRemoved >= 10)) {
castleEnemy._upgrade2Started = true;
enemyGold -= 800;
castleEnemy.hasUpgrade2 = true;
castleEnemy.health = 2500;
castleEnemy.attack = 4 + Math.floor(Math.random() * 3); // 4-6
castleEnemy.arrowCooldownMax = 40;
GOLD_PER_TICK = GOLD_PER_TICK + 5;
updateGui();
return;
}
}, enemyDeployInterval);
gameInputEnabled = true;
diffOverlay.destroy();
// Show solder button in the middle section (archerBtn will appear after upgrade)
archerBtn.visible = false;
solderBtn.visible = true;
solderBtnLabel.visible = true;
wallBtn.visible = true;
wallBtnLabel.visible = true;
}
// Add touch/click handlers for buttons
btnEasy.down = function (x, y, obj) {
selectDifficulty("easy");
};
btnNormal.down = function (x, y, obj) {
selectDifficulty("normal");
};
btnHard.down = function (x, y, obj) {
selectDifficulty("hard");
};
// --- State variables ---
// (removed, handled by castlePlayer.health and castleEnemy.health)
var playerGold = GOLD_INIT;
var enemyGold = GOLD_INIT;
var playerSoldiers = [];
var enemySoldiers = [];
// --- Custom score tracking variables ---
var scoreStats = {
solderEnemyKills: 0,
archerEnemyKills: 0,
wallEnemyDestroyed: 0,
solderPlayerDeaths: 0,
archerPlayerDeaths: 0,
wallPlayerDestroyed: 0,
castlePlayerFullHealth: 0,
// 1 if full health at win
castlePlayerHealthLost: 0,
// amount lost
castlePlayerNoDamageGiven: 0 // 1 if no damage given to enemy
};
// --- Game over state variables ---
var gameOverState = null; // 'win' or 'lose'
var gameOverOverlay = null;
var gameOverTimer = null;
var pointsCalculated = false;
var totalPoints = 0;
// --- Castles ---
var castlePlayer = new Castle();
castlePlayer.init('player', CASTLE_OFFSET_X, CASTLE_OFFSET_Y);
game.addChild(castlePlayer);
var castleEnemy = new Castle();
castleEnemy.init('enemy', GAME_W - CASTLE_OFFSET_X, CASTLE_OFFSET_Y);
game.addChild(castleEnemy);
// --- Enemy castle health display above castle ---
var castleEnemyHealthTxt = new Text2(castleEnemy.health + '', {
size: 60,
fill: '#fff',
font: "PressStart2P,Pixel,monospace"
});
castleEnemyHealthTxt.anchor.set(0.5, 1.2);
castleEnemyHealthTxt.x = castleEnemy.x;
castleEnemyHealthTxt.y = castleEnemy.y - 260; // above the castle
game.addChild(castleEnemyHealthTxt);
// --- Player castle unit cost labels above castle ---
// --- Player castle upgrade cost label under player castle ---
var playerCastleUpgradeTxt = new Text2("YOU NEED 350 GOLD\nTO UPGRADE.", {
size: 54,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
playerCastleUpgradeTxt.anchor.set(0.5, -0.2); // anchor above the text baseline
playerCastleUpgradeTxt.x = castlePlayer.x + 180;
playerCastleUpgradeTxt.y = castlePlayer.y + 320; // under the castle
game.addChild(playerCastleUpgradeTxt);
// --- Player castle second upgrade cost label (800 gold, only after first upgrade) ---
var playerCastleUpgrade2Txt = new Text2("YOU NEED 800 GOLD\nTO UPGRADE AGAIN.", {
size: 54,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
playerCastleUpgrade2Txt.anchor.set(0.5, -0.2);
playerCastleUpgrade2Txt.x = castlePlayer.x + 180;
playerCastleUpgrade2Txt.y = castlePlayer.y + 320;
playerCastleUpgrade2Txt.visible = false;
game.addChild(playerCastleUpgrade2Txt);
// --- Track second upgrade state for both castles ---
castlePlayer.hasUpgrade2 = false;
castleEnemy.hasUpgrade2 = false; // After 800 gold upgrade, mine-like feature is enabled for enemy
// --- Left side: archer_button and solder_button side by side, visible on screen ---
var leftButtonsY = GAME_H / 2 + 100;
var leftButtonSpacing = 40;
var leftButtonStartX = 120 + 75; // 120px margin + half button width (150/2)
// Archer button (leftmost)
var archerBtn = LK.getAsset('archer_button', {
anchorX: 0.5,
anchorY: 0.5,
x: leftButtonStartX,
y: leftButtonsY
});
archerBtn.visible = false;
game.addChild(archerBtn);
// Add "150 G" label under archerBtn, but only show after castle is upgraded
var archerBtnLabel = new Text2("150 G", {
size: 48,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
archerBtnLabel.anchor.set(0.5, 0);
archerBtnLabel.x = archerBtn.x;
archerBtnLabel.y = archerBtn.y + 90; // 90px below center of button (button is 150px tall)
archerBtnLabel.visible = false;
game.addChild(archerBtnLabel);
// Solder button (right of archer)
var solderBtn = LK.getAsset('solder_button', {
anchorX: 0.5,
anchorY: 0.5,
x: leftButtonStartX + 150 + leftButtonSpacing,
// 150 is button width
y: leftButtonsY
});
solderBtn.visible = false;
game.addChild(solderBtn);
// Add "100 G" label under solderBtn
var solderBtnLabel = new Text2("100 G", {
size: 48,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
solderBtnLabel.anchor.set(0.5, 0);
solderBtnLabel.x = solderBtn.x;
solderBtnLabel.y = solderBtn.y + 90; // 90px below center of button (button is 150px tall)
solderBtnLabel.visible = false;
game.addChild(solderBtnLabel);
// Wall button (right of solderBtn)
var wallBtn = LK.getAsset('wall_button', {
anchorX: 0.5,
anchorY: 0.5,
x: solderBtn.x + 150 + leftButtonSpacing,
y: leftButtonsY
});
wallBtn.visible = false;
game.addChild(wallBtn);
// Add "50 G" label under wallBtn
var wallBtnLabel = new Text2("50 G", {
size: 48,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
wallBtnLabel.anchor.set(0.5, 0);
wallBtnLabel.x = wallBtn.x;
wallBtnLabel.y = wallBtn.y + 90;
wallBtnLabel.visible = false;
game.addChild(wallBtnLabel);
// --- Healing Tent Button and 250G label (appear after 850 gold upgrade) ---
var healingTentBtn = LK.getAsset('HEALING_TENT_KEY_PLAYER', {
anchorX: 0.5,
anchorY: 0.5,
x: wallBtn.x + 150 + leftButtonSpacing,
y: leftButtonsY
});
healingTentBtn.visible = false;
game.addChild(healingTentBtn);
var healingTentLabel = new Text2("250 G", {
size: 48,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
healingTentLabel.anchor.set(0.5, 0);
healingTentLabel.x = healingTentBtn.x;
healingTentLabel.y = healingTentBtn.y + 90;
healingTentLabel.visible = false;
game.addChild(healingTentLabel);
// --- Healing tent state ---
var healingTentPlayer = null;
var healingTentTimer = null;
var healingTentCountdownTxt = null;
// --- Enemy healing tent state ---
var healingTentEnemy = null;
var healingTentEnemyTimer = null;
var healingTentEnemyCountdownTxt = null;
var healingTentEnemyActive = false;
var healingTentEnemyNextReady = false;
var healingTentEnemyCooldownTimer = null;
var healingTentEnemyCountdown = 0;
var healingTentEnemyBoostActive = false;
var healingTentEnemyLastTick = 0;
// --- Healing tent purchase and placement ---
healingTentBtn.down = function (x, y, obj) {
if (!gameInputEnabled) return;
if (playerGold < 250) {
LK.getSound('dont-click').play();
return;
}
if (healingTentPlayer) return; // Only one tent at a time
playerGold -= 250;
updateGui();
// Find position between player castle and wall (or default if no wall)
var tentX;
var tentY = CASTLE_OFFSET_Y + 120 + 120; // Move tent further down (120px)
if (wallPlayers.length > 0) {
// Place between castle and first wall
var wall = wallPlayers[0];
tentX = (castlePlayer.x + wall.x) / 2;
} else {
// Place at 1/3 between castle and enemy castle
tentX = castlePlayer.x + (castleEnemy.x - castlePlayer.x) / 3;
}
healingTentPlayer = LK.getAsset('HEALING_TENT_PLAYER', {
anchorX: 0.5,
anchorY: 0.5,
x: tentX,
y: tentY
});
// Add health text label above healing tent (use 30 as "health" for tent, or just show "30" for 30s)
if (healingTentPlayer.healthTxt && typeof healingTentPlayer.healthTxt.destroy === "function") {
healingTentPlayer.healthTxt.destroy();
}
healingTentPlayer.healthTxt = new Text2("30", {
size: 38,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
healingTentPlayer.healthTxt.anchor.set(0.5, 1.2);
healingTentPlayer.healthTxt.x = 0;
healingTentPlayer.healthTxt.y = -170;
healingTentPlayer.addChild(healingTentPlayer.healthTxt);
game.addChild(healingTentPlayer);
// Ensure tent is in front of all other units by re-adding as last child
if (typeof healingTentPlayer !== "undefined" && _typeof(healingTentPlayer.parent) === "object") {
healingTentPlayer.parent.removeChild(healingTentPlayer);
game.addChild(healingTentPlayer);
}
// Remove previous countdown if any
if (healingTentCountdownTxt && typeof healingTentCountdownTxt.destroy === "function") {
healingTentCountdownTxt.destroy();
}
// Create countdown text below tent
healingTentCountdownTxt = new Text2("30", {
size: 60,
fill: 0xFF0000,
font: "PressStart2P,Pixel,monospace"
});
healingTentCountdownTxt.anchor.set(0.5, 0);
healingTentCountdownTxt.x = tentX;
healingTentCountdownTxt.y = tentY + 170; // below tent (tent is 300px tall)
game.addChild(healingTentCountdownTxt);
// Remove previous timer if any
if (healingTentTimer) {
LK.clearInterval(healingTentTimer);
healingTentTimer = null;
}
var tentCountdown = 30;
healingTentCountdownTxt.setText(tentCountdown + "");
healingTentTimer = LK.setInterval(function () {
tentCountdown--;
if (tentCountdown >= 0) {
healingTentCountdownTxt.setText(tentCountdown + "");
if (healingTentPlayer && healingTentPlayer.healthTxt) {
healingTentPlayer.healthTxt.setText(tentCountdown + "");
}
}
if (tentCountdown <= 0) {
// Remove tent and countdown
if (healingTentPlayer && typeof healingTentPlayer.destroy === "function") {
if (healingTentPlayer.healthTxt && typeof healingTentPlayer.healthTxt.destroy === "function") {
healingTentPlayer.healthTxt.destroy();
}
healingTentPlayer.destroy();
}
healingTentPlayer = null;
if (healingTentCountdownTxt && typeof healingTentCountdownTxt.destroy === "function") {
healingTentCountdownTxt.destroy();
}
healingTentCountdownTxt = null;
if (healingTentTimer) {
LK.clearInterval(healingTentTimer);
healingTentTimer = null;
}
}
}, 1000);
};
// --- Walls array to track all wall_player objects ---
var wallPlayers = [];
// --- Walls array to track all wall_enemy objects ---
var wallEnemies = [];
// Deduct 50 gold from player when wallBtn is pressed
wallBtn.down = function (x, y, obj) {
if (!gameInputEnabled) return;
if (playerGold >= 50) {
playerGold -= 50;
updateGui();
// Place wall_player in the middle of the two castles
var wallX = (castlePlayer.x + castleEnemy.x) / 2;
var wallY = CASTLE_OFFSET_Y + 120; // align with soldiers' path
var wallPlayer = LK.getAsset('wall_player', {
anchorX: 0.5,
anchorY: 0.5,
x: wallX,
y: wallY
});
// Wall health: 400 base, +200 per castlePlayer upgrade
var wallBaseHealth = 400 + (castlePlayer.hasUpgrade ? 200 : 0) + (castlePlayer.hasUpgrade2 ? 200 : 0);
wallPlayer.health = wallBaseHealth;
wallPlayer.maxHealth = wallBaseHealth;
wallPlayer.destroyed = false;
// Add health text label above wall
if (wallPlayer.healthTxt && typeof wallPlayer.healthTxt.destroy === "function") {
wallPlayer.healthTxt.destroy();
}
wallPlayer.healthTxt = new Text2(Math.round(wallPlayer.health) + "", {
size: 38,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
wallPlayer.healthTxt.anchor.set(0.5, 1.2);
wallPlayer.healthTxt.x = 0;
wallPlayer.healthTxt.y = -140;
wallPlayer.addChild(wallPlayer.healthTxt);
game.addChild(wallPlayer);
wallPlayers.push(wallPlayer);
} else {
LK.getSound('dont-click').play();
}
};
// --- Enemy AI: Deploy wall_enemy if enough gold and no wall exists ---
// Track total wall_enemy deployed since game start for enemy 2nd upgrade
var enemyWallEnemyDeployCount = 0;
function deployWallEnemy() {
// Only one wall_enemy at a time
if (wallEnemies.length > 0) return false;
if (enemyGold < 50) return false;
enemyGold -= 50;
updateGui();
var wallX = (castlePlayer.x + castleEnemy.x) / 2;
var wallY = CASTLE_OFFSET_Y + 120;
var wallEnemy = LK.getAsset('wall_enemy', {
anchorX: 0.5,
anchorY: 0.5,
x: wallX,
y: wallY
});
// Wall health: 400 base, +200 per castleEnemy upgrade
var wallBaseHealth = 400 + (castleEnemy.hasUpgrade ? 200 : 0) + (castleEnemy.hasUpgrade2 ? 200 : 0);
wallEnemy.health = wallBaseHealth;
wallEnemy.maxHealth = wallBaseHealth;
wallEnemy.destroyed = false;
// Add health text label above wall_enemy
if (wallEnemy.healthTxt && typeof wallEnemy.healthTxt.destroy === "function") {
wallEnemy.healthTxt.destroy();
}
wallEnemy.healthTxt = new Text2(Math.round(wallEnemy.health) + "", {
size: 38,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
wallEnemy.healthTxt.anchor.set(0.5, 1.2);
wallEnemy.healthTxt.x = 0;
wallEnemy.healthTxt.y = -140;
wallEnemy.addChild(wallEnemy.healthTxt);
game.addChild(wallEnemy);
wallEnemies.push(wallEnemy);
// Increment wall_enemy deploy count for enemy
enemyWallEnemyDeployCount++;
// If enemy has not upgraded, and has not yet started upgrade, and wall_enemy count >= 0, do first upgrade automatically (even if gold goes negative)
if (!castleEnemy.hasUpgrade && enemyWallEnemyDeployCount >= 0 && !castleEnemy._upgradeStarted) {
castleEnemy._upgradeStarted = true;
enemyGold -= 350;
castleEnemy.upgrade();
updateGui();
}
// If enemy has first upgrade, not yet second, and deployed 15 wall_enemy, do 2nd upgrade automatically (even if gold goes negative)
if (castleEnemy.hasUpgrade && !castleEnemy.hasUpgrade2 && enemyWallEnemyDeployCount >= 15 && !castleEnemy._upgrade2Started) {
castleEnemy._upgrade2Started = true;
enemyGold -= 800;
castleEnemy.hasUpgrade2 = true;
// Apply second upgrade effects (same as player)
castleEnemy.health = 2500;
castleEnemy.attack = 4 + Math.floor(Math.random() * 3); // 4-6
castleEnemy.arrowCooldownMax = 40; // faster arrows
// --- MINE-LIKE FEATURE: Increase enemy gold income after 2nd upgrade ---
GOLD_PER_TICK = GOLD_PER_TICK + 5; // Increase gold income for both, or you can use a separate enemyGoldPerTick if needed
updateGui();
}
return true;
}
// --- GUI: Health and Gold displays ---
var playerHealthTxt = new Text2('1000', {
size: 70,
fill: '#fff',
font: "PressStart2P,Pixel,monospace"
});
playerHealthTxt.anchor.set(0, 0);
LK.gui.top.addChild(playerHealthTxt);
// Player LIFE label
var playerLifeLabel = new Text2('LIFE', {
size: 38,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
playerLifeLabel.anchor.set(0, 0);
LK.gui.top.addChild(playerLifeLabel);
var playerGoldTxt = new Text2('200', {
size: 60,
fill: '#ffe600',
font: "PressStart2P,Pixel,monospace"
});
playerGoldTxt.anchor.set(0, 0);
LK.gui.top.addChild(playerGoldTxt);
// Add soldier/cavalry cost labels to the left of the tan score
// Removed soldier/cavalry cost labels from GUI
// Player GOLD label
var playerGoldLabel = new Text2('GOLD', {
size: 38,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
playerGoldLabel.anchor.set(0, 0);
LK.gui.top.addChild(playerGoldLabel);
var enemyHealthTxt = new Text2('1000', {
size: 70,
fill: '#fff',
font: "PressStart2P,Pixel,monospace"
});
enemyHealthTxt.anchor.set(1, 0);
LK.gui.top.addChild(enemyHealthTxt);
// Enemy LIFE label
var enemyLifeLabel = new Text2('LIFE', {
size: 38,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
enemyLifeLabel.anchor.set(1, 0);
LK.gui.top.addChild(enemyLifeLabel);
var enemyGoldTxt = new Text2('200', {
size: 60,
fill: '#ffe600',
font: "PressStart2P,Pixel,monospace"
});
enemyGoldTxt.anchor.set(1, 0);
LK.gui.top.addChild(enemyGoldTxt);
// Enemy GOLD label
var enemyGoldLabel = new Text2('GOLD', {
size: 38,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
enemyGoldLabel.anchor.set(1, 0);
LK.gui.top.addChild(enemyGoldLabel);
// --- Win Counter (Rounds Won) ---
// Use storage plugin for persistence
var roundsWon = storage.roundsWon || 0;
var roundsWonTxt = new Text2('Rounds Won: ' + roundsWon, {
size: 60,
fill: '#00ff00',
font: "PressStart2P,Pixel,monospace"
});
roundsWonTxt.anchor.set(1, 0);
LK.gui.top.addChild(roundsWonTxt);
roundsWonTxt.x = LK.gui.width - 40;
roundsWonTxt.y = 220;
// Position GUI elements
playerHealthTxt.x = 120;
playerHealthTxt.y = 40;
playerLifeLabel.x = playerHealthTxt.x + playerHealthTxt.width + 18;
playerLifeLabel.y = playerHealthTxt.y + 10;
playerGoldTxt.x = 120;
playerGoldTxt.y = 120;
playerGoldLabel.x = playerGoldTxt.x + playerGoldTxt.width + 18;
playerGoldLabel.y = playerGoldTxt.y + 8;
// (Removed: Position soldier/cavalry cost labels to the left of the tan score)
enemyHealthTxt.x = LK.gui.width - 120;
enemyHealthTxt.y = 40;
enemyLifeLabel.x = enemyHealthTxt.x - enemyHealthTxt.width - 18;
enemyLifeLabel.y = enemyHealthTxt.y + 10;
enemyGoldTxt.x = LK.gui.width - 120;
enemyGoldTxt.y = 120;
enemyGoldLabel.x = enemyGoldTxt.x - enemyGoldTxt.width - 18;
enemyGoldLabel.y = enemyGoldTxt.y + 8;
// --- Gold income timer ---
var goldTimer = LK.setInterval(function () {
// Only increase gold if difficulty has been chosen
if (!gameInputEnabled) return;
if (speedMode) {
playerGold += GOLD_PER_TICK * 3;
enemyGold += GOLD_PER_TICK * 3;
} else {
playerGold += GOLD_PER_TICK;
enemyGold += GOLD_PER_TICK;
}
updateGui();
}, GOLD_TICK_MS);
// --- GUI update function ---
function updateGui() {
playerHealthTxt.setText(castlePlayer.health);
playerLifeLabel.x = playerHealthTxt.x + playerHealthTxt.width + 18;
playerLifeLabel.y = playerHealthTxt.y + 10;
playerGoldTxt.setText(playerGold);
playerGoldLabel.x = playerGoldTxt.x + playerGoldTxt.width + 18;
playerGoldLabel.y = playerGoldTxt.y + 8;
enemyHealthTxt.setText(castleEnemy.health);
enemyLifeLabel.x = enemyHealthTxt.x - enemyHealthTxt.width - 18;
enemyLifeLabel.y = enemyHealthTxt.y + 10;
enemyGoldTxt.setText(enemyGold);
enemyGoldLabel.x = enemyGoldTxt.x - enemyGoldTxt.width - 18;
enemyGoldLabel.y = enemyGoldTxt.y + 8;
// Update enemy castle health text and position
castleEnemyHealthTxt.setText(castleEnemy.health);
castleEnemyHealthTxt.x = castleEnemy.x;
castleEnemyHealthTxt.y = castleEnemy.y - 260;
}
// --- Deploy soldier function ---
function deploySoldier(team) {
// Block deployment if difficulty not chosen
if (!gameInputEnabled) return false;
var gold = team === 'player' ? playerGold : enemyGold;
var cost = team === 'player' ? SOLDIER_COST : ENEMY_SOLDIER_COST;
if (gold < cost) return false;
// Deduct gold
if (team === 'player') playerGold -= SOLDIER_COST;else enemyGold -= ENEMY_SOLDIER_COST;
// Fixed health and random attack
var health = 25;
var attack = 5 + Math.floor(Math.random() * 6); // 5-10 inclusive
// Create soldier
var s = new Soldier();
s.init(team, health, attack);
s.speed = SOLDIER_SPEED;
s.inCombat = false;
// Position
s.y = CASTLE_OFFSET_Y + 120; // Move soldiers' exit position down by 120px
if (team === 'player') {
s.x = castlePlayer.x + 120;
playerSoldiers.push(s);
} else {
s.x = castleEnemy.x - 120;
enemySoldiers.push(s);
}
game.addChild(s);
// Play sound when unit leaves the castle
LK.getSound('unit_leave_castle').play();
return true;
}
// --- Player tap to upgrade castle only (soldier deploy moved to solderBtn.down) ---
game.down = function (x, y, obj) {
// Play click sound on any touch/click
LK.getSound('click').play();
// Block input until difficulty is chosen
if (!gameInputEnabled) return;
// --- Block all upgrades in first 30 seconds ---
// (Removed: No upgrades allowed in first 30 seconds for player)
// Check if player clicked on their castle for upgrade
var dx = x - castlePlayer.x;
var dy = y - castlePlayer.y;
if (dx * dx + dy * dy < 250 * 250 && !castlePlayer.hasUpgrade && playerGold >= 350) {
playerGold -= 350;
castlePlayer.upgrade();
updateGui();
// Show archerBtn after upgrade
archerBtn.visible = true;
// Show '150 G' label under archerBtn after upgrade
archerBtnLabel.visible = true;
// Hide the first upgrade text, show the second upgrade text
if (playerCastleUpgradeTxt && typeof playerCastleUpgradeTxt.destroy === "function") {
playerCastleUpgradeTxt.visible = false;
}
playerCastleUpgrade2Txt.visible = true;
return;
}
// Second upgrade: only if first upgrade is done, not already done, and enough gold
if (dx * dx + dy * dy < 250 * 250 && castlePlayer.hasUpgrade && !castlePlayer.hasUpgrade2 && playerGold >= 800) {
playerGold -= 800;
castlePlayer.hasUpgrade2 = true;
// Apply second upgrade effects
castlePlayer.health = 2500;
castlePlayer.attack = 4 + Math.floor(Math.random() * 3); // 4-6
castlePlayer.arrowCooldownMax = 40; // faster arrows
// Update asset for second upgrade
if (castlePlayer.asset) {
castlePlayer.removeChild(castlePlayer.asset);
}
castlePlayer.asset = castlePlayer.attachAsset('castle_player_3', {
anchorX: 0.5,
anchorY: 0.5
});
updateGui();
// Hide the second upgrade text
playerCastleUpgrade2Txt.visible = false;
// Show healing tent button and 250G label
healingTentBtn.visible = true;
healingTentLabel.visible = true;
return;
}
// (Removed: deploySoldier on screen tap)
};
// --- Deploy soldier when solderBtn is pressed ---
solderBtn.down = function (x, y, obj) {
if (!gameInputEnabled) return;
if (playerGold >= SOLDIER_COST) {
deploySoldier('player');
updateGui();
} else {
LK.getSound('dont-click').play();
}
};
// --- Deploy archer_player when archerBtn is pressed (uses archer_player asset, cost 10 gold) ---
archerBtn.down = function (x, y, obj) {
if (!gameInputEnabled) return;
if (playerGold >= 150) {
playerGold -= 150;
// Create a new Soldier but use archer_player asset
var s = new Soldier();
// Set archer_player health to be twice that of soldier_player
var baseSoldierHealth = 25;
s.init('player', baseSoldierHealth * 2, 5 + Math.floor(Math.random() * 6)); // health 50, attack 5-10
s.speed = SOLDIER_SPEED * 0.8; // Already uses updated SOLDIER_SPEED, now 75% faster
s.inCombat = false;
// Remove previous asset and attach archer_player asset
if (s.asset) {
s.removeChild(s.asset);
}
s.asset = s.attachAsset('archer_player', {
anchorX: 0.5,
anchorY: 0.5
});
// Position
s.y = CASTLE_OFFSET_Y + 120;
s.x = castlePlayer.x + 120;
playerSoldiers.push(s);
game.addChild(s);
// Play sound when archer leaves the castle
LK.getSound('unit_leave_castle').play();
updateGui();
} else {
LK.getSound('dont-click').play();
}
};
// --- Enemy AI: let the enemy play like a player: send soldiers, build walls, send archers, upgrade castle, and upgrade again, in a natural order ---
// Track enemy AI state
var enemySoldierDeployCount = 0;
var enemyNextUnit = 'soldier'; // always start with soldier
var enemyTotalSoldierDeployed = 0;
var enemyUpgradeGoldReserved = 200;
var enemySoldierSinceLastWall = 0;
castleEnemy.hasUpgrade2 = false;
// Track enemy AI upgrade state
castleEnemy._upgradeStarted = false;
castleEnemy._upgrade2Started = false;
// Track archer unlock for enemy (after first upgrade)
var enemyArcherUnlocked = false;
// Track how many enemy units have been removed (killed by player)
var enemyUnitsRemoved = 0;
// Patch: Increment enemyUnitsRemoved when enemy soldier is killed (player gets gold for kill)
var _oldPlayerSoldierCombat = true;
if (!_oldPlayerSoldierCombat) {// never runs, just for context
// see player soldier combat loop
}
// Patch: Increment enemyUnitsRemoved when enemy soldier is killed
// (Find the code awarding playerGold += 10 for killing enemy soldier, and increment enemyUnitsRemoved there)
var _oldEnemyUnitsRemovedPatch = true;
// Enemy AI main loop
// --- Add a flag and timer for enemy first upgrade delay ---
var enemyFirstUpgradeAllowed = false;
LK.setTimeout(function () {
enemyFirstUpgradeAllowed = true;
}, 15000);
var enemyDeployTimer = LK.setInterval(function () {
if (!gameInputEnabled) return;
// --- Initial Phase: 0-30 seconds ---
if (LK.ticks < 1800) {
// Only send soldiers, no upgrades, no archers.
// If player sends soldiers, let enemy build walls.
// If more than 3 enemy soldiers alive, let them build walls.
// (Blowing up walls is allowed, but not archers or upgrades.)
// Only send soldiers
if (enemyGold >= ENEMY_SOLDIER_COST) {
if (deploySoldier('enemy')) {
updateGui();
enemySoldierDeployCount++;
enemyTotalSoldierDeployed++;
enemySoldierSinceLastWall++;
}
}
// If player has sent soldiers (playerSoldiers.length > 0) or more than 3 enemy soldiers alive, build wall if possible
if ((playerSoldiers.length > 0 || enemySoldiers.length > 3) && enemyGold >= 50 && wallEnemies.length === 0) {
deployWallEnemy();
}
return;
}
// --- Middle Phase: 30-60 seconds ---
if (LK.ticks >= 1800 && LK.ticks < 3600) {
// Soldier + Archer + Wall alternately, each as rolling 1-3 dice and send that many
// Do not send archers without doing the 1st upgrade
// 50% chance to upgrade if enough gold and not immediately
if (typeof enemyMiddlePhase === "undefined") {
enemyMiddlePhase = {
step: 0,
upgradeTried: false
};
}
// 0: soldier, 1: archer, 2: wall, repeat
var phaseType = enemyMiddlePhase.step % 3;
// Roll 1-3 dice for how many to send
var toSend = 1 + Math.floor(Math.random() * 3);
if (phaseType === 0) {
// Soldier
for (var i = 0; i < toSend; i++) {
if (enemyGold >= ENEMY_SOLDIER_COST) {
if (deploySoldier('enemy')) {
updateGui();
enemyTotalSoldierDeployed++;
enemySoldierSinceLastWall++;
}
}
}
} else if (phaseType === 1) {
// Archer
if (castleEnemy.hasUpgrade) {
for (var i = 0; i < toSend; i++) {
if (enemyGold >= 150) {
enemyGold -= 150;
var s = new Soldier();
var baseSoldierHealth = 25;
s.init('enemy', baseSoldierHealth * 2, 5 + Math.floor(Math.random() * 6));
s.speed = SOLDIER_SPEED * 0.8;
s.inCombat = false;
if (s.asset) {
s.removeChild(s.asset);
}
s.asset = s.attachAsset('archer_enemy', {
anchorX: 0.5,
anchorY: 0.5
});
s.y = CASTLE_OFFSET_Y + 120;
s.x = castleEnemy.x - 120;
enemySoldiers.push(s);
game.addChild(s);
LK.getSound('unit_leave_castle').play();
updateGui();
}
}
}
// If not upgraded, skip archer phase (do nothing)
} else if (phaseType === 2) {
// Wall
for (var i = 0; i < toSend; i++) {
if (enemyGold >= 50 && wallEnemies.length === 0) {
deployWallEnemy();
}
}
}
enemyMiddlePhase.step++;
// 50% chance to upgrade if enough gold and not upgraded yet, but not immediately
if (!castleEnemy.hasUpgrade && !enemyMiddlePhase.upgradeTried && enemyGold >= 350) {
if (Math.random() < 0.5) {
enemyGold -= 350;
castleEnemy.upgrade();
updateGui();
}
enemyMiddlePhase.upgradeTried = true;
}
// Second upgrade logic (after player upgrades or 10 units lost)
if (castleEnemy.hasUpgrade && !castleEnemy.hasUpgrade2 && !castleEnemy._upgrade2Started && (castlePlayer.hasUpgrade || enemyUnitsRemoved >= 10)) {
castleEnemy._upgrade2Started = true;
enemyGold -= 800;
castleEnemy.hasUpgrade2 = true;
castleEnemy.health = 2500;
castleEnemy.attack = 4 + Math.floor(Math.random() * 3); // 4-6
castleEnemy.arrowCooldownMax = 40;
GOLD_PER_TICK = GOLD_PER_TICK + 5;
updateGui();
return;
}
return;
}
// --- Advanced Phase: 60s+ ---
// Upgradeable, soldier + archer + wall alternately, dice-based, archers only after upgrade
if (typeof enemyAdvancedPhase === "undefined") {
enemyAdvancedPhase = {
step: 0
};
}
var advType = enemyAdvancedPhase.step % 3;
var advToSend = 1 + Math.floor(Math.random() * 3);
if (advType === 0) {
// Soldier
for (var i = 0; i < advToSend; i++) {
if (enemyGold >= ENEMY_SOLDIER_COST) {
if (deploySoldier('enemy')) {
updateGui();
enemyTotalSoldierDeployed++;
enemySoldierSinceLastWall++;
}
}
}
} else if (advType === 1) {
// Archer
if (castleEnemy.hasUpgrade) {
for (var i = 0; i < advToSend; i++) {
if (enemyGold >= 150) {
enemyGold -= 150;
var s = new Soldier();
var baseSoldierHealth = 25;
s.init('enemy', baseSoldierHealth * 2, 5 + Math.floor(Math.random() * 6));
s.speed = SOLDIER_SPEED * 0.8;
s.inCombat = false;
if (s.asset) {
s.removeChild(s.asset);
}
s.asset = s.attachAsset('archer_enemy', {
anchorX: 0.5,
anchorY: 0.5
});
s.y = CASTLE_OFFSET_Y + 120;
s.x = castleEnemy.x - 120;
enemySoldiers.push(s);
game.addChild(s);
LK.getSound('unit_leave_castle').play();
updateGui();
}
}
}
// If not upgraded, skip archer phase (do nothing)
} else if (advType === 2) {
// Wall
for (var i = 0; i < advToSend; i++) {
if (enemyGold >= 50 && wallEnemies.length === 0) {
deployWallEnemy();
}
}
}
enemyAdvancedPhase.step++;
// Allow upgrades at any time if enough gold and not upgraded
if (!castleEnemy.hasUpgrade && enemyGold >= 350) {
enemyGold -= 350;
castleEnemy.upgrade();
updateGui();
}
// Second upgrade logic (after player upgrades or 10 units lost)
if (castleEnemy.hasUpgrade && !castleEnemy.hasUpgrade2 && !castleEnemy._upgrade2Started && (castlePlayer.hasUpgrade || enemyUnitsRemoved >= 10)) {
castleEnemy._upgrade2Started = true;
enemyGold -= 800;
castleEnemy.hasUpgrade2 = true;
castleEnemy.health = 2500;
castleEnemy.attack = 4 + Math.floor(Math.random() * 3); // 4-6
castleEnemy.arrowCooldownMax = 40;
GOLD_PER_TICK = GOLD_PER_TICK + 5;
updateGui();
return;
}
}, 600);
// --- Arrows array ---
var arrows = [];
// --- Main update loop ---
game.update = function () {
// --- Update castles (health, arrows) ---
castlePlayer.update();
castleEnemy.update();
// --- Enemy healing tent logic ---
// Only allow after enemy's 2nd upgrade
if (castleEnemy.hasUpgrade2) {
// Start the healing tent cycle if not already started
if (!healingTentEnemyCooldownTimer && !healingTentEnemyActive) {
healingTentEnemyNextReady = true;
healingTentEnemyCountdown = 0;
healingTentEnemyLastTick = LK.ticks;
healingTentEnemyCooldownTimer = LK.setInterval(function () {
if (!healingTentEnemyActive && castleEnemy.hasUpgrade2) {
healingTentEnemyCountdown++;
if (healingTentEnemyCountdown >= 100) {
// 100 seconds
healingTentEnemyNextReady = true;
healingTentEnemyCountdown = 0;
}
}
}, 1000);
}
// If ready and not active, spawn the tent
if (healingTentEnemyNextReady && !healingTentEnemyActive) {
// Place tent between enemy castle and wall (or default if no wall)
var tentX;
var tentY = CASTLE_OFFSET_Y + 120 + 120;
if (wallEnemies.length > 0) {
var wall = wallEnemies[0];
tentX = (castleEnemy.x + wall.x) / 2;
} else {
tentX = castleEnemy.x - (castleEnemy.x - castlePlayer.x) / 3;
}
healingTentEnemy = LK.getAsset('HEALING_TENT_ENEMY', {
anchorX: 0.5,
anchorY: 0.5,
x: tentX,
y: tentY
});
// Add health text label above healing tent (use 30 as "health" for tent, or just show "30" for 30s)
if (healingTentEnemy.healthTxt && typeof healingTentEnemy.healthTxt.destroy === "function") {
healingTentEnemy.healthTxt.destroy();
}
healingTentEnemy.healthTxt = new Text2("30", {
size: 38,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
healingTentEnemy.healthTxt.anchor.set(0.5, 1.2);
healingTentEnemy.healthTxt.x = 0;
healingTentEnemy.healthTxt.y = -170;
healingTentEnemy.addChild(healingTentEnemy.healthTxt);
game.addChild(healingTentEnemy);
// Ensure tent is in front of all other units by re-adding as last child
if (typeof healingTentEnemy !== "undefined" && healingTentEnemy.parent) {
healingTentEnemy.parent.removeChild(healingTentEnemy);
game.addChild(healingTentEnemy);
}
// Remove previous countdown if any
if (healingTentEnemyCountdownTxt && typeof healingTentEnemyCountdownTxt.destroy === "function") {
healingTentEnemyCountdownTxt.destroy();
}
// Create countdown text below tent
healingTentEnemyCountdownTxt = new Text2("30", {
size: 60,
fill: 0xFF0000,
font: "PressStart2P,Pixel,monospace"
});
healingTentEnemyCountdownTxt.anchor.set(0.5, 0);
healingTentEnemyCountdownTxt.x = tentX;
healingTentEnemyCountdownTxt.y = tentY + 170;
game.addChild(healingTentEnemyCountdownTxt);
// Remove previous timer if any
if (healingTentEnemyTimer) {
LK.clearInterval(healingTentEnemyTimer);
healingTentEnemyTimer = null;
}
var tentCountdown = 30;
healingTentEnemyCountdownTxt.setText(tentCountdown + "");
healingTentEnemyActive = true;
healingTentEnemyBoostActive = true;
healingTentEnemyTimer = LK.setInterval(function () {
tentCountdown--;
if (tentCountdown >= 0) {
healingTentEnemyCountdownTxt.setText(tentCountdown + "");
if (healingTentEnemy && healingTentEnemy.healthTxt) {
healingTentEnemy.healthTxt.setText(tentCountdown + "");
}
}
if (tentCountdown <= 0) {
// Remove tent and countdown
if (healingTentEnemy && typeof healingTentEnemy.destroy === "function") {
if (healingTentEnemy.healthTxt && typeof healingTentEnemy.healthTxt.destroy === "function") {
healingTentEnemy.healthTxt.destroy();
}
healingTentEnemy.destroy();
}
healingTentEnemy = null;
if (healingTentEnemyCountdownTxt && typeof healingTentEnemyCountdownTxt.destroy === "function") {
healingTentEnemyCountdownTxt.destroy();
}
healingTentEnemyCountdownTxt = null;
if (healingTentEnemyTimer) {
LK.clearInterval(healingTentEnemyTimer);
healingTentEnemyTimer = null;
}
healingTentEnemyActive = false;
healingTentEnemyBoostActive = false;
healingTentEnemyNextReady = false;
healingTentEnemyCountdown = 0;
}
}, 1000);
}
} else {
// If enemy loses 2nd upgrade, clear timers and tent
if (healingTentEnemyCooldownTimer) {
LK.clearInterval(healingTentEnemyCooldownTimer);
healingTentEnemyCooldownTimer = null;
}
if (healingTentEnemyTimer) {
LK.clearInterval(healingTentEnemyTimer);
healingTentEnemyTimer = null;
}
if (healingTentEnemy && typeof healingTentEnemy.destroy === "function") {
if (healingTentEnemy.healthTxt && typeof healingTentEnemy.healthTxt.destroy === "function") {
healingTentEnemy.healthTxt.destroy();
}
healingTentEnemy.destroy();
}
healingTentEnemy = null;
if (healingTentEnemyCountdownTxt && typeof healingTentEnemyCountdownTxt.destroy === "function") {
healingTentEnemyCountdownTxt.destroy();
}
healingTentEnemyCountdownTxt = null;
healingTentEnemyActive = false;
healingTentEnemyBoostActive = false;
healingTentEnemyNextReady = false;
healingTentEnemyCountdown = 0;
}
// --- Apply healing tent effect: 15% health boost to all player units while tent is present ---
if (healingTentPlayer) {
// Always keep healing tent in front of all units, even those created after it
if (healingTentPlayer.parent) {
healingTentPlayer.parent.removeChild(healingTentPlayer);
game.addChild(healingTentPlayer);
}
for (var i = 0; i < playerSoldiers.length; i++) {
var s = playerSoldiers[i];
if (!s._healingTentBoosted) {
s.health = Math.ceil(s.health * 1.15);
s._healingTentBoosted = true;
}
}
} else {
// Remove boost flag if tent is gone (do not revert health, only allow boost once per tent appearance)
for (var i = 0; i < playerSoldiers.length; i++) {
var s = playerSoldiers[i];
if (s._healingTentBoosted) {
s._healingTentBoosted = false;
}
}
}
// --- Apply healing tent effect: 15% health boost to all enemy units while enemy tent is present ---
if (healingTentEnemyBoostActive) {
// Always keep healing tent in front of all units, even those created after it
if (healingTentEnemy && healingTentEnemy.parent) {
healingTentEnemy.parent.removeChild(healingTentEnemy);
game.addChild(healingTentEnemy);
}
for (var i = 0; i < enemySoldiers.length; i++) {
var s = enemySoldiers[i];
if (!s._healingTentEnemyBoosted) {
s.health = Math.ceil(s.health * 1.15);
s._healingTentEnemyBoosted = true;
}
}
} else {
// Remove boost flag if tent is gone (do not revert health, only allow boost once per tent appearance)
for (var i = 0; i < enemySoldiers.length; i++) {
var s = enemySoldiers[i];
if (s._healingTentEnemyBoosted) {
s._healingTentEnemyBoosted = false;
}
}
}
// --- Player soldiers ---
for (var i = playerSoldiers.length - 1; i >= 0; i--) {
var s = playerSoldiers[i];
s.inCombat = false;
// --- Check for wall_enemy collision ---
var wallEngaged = false;
for (var w = 0; w < wallEnemies.length; w++) {
var wall = wallEnemies[w];
if (wall.destroyed) continue;
// Simple collision: check if close enough horizontally and vertically
if (Math.abs(s.x - wall.x) < 80 && Math.abs(s.y - wall.y) < 180) {
s.inCombat = true;
wallEngaged = true;
// Player attacks wall only if at least 2 player soldiers are engaging this wall
if (LK.ticks % 12 === 0) {
var engagedCount = 0;
for (var ps = 0; ps < playerSoldiers.length; ps++) {
var psold = playerSoldiers[ps];
if (Math.abs(psold.x - wall.x) < 80 && Math.abs(psold.y - wall.y) < 180 && !psold.destroyed) {
engagedCount++;
}
}
if (engagedCount >= 2) {
wall.health -= s.attack;
if (typeof wall.health === "number") {
wall.health = Math.round(wall.health);
}
}
}
// If wall destroyed, remove from game and array
if (wall.healthTxt) {
wall.healthTxt.setText(Math.max(0, Math.round(wall.health)) + "");
}
if (wall.health <= 0) {
scoreStats.wallEnemyDestroyed++;
wall.destroyed = true;
if (wall.healthTxt && typeof wall.healthTxt.destroy === "function") {
wall.healthTxt.destroy();
}
if (typeof wall.destroy === "function") wall.destroy();
wallEnemies.splice(w, 1);
w--;
}
break; // Only engage one wall at a time
}
}
if (wallEngaged) continue;
// Check for enemy soldier in range
var engaged = false;
// Prevent player soldiers from damaging enemy units if any wall_enemy is present and not destroyed and player soldier is engaged with it
var wallEnemyBlocking = false;
for (var w = 0; w < wallEnemies.length; w++) {
var wall = wallEnemies[w];
if (!wall.destroyed && Math.abs(s.x - wall.x) < 80 && Math.abs(s.y - wall.y) < 180) {
wallEnemyBlocking = true;
break;
}
}
for (var j = 0; j < enemySoldiers.length; j++) {
var e = enemySoldiers[j];
// If close enough (overlap)
if (Math.abs(s.x - e.x) < 80) {
// Engage in combat
s.inCombat = true;
e.inCombat = true;
// Both attack each other
if (LK.ticks % 12 === 0) {
// Only allow damage if not blocked by wall_enemy
if (!wallEnemyBlocking) {
// Attack every 12 frames (~5 times/sec)
e.health -= s.attack;
s.health -= e.attack;
}
}
// Remove dead soldiers
if (e.health <= 0) {
// Determine if enemy is archer or soldier
if (e.asset && e.asset.assetId === 'archer_enemy') {
scoreStats.archerEnemyKills++;
} else {
scoreStats.solderEnemyKills++;
}
e.destroy();
enemySoldiers.splice(j, 1);
j--;
// Award 10 gold to player for killing enemy soldier
playerGold += 10;
enemyUnitsRemoved = (typeof enemyUnitsRemoved === "number" ? enemyUnitsRemoved : 0) + 1; // increment removed count
updateGui();
}
if (s.health <= 0) {
// Determine if player is archer or soldier
if (s.asset && s.asset.assetId === 'archer_player') {
scoreStats.archerPlayerDeaths++;
} else {
scoreStats.solderPlayerDeaths++;
}
s.destroy();
playerSoldiers.splice(i, 1);
i--;
// Award 10 gold to enemy for killing player soldier
enemyGold += 10;
updateGui();
engaged = true;
break;
}
engaged = true;
break;
}
}
if (engaged) continue;
// If not in combat, check if at enemy castle
if (Math.abs(s.x - castleEnemy.x) < 120) {
s.inCombat = true;
if (LK.ticks % 12 === 0) {
castleEnemy.health -= s.attack;
if (castleEnemy.health < 0) castleEnemy.health = 0;
updateGui();
}
// Remove soldier if castle destroyed
if (castleEnemy.health <= 0) {
s.destroy();
playerSoldiers.splice(i, 1);
i--;
}
}
}
// --- Enemy soldiers ---
for (var i = enemySoldiers.length - 1; i >= 0; i--) {
var s = enemySoldiers[i];
s.inCombat = false;
// --- Check for wall_player collision ---
var wallEngaged = false;
for (var w = 0; w < wallPlayers.length; w++) {
var wall = wallPlayers[w];
if (wall.destroyed) continue;
// Simple collision: check if close enough horizontally and vertically
if (Math.abs(s.x - wall.x) < 80 && Math.abs(s.y - wall.y) < 180) {
s.inCombat = true;
wallEngaged = true;
// Enemy attacks wall only if at least 2 enemy soldiers are engaging this wall
if (LK.ticks % 12 === 0) {
var engagedCount = 0;
for (var es = 0; es < enemySoldiers.length; es++) {
var esold = enemySoldiers[es];
if (Math.abs(esold.x - wall.x) < 80 && Math.abs(esold.y - wall.y) < 180 && !esold.destroyed) {
engagedCount++;
}
}
if (engagedCount >= 2) {
wall.health -= s.attack;
if (typeof wall.health === "number") {
wall.health = Math.round(wall.health);
}
}
}
// If wall destroyed, remove from game and array
if (wall.healthTxt) {
wall.healthTxt.setText(Math.max(0, Math.round(wall.health)) + "");
}
if (wall.health <= 0) {
scoreStats.wallPlayerDestroyed++;
wall.destroyed = true;
if (wall.healthTxt && typeof wall.healthTxt.destroy === "function") {
wall.healthTxt.destroy();
}
if (typeof wall.destroy === "function") wall.destroy();
wallPlayers.splice(w, 1);
w--;
}
break; // Only engage one wall at a time
}
}
if (wallEngaged) continue;
// Check for player soldier in range
var engaged = false;
// Prevent enemy soldiers from damaging player units if any wall_player is present and not destroyed and enemy soldier is engaged with it
var wallPlayerBlocking = false;
for (var w = 0; w < wallPlayers.length; w++) {
var wall = wallPlayers[w];
if (!wall.destroyed && Math.abs(s.x - wall.x) < 80 && Math.abs(s.y - wall.y) < 180) {
wallPlayerBlocking = true;
break;
}
}
for (var j = 0; j < playerSoldiers.length; j++) {
var e = playerSoldiers[j];
if (Math.abs(s.x - e.x) < 80) {
s.inCombat = true;
e.inCombat = true;
if (LK.ticks % 12 === 0) {
// Only allow damage if not blocked by wall_player
if (!wallPlayerBlocking) {
e.health -= s.attack;
s.health -= e.attack;
}
}
if (e.health <= 0) {
e.destroy();
playerSoldiers.splice(j, 1);
j--;
// Award 10 gold to enemy for killing player soldier
enemyGold += 10;
updateGui();
}
if (s.health <= 0) {
s.destroy();
enemySoldiers.splice(i, 1);
i--;
// Award 10 gold to player for killing enemy soldier
playerGold += 10;
updateGui();
engaged = true;
break;
}
engaged = true;
break;
}
}
if (engaged) continue;
// If not in combat, check if at player castle
if (Math.abs(s.x - castlePlayer.x) < 120) {
s.inCombat = true;
if (LK.ticks % 12 === 0) {
castlePlayer.health -= s.attack;
if (castlePlayer.health < 0) castlePlayer.health = 0;
updateGui();
}
if (castlePlayer.health <= 0) {
s.destroy();
enemySoldiers.splice(i, 1);
i--;
}
}
}
// --- Move soldiers ---
for (var i = 0; i < playerSoldiers.length; i++) {
playerSoldiers[i].update();
}
for (var i = 0; i < enemySoldiers.length; i++) {
enemySoldiers[i].update();
}
// --- Update arrows ---
for (var i = arrows.length - 1; i >= 0; i--) {
var a = arrows[i];
a.update();
if (a.destroyed) {
arrows.splice(i, 1);
}
}
// --- Check win/lose ---
if (castlePlayer.health <= 0 && !gameOverState) {
gameOverState = 'lose';
showCustomGameOver();
return;
}
if (castleEnemy.health <= 0 && !gameOverState) {
gameOverState = 'win';
// --- Custom score stats for win ---
scoreStats.castlePlayerFullHealth = castlePlayer.health === CASTLE_HEALTH_INIT || castlePlayer.health === 2500 ? 1 : 0;
scoreStats.castlePlayerHealthLost = castlePlayer.health < (castlePlayer.hasUpgrade2 ? 2500 : CASTLE_HEALTH_INIT) ? (castlePlayer.hasUpgrade2 ? 2500 : CASTLE_HEALTH_INIT) - castlePlayer.health : 0;
scoreStats.castlePlayerNoDamageGiven = castlePlayer.health === (castlePlayer.hasUpgrade2 ? 2500 : CASTLE_HEALTH_INIT) && scoreStats.solderPlayerDeaths === 0 && scoreStats.archerPlayerDeaths === 0 && scoreStats.wallPlayerDestroyed === 0 ? 1 : 0;
// Increase and persist rounds won
roundsWon = (storage.roundsWon || 0) + 1;
storage.roundsWon = roundsWon;
roundsWonTxt.setText('Rounds Won: ' + roundsWon);
showCustomGameOver();
return;
}
};
// --- Custom game over function ---
function showCustomGameOver() {
// Create overlay
gameOverOverlay = new Container();
gameOverOverlay.zIndex = 20000;
// Background
var overlayBg = new Text2(" ", {
size: 10,
fill: 0x000000
});
overlayBg.width = GAME_W;
overlayBg.height = GAME_H;
overlayBg.alpha = 0.85;
overlayBg.x = 0;
overlayBg.y = 0;
gameOverOverlay.addChild(overlayBg);
// Main message
var mainMessage;
if (gameOverState === 'win') {
mainMessage = new Text2("YOU WON THE GAME", {
size: 120,
fill: 0x00FF00,
font: "PressStart2P,Pixel,monospace"
});
LK.effects.flashScreen(0x00ff00, 1000);
} else {
mainMessage = new Text2("YOU LOST THE GAME", {
size: 120,
fill: 0xFF0000,
font: "PressStart2P,Pixel,monospace"
});
LK.effects.flashScreen(0xff0000, 1000);
}
mainMessage.anchor.set(0.5, 0.5);
mainMessage.x = GAME_W / 2;
mainMessage.y = GAME_H / 2 - 200;
gameOverOverlay.addChild(mainMessage);
game.addChild(gameOverOverlay);
// Start timer sequence
gameOverTimer = LK.setTimeout(function () {
if (gameOverState === 'win') {
showPointsEarned();
} else {
// For lose, wait 5 seconds then go to login screen
gameOverTimer = LK.setTimeout(function () {
goToLoginScreen();
}, 5000);
}
}, 5000);
}
function showPointsEarned() {
if (pointsCalculated) return;
pointsCalculated = true;
// Calculate points
totalPoints = 0;
totalPoints += scoreStats.solderEnemyKills * 10;
totalPoints += scoreStats.archerEnemyKills * 25;
totalPoints += scoreStats.wallEnemyDestroyed * 50;
if (scoreStats.solderPlayerDeaths < 50) totalPoints += 1000;
if (scoreStats.archerPlayerDeaths < 50) totalPoints += 3000;
if (scoreStats.wallPlayerDestroyed < 20) totalPoints += 2000;
if (scoreStats.castlePlayerFullHealth) totalPoints += 5000;
if (scoreStats.castlePlayerHealthLost > 0) totalPoints += scoreStats.castlePlayerHealthLost;
if (scoreStats.castlePlayerNoDamageGiven) totalPoints += 10000;
// Store total points
var allTimePoints = storage.allTimePoints || 0;
allTimePoints += totalPoints;
storage.allTimePoints = allTimePoints;
// Create points text
var pointsText = new Text2("POINTS EARNED: " + totalPoints, {
size: 100,
fill: 0xFFD700,
font: "PressStart2P,Pixel,monospace"
});
pointsText.anchor.set(0.5, 0.5);
pointsText.x = GAME_W / 2;
pointsText.y = GAME_H / 2 + 100;
pointsText.alpha = 0;
gameOverOverlay.addChild(pointsText);
// Animate points in
tween(pointsText, {
alpha: 1
}, {
duration: 1000
});
// Wait 5 seconds then go to login
gameOverTimer = LK.setTimeout(function () {
goToLoginScreen();
}, 5000);
}
function goToLoginScreen() {
// Wait 5 seconds, then reset and return to difficulty selection
LK.setTimeout(function () {
// Reset all game state variables
gameOverState = null;
pointsCalculated = false;
totalPoints = 0;
difficulty = null;
gameInputEnabled = false;
speedMode = false;
speedModeMenuSelected = false;
// Reset castle states
castlePlayer.health = CASTLE_HEALTH_INIT;
castlePlayer.hasUpgrade = false;
castlePlayer.hasUpgrade2 = false;
castlePlayer._upgradeStarted = false;
castlePlayer._upgrade2Started = false;
castleEnemy.health = CASTLE_HEALTH_INIT;
castleEnemy.hasUpgrade = false;
castleEnemy.hasUpgrade2 = false;
castleEnemy._upgradeStarted = false;
castleEnemy._upgrade2Started = false;
// Reset gold
playerGold = GOLD_INIT;
enemyGold = GOLD_INIT;
GOLD_PER_TICK = 5;
// Clear all soldiers and arrows
for (var i = 0; i < playerSoldiers.length; i++) {
if (playerSoldiers[i] && typeof playerSoldiers[i].destroy === "function") {
playerSoldiers[i].destroy();
}
}
for (var i = 0; i < enemySoldiers.length; i++) {
if (enemySoldiers[i] && typeof enemySoldiers[i].destroy === "function") {
enemySoldiers[i].destroy();
}
}
for (var i = 0; i < arrows.length; i++) {
if (arrows[i] && typeof arrows[i].destroy === "function") {
arrows[i].destroy();
}
}
playerSoldiers = [];
enemySoldiers = [];
arrows = [];
// Clear walls
for (var i = 0; i < wallPlayers.length; i++) {
if (wallPlayers[i] && typeof wallPlayers[i].destroy === "function") {
wallPlayers[i].destroy();
}
}
for (var i = 0; i < wallEnemies.length; i++) {
if (wallEnemies[i] && typeof wallEnemies[i].destroy === "function") {
wallEnemies[i].destroy();
}
}
wallPlayers = [];
wallEnemies = [];
// Clear healing tents and timers
if (healingTentPlayer && typeof healingTentPlayer.destroy === "function") {
healingTentPlayer.destroy();
}
if (healingTentEnemy && typeof healingTentEnemy.destroy === "function") {
healingTentEnemy.destroy();
}
if (healingTentCountdownTxt && typeof healingTentCountdownTxt.destroy === "function") {
healingTentCountdownTxt.destroy();
}
if (healingTentEnemyCountdownTxt && typeof healingTentEnemyCountdownTxt.destroy === "function") {
healingTentEnemyCountdownTxt.destroy();
}
if (healingTentTimer) {
LK.clearInterval(healingTentTimer);
healingTentTimer = null;
}
if (healingTentEnemyTimer) {
LK.clearInterval(healingTentEnemyTimer);
healingTentEnemyTimer = null;
}
if (healingTentEnemyCooldownTimer) {
LK.clearInterval(healingTentEnemyCooldownTimer);
healingTentEnemyCooldownTimer = null;
}
healingTentPlayer = null;
healingTentEnemy = null;
healingTentCountdownTxt = null;
healingTentEnemyCountdownTxt = null;
healingTentEnemyActive = false;
healingTentEnemyNextReady = false;
healingTentEnemyCountdown = 0;
healingTentEnemyBoostActive = false;
// Reset enemy AI variables
enemySoldierDeployCount = 0;
enemyTotalSoldierDeployed = 0;
enemyUnitsRemoved = 0;
enemySoldierSinceLastWall = 0;
enemyWallEnemyDeployCount = 0;
if (typeof enemyMiddlePhase !== "undefined") {
enemyMiddlePhase = undefined;
}
if (typeof enemyAdvancedPhase !== "undefined") {
enemyAdvancedPhase = undefined;
}
// Reset castle assets
castlePlayer.init('player', CASTLE_OFFSET_X, CASTLE_OFFSET_Y);
castleEnemy.init('enemy', GAME_W - CASTLE_OFFSET_X, CASTLE_OFFSET_Y);
// Reset button visibility
archerBtn.visible = false;
archerBtnLabel.visible = false;
solderBtn.visible = false;
solderBtnLabel.visible = false;
wallBtn.visible = false;
wallBtnLabel.visible = false;
healingTentBtn.visible = false;
healingTentLabel.visible = false;
// Reset upgrade text visibility
playerCastleUpgradeTxt.visible = true;
playerCastleUpgrade2Txt.visible = false;
// Reset score stats
scoreStats = {
solderEnemyKills: 0,
archerEnemyKills: 0,
wallEnemyDestroyed: 0,
solderPlayerDeaths: 0,
archerPlayerDeaths: 0,
wallPlayerDestroyed: 0,
castlePlayerFullHealth: 0,
castlePlayerHealthLost: 0,
castlePlayerNoDamageGiven: 0
};
// Clear game over overlays
if (gameOverOverlay && typeof gameOverOverlay.destroy === "function") {
gameOverOverlay.destroy();
gameOverOverlay = null;
}
if (gameOverTimer) {
LK.clearTimeout(gameOverTimer);
gameOverTimer = null;
}
// Clear enemy deploy timer
if (enemyDeployTimer) {
LK.clearInterval(enemyDeployTimer);
}
// Update GUI
updateGui();
// Recreate and show difficulty selection overlay
diffOverlay = new Container();
diffOverlay.zIndex = 10000;
diffOverlay.width = GAME_W;
diffOverlay.height = GAME_H;
// Background
overlayBg = new Text2(" ", {
size: 10,
fill: 0x000000
});
overlayBg.width = GAME_W;
overlayBg.height = GAME_H;
overlayBg.alpha = 0.7;
overlayBg.x = 0;
overlayBg.y = 0;
diffOverlay.addChild(overlayBg);
// Recreate all difficulty selection elements
diffTitleBg = new Text2(" ", {
size: 10,
fill: 0x000000
});
diffTitleBg.width = 900;
diffTitleBg.height = 160;
diffTitleBg.alpha = 0.95;
diffTitleBg.anchor.set(0.5, 0.5);
diffTitleBg.x = GAME_W / 2;
diffTitleBg.y = GAME_H / 2 - 300;
diffOverlay.addChild(diffTitleBg);
diffTitle = new Text2("Select Difficulty", {
size: 120,
fill: "#fff",
font: "PressStart2P,Pixel,monospace"
});
diffTitle.anchor.set(0.5, 0.5);
diffTitle.x = GAME_W / 2;
diffTitle.y = GAME_H / 2 - 300;
diffOverlay.addChild(diffTitle);
// Easy button
btnEasyBg = new Text2(" ", {
size: 10,
fill: 0x000000
});
btnEasyBg.width = 600;
btnEasyBg.height = 120;
btnEasyBg.alpha = 0.95;
btnEasyBg.anchor.set(0.5, 0.5);
btnEasyBg.x = GAME_W / 2;
btnEasyBg.y = GAME_H / 2 - 80;
diffOverlay.addChild(btnEasyBg);
btnEasy = new Text2("Easy", {
size: 100,
fill: 0x00FF00,
font: "PressStart2P,Pixel,monospace"
});
btnEasy.anchor.set(0.5, 0.5);
btnEasy.x = GAME_W / 2;
btnEasy.y = GAME_H / 2 - 80;
btnEasy.down = function (x, y, obj) {
selectDifficulty("easy");
};
diffOverlay.addChild(btnEasy);
// Normal button
btnNormalBg = new Text2(" ", {
size: 10,
fill: 0x000000
});
btnNormalBg.width = 600;
btnNormalBg.height = 120;
btnNormalBg.alpha = 0.95;
btnNormalBg.anchor.set(0.5, 0.5);
btnNormalBg.x = GAME_W / 2;
btnNormalBg.y = GAME_H / 2 + 80;
diffOverlay.addChild(btnNormalBg);
btnNormal = new Text2("Normal", {
size: 100,
fill: 0xFFFF00,
font: "PressStart2P,Pixel,monospace"
});
btnNormal.anchor.set(0.5, 0.5);
btnNormal.x = GAME_W / 2;
btnNormal.y = GAME_H / 2 + 80;
btnNormal.down = function (x, y, obj) {
selectDifficulty("normal");
};
diffOverlay.addChild(btnNormal);
// Hard button
btnHardBg = new Text2(" ", {
size: 10,
fill: 0x000000
});
btnHardBg.width = 600;
btnHardBg.height = 120;
btnHardBg.alpha = 0.95;
btnHardBg.anchor.set(0.5, 0.5);
btnHardBg.x = GAME_W / 2;
btnHardBg.y = GAME_H / 2 + 240;
diffOverlay.addChild(btnHardBg);
btnHard = new Text2("Hard", {
size: 100,
fill: 0xFF0000,
font: "PressStart2P,Pixel,monospace"
});
btnHard.anchor.set(0.5, 0.5);
btnHard.x = GAME_W / 2;
btnHard.y = GAME_H / 2 + 240;
btnHard.down = function (x, y, obj) {
selectDifficulty("hard");
};
diffOverlay.addChild(btnHard);
// Speed Mode button
btnSpeedBg = new Text2(" ", {
size: 10,
fill: 0x000000
});
btnSpeedBg.width = 600;
btnSpeedBg.height = 120;
btnSpeedBg.alpha = 0.95;
btnSpeedBg.anchor.set(0.5, 0.5);
btnSpeedBg.x = GAME_W / 2;
btnSpeedBg.y = GAME_H / 2 + 400;
diffOverlay.addChild(btnSpeedBg);
btnSpeed = new Text2("Speed Mode: OFF", {
size: 80,
fill: 0x00E0FF,
font: "PressStart2P,Pixel,monospace"
});
btnSpeed.anchor.set(0.5, 0.5);
btnSpeed.x = GAME_W / 2;
btnSpeed.y = GAME_H / 2 + 400;
btnSpeed.down = function (x, y, obj) {
speedModeMenuSelected = !speedModeMenuSelected;
btnSpeed.setText(speedModeMenuSelected ? "Speed Mode: ON" : "Speed Mode: OFF");
};
diffOverlay.addChild(btnSpeed);
game.addChild(diffOverlay);
}, 5000);
}
// --- Clean up timers on game over ---
game.destroy = function () {
LK.clearInterval(goldTimer);
LK.clearInterval(enemyDeployTimer);
}; /****
* Plugins
****/
var storage = LK.import("@upit/storage.v1");
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Arrow class for castle arrow shooting
var Arrow = Container.expand(function () {
var self = Container.call(this);
// Increase arrow speed for longer distance
self.speed = 40; // px per frame (was 32)
self.damage = 2 + Math.floor(Math.random() * 3); // 2-4 damage
self.target = null;
self.team = null;
self.destroyed = false;
self.asset = null;
self.lastX = 0;
self.lastY = 0;
// Initialize arrow
self.init = function (x, y, target, damage, team) {
self.x = x;
self.y = y;
self.target = target;
// Arrow damage is randomized 2-4, ignore passed damage param for castle arrows
self.damage = 2 + Math.floor(Math.random() * 3);
self.team = team;
// Remove previous asset if any
if (self.asset) {
self.removeChild(self.asset);
}
// Use a visible arrow using a rectangle shape since centerCircle is removed
self.asset = self.attachAsset('arrow_shape', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 0.3,
tint: team === 'player' ? 0x00aaff : 0xffaa00 // more visible blue/orange
});
};
// Update arrow position and check for hit
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
if (!self.target || self.target.destroyed || self.destroyed) {
self.destroyed = true;
self.destroy();
return;
}
// Move towards target
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 1) dist = 1;
var vx = dx / dist * self.speed;
var vy = dy / dist * self.speed;
self.x += vx;
self.y += vy;
// Rotate arrow to face target
self.asset.rotation = Math.atan2(dy, dx);
// Check for collision with target (simple distance check)
if (dist < 60) {
// Hit!
if (typeof self.target.health === "number") {
self.target.health -= self.damage;
self.target.health = Math.round(self.target.health);
// If the target is a soldier and its health drops to 0 or below, destroy it and remove from array
if (self.target.health <= 0 && typeof self.target.team === "string") {
self.target.destroyed = true;
self.target.destroy();
// Remove from correct array
if (self.target.team === "enemy" && typeof enemySoldiers !== "undefined") {
for (var i = 0; i < enemySoldiers.length; i++) {
if (enemySoldiers[i] === self.target) {
enemySoldiers.splice(i, 1);
break;
}
}
} else if (self.target.team === "player" && typeof playerSoldiers !== "undefined") {
for (var i = 0; i < playerSoldiers.length; i++) {
if (playerSoldiers[i] === self.target) {
playerSoldiers.splice(i, 1);
break;
}
}
}
}
}
self.destroyed = true;
self.destroy();
}
// Remove if out of bounds (double the distance for longer arrows)
if (self.x < -600 || self.x > 3200 || self.y < -600 || self.y > 4000) {
self.destroyed = true;
self.destroy();
}
};
return self;
});
// Castle class for player and enemy castles
var Castle = Container.expand(function () {
var self = Container.call(this);
// Properties
self.team = null;
self.health = CASTLE_HEALTH_INIT;
self.hasUpgrade = false;
self.attack = 0;
self.arrowCooldown = 0;
self.arrowCooldownMax = 60; // frames between shots
self.attackRadius = 500; // px, for arrow shooting
self.asset = null;
// Initialize castle
self.init = function (team, x, y) {
self.team = team;
self.x = x;
self.y = y;
self.health = CASTLE_HEALTH_INIT;
self.hasUpgrade = false;
self.attack = 0;
self.arrowCooldown = 0;
// Remove previous asset if any
if (self.asset) {
self.removeChild(self.asset);
}
// Add correct asset
if (team === 'player') {
var assetId = 'castle_player';
if (self.hasUpgrade2) {
assetId = 'castle_player_3';
} else if (self.hasUpgrade) {
assetId = 'castle_player_2';
}
self.asset = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
} else {
var assetId = 'castle_enemy';
if (self.hasUpgrade2 || self.health >= 2500) {
assetId = 'castle_enemy_3';
} else if (self.hasUpgrade) {
assetId = 'castle_enemy_2';
}
self.asset = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
}
};
// Upgrade castle
self.upgrade = function () {
self.hasUpgrade = true;
self.health = 1500;
self.attack = 1 + Math.floor(Math.random() * 3); // 1-3
self.arrowCooldown = 0;
// Update asset for first upgrade
if (self.asset) {
self.removeChild(self.asset);
}
var assetId = self.team === 'player' ? 'castle_player_2' : 'castle_enemy_2';
self.asset = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Second upgrade handled externally (see gamecode.js)
};
// Update castle (shoot arrows if upgraded)
self.update = function () {
// Always round health to integer for display and logic
if (typeof self.health === "number") {
self.health = Math.round(self.health);
}
// Switch to castle_enemy_3 asset if health reaches 2500 and not already set
if (self.team === 'enemy' && self.health >= 2500 && (!self.asset || self.asset.assetId !== 'castle_enemy_3')) {
if (self.asset) {
self.removeChild(self.asset);
}
self.asset = self.attachAsset('castle_enemy_3', {
anchorX: 0.5,
anchorY: 0.5
});
// Ensure assetId is set for future checks
self.asset.assetId = 'castle_enemy_3';
// Immediately update the castleEnemy reference in game if this is the main enemy castle
if (typeof castleEnemy !== "undefined" && castleEnemy === self) {
castleEnemy.asset = self.asset;
}
}
if (self.hasUpgrade && self.health > 0) {
// Only shoot at enemy soldiers in range
var targets = [];
if (self.team === 'player') {
for (var i = 0; i < enemySoldiers.length; i++) {
var e = enemySoldiers[i];
var dx = e.x - self.x;
var dy = e.y - self.y;
if (dx * dx + dy * dy <= self.attackRadius * self.attackRadius) {
targets.push(e);
}
}
} else {
for (var i = 0; i < playerSoldiers.length; i++) {
var e = playerSoldiers[i];
var dx = e.x - self.x;
var dy = e.y - self.y;
if (dx * dx + dy * dy <= self.attackRadius * self.attackRadius) {
targets.push(e);
}
}
}
// Shoot at first target if cooldown is ready
if (targets.length > 0 && self.arrowCooldown <= 0) {
var target = targets[0];
var arrow = new Arrow();
arrow.init(self.x, self.y, target, self.attack, self.team);
arrows.push(arrow);
game.addChild(arrow);
self.arrowCooldown = self.arrowCooldownMax;
}
if (self.arrowCooldown > 0) self.arrowCooldown--;
}
};
return self;
});
// Soldier class for both player and enemy soldiers
var Soldier = Container.expand(function () {
var self = Container.call(this);
self.team = null;
self.health = 100;
self.attack = 10;
self.speed = SOLDIER_SPEED;
self.inCombat = false;
self.asset = null;
self.lastX = 0;
self.lastY = 0;
// Initialize soldier
self.init = function (team, health, attack) {
self.team = team;
self.health = health;
self.attack = attack;
self.inCombat = false;
// Remove previous asset if any
if (self.asset) {
self.removeChild(self.asset);
}
// Add correct asset
if (team === 'player') {
self.asset = self.attachAsset('soldier_player', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
self.asset = self.attachAsset('soldier_enemy', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Add health text label above the soldier
if (self.healthTxt && typeof self.healthTxt.destroy === "function") {
self.healthTxt.destroy();
}
self.healthTxt = new Text2(Math.round(self.health) + "", {
size: 38,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
self.healthTxt.anchor.set(0.5, 1.2);
self.healthTxt.x = 0;
self.healthTxt.y = -90;
self.addChild(self.healthTxt);
};
// Update soldier position (move forward if not in combat)
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
// Always round health to integer for display and logic
if (typeof self.health === "number") {
self.health = Math.round(self.health);
}
// Update health text label
if (self.healthTxt) {
self.healthTxt.setText(self.health + "");
}
if (!self.inCombat) {
if (self.team === 'player') {
self.x += self.speed;
} else {
self.x -= self.speed;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xBFFF00
});
/****
* Game Code
****/
// --- Add background image ---
// Ottoman-inspired church music with kanun, tambourine, zurna melodies
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
var background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: GAME_W,
height: GAME_H
});
game.addChild(background);
// Play Ottoman-inspired church music with kanun, tambourine, zurna melodies
LK.playMusic('ottoman_church_bg');
// --- Game constants ---
// Player castle (blue)
// Enemy castle (red)
// Player soldier (green)
// --- Game constants ---
var GAME_W = 2048,
GAME_H = 2732;
var CASTLE_OFFSET_X = 120,
CASTLE_OFFSET_Y = GAME_H - 520; // moved up by 200px
var CASTLE_HEALTH_INIT = 1000;
var GOLD_INIT = 200;
var GOLD_PER_TICK = 5;
// After enemy 800 gold upgrade, this will be increased for mine-like feature
var GOLD_TICK_MS = 200;
var SOLDIER_COST = 100;
var SOLDIER_HEALTH_MIN = 80,
SOLDIER_HEALTH_MAX = 160;
var SOLDIER_ATTACK_MIN = 20,
SOLDIER_ATTACK_MAX = 50;
var SOLDIER_SPEED = 14 / 5 * 0.1 * 4.0; // px per frame (now 100% faster than previous)
// --- Speed mode state ---
var speedMode = false;
// --- Speed mode toggle button (top right, not in topLeft 100x100) ---
var speedBtn = new Text2("SPEED x1", {
size: 60,
fill: "#fff",
font: "PressStart2P,Pixel,monospace"
});
speedBtn.anchor.set(1, 0);
speedBtn.x = LK.gui.width - 40;
speedBtn.y = 40;
LK.gui.top.addChild(speedBtn);
speedBtn.down = function (x, y, obj) {
speedMode = !speedMode;
speedBtn.setText(speedMode ? "SPEED x3" : "SPEED x1");
};
// --- Difficulty selection ---
var difficulty = null;
var ENEMY_SOLDIER_COST = 100; // default, will be set by difficulty
// Show difficulty selection overlay
var diffOverlay = new Container();
diffOverlay.zIndex = 10000; // ensure on top
diffOverlay.width = GAME_W;
diffOverlay.height = GAME_H;
// Use a Text2 as a fake overlay background (solid block) since centerCircle is removed
var overlayBg = new Text2(" ", {
size: 10,
fill: 0x000000
});
overlayBg.width = GAME_W;
overlayBg.height = GAME_H;
overlayBg.alpha = 0.7;
overlayBg.x = 0;
overlayBg.y = 0;
diffOverlay.addChild(overlayBg);
// Difficulty title with black background
var diffTitleBg = new Text2(" ", {
size: 10,
fill: 0x000000
});
diffTitleBg.width = 900;
diffTitleBg.height = 160;
diffTitleBg.alpha = 0.95;
diffTitleBg.anchor.set(0.5, 0.5);
diffTitleBg.x = GAME_W / 2;
diffTitleBg.y = GAME_H / 2 - 300;
diffOverlay.addChild(diffTitleBg);
var diffTitle = new Text2("Select Difficulty", {
size: 120,
fill: "#fff",
font: "PressStart2P,Pixel,monospace"
});
diffTitle.anchor.set(0.5, 0.5);
diffTitle.x = GAME_W / 2;
diffTitle.y = GAME_H / 2 - 300;
diffOverlay.addChild(diffTitle);
// Easy button with black background
var btnEasyBg = new Text2(" ", {
size: 10,
fill: 0x000000
});
btnEasyBg.width = 600;
btnEasyBg.height = 120;
btnEasyBg.alpha = 0.95;
btnEasyBg.anchor.set(0.5, 0.5);
btnEasyBg.x = GAME_W / 2;
btnEasyBg.y = GAME_H / 2 - 80;
diffOverlay.addChild(btnEasyBg);
var btnEasy = new Text2("Easy", {
size: 100,
fill: 0x00FF00,
font: "PressStart2P,Pixel,monospace"
});
btnEasy.anchor.set(0.5, 0.5);
btnEasy.x = GAME_W / 2;
btnEasy.y = GAME_H / 2 - 80;
diffOverlay.addChild(btnEasy);
// Normal button with black background
var btnNormalBg = new Text2(" ", {
size: 10,
fill: 0x000000
});
btnNormalBg.width = 600;
btnNormalBg.height = 120;
btnNormalBg.alpha = 0.95;
btnNormalBg.anchor.set(0.5, 0.5);
btnNormalBg.x = GAME_W / 2;
btnNormalBg.y = GAME_H / 2 + 80;
diffOverlay.addChild(btnNormalBg);
var btnNormal = new Text2("Normal", {
size: 100,
fill: 0xFFFF00,
font: "PressStart2P,Pixel,monospace"
});
btnNormal.anchor.set(0.5, 0.5);
btnNormal.x = GAME_W / 2;
btnNormal.y = GAME_H / 2 + 80;
diffOverlay.addChild(btnNormal);
// Hard button with black background
var btnHardBg = new Text2(" ", {
size: 10,
fill: 0x000000
});
btnHardBg.width = 600;
btnHardBg.height = 120;
btnHardBg.alpha = 0.95;
btnHardBg.anchor.set(0.5, 0.5);
btnHardBg.x = GAME_W / 2;
btnHardBg.y = GAME_H / 2 + 240;
diffOverlay.addChild(btnHardBg);
var btnHard = new Text2("Hard", {
size: 100,
fill: 0xFF0000,
font: "PressStart2P,Pixel,monospace"
});
btnHard.anchor.set(0.5, 0.5);
btnHard.x = GAME_W / 2;
btnHard.y = GAME_H / 2 + 240;
diffOverlay.addChild(btnHard);
// Speed Mode button (below hard)
var btnSpeedBg = new Text2(" ", {
size: 10,
fill: 0x000000
});
btnSpeedBg.width = 600;
btnSpeedBg.height = 120;
btnSpeedBg.alpha = 0.95;
btnSpeedBg.anchor.set(0.5, 0.5);
btnSpeedBg.x = GAME_W / 2;
btnSpeedBg.y = GAME_H / 2 + 400;
diffOverlay.addChild(btnSpeedBg);
var btnSpeed = new Text2("Speed Mode: OFF", {
size: 80,
fill: 0x00E0FF,
font: "PressStart2P,Pixel,monospace"
});
btnSpeed.anchor.set(0.5, 0.5);
btnSpeed.x = GAME_W / 2;
btnSpeed.y = GAME_H / 2 + 400;
diffOverlay.addChild(btnSpeed);
var speedModeMenuSelected = false;
btnSpeed.down = function (x, y, obj) {
speedModeMenuSelected = !speedModeMenuSelected;
btnSpeed.setText(speedModeMenuSelected ? "Speed Mode: ON" : "Speed Mode: OFF");
};
game.addChild(diffOverlay);
// Disable game input until difficulty is chosen
var gameInputEnabled = false;
// Helper to start game with selected difficulty
function selectDifficulty(level) {
difficulty = level;
if (difficulty === "easy") {
ENEMY_SOLDIER_COST = SOLDIER_COST; // same as player
} else if (difficulty === "normal") {
ENEMY_SOLDIER_COST = Math.floor(SOLDIER_COST * 0.9); // 0.9 of player
} else if (difficulty === "hard") {
ENEMY_SOLDIER_COST = Math.floor(SOLDIER_COST * 0.7); // 0.7 of player
}
// Enable speed mode if selected in menu
if (typeof speedModeMenuSelected !== "undefined" && speedModeMenuSelected) {
speedMode = true;
if (typeof speedBtn !== "undefined") speedBtn.setText("SPEED x3");
} else {
speedMode = false;
if (typeof speedBtn !== "undefined") speedBtn.setText("SPEED x1");
}
// Set enemy deploy interval based on difficulty
if (typeof enemyDeployTimer !== "undefined") {
LK.clearInterval(enemyDeployTimer);
}
var enemyDeployInterval = 600; // default
if (difficulty === "easy") {
enemyDeployInterval = 900;
} else if (difficulty === "normal") {
enemyDeployInterval = 700;
} else if (difficulty === "hard") {
enemyDeployInterval = 500;
}
enemyDeployTimer = LK.setInterval(function () {
if (!gameInputEnabled) return;
// --- Initial Phase: 0-30 seconds ---
if (LK.ticks < 1800) {
// Only send soldiers, no upgrades, no archers.
// If player sends soldiers, let enemy build walls.
// If more than 3 enemy soldiers alive, let them build walls.
// (Blowing up walls is allowed, but not archers or upgrades.)
// Only send soldiers
if (enemyGold >= ENEMY_SOLDIER_COST) {
if (deploySoldier('enemy')) {
updateGui();
enemySoldierDeployCount++;
enemyTotalSoldierDeployed++;
enemySoldierSinceLastWall++;
}
}
// If player has sent soldiers (playerSoldiers.length > 0) or more than 3 enemy soldiers alive, build wall if possible
if ((playerSoldiers.length > 0 || enemySoldiers.length > 3) && enemyGold >= 50 && wallEnemies.length === 0) {
deployWallEnemy();
}
return;
}
// --- Middle Phase: 30-60 seconds ---
if (LK.ticks >= 1800 && LK.ticks < 3600) {
// Soldier + Archer + Wall alternately, each as rolling 1-3 dice and send that many
// Do not send archers without doing the 1st upgrade
// 50% chance to upgrade if enough gold and not immediately
if (typeof enemyMiddlePhase === "undefined") {
enemyMiddlePhase = {
step: 0,
upgradeTried: false
};
}
// 0: soldier, 1: archer, 2: wall, repeat
var phaseType = enemyMiddlePhase.step % 3;
// Roll 1-3 dice for how many to send
var toSend = 1 + Math.floor(Math.random() * 3);
if (phaseType === 0) {
// Soldier
for (var i = 0; i < toSend; i++) {
if (enemyGold >= ENEMY_SOLDIER_COST) {
if (deploySoldier('enemy')) {
updateGui();
enemyTotalSoldierDeployed++;
enemySoldierSinceLastWall++;
}
}
}
} else if (phaseType === 1) {
// Archer
if (castleEnemy.hasUpgrade) {
for (var i = 0; i < toSend; i++) {
if (enemyGold >= 150) {
enemyGold -= 150;
var s = new Soldier();
var baseSoldierHealth = 25;
s.init('enemy', baseSoldierHealth * 2, 5 + Math.floor(Math.random() * 6));
s.speed = SOLDIER_SPEED * 0.8;
s.inCombat = false;
if (s.asset) {
s.removeChild(s.asset);
}
s.asset = s.attachAsset('archer_enemy', {
anchorX: 0.5,
anchorY: 0.5
});
s.y = CASTLE_OFFSET_Y + 120;
s.x = castleEnemy.x - 120;
enemySoldiers.push(s);
game.addChild(s);
LK.getSound('unit_leave_castle').play();
updateGui();
}
}
}
// If not upgraded, skip archer phase (do nothing)
} else if (phaseType === 2) {
// Wall
for (var i = 0; i < toSend; i++) {
if (enemyGold >= 50 && wallEnemies.length === 0) {
deployWallEnemy();
}
}
}
enemyMiddlePhase.step++;
// 50% chance to upgrade if enough gold and not upgraded yet, but not immediately
if (!castleEnemy.hasUpgrade && !enemyMiddlePhase.upgradeTried && enemyGold >= 350) {
if (Math.random() < 0.5) {
enemyGold -= 350;
castleEnemy.upgrade();
updateGui();
}
enemyMiddlePhase.upgradeTried = true;
}
// Second upgrade logic (after player upgrades or 10 units lost)
if (castleEnemy.hasUpgrade && !castleEnemy.hasUpgrade2 && !castleEnemy._upgrade2Started && (castlePlayer.hasUpgrade || enemyUnitsRemoved >= 10)) {
castleEnemy._upgrade2Started = true;
enemyGold -= 800;
castleEnemy.hasUpgrade2 = true;
castleEnemy.health = 2500;
castleEnemy.attack = 4 + Math.floor(Math.random() * 3); // 4-6
castleEnemy.arrowCooldownMax = 40;
GOLD_PER_TICK = GOLD_PER_TICK + 5;
updateGui();
return;
}
return;
}
// --- Advanced Phase: 60s+ ---
// Upgradeable, soldier + archer + wall alternately, dice-based, archers only after upgrade
if (typeof enemyAdvancedPhase === "undefined") {
enemyAdvancedPhase = {
step: 0
};
}
var advType = enemyAdvancedPhase.step % 3;
var advToSend = 1 + Math.floor(Math.random() * 3);
if (advType === 0) {
// Soldier
for (var i = 0; i < advToSend; i++) {
if (enemyGold >= ENEMY_SOLDIER_COST) {
if (deploySoldier('enemy')) {
updateGui();
enemyTotalSoldierDeployed++;
enemySoldierSinceLastWall++;
}
}
}
} else if (advType === 1) {
// Archer
if (castleEnemy.hasUpgrade) {
for (var i = 0; i < advToSend; i++) {
if (enemyGold >= 150) {
enemyGold -= 150;
var s = new Soldier();
var baseSoldierHealth = 25;
s.init('enemy', baseSoldierHealth * 2, 5 + Math.floor(Math.random() * 6));
s.speed = SOLDIER_SPEED * 0.8;
s.inCombat = false;
if (s.asset) {
s.removeChild(s.asset);
}
s.asset = s.attachAsset('archer_enemy', {
anchorX: 0.5,
anchorY: 0.5
});
s.y = CASTLE_OFFSET_Y + 120;
s.x = castleEnemy.x - 120;
enemySoldiers.push(s);
game.addChild(s);
LK.getSound('unit_leave_castle').play();
updateGui();
}
}
}
// If not upgraded, skip archer phase (do nothing)
} else if (advType === 2) {
// Wall
for (var i = 0; i < advToSend; i++) {
if (enemyGold >= 50 && wallEnemies.length === 0) {
deployWallEnemy();
}
}
}
enemyAdvancedPhase.step++;
// Allow upgrades at any time if enough gold and not upgraded
if (!castleEnemy.hasUpgrade && enemyGold >= 350) {
enemyGold -= 350;
castleEnemy.upgrade();
updateGui();
}
// Second upgrade logic (after player upgrades or 10 units lost)
if (castleEnemy.hasUpgrade && !castleEnemy.hasUpgrade2 && !castleEnemy._upgrade2Started && (castlePlayer.hasUpgrade || enemyUnitsRemoved >= 10)) {
castleEnemy._upgrade2Started = true;
enemyGold -= 800;
castleEnemy.hasUpgrade2 = true;
castleEnemy.health = 2500;
castleEnemy.attack = 4 + Math.floor(Math.random() * 3); // 4-6
castleEnemy.arrowCooldownMax = 40;
GOLD_PER_TICK = GOLD_PER_TICK + 5;
updateGui();
return;
}
}, enemyDeployInterval);
gameInputEnabled = true;
diffOverlay.destroy();
// Show solder button in the middle section (archerBtn will appear after upgrade)
archerBtn.visible = false;
solderBtn.visible = true;
solderBtnLabel.visible = true;
wallBtn.visible = true;
wallBtnLabel.visible = true;
}
// Add touch/click handlers for buttons
btnEasy.down = function (x, y, obj) {
selectDifficulty("easy");
};
btnNormal.down = function (x, y, obj) {
selectDifficulty("normal");
};
btnHard.down = function (x, y, obj) {
selectDifficulty("hard");
};
// --- State variables ---
// (removed, handled by castlePlayer.health and castleEnemy.health)
var playerGold = GOLD_INIT;
var enemyGold = GOLD_INIT;
var playerSoldiers = [];
var enemySoldiers = [];
// --- Custom score tracking variables ---
var scoreStats = {
solderEnemyKills: 0,
archerEnemyKills: 0,
wallEnemyDestroyed: 0,
solderPlayerDeaths: 0,
archerPlayerDeaths: 0,
wallPlayerDestroyed: 0,
castlePlayerFullHealth: 0,
// 1 if full health at win
castlePlayerHealthLost: 0,
// amount lost
castlePlayerNoDamageGiven: 0 // 1 if no damage given to enemy
};
// --- Game over state variables ---
var gameOverState = null; // 'win' or 'lose'
var gameOverOverlay = null;
var gameOverTimer = null;
var pointsCalculated = false;
var totalPoints = 0;
// --- Castles ---
var castlePlayer = new Castle();
castlePlayer.init('player', CASTLE_OFFSET_X, CASTLE_OFFSET_Y);
game.addChild(castlePlayer);
var castleEnemy = new Castle();
castleEnemy.init('enemy', GAME_W - CASTLE_OFFSET_X, CASTLE_OFFSET_Y);
game.addChild(castleEnemy);
// --- Enemy castle health display above castle ---
var castleEnemyHealthTxt = new Text2(castleEnemy.health + '', {
size: 60,
fill: '#fff',
font: "PressStart2P,Pixel,monospace"
});
castleEnemyHealthTxt.anchor.set(0.5, 1.2);
castleEnemyHealthTxt.x = castleEnemy.x;
castleEnemyHealthTxt.y = castleEnemy.y - 260; // above the castle
game.addChild(castleEnemyHealthTxt);
// --- Player castle unit cost labels above castle ---
// --- Player castle upgrade cost label under player castle ---
var playerCastleUpgradeTxt = new Text2("YOU NEED 350 GOLD\nTO UPGRADE.", {
size: 54,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
playerCastleUpgradeTxt.anchor.set(0.5, -0.2); // anchor above the text baseline
playerCastleUpgradeTxt.x = castlePlayer.x + 180;
playerCastleUpgradeTxt.y = castlePlayer.y + 320; // under the castle
game.addChild(playerCastleUpgradeTxt);
// --- Player castle second upgrade cost label (800 gold, only after first upgrade) ---
var playerCastleUpgrade2Txt = new Text2("YOU NEED 800 GOLD\nTO UPGRADE AGAIN.", {
size: 54,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
playerCastleUpgrade2Txt.anchor.set(0.5, -0.2);
playerCastleUpgrade2Txt.x = castlePlayer.x + 180;
playerCastleUpgrade2Txt.y = castlePlayer.y + 320;
playerCastleUpgrade2Txt.visible = false;
game.addChild(playerCastleUpgrade2Txt);
// --- Track second upgrade state for both castles ---
castlePlayer.hasUpgrade2 = false;
castleEnemy.hasUpgrade2 = false; // After 800 gold upgrade, mine-like feature is enabled for enemy
// --- Left side: archer_button and solder_button side by side, visible on screen ---
var leftButtonsY = GAME_H / 2 + 100;
var leftButtonSpacing = 40;
var leftButtonStartX = 120 + 75; // 120px margin + half button width (150/2)
// Archer button (leftmost)
var archerBtn = LK.getAsset('archer_button', {
anchorX: 0.5,
anchorY: 0.5,
x: leftButtonStartX,
y: leftButtonsY
});
archerBtn.visible = false;
game.addChild(archerBtn);
// Add "150 G" label under archerBtn, but only show after castle is upgraded
var archerBtnLabel = new Text2("150 G", {
size: 48,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
archerBtnLabel.anchor.set(0.5, 0);
archerBtnLabel.x = archerBtn.x;
archerBtnLabel.y = archerBtn.y + 90; // 90px below center of button (button is 150px tall)
archerBtnLabel.visible = false;
game.addChild(archerBtnLabel);
// Solder button (right of archer)
var solderBtn = LK.getAsset('solder_button', {
anchorX: 0.5,
anchorY: 0.5,
x: leftButtonStartX + 150 + leftButtonSpacing,
// 150 is button width
y: leftButtonsY
});
solderBtn.visible = false;
game.addChild(solderBtn);
// Add "100 G" label under solderBtn
var solderBtnLabel = new Text2("100 G", {
size: 48,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
solderBtnLabel.anchor.set(0.5, 0);
solderBtnLabel.x = solderBtn.x;
solderBtnLabel.y = solderBtn.y + 90; // 90px below center of button (button is 150px tall)
solderBtnLabel.visible = false;
game.addChild(solderBtnLabel);
// Wall button (right of solderBtn)
var wallBtn = LK.getAsset('wall_button', {
anchorX: 0.5,
anchorY: 0.5,
x: solderBtn.x + 150 + leftButtonSpacing,
y: leftButtonsY
});
wallBtn.visible = false;
game.addChild(wallBtn);
// Add "50 G" label under wallBtn
var wallBtnLabel = new Text2("50 G", {
size: 48,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
wallBtnLabel.anchor.set(0.5, 0);
wallBtnLabel.x = wallBtn.x;
wallBtnLabel.y = wallBtn.y + 90;
wallBtnLabel.visible = false;
game.addChild(wallBtnLabel);
// --- Healing Tent Button and 250G label (appear after 850 gold upgrade) ---
var healingTentBtn = LK.getAsset('HEALING_TENT_KEY_PLAYER', {
anchorX: 0.5,
anchorY: 0.5,
x: wallBtn.x + 150 + leftButtonSpacing,
y: leftButtonsY
});
healingTentBtn.visible = false;
game.addChild(healingTentBtn);
var healingTentLabel = new Text2("250 G", {
size: 48,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
healingTentLabel.anchor.set(0.5, 0);
healingTentLabel.x = healingTentBtn.x;
healingTentLabel.y = healingTentBtn.y + 90;
healingTentLabel.visible = false;
game.addChild(healingTentLabel);
// --- Healing tent state ---
var healingTentPlayer = null;
var healingTentTimer = null;
var healingTentCountdownTxt = null;
// --- Enemy healing tent state ---
var healingTentEnemy = null;
var healingTentEnemyTimer = null;
var healingTentEnemyCountdownTxt = null;
var healingTentEnemyActive = false;
var healingTentEnemyNextReady = false;
var healingTentEnemyCooldownTimer = null;
var healingTentEnemyCountdown = 0;
var healingTentEnemyBoostActive = false;
var healingTentEnemyLastTick = 0;
// --- Healing tent purchase and placement ---
healingTentBtn.down = function (x, y, obj) {
if (!gameInputEnabled) return;
if (playerGold < 250) {
LK.getSound('dont-click').play();
return;
}
if (healingTentPlayer) return; // Only one tent at a time
playerGold -= 250;
updateGui();
// Find position between player castle and wall (or default if no wall)
var tentX;
var tentY = CASTLE_OFFSET_Y + 120 + 120; // Move tent further down (120px)
if (wallPlayers.length > 0) {
// Place between castle and first wall
var wall = wallPlayers[0];
tentX = (castlePlayer.x + wall.x) / 2;
} else {
// Place at 1/3 between castle and enemy castle
tentX = castlePlayer.x + (castleEnemy.x - castlePlayer.x) / 3;
}
healingTentPlayer = LK.getAsset('HEALING_TENT_PLAYER', {
anchorX: 0.5,
anchorY: 0.5,
x: tentX,
y: tentY
});
// Add health text label above healing tent (use 30 as "health" for tent, or just show "30" for 30s)
if (healingTentPlayer.healthTxt && typeof healingTentPlayer.healthTxt.destroy === "function") {
healingTentPlayer.healthTxt.destroy();
}
healingTentPlayer.healthTxt = new Text2("30", {
size: 38,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
healingTentPlayer.healthTxt.anchor.set(0.5, 1.2);
healingTentPlayer.healthTxt.x = 0;
healingTentPlayer.healthTxt.y = -170;
healingTentPlayer.addChild(healingTentPlayer.healthTxt);
game.addChild(healingTentPlayer);
// Ensure tent is in front of all other units by re-adding as last child
if (typeof healingTentPlayer !== "undefined" && _typeof(healingTentPlayer.parent) === "object") {
healingTentPlayer.parent.removeChild(healingTentPlayer);
game.addChild(healingTentPlayer);
}
// Remove previous countdown if any
if (healingTentCountdownTxt && typeof healingTentCountdownTxt.destroy === "function") {
healingTentCountdownTxt.destroy();
}
// Create countdown text below tent
healingTentCountdownTxt = new Text2("30", {
size: 60,
fill: 0xFF0000,
font: "PressStart2P,Pixel,monospace"
});
healingTentCountdownTxt.anchor.set(0.5, 0);
healingTentCountdownTxt.x = tentX;
healingTentCountdownTxt.y = tentY + 170; // below tent (tent is 300px tall)
game.addChild(healingTentCountdownTxt);
// Remove previous timer if any
if (healingTentTimer) {
LK.clearInterval(healingTentTimer);
healingTentTimer = null;
}
var tentCountdown = 30;
healingTentCountdownTxt.setText(tentCountdown + "");
healingTentTimer = LK.setInterval(function () {
tentCountdown--;
if (tentCountdown >= 0) {
healingTentCountdownTxt.setText(tentCountdown + "");
if (healingTentPlayer && healingTentPlayer.healthTxt) {
healingTentPlayer.healthTxt.setText(tentCountdown + "");
}
}
if (tentCountdown <= 0) {
// Remove tent and countdown
if (healingTentPlayer && typeof healingTentPlayer.destroy === "function") {
if (healingTentPlayer.healthTxt && typeof healingTentPlayer.healthTxt.destroy === "function") {
healingTentPlayer.healthTxt.destroy();
}
healingTentPlayer.destroy();
}
healingTentPlayer = null;
if (healingTentCountdownTxt && typeof healingTentCountdownTxt.destroy === "function") {
healingTentCountdownTxt.destroy();
}
healingTentCountdownTxt = null;
if (healingTentTimer) {
LK.clearInterval(healingTentTimer);
healingTentTimer = null;
}
}
}, 1000);
};
// --- Walls array to track all wall_player objects ---
var wallPlayers = [];
// --- Walls array to track all wall_enemy objects ---
var wallEnemies = [];
// Deduct 50 gold from player when wallBtn is pressed
wallBtn.down = function (x, y, obj) {
if (!gameInputEnabled) return;
if (playerGold >= 50) {
playerGold -= 50;
updateGui();
// Place wall_player in the middle of the two castles
var wallX = (castlePlayer.x + castleEnemy.x) / 2;
var wallY = CASTLE_OFFSET_Y + 120; // align with soldiers' path
var wallPlayer = LK.getAsset('wall_player', {
anchorX: 0.5,
anchorY: 0.5,
x: wallX,
y: wallY
});
// Wall health: 400 base, +200 per castlePlayer upgrade
var wallBaseHealth = 400 + (castlePlayer.hasUpgrade ? 200 : 0) + (castlePlayer.hasUpgrade2 ? 200 : 0);
wallPlayer.health = wallBaseHealth;
wallPlayer.maxHealth = wallBaseHealth;
wallPlayer.destroyed = false;
// Add health text label above wall
if (wallPlayer.healthTxt && typeof wallPlayer.healthTxt.destroy === "function") {
wallPlayer.healthTxt.destroy();
}
wallPlayer.healthTxt = new Text2(Math.round(wallPlayer.health) + "", {
size: 38,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
wallPlayer.healthTxt.anchor.set(0.5, 1.2);
wallPlayer.healthTxt.x = 0;
wallPlayer.healthTxt.y = -140;
wallPlayer.addChild(wallPlayer.healthTxt);
game.addChild(wallPlayer);
wallPlayers.push(wallPlayer);
} else {
LK.getSound('dont-click').play();
}
};
// --- Enemy AI: Deploy wall_enemy if enough gold and no wall exists ---
// Track total wall_enemy deployed since game start for enemy 2nd upgrade
var enemyWallEnemyDeployCount = 0;
function deployWallEnemy() {
// Only one wall_enemy at a time
if (wallEnemies.length > 0) return false;
if (enemyGold < 50) return false;
enemyGold -= 50;
updateGui();
var wallX = (castlePlayer.x + castleEnemy.x) / 2;
var wallY = CASTLE_OFFSET_Y + 120;
var wallEnemy = LK.getAsset('wall_enemy', {
anchorX: 0.5,
anchorY: 0.5,
x: wallX,
y: wallY
});
// Wall health: 400 base, +200 per castleEnemy upgrade
var wallBaseHealth = 400 + (castleEnemy.hasUpgrade ? 200 : 0) + (castleEnemy.hasUpgrade2 ? 200 : 0);
wallEnemy.health = wallBaseHealth;
wallEnemy.maxHealth = wallBaseHealth;
wallEnemy.destroyed = false;
// Add health text label above wall_enemy
if (wallEnemy.healthTxt && typeof wallEnemy.healthTxt.destroy === "function") {
wallEnemy.healthTxt.destroy();
}
wallEnemy.healthTxt = new Text2(Math.round(wallEnemy.health) + "", {
size: 38,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
wallEnemy.healthTxt.anchor.set(0.5, 1.2);
wallEnemy.healthTxt.x = 0;
wallEnemy.healthTxt.y = -140;
wallEnemy.addChild(wallEnemy.healthTxt);
game.addChild(wallEnemy);
wallEnemies.push(wallEnemy);
// Increment wall_enemy deploy count for enemy
enemyWallEnemyDeployCount++;
// If enemy has not upgraded, and has not yet started upgrade, and wall_enemy count >= 0, do first upgrade automatically (even if gold goes negative)
if (!castleEnemy.hasUpgrade && enemyWallEnemyDeployCount >= 0 && !castleEnemy._upgradeStarted) {
castleEnemy._upgradeStarted = true;
enemyGold -= 350;
castleEnemy.upgrade();
updateGui();
}
// If enemy has first upgrade, not yet second, and deployed 15 wall_enemy, do 2nd upgrade automatically (even if gold goes negative)
if (castleEnemy.hasUpgrade && !castleEnemy.hasUpgrade2 && enemyWallEnemyDeployCount >= 15 && !castleEnemy._upgrade2Started) {
castleEnemy._upgrade2Started = true;
enemyGold -= 800;
castleEnemy.hasUpgrade2 = true;
// Apply second upgrade effects (same as player)
castleEnemy.health = 2500;
castleEnemy.attack = 4 + Math.floor(Math.random() * 3); // 4-6
castleEnemy.arrowCooldownMax = 40; // faster arrows
// --- MINE-LIKE FEATURE: Increase enemy gold income after 2nd upgrade ---
GOLD_PER_TICK = GOLD_PER_TICK + 5; // Increase gold income for both, or you can use a separate enemyGoldPerTick if needed
updateGui();
}
return true;
}
// --- GUI: Health and Gold displays ---
var playerHealthTxt = new Text2('1000', {
size: 70,
fill: '#fff',
font: "PressStart2P,Pixel,monospace"
});
playerHealthTxt.anchor.set(0, 0);
LK.gui.top.addChild(playerHealthTxt);
// Player LIFE label
var playerLifeLabel = new Text2('LIFE', {
size: 38,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
playerLifeLabel.anchor.set(0, 0);
LK.gui.top.addChild(playerLifeLabel);
var playerGoldTxt = new Text2('200', {
size: 60,
fill: '#ffe600',
font: "PressStart2P,Pixel,monospace"
});
playerGoldTxt.anchor.set(0, 0);
LK.gui.top.addChild(playerGoldTxt);
// Add soldier/cavalry cost labels to the left of the tan score
// Removed soldier/cavalry cost labels from GUI
// Player GOLD label
var playerGoldLabel = new Text2('GOLD', {
size: 38,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
playerGoldLabel.anchor.set(0, 0);
LK.gui.top.addChild(playerGoldLabel);
var enemyHealthTxt = new Text2('1000', {
size: 70,
fill: '#fff',
font: "PressStart2P,Pixel,monospace"
});
enemyHealthTxt.anchor.set(1, 0);
LK.gui.top.addChild(enemyHealthTxt);
// Enemy LIFE label
var enemyLifeLabel = new Text2('LIFE', {
size: 38,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
enemyLifeLabel.anchor.set(1, 0);
LK.gui.top.addChild(enemyLifeLabel);
var enemyGoldTxt = new Text2('200', {
size: 60,
fill: '#ffe600',
font: "PressStart2P,Pixel,monospace"
});
enemyGoldTxt.anchor.set(1, 0);
LK.gui.top.addChild(enemyGoldTxt);
// Enemy GOLD label
var enemyGoldLabel = new Text2('GOLD', {
size: 38,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
enemyGoldLabel.anchor.set(1, 0);
LK.gui.top.addChild(enemyGoldLabel);
// --- Win Counter (Rounds Won) ---
// Use storage plugin for persistence
var roundsWon = storage.roundsWon || 0;
var roundsWonTxt = new Text2('Rounds Won: ' + roundsWon, {
size: 60,
fill: '#00ff00',
font: "PressStart2P,Pixel,monospace"
});
roundsWonTxt.anchor.set(1, 0);
LK.gui.top.addChild(roundsWonTxt);
roundsWonTxt.x = LK.gui.width - 40;
roundsWonTxt.y = 220;
// Position GUI elements
playerHealthTxt.x = 120;
playerHealthTxt.y = 40;
playerLifeLabel.x = playerHealthTxt.x + playerHealthTxt.width + 18;
playerLifeLabel.y = playerHealthTxt.y + 10;
playerGoldTxt.x = 120;
playerGoldTxt.y = 120;
playerGoldLabel.x = playerGoldTxt.x + playerGoldTxt.width + 18;
playerGoldLabel.y = playerGoldTxt.y + 8;
// (Removed: Position soldier/cavalry cost labels to the left of the tan score)
enemyHealthTxt.x = LK.gui.width - 120;
enemyHealthTxt.y = 40;
enemyLifeLabel.x = enemyHealthTxt.x - enemyHealthTxt.width - 18;
enemyLifeLabel.y = enemyHealthTxt.y + 10;
enemyGoldTxt.x = LK.gui.width - 120;
enemyGoldTxt.y = 120;
enemyGoldLabel.x = enemyGoldTxt.x - enemyGoldTxt.width - 18;
enemyGoldLabel.y = enemyGoldTxt.y + 8;
// --- Gold income timer ---
var goldTimer = LK.setInterval(function () {
// Only increase gold if difficulty has been chosen
if (!gameInputEnabled) return;
if (speedMode) {
playerGold += GOLD_PER_TICK * 3;
enemyGold += GOLD_PER_TICK * 3;
} else {
playerGold += GOLD_PER_TICK;
enemyGold += GOLD_PER_TICK;
}
updateGui();
}, GOLD_TICK_MS);
// --- GUI update function ---
function updateGui() {
playerHealthTxt.setText(castlePlayer.health);
playerLifeLabel.x = playerHealthTxt.x + playerHealthTxt.width + 18;
playerLifeLabel.y = playerHealthTxt.y + 10;
playerGoldTxt.setText(playerGold);
playerGoldLabel.x = playerGoldTxt.x + playerGoldTxt.width + 18;
playerGoldLabel.y = playerGoldTxt.y + 8;
enemyHealthTxt.setText(castleEnemy.health);
enemyLifeLabel.x = enemyHealthTxt.x - enemyHealthTxt.width - 18;
enemyLifeLabel.y = enemyHealthTxt.y + 10;
enemyGoldTxt.setText(enemyGold);
enemyGoldLabel.x = enemyGoldTxt.x - enemyGoldTxt.width - 18;
enemyGoldLabel.y = enemyGoldTxt.y + 8;
// Update enemy castle health text and position
castleEnemyHealthTxt.setText(castleEnemy.health);
castleEnemyHealthTxt.x = castleEnemy.x;
castleEnemyHealthTxt.y = castleEnemy.y - 260;
}
// --- Deploy soldier function ---
function deploySoldier(team) {
// Block deployment if difficulty not chosen
if (!gameInputEnabled) return false;
var gold = team === 'player' ? playerGold : enemyGold;
var cost = team === 'player' ? SOLDIER_COST : ENEMY_SOLDIER_COST;
if (gold < cost) return false;
// Deduct gold
if (team === 'player') playerGold -= SOLDIER_COST;else enemyGold -= ENEMY_SOLDIER_COST;
// Fixed health and random attack
var health = 25;
var attack = 5 + Math.floor(Math.random() * 6); // 5-10 inclusive
// Create soldier
var s = new Soldier();
s.init(team, health, attack);
s.speed = SOLDIER_SPEED;
s.inCombat = false;
// Position
s.y = CASTLE_OFFSET_Y + 120; // Move soldiers' exit position down by 120px
if (team === 'player') {
s.x = castlePlayer.x + 120;
playerSoldiers.push(s);
} else {
s.x = castleEnemy.x - 120;
enemySoldiers.push(s);
}
game.addChild(s);
// Play sound when unit leaves the castle
LK.getSound('unit_leave_castle').play();
return true;
}
// --- Player tap to upgrade castle only (soldier deploy moved to solderBtn.down) ---
game.down = function (x, y, obj) {
// Play click sound on any touch/click
LK.getSound('click').play();
// Block input until difficulty is chosen
if (!gameInputEnabled) return;
// --- Block all upgrades in first 30 seconds ---
// (Removed: No upgrades allowed in first 30 seconds for player)
// Check if player clicked on their castle for upgrade
var dx = x - castlePlayer.x;
var dy = y - castlePlayer.y;
if (dx * dx + dy * dy < 250 * 250 && !castlePlayer.hasUpgrade && playerGold >= 350) {
playerGold -= 350;
castlePlayer.upgrade();
updateGui();
// Show archerBtn after upgrade
archerBtn.visible = true;
// Show '150 G' label under archerBtn after upgrade
archerBtnLabel.visible = true;
// Hide the first upgrade text, show the second upgrade text
if (playerCastleUpgradeTxt && typeof playerCastleUpgradeTxt.destroy === "function") {
playerCastleUpgradeTxt.visible = false;
}
playerCastleUpgrade2Txt.visible = true;
return;
}
// Second upgrade: only if first upgrade is done, not already done, and enough gold
if (dx * dx + dy * dy < 250 * 250 && castlePlayer.hasUpgrade && !castlePlayer.hasUpgrade2 && playerGold >= 800) {
playerGold -= 800;
castlePlayer.hasUpgrade2 = true;
// Apply second upgrade effects
castlePlayer.health = 2500;
castlePlayer.attack = 4 + Math.floor(Math.random() * 3); // 4-6
castlePlayer.arrowCooldownMax = 40; // faster arrows
// Update asset for second upgrade
if (castlePlayer.asset) {
castlePlayer.removeChild(castlePlayer.asset);
}
castlePlayer.asset = castlePlayer.attachAsset('castle_player_3', {
anchorX: 0.5,
anchorY: 0.5
});
updateGui();
// Hide the second upgrade text
playerCastleUpgrade2Txt.visible = false;
// Show healing tent button and 250G label
healingTentBtn.visible = true;
healingTentLabel.visible = true;
return;
}
// (Removed: deploySoldier on screen tap)
};
// --- Deploy soldier when solderBtn is pressed ---
solderBtn.down = function (x, y, obj) {
if (!gameInputEnabled) return;
if (playerGold >= SOLDIER_COST) {
deploySoldier('player');
updateGui();
} else {
LK.getSound('dont-click').play();
}
};
// --- Deploy archer_player when archerBtn is pressed (uses archer_player asset, cost 10 gold) ---
archerBtn.down = function (x, y, obj) {
if (!gameInputEnabled) return;
if (playerGold >= 150) {
playerGold -= 150;
// Create a new Soldier but use archer_player asset
var s = new Soldier();
// Set archer_player health to be twice that of soldier_player
var baseSoldierHealth = 25;
s.init('player', baseSoldierHealth * 2, 5 + Math.floor(Math.random() * 6)); // health 50, attack 5-10
s.speed = SOLDIER_SPEED * 0.8; // Already uses updated SOLDIER_SPEED, now 75% faster
s.inCombat = false;
// Remove previous asset and attach archer_player asset
if (s.asset) {
s.removeChild(s.asset);
}
s.asset = s.attachAsset('archer_player', {
anchorX: 0.5,
anchorY: 0.5
});
// Position
s.y = CASTLE_OFFSET_Y + 120;
s.x = castlePlayer.x + 120;
playerSoldiers.push(s);
game.addChild(s);
// Play sound when archer leaves the castle
LK.getSound('unit_leave_castle').play();
updateGui();
} else {
LK.getSound('dont-click').play();
}
};
// --- Enemy AI: let the enemy play like a player: send soldiers, build walls, send archers, upgrade castle, and upgrade again, in a natural order ---
// Track enemy AI state
var enemySoldierDeployCount = 0;
var enemyNextUnit = 'soldier'; // always start with soldier
var enemyTotalSoldierDeployed = 0;
var enemyUpgradeGoldReserved = 200;
var enemySoldierSinceLastWall = 0;
castleEnemy.hasUpgrade2 = false;
// Track enemy AI upgrade state
castleEnemy._upgradeStarted = false;
castleEnemy._upgrade2Started = false;
// Track archer unlock for enemy (after first upgrade)
var enemyArcherUnlocked = false;
// Track how many enemy units have been removed (killed by player)
var enemyUnitsRemoved = 0;
// Patch: Increment enemyUnitsRemoved when enemy soldier is killed (player gets gold for kill)
var _oldPlayerSoldierCombat = true;
if (!_oldPlayerSoldierCombat) {// never runs, just for context
// see player soldier combat loop
}
// Patch: Increment enemyUnitsRemoved when enemy soldier is killed
// (Find the code awarding playerGold += 10 for killing enemy soldier, and increment enemyUnitsRemoved there)
var _oldEnemyUnitsRemovedPatch = true;
// Enemy AI main loop
// --- Add a flag and timer for enemy first upgrade delay ---
var enemyFirstUpgradeAllowed = false;
LK.setTimeout(function () {
enemyFirstUpgradeAllowed = true;
}, 15000);
var enemyDeployTimer = LK.setInterval(function () {
if (!gameInputEnabled) return;
// --- Initial Phase: 0-30 seconds ---
if (LK.ticks < 1800) {
// Only send soldiers, no upgrades, no archers.
// If player sends soldiers, let enemy build walls.
// If more than 3 enemy soldiers alive, let them build walls.
// (Blowing up walls is allowed, but not archers or upgrades.)
// Only send soldiers
if (enemyGold >= ENEMY_SOLDIER_COST) {
if (deploySoldier('enemy')) {
updateGui();
enemySoldierDeployCount++;
enemyTotalSoldierDeployed++;
enemySoldierSinceLastWall++;
}
}
// If player has sent soldiers (playerSoldiers.length > 0) or more than 3 enemy soldiers alive, build wall if possible
if ((playerSoldiers.length > 0 || enemySoldiers.length > 3) && enemyGold >= 50 && wallEnemies.length === 0) {
deployWallEnemy();
}
return;
}
// --- Middle Phase: 30-60 seconds ---
if (LK.ticks >= 1800 && LK.ticks < 3600) {
// Soldier + Archer + Wall alternately, each as rolling 1-3 dice and send that many
// Do not send archers without doing the 1st upgrade
// 50% chance to upgrade if enough gold and not immediately
if (typeof enemyMiddlePhase === "undefined") {
enemyMiddlePhase = {
step: 0,
upgradeTried: false
};
}
// 0: soldier, 1: archer, 2: wall, repeat
var phaseType = enemyMiddlePhase.step % 3;
// Roll 1-3 dice for how many to send
var toSend = 1 + Math.floor(Math.random() * 3);
if (phaseType === 0) {
// Soldier
for (var i = 0; i < toSend; i++) {
if (enemyGold >= ENEMY_SOLDIER_COST) {
if (deploySoldier('enemy')) {
updateGui();
enemyTotalSoldierDeployed++;
enemySoldierSinceLastWall++;
}
}
}
} else if (phaseType === 1) {
// Archer
if (castleEnemy.hasUpgrade) {
for (var i = 0; i < toSend; i++) {
if (enemyGold >= 150) {
enemyGold -= 150;
var s = new Soldier();
var baseSoldierHealth = 25;
s.init('enemy', baseSoldierHealth * 2, 5 + Math.floor(Math.random() * 6));
s.speed = SOLDIER_SPEED * 0.8;
s.inCombat = false;
if (s.asset) {
s.removeChild(s.asset);
}
s.asset = s.attachAsset('archer_enemy', {
anchorX: 0.5,
anchorY: 0.5
});
s.y = CASTLE_OFFSET_Y + 120;
s.x = castleEnemy.x - 120;
enemySoldiers.push(s);
game.addChild(s);
LK.getSound('unit_leave_castle').play();
updateGui();
}
}
}
// If not upgraded, skip archer phase (do nothing)
} else if (phaseType === 2) {
// Wall
for (var i = 0; i < toSend; i++) {
if (enemyGold >= 50 && wallEnemies.length === 0) {
deployWallEnemy();
}
}
}
enemyMiddlePhase.step++;
// 50% chance to upgrade if enough gold and not upgraded yet, but not immediately
if (!castleEnemy.hasUpgrade && !enemyMiddlePhase.upgradeTried && enemyGold >= 350) {
if (Math.random() < 0.5) {
enemyGold -= 350;
castleEnemy.upgrade();
updateGui();
}
enemyMiddlePhase.upgradeTried = true;
}
// Second upgrade logic (after player upgrades or 10 units lost)
if (castleEnemy.hasUpgrade && !castleEnemy.hasUpgrade2 && !castleEnemy._upgrade2Started && (castlePlayer.hasUpgrade || enemyUnitsRemoved >= 10)) {
castleEnemy._upgrade2Started = true;
enemyGold -= 800;
castleEnemy.hasUpgrade2 = true;
castleEnemy.health = 2500;
castleEnemy.attack = 4 + Math.floor(Math.random() * 3); // 4-6
castleEnemy.arrowCooldownMax = 40;
GOLD_PER_TICK = GOLD_PER_TICK + 5;
updateGui();
return;
}
return;
}
// --- Advanced Phase: 60s+ ---
// Upgradeable, soldier + archer + wall alternately, dice-based, archers only after upgrade
if (typeof enemyAdvancedPhase === "undefined") {
enemyAdvancedPhase = {
step: 0
};
}
var advType = enemyAdvancedPhase.step % 3;
var advToSend = 1 + Math.floor(Math.random() * 3);
if (advType === 0) {
// Soldier
for (var i = 0; i < advToSend; i++) {
if (enemyGold >= ENEMY_SOLDIER_COST) {
if (deploySoldier('enemy')) {
updateGui();
enemyTotalSoldierDeployed++;
enemySoldierSinceLastWall++;
}
}
}
} else if (advType === 1) {
// Archer
if (castleEnemy.hasUpgrade) {
for (var i = 0; i < advToSend; i++) {
if (enemyGold >= 150) {
enemyGold -= 150;
var s = new Soldier();
var baseSoldierHealth = 25;
s.init('enemy', baseSoldierHealth * 2, 5 + Math.floor(Math.random() * 6));
s.speed = SOLDIER_SPEED * 0.8;
s.inCombat = false;
if (s.asset) {
s.removeChild(s.asset);
}
s.asset = s.attachAsset('archer_enemy', {
anchorX: 0.5,
anchorY: 0.5
});
s.y = CASTLE_OFFSET_Y + 120;
s.x = castleEnemy.x - 120;
enemySoldiers.push(s);
game.addChild(s);
LK.getSound('unit_leave_castle').play();
updateGui();
}
}
}
// If not upgraded, skip archer phase (do nothing)
} else if (advType === 2) {
// Wall
for (var i = 0; i < advToSend; i++) {
if (enemyGold >= 50 && wallEnemies.length === 0) {
deployWallEnemy();
}
}
}
enemyAdvancedPhase.step++;
// Allow upgrades at any time if enough gold and not upgraded
if (!castleEnemy.hasUpgrade && enemyGold >= 350) {
enemyGold -= 350;
castleEnemy.upgrade();
updateGui();
}
// Second upgrade logic (after player upgrades or 10 units lost)
if (castleEnemy.hasUpgrade && !castleEnemy.hasUpgrade2 && !castleEnemy._upgrade2Started && (castlePlayer.hasUpgrade || enemyUnitsRemoved >= 10)) {
castleEnemy._upgrade2Started = true;
enemyGold -= 800;
castleEnemy.hasUpgrade2 = true;
castleEnemy.health = 2500;
castleEnemy.attack = 4 + Math.floor(Math.random() * 3); // 4-6
castleEnemy.arrowCooldownMax = 40;
GOLD_PER_TICK = GOLD_PER_TICK + 5;
updateGui();
return;
}
}, 600);
// --- Arrows array ---
var arrows = [];
// --- Main update loop ---
game.update = function () {
// --- Update castles (health, arrows) ---
castlePlayer.update();
castleEnemy.update();
// --- Enemy healing tent logic ---
// Only allow after enemy's 2nd upgrade
if (castleEnemy.hasUpgrade2) {
// Start the healing tent cycle if not already started
if (!healingTentEnemyCooldownTimer && !healingTentEnemyActive) {
healingTentEnemyNextReady = true;
healingTentEnemyCountdown = 0;
healingTentEnemyLastTick = LK.ticks;
healingTentEnemyCooldownTimer = LK.setInterval(function () {
if (!healingTentEnemyActive && castleEnemy.hasUpgrade2) {
healingTentEnemyCountdown++;
if (healingTentEnemyCountdown >= 100) {
// 100 seconds
healingTentEnemyNextReady = true;
healingTentEnemyCountdown = 0;
}
}
}, 1000);
}
// If ready and not active, spawn the tent
if (healingTentEnemyNextReady && !healingTentEnemyActive) {
// Place tent between enemy castle and wall (or default if no wall)
var tentX;
var tentY = CASTLE_OFFSET_Y + 120 + 120;
if (wallEnemies.length > 0) {
var wall = wallEnemies[0];
tentX = (castleEnemy.x + wall.x) / 2;
} else {
tentX = castleEnemy.x - (castleEnemy.x - castlePlayer.x) / 3;
}
healingTentEnemy = LK.getAsset('HEALING_TENT_ENEMY', {
anchorX: 0.5,
anchorY: 0.5,
x: tentX,
y: tentY
});
// Add health text label above healing tent (use 30 as "health" for tent, or just show "30" for 30s)
if (healingTentEnemy.healthTxt && typeof healingTentEnemy.healthTxt.destroy === "function") {
healingTentEnemy.healthTxt.destroy();
}
healingTentEnemy.healthTxt = new Text2("30", {
size: 38,
fill: "#000",
font: "PressStart2P,Pixel,monospace"
});
healingTentEnemy.healthTxt.anchor.set(0.5, 1.2);
healingTentEnemy.healthTxt.x = 0;
healingTentEnemy.healthTxt.y = -170;
healingTentEnemy.addChild(healingTentEnemy.healthTxt);
game.addChild(healingTentEnemy);
// Ensure tent is in front of all other units by re-adding as last child
if (typeof healingTentEnemy !== "undefined" && healingTentEnemy.parent) {
healingTentEnemy.parent.removeChild(healingTentEnemy);
game.addChild(healingTentEnemy);
}
// Remove previous countdown if any
if (healingTentEnemyCountdownTxt && typeof healingTentEnemyCountdownTxt.destroy === "function") {
healingTentEnemyCountdownTxt.destroy();
}
// Create countdown text below tent
healingTentEnemyCountdownTxt = new Text2("30", {
size: 60,
fill: 0xFF0000,
font: "PressStart2P,Pixel,monospace"
});
healingTentEnemyCountdownTxt.anchor.set(0.5, 0);
healingTentEnemyCountdownTxt.x = tentX;
healingTentEnemyCountdownTxt.y = tentY + 170;
game.addChild(healingTentEnemyCountdownTxt);
// Remove previous timer if any
if (healingTentEnemyTimer) {
LK.clearInterval(healingTentEnemyTimer);
healingTentEnemyTimer = null;
}
var tentCountdown = 30;
healingTentEnemyCountdownTxt.setText(tentCountdown + "");
healingTentEnemyActive = true;
healingTentEnemyBoostActive = true;
healingTentEnemyTimer = LK.setInterval(function () {
tentCountdown--;
if (tentCountdown >= 0) {
healingTentEnemyCountdownTxt.setText(tentCountdown + "");
if (healingTentEnemy && healingTentEnemy.healthTxt) {
healingTentEnemy.healthTxt.setText(tentCountdown + "");
}
}
if (tentCountdown <= 0) {
// Remove tent and countdown
if (healingTentEnemy && typeof healingTentEnemy.destroy === "function") {
if (healingTentEnemy.healthTxt && typeof healingTentEnemy.healthTxt.destroy === "function") {
healingTentEnemy.healthTxt.destroy();
}
healingTentEnemy.destroy();
}
healingTentEnemy = null;
if (healingTentEnemyCountdownTxt && typeof healingTentEnemyCountdownTxt.destroy === "function") {
healingTentEnemyCountdownTxt.destroy();
}
healingTentEnemyCountdownTxt = null;
if (healingTentEnemyTimer) {
LK.clearInterval(healingTentEnemyTimer);
healingTentEnemyTimer = null;
}
healingTentEnemyActive = false;
healingTentEnemyBoostActive = false;
healingTentEnemyNextReady = false;
healingTentEnemyCountdown = 0;
}
}, 1000);
}
} else {
// If enemy loses 2nd upgrade, clear timers and tent
if (healingTentEnemyCooldownTimer) {
LK.clearInterval(healingTentEnemyCooldownTimer);
healingTentEnemyCooldownTimer = null;
}
if (healingTentEnemyTimer) {
LK.clearInterval(healingTentEnemyTimer);
healingTentEnemyTimer = null;
}
if (healingTentEnemy && typeof healingTentEnemy.destroy === "function") {
if (healingTentEnemy.healthTxt && typeof healingTentEnemy.healthTxt.destroy === "function") {
healingTentEnemy.healthTxt.destroy();
}
healingTentEnemy.destroy();
}
healingTentEnemy = null;
if (healingTentEnemyCountdownTxt && typeof healingTentEnemyCountdownTxt.destroy === "function") {
healingTentEnemyCountdownTxt.destroy();
}
healingTentEnemyCountdownTxt = null;
healingTentEnemyActive = false;
healingTentEnemyBoostActive = false;
healingTentEnemyNextReady = false;
healingTentEnemyCountdown = 0;
}
// --- Apply healing tent effect: 15% health boost to all player units while tent is present ---
if (healingTentPlayer) {
// Always keep healing tent in front of all units, even those created after it
if (healingTentPlayer.parent) {
healingTentPlayer.parent.removeChild(healingTentPlayer);
game.addChild(healingTentPlayer);
}
for (var i = 0; i < playerSoldiers.length; i++) {
var s = playerSoldiers[i];
if (!s._healingTentBoosted) {
s.health = Math.ceil(s.health * 1.15);
s._healingTentBoosted = true;
}
}
} else {
// Remove boost flag if tent is gone (do not revert health, only allow boost once per tent appearance)
for (var i = 0; i < playerSoldiers.length; i++) {
var s = playerSoldiers[i];
if (s._healingTentBoosted) {
s._healingTentBoosted = false;
}
}
}
// --- Apply healing tent effect: 15% health boost to all enemy units while enemy tent is present ---
if (healingTentEnemyBoostActive) {
// Always keep healing tent in front of all units, even those created after it
if (healingTentEnemy && healingTentEnemy.parent) {
healingTentEnemy.parent.removeChild(healingTentEnemy);
game.addChild(healingTentEnemy);
}
for (var i = 0; i < enemySoldiers.length; i++) {
var s = enemySoldiers[i];
if (!s._healingTentEnemyBoosted) {
s.health = Math.ceil(s.health * 1.15);
s._healingTentEnemyBoosted = true;
}
}
} else {
// Remove boost flag if tent is gone (do not revert health, only allow boost once per tent appearance)
for (var i = 0; i < enemySoldiers.length; i++) {
var s = enemySoldiers[i];
if (s._healingTentEnemyBoosted) {
s._healingTentEnemyBoosted = false;
}
}
}
// --- Player soldiers ---
for (var i = playerSoldiers.length - 1; i >= 0; i--) {
var s = playerSoldiers[i];
s.inCombat = false;
// --- Check for wall_enemy collision ---
var wallEngaged = false;
for (var w = 0; w < wallEnemies.length; w++) {
var wall = wallEnemies[w];
if (wall.destroyed) continue;
// Simple collision: check if close enough horizontally and vertically
if (Math.abs(s.x - wall.x) < 80 && Math.abs(s.y - wall.y) < 180) {
s.inCombat = true;
wallEngaged = true;
// Player attacks wall only if at least 2 player soldiers are engaging this wall
if (LK.ticks % 12 === 0) {
var engagedCount = 0;
for (var ps = 0; ps < playerSoldiers.length; ps++) {
var psold = playerSoldiers[ps];
if (Math.abs(psold.x - wall.x) < 80 && Math.abs(psold.y - wall.y) < 180 && !psold.destroyed) {
engagedCount++;
}
}
if (engagedCount >= 2) {
wall.health -= s.attack;
if (typeof wall.health === "number") {
wall.health = Math.round(wall.health);
}
}
}
// If wall destroyed, remove from game and array
if (wall.healthTxt) {
wall.healthTxt.setText(Math.max(0, Math.round(wall.health)) + "");
}
if (wall.health <= 0) {
scoreStats.wallEnemyDestroyed++;
wall.destroyed = true;
if (wall.healthTxt && typeof wall.healthTxt.destroy === "function") {
wall.healthTxt.destroy();
}
if (typeof wall.destroy === "function") wall.destroy();
wallEnemies.splice(w, 1);
w--;
}
break; // Only engage one wall at a time
}
}
if (wallEngaged) continue;
// Check for enemy soldier in range
var engaged = false;
// Prevent player soldiers from damaging enemy units if any wall_enemy is present and not destroyed and player soldier is engaged with it
var wallEnemyBlocking = false;
for (var w = 0; w < wallEnemies.length; w++) {
var wall = wallEnemies[w];
if (!wall.destroyed && Math.abs(s.x - wall.x) < 80 && Math.abs(s.y - wall.y) < 180) {
wallEnemyBlocking = true;
break;
}
}
for (var j = 0; j < enemySoldiers.length; j++) {
var e = enemySoldiers[j];
// If close enough (overlap)
if (Math.abs(s.x - e.x) < 80) {
// Engage in combat
s.inCombat = true;
e.inCombat = true;
// Both attack each other
if (LK.ticks % 12 === 0) {
// Only allow damage if not blocked by wall_enemy
if (!wallEnemyBlocking) {
// Attack every 12 frames (~5 times/sec)
e.health -= s.attack;
s.health -= e.attack;
}
}
// Remove dead soldiers
if (e.health <= 0) {
// Determine if enemy is archer or soldier
if (e.asset && e.asset.assetId === 'archer_enemy') {
scoreStats.archerEnemyKills++;
} else {
scoreStats.solderEnemyKills++;
}
e.destroy();
enemySoldiers.splice(j, 1);
j--;
// Award 10 gold to player for killing enemy soldier
playerGold += 10;
enemyUnitsRemoved = (typeof enemyUnitsRemoved === "number" ? enemyUnitsRemoved : 0) + 1; // increment removed count
updateGui();
}
if (s.health <= 0) {
// Determine if player is archer or soldier
if (s.asset && s.asset.assetId === 'archer_player') {
scoreStats.archerPlayerDeaths++;
} else {
scoreStats.solderPlayerDeaths++;
}
s.destroy();
playerSoldiers.splice(i, 1);
i--;
// Award 10 gold to enemy for killing player soldier
enemyGold += 10;
updateGui();
engaged = true;
break;
}
engaged = true;
break;
}
}
if (engaged) continue;
// If not in combat, check if at enemy castle
if (Math.abs(s.x - castleEnemy.x) < 120) {
s.inCombat = true;
if (LK.ticks % 12 === 0) {
castleEnemy.health -= s.attack;
if (castleEnemy.health < 0) castleEnemy.health = 0;
updateGui();
}
// Remove soldier if castle destroyed
if (castleEnemy.health <= 0) {
s.destroy();
playerSoldiers.splice(i, 1);
i--;
}
}
}
// --- Enemy soldiers ---
for (var i = enemySoldiers.length - 1; i >= 0; i--) {
var s = enemySoldiers[i];
s.inCombat = false;
// --- Check for wall_player collision ---
var wallEngaged = false;
for (var w = 0; w < wallPlayers.length; w++) {
var wall = wallPlayers[w];
if (wall.destroyed) continue;
// Simple collision: check if close enough horizontally and vertically
if (Math.abs(s.x - wall.x) < 80 && Math.abs(s.y - wall.y) < 180) {
s.inCombat = true;
wallEngaged = true;
// Enemy attacks wall only if at least 2 enemy soldiers are engaging this wall
if (LK.ticks % 12 === 0) {
var engagedCount = 0;
for (var es = 0; es < enemySoldiers.length; es++) {
var esold = enemySoldiers[es];
if (Math.abs(esold.x - wall.x) < 80 && Math.abs(esold.y - wall.y) < 180 && !esold.destroyed) {
engagedCount++;
}
}
if (engagedCount >= 2) {
wall.health -= s.attack;
if (typeof wall.health === "number") {
wall.health = Math.round(wall.health);
}
}
}
// If wall destroyed, remove from game and array
if (wall.healthTxt) {
wall.healthTxt.setText(Math.max(0, Math.round(wall.health)) + "");
}
if (wall.health <= 0) {
scoreStats.wallPlayerDestroyed++;
wall.destroyed = true;
if (wall.healthTxt && typeof wall.healthTxt.destroy === "function") {
wall.healthTxt.destroy();
}
if (typeof wall.destroy === "function") wall.destroy();
wallPlayers.splice(w, 1);
w--;
}
break; // Only engage one wall at a time
}
}
if (wallEngaged) continue;
// Check for player soldier in range
var engaged = false;
// Prevent enemy soldiers from damaging player units if any wall_player is present and not destroyed and enemy soldier is engaged with it
var wallPlayerBlocking = false;
for (var w = 0; w < wallPlayers.length; w++) {
var wall = wallPlayers[w];
if (!wall.destroyed && Math.abs(s.x - wall.x) < 80 && Math.abs(s.y - wall.y) < 180) {
wallPlayerBlocking = true;
break;
}
}
for (var j = 0; j < playerSoldiers.length; j++) {
var e = playerSoldiers[j];
if (Math.abs(s.x - e.x) < 80) {
s.inCombat = true;
e.inCombat = true;
if (LK.ticks % 12 === 0) {
// Only allow damage if not blocked by wall_player
if (!wallPlayerBlocking) {
e.health -= s.attack;
s.health -= e.attack;
}
}
if (e.health <= 0) {
e.destroy();
playerSoldiers.splice(j, 1);
j--;
// Award 10 gold to enemy for killing player soldier
enemyGold += 10;
updateGui();
}
if (s.health <= 0) {
s.destroy();
enemySoldiers.splice(i, 1);
i--;
// Award 10 gold to player for killing enemy soldier
playerGold += 10;
updateGui();
engaged = true;
break;
}
engaged = true;
break;
}
}
if (engaged) continue;
// If not in combat, check if at player castle
if (Math.abs(s.x - castlePlayer.x) < 120) {
s.inCombat = true;
if (LK.ticks % 12 === 0) {
castlePlayer.health -= s.attack;
if (castlePlayer.health < 0) castlePlayer.health = 0;
updateGui();
}
if (castlePlayer.health <= 0) {
s.destroy();
enemySoldiers.splice(i, 1);
i--;
}
}
}
// --- Move soldiers ---
for (var i = 0; i < playerSoldiers.length; i++) {
playerSoldiers[i].update();
}
for (var i = 0; i < enemySoldiers.length; i++) {
enemySoldiers[i].update();
}
// --- Update arrows ---
for (var i = arrows.length - 1; i >= 0; i--) {
var a = arrows[i];
a.update();
if (a.destroyed) {
arrows.splice(i, 1);
}
}
// --- Check win/lose ---
if (castlePlayer.health <= 0 && !gameOverState) {
gameOverState = 'lose';
showCustomGameOver();
return;
}
if (castleEnemy.health <= 0 && !gameOverState) {
gameOverState = 'win';
// --- Custom score stats for win ---
scoreStats.castlePlayerFullHealth = castlePlayer.health === CASTLE_HEALTH_INIT || castlePlayer.health === 2500 ? 1 : 0;
scoreStats.castlePlayerHealthLost = castlePlayer.health < (castlePlayer.hasUpgrade2 ? 2500 : CASTLE_HEALTH_INIT) ? (castlePlayer.hasUpgrade2 ? 2500 : CASTLE_HEALTH_INIT) - castlePlayer.health : 0;
scoreStats.castlePlayerNoDamageGiven = castlePlayer.health === (castlePlayer.hasUpgrade2 ? 2500 : CASTLE_HEALTH_INIT) && scoreStats.solderPlayerDeaths === 0 && scoreStats.archerPlayerDeaths === 0 && scoreStats.wallPlayerDestroyed === 0 ? 1 : 0;
// Increase and persist rounds won
roundsWon = (storage.roundsWon || 0) + 1;
storage.roundsWon = roundsWon;
roundsWonTxt.setText('Rounds Won: ' + roundsWon);
showCustomGameOver();
return;
}
};
// --- Custom game over function ---
function showCustomGameOver() {
// Create overlay
gameOverOverlay = new Container();
gameOverOverlay.zIndex = 20000;
// Background
var overlayBg = new Text2(" ", {
size: 10,
fill: 0x000000
});
overlayBg.width = GAME_W;
overlayBg.height = GAME_H;
overlayBg.alpha = 0.85;
overlayBg.x = 0;
overlayBg.y = 0;
gameOverOverlay.addChild(overlayBg);
// Main message
var mainMessage;
if (gameOverState === 'win') {
mainMessage = new Text2("YOU WON THE GAME", {
size: 120,
fill: 0x00FF00,
font: "PressStart2P,Pixel,monospace"
});
LK.effects.flashScreen(0x00ff00, 1000);
} else {
mainMessage = new Text2("YOU LOST THE GAME", {
size: 120,
fill: 0xFF0000,
font: "PressStart2P,Pixel,monospace"
});
LK.effects.flashScreen(0xff0000, 1000);
}
mainMessage.anchor.set(0.5, 0.5);
mainMessage.x = GAME_W / 2;
mainMessage.y = GAME_H / 2 - 200;
gameOverOverlay.addChild(mainMessage);
game.addChild(gameOverOverlay);
// Start timer sequence
gameOverTimer = LK.setTimeout(function () {
if (gameOverState === 'win') {
showPointsEarned();
} else {
// For lose, wait 5 seconds then go to login screen
gameOverTimer = LK.setTimeout(function () {
goToLoginScreen();
}, 5000);
}
}, 5000);
}
function showPointsEarned() {
if (pointsCalculated) return;
pointsCalculated = true;
// Calculate points
totalPoints = 0;
totalPoints += scoreStats.solderEnemyKills * 10;
totalPoints += scoreStats.archerEnemyKills * 25;
totalPoints += scoreStats.wallEnemyDestroyed * 50;
if (scoreStats.solderPlayerDeaths < 50) totalPoints += 1000;
if (scoreStats.archerPlayerDeaths < 50) totalPoints += 3000;
if (scoreStats.wallPlayerDestroyed < 20) totalPoints += 2000;
if (scoreStats.castlePlayerFullHealth) totalPoints += 5000;
if (scoreStats.castlePlayerHealthLost > 0) totalPoints += scoreStats.castlePlayerHealthLost;
if (scoreStats.castlePlayerNoDamageGiven) totalPoints += 10000;
// Store total points
var allTimePoints = storage.allTimePoints || 0;
allTimePoints += totalPoints;
storage.allTimePoints = allTimePoints;
// Create points text
var pointsText = new Text2("POINTS EARNED: " + totalPoints, {
size: 100,
fill: 0xFFD700,
font: "PressStart2P,Pixel,monospace"
});
pointsText.anchor.set(0.5, 0.5);
pointsText.x = GAME_W / 2;
pointsText.y = GAME_H / 2 + 100;
pointsText.alpha = 0;
gameOverOverlay.addChild(pointsText);
// Animate points in
tween(pointsText, {
alpha: 1
}, {
duration: 1000
});
// Wait 5 seconds then go to login
gameOverTimer = LK.setTimeout(function () {
goToLoginScreen();
}, 5000);
}
function goToLoginScreen() {
// Wait 5 seconds, then reset and return to difficulty selection
LK.setTimeout(function () {
// Reset all game state variables
gameOverState = null;
pointsCalculated = false;
totalPoints = 0;
difficulty = null;
gameInputEnabled = false;
speedMode = false;
speedModeMenuSelected = false;
// Reset castle states
castlePlayer.health = CASTLE_HEALTH_INIT;
castlePlayer.hasUpgrade = false;
castlePlayer.hasUpgrade2 = false;
castlePlayer._upgradeStarted = false;
castlePlayer._upgrade2Started = false;
castleEnemy.health = CASTLE_HEALTH_INIT;
castleEnemy.hasUpgrade = false;
castleEnemy.hasUpgrade2 = false;
castleEnemy._upgradeStarted = false;
castleEnemy._upgrade2Started = false;
// Reset gold
playerGold = GOLD_INIT;
enemyGold = GOLD_INIT;
GOLD_PER_TICK = 5;
// Clear all soldiers and arrows
for (var i = 0; i < playerSoldiers.length; i++) {
if (playerSoldiers[i] && typeof playerSoldiers[i].destroy === "function") {
playerSoldiers[i].destroy();
}
}
for (var i = 0; i < enemySoldiers.length; i++) {
if (enemySoldiers[i] && typeof enemySoldiers[i].destroy === "function") {
enemySoldiers[i].destroy();
}
}
for (var i = 0; i < arrows.length; i++) {
if (arrows[i] && typeof arrows[i].destroy === "function") {
arrows[i].destroy();
}
}
playerSoldiers = [];
enemySoldiers = [];
arrows = [];
// Clear walls
for (var i = 0; i < wallPlayers.length; i++) {
if (wallPlayers[i] && typeof wallPlayers[i].destroy === "function") {
wallPlayers[i].destroy();
}
}
for (var i = 0; i < wallEnemies.length; i++) {
if (wallEnemies[i] && typeof wallEnemies[i].destroy === "function") {
wallEnemies[i].destroy();
}
}
wallPlayers = [];
wallEnemies = [];
// Clear healing tents and timers
if (healingTentPlayer && typeof healingTentPlayer.destroy === "function") {
healingTentPlayer.destroy();
}
if (healingTentEnemy && typeof healingTentEnemy.destroy === "function") {
healingTentEnemy.destroy();
}
if (healingTentCountdownTxt && typeof healingTentCountdownTxt.destroy === "function") {
healingTentCountdownTxt.destroy();
}
if (healingTentEnemyCountdownTxt && typeof healingTentEnemyCountdownTxt.destroy === "function") {
healingTentEnemyCountdownTxt.destroy();
}
if (healingTentTimer) {
LK.clearInterval(healingTentTimer);
healingTentTimer = null;
}
if (healingTentEnemyTimer) {
LK.clearInterval(healingTentEnemyTimer);
healingTentEnemyTimer = null;
}
if (healingTentEnemyCooldownTimer) {
LK.clearInterval(healingTentEnemyCooldownTimer);
healingTentEnemyCooldownTimer = null;
}
healingTentPlayer = null;
healingTentEnemy = null;
healingTentCountdownTxt = null;
healingTentEnemyCountdownTxt = null;
healingTentEnemyActive = false;
healingTentEnemyNextReady = false;
healingTentEnemyCountdown = 0;
healingTentEnemyBoostActive = false;
// Reset enemy AI variables
enemySoldierDeployCount = 0;
enemyTotalSoldierDeployed = 0;
enemyUnitsRemoved = 0;
enemySoldierSinceLastWall = 0;
enemyWallEnemyDeployCount = 0;
if (typeof enemyMiddlePhase !== "undefined") {
enemyMiddlePhase = undefined;
}
if (typeof enemyAdvancedPhase !== "undefined") {
enemyAdvancedPhase = undefined;
}
// Reset castle assets
castlePlayer.init('player', CASTLE_OFFSET_X, CASTLE_OFFSET_Y);
castleEnemy.init('enemy', GAME_W - CASTLE_OFFSET_X, CASTLE_OFFSET_Y);
// Reset button visibility
archerBtn.visible = false;
archerBtnLabel.visible = false;
solderBtn.visible = false;
solderBtnLabel.visible = false;
wallBtn.visible = false;
wallBtnLabel.visible = false;
healingTentBtn.visible = false;
healingTentLabel.visible = false;
// Reset upgrade text visibility
playerCastleUpgradeTxt.visible = true;
playerCastleUpgrade2Txt.visible = false;
// Reset score stats
scoreStats = {
solderEnemyKills: 0,
archerEnemyKills: 0,
wallEnemyDestroyed: 0,
solderPlayerDeaths: 0,
archerPlayerDeaths: 0,
wallPlayerDestroyed: 0,
castlePlayerFullHealth: 0,
castlePlayerHealthLost: 0,
castlePlayerNoDamageGiven: 0
};
// Clear game over overlays
if (gameOverOverlay && typeof gameOverOverlay.destroy === "function") {
gameOverOverlay.destroy();
gameOverOverlay = null;
}
if (gameOverTimer) {
LK.clearTimeout(gameOverTimer);
gameOverTimer = null;
}
// Clear enemy deploy timer
if (enemyDeployTimer) {
LK.clearInterval(enemyDeployTimer);
}
// Update GUI
updateGui();
// Recreate and show difficulty selection overlay
diffOverlay = new Container();
diffOverlay.zIndex = 10000;
diffOverlay.width = GAME_W;
diffOverlay.height = GAME_H;
// Background
overlayBg = new Text2(" ", {
size: 10,
fill: 0x000000
});
overlayBg.width = GAME_W;
overlayBg.height = GAME_H;
overlayBg.alpha = 0.7;
overlayBg.x = 0;
overlayBg.y = 0;
diffOverlay.addChild(overlayBg);
// Recreate all difficulty selection elements
diffTitleBg = new Text2(" ", {
size: 10,
fill: 0x000000
});
diffTitleBg.width = 900;
diffTitleBg.height = 160;
diffTitleBg.alpha = 0.95;
diffTitleBg.anchor.set(0.5, 0.5);
diffTitleBg.x = GAME_W / 2;
diffTitleBg.y = GAME_H / 2 - 300;
diffOverlay.addChild(diffTitleBg);
diffTitle = new Text2("Select Difficulty", {
size: 120,
fill: "#fff",
font: "PressStart2P,Pixel,monospace"
});
diffTitle.anchor.set(0.5, 0.5);
diffTitle.x = GAME_W / 2;
diffTitle.y = GAME_H / 2 - 300;
diffOverlay.addChild(diffTitle);
// Easy button
btnEasyBg = new Text2(" ", {
size: 10,
fill: 0x000000
});
btnEasyBg.width = 600;
btnEasyBg.height = 120;
btnEasyBg.alpha = 0.95;
btnEasyBg.anchor.set(0.5, 0.5);
btnEasyBg.x = GAME_W / 2;
btnEasyBg.y = GAME_H / 2 - 80;
diffOverlay.addChild(btnEasyBg);
btnEasy = new Text2("Easy", {
size: 100,
fill: 0x00FF00,
font: "PressStart2P,Pixel,monospace"
});
btnEasy.anchor.set(0.5, 0.5);
btnEasy.x = GAME_W / 2;
btnEasy.y = GAME_H / 2 - 80;
btnEasy.down = function (x, y, obj) {
selectDifficulty("easy");
};
diffOverlay.addChild(btnEasy);
// Normal button
btnNormalBg = new Text2(" ", {
size: 10,
fill: 0x000000
});
btnNormalBg.width = 600;
btnNormalBg.height = 120;
btnNormalBg.alpha = 0.95;
btnNormalBg.anchor.set(0.5, 0.5);
btnNormalBg.x = GAME_W / 2;
btnNormalBg.y = GAME_H / 2 + 80;
diffOverlay.addChild(btnNormalBg);
btnNormal = new Text2("Normal", {
size: 100,
fill: 0xFFFF00,
font: "PressStart2P,Pixel,monospace"
});
btnNormal.anchor.set(0.5, 0.5);
btnNormal.x = GAME_W / 2;
btnNormal.y = GAME_H / 2 + 80;
btnNormal.down = function (x, y, obj) {
selectDifficulty("normal");
};
diffOverlay.addChild(btnNormal);
// Hard button
btnHardBg = new Text2(" ", {
size: 10,
fill: 0x000000
});
btnHardBg.width = 600;
btnHardBg.height = 120;
btnHardBg.alpha = 0.95;
btnHardBg.anchor.set(0.5, 0.5);
btnHardBg.x = GAME_W / 2;
btnHardBg.y = GAME_H / 2 + 240;
diffOverlay.addChild(btnHardBg);
btnHard = new Text2("Hard", {
size: 100,
fill: 0xFF0000,
font: "PressStart2P,Pixel,monospace"
});
btnHard.anchor.set(0.5, 0.5);
btnHard.x = GAME_W / 2;
btnHard.y = GAME_H / 2 + 240;
btnHard.down = function (x, y, obj) {
selectDifficulty("hard");
};
diffOverlay.addChild(btnHard);
// Speed Mode button
btnSpeedBg = new Text2(" ", {
size: 10,
fill: 0x000000
});
btnSpeedBg.width = 600;
btnSpeedBg.height = 120;
btnSpeedBg.alpha = 0.95;
btnSpeedBg.anchor.set(0.5, 0.5);
btnSpeedBg.x = GAME_W / 2;
btnSpeedBg.y = GAME_H / 2 + 400;
diffOverlay.addChild(btnSpeedBg);
btnSpeed = new Text2("Speed Mode: OFF", {
size: 80,
fill: 0x00E0FF,
font: "PressStart2P,Pixel,monospace"
});
btnSpeed.anchor.set(0.5, 0.5);
btnSpeed.x = GAME_W / 2;
btnSpeed.y = GAME_H / 2 + 400;
btnSpeed.down = function (x, y, obj) {
speedModeMenuSelected = !speedModeMenuSelected;
btnSpeed.setText(speedModeMenuSelected ? "Speed Mode: ON" : "Speed Mode: OFF");
};
diffOverlay.addChild(btnSpeed);
game.addChild(diffOverlay);
}, 5000);
}
// --- Clean up timers on game over ---
game.destroy = function () {
LK.clearInterval(goldTimer);
LK.clearInterval(enemyDeployTimer);
};
ottoman castle. In-Game asset. 2d. High contrast. No shadows. pixel art
roma knight. In-Game asset. 2d. High contrast. No shadows
Ottoman Janissary. In-Game asset. 2d. High contrast. No shadows
sword. In-Game asset. 2d. High contrast. No shadows
Ottoman camel warrior. In-Game asset. 2d. High contrast. No shadows. pixel art
Roman cavalry. In-Game asset. 2d. High contrast. No shadows
camel face. In-Game asset. 2d. High contrast. No shadows. pixel art
wall icon game. In-Game asset. 2d. High contrast. No shadows
RED CRESCENT ICON. In-Game asset. 2d. High contrast. No shadows
HEALING TENT OTTOMAN. In-Game asset. 2d. High contrast. No shadows