Code edit (1 edits merged)
Please save this source code
User prompt
Castle Clash: Gold Rush
Initial prompt
background image 1 player's castle is on the bottom left of the screen, 1 enemy's castle is on the bottom right of the screen, they both have 1000 health, health values are written on both of them, add gold logic to the game, 1 gold is earned every 0.2 seconds, both the player and the enemy have gold and use this gold to send out soldiers, the player also uses this gold to send out soldiers, the enemy's is automatic, the player clicks on the screen to send them out, the gold values are reduced by the cost of the soldiers on both sides, the amount of gold belonging to the player is written in the middle of the screen, the soldiers stop when they touch each other and hit each other according to their attack values and disappear when their health values run out, the health and attack values of these soldiers are random between 5-25, and the soldiers sent by the enemy attack as if they encountered my soldier when they touch the block where my pen is defined, of course the castle does not attack him, the enemy soldier will continue to attack until I send out a new soldier and kill him, the same thing happens to the player soldiers The same goes for them, when they reach the enemy castle they will stop and attack.
/**** * 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