User prompt
Add new units: skeleton swordsman (melee); Skeleton archer (range)
User prompt
Add new human enemies: Guard of the village, Millitia archer; they start appearing on the fifth village
User prompt
Rookie uses skeletonRookie asset; meanwhile Trainee archer uses skeletonTraineearcher asset
User prompt
Upgrading is REMOVED! instead, it's replaced with research which changes the buttons to make them spawn the new and stronger units instead of the old units; researching uses gold
User prompt
Skeleton millitia turns into skeleton rookie
User prompt
The new skeletons use their respective assets!
User prompt
Now you can upgrade your units by tapping them! This will cost gold, obviously. Example of upgrading: Tap on millitia, it will turn into rookie [example]. Units (from weakest to stronghest): melee: Millitia, Rookie, Swordsman; Ranged: Slinger, Trainee archer, archer
User prompt
Here's how upgrading works: Tap on a millitia, it will turn into a rookie (Skeletons[from weakest to stronghest]: Melee: millitia, rookie, swordsman; Ranged: slinger, trainee archer, archer
User prompt
Now you can upgrade your units by tapping them! This will cost gold, obviously
User prompt
Now you gain more gold the more enemies you kill
User prompt
Humans gain more units the more you win
User prompt
You start out with 10 gold
User prompt
You start with a millitia
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'length')' in or related to this line: 'for (var i = 0; i < enemyArray.length; i++) {' Line Number: 86
Code edit (1 edits merged)
Please save this source code
User prompt
Skeleton War: Village Raid
Initial prompt
Skeleton war: a game where you are the leader of an army of Skeletons. You need to take revenge from humans that have destroyed your castle. You start with some weak skeletonsbbut, by destroying enemy villages, you get gold that can be used to buy more skeletons or upgrade your other skeletons (Skeletons(from weakest to stronghest): Melee: skeleton millitia, skeleton rookie, skeleton swordsman; Ranged: Skeleton slinger, skeleton trainee archer, skeleton archer; Defender: Skeleton spearman, skeleton holpite, skeleton conniesur; Melee cavaliry: Skeleton scout, light skeleton rider, skeleton rider) (Humans (enemies from weakest to stronghest): Melee: Farmer, swordsman; Ranged: Slinger, archer; Defender: shieldman, holpite; Melee cavaliry: Scout, light rider)
/**** * Classes ****/ // Ensure ranged sound exists // Simple Button Class var Button = Container.expand(function (text, config) { var self = Container.call(this); // Methods must be defined before event handlers if called internally self.setText = function (newText) { self.label.setText(newText); }; self.setEnabled = function (enabled) { self.interactive = enabled; // Control interactability // LK engine doesn't directly use obj.interactive for event routing like standard PIXI, // but good practice. Use alpha for visual cue. self.alpha = enabled ? 1.0 : 0.5; }; // --- Initialize --- self.config = config || {}; var width = self.config.width || 300; var height = self.config.height || 100; var bgColor = self.config.bgColor || 0x555555; var textColor = self.config.textColor || '#FFFFFF'; var textSize = self.config.textSize || 40; var bg = self.attachAsset('buttonBg', { width: width, height: height, // color: bgColor, // Shape color is set at init time, cannot override here anchorX: 0.5, anchorY: 0.5 }); // Tint the background shape if a color was provided in config bg.tint = bgColor; self.label = new Text2(text, { size: textSize, fill: textColor }); self.label.anchor.set(0.5, 0.5); self.addChild(self.label); // Placeholder for the action, assign this after creating an instance self.onClick = null; // Must be assigned externally // LK engine automatically calls self.down if attached and interactive self.down = function (x, y, obj) { if (!self.interactive) { return; } // Respect the enabled state // Visual feedback for press self.scale.set(0.95); }; // LK engine automatically calls self.up if attached and interactive self.up = function (x, y, obj) { if (!self.interactive) { return; } // Respect the enabled state self.scale.set(1.0); // Restore scale // Check bounds roughly (optional, prevents firing if dragged off significantly) if (x >= -width / 2 && x <= width / 2 && y >= -height / 2 && y <= height / 2) { if (self.onClick) { self.onClick(); // Execute the assigned action } } }; // Initial state self.interactive = true; // Default to enabled return self; }); // var tween = LK.import('@upit/tween.v1'); // Not strictly needed for MVP // Base class for all units var Unit = Container.expand(function (config) { var self = Container.call(this); // Define methods first self.findTarget = function (enemyArray) { var closestTarget = null; var minDistance = Infinity; // Make sure enemyArray is defined and is an array if (!enemyArray || !Array.isArray(enemyArray)) { return null; } for (var i = 0; i < enemyArray.length; i++) { var enemy = enemyArray[i]; if (enemy.isDead) { continue; } // Using horizontal distance for simplicity var distance = Math.abs(self.x - enemy.x); if (distance < minDistance) { minDistance = distance; closestTarget = enemy; } } if (closestTarget && minDistance <= self.attackRange) { return closestTarget; // Return target if it's within attack range } else { return null; // No target in range } }; self.attack = function () { if (self.target && !self.target.isDead && self.attackCooldown <= 0) { try { // Defensive coding for sound playback LK.getSound(self.attackSoundId).play(); } catch (e) { console.log("Sound error playing " + self.attackSoundId + ": " + e); } self.target.takeDamage(self.attackPower); self.attackCooldown = self.attackCooldownTime; } }; self.move = function () { // Move towards the enemy side self.x += self.speed * (self.isEnemy ? -1 : 1); // Basic boundary check if (self.x < -100 || self.x > GAME_WIDTH + 100) { self.die(); // Remove if goes way off screen } }; self.takeDamage = function (damage) { if (self.isDead) { return; } // Already dead self.hp -= damage; // Optional: Add a visual effect like tinting red briefly // LK.effects.flashObject(self, 0xFF0000, 100); // Flashing the container might work if (self.hp <= 0) { // Award gold if an enemy dies from player attack if (self.isEnemy) { // Award gold based on unit type/strength gold += 5; // Base gold reward updateGoldDisplay(); // Update the display immediately } self.die(); } }; self.die = function () { if (!self.isDead) { try { // Defensive coding for sound playback LK.getSound('unitDeath').play(); } catch (e) { console.log("Sound error playing unitDeath: " + e); } self.isDead = true; // It will be removed from the game and array in game.update } }; // LK calls this automatically if the instance is attached to the stage/game self.update = function (enemyArray) { // This method requires the global `isBattling` to be accessible. if (self.isDead || !isBattling) { return; } // Don't update if dead or battle not active // Update cooldown if (self.attackCooldown > 0) { self.attackCooldown--; } // Find target self.target = self.findTarget(enemyArray); // Act based on target if (self.target) { self.isMoving = false; // Stop moving when target is in range self.attack(); } else { self.isMoving = true; // No target in range, resume moving } // Move if needed if (self.isMoving) { self.move(); } self.lastX = self.x; // Update last known state }; // --- Initialize --- self.isEnemy = config.isEnemy; self.hp = config.hp; self.maxHp = config.hp; // Store max HP self.attackPower = config.attackPower; self.attackRange = config.attackRange; self.attackCooldownTime = config.attackCooldownTime || 60; // Default 1 sec cooldown self.speed = config.speed; self.assetId = config.assetId; self.attackSoundId = config.attackSoundId || 'attackMelee'; self.unitType = config.unitType; // Store the unit type identifier // Attach graphics *after* properties are set self.graphics = self.attachAsset(self.assetId, { anchorX: 0.5, anchorY: 0.5 }); // State variables self.target = null; self.attackCooldown = 0; // Start ready to attack self.isMoving = true; self.isDead = false; self.lastX = 0; // Initialize lastX, will be set correctly on spawn // Upgrade logic removed - replaced by global research system return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x101020 // Dark background for the war theme }); /**** * Game Code ****/ // Button background // Blue ellipse // Green box // Gray ellipse // White box // Define logical assets for units and UI. // LK Engine automatically creates assets based on usage. // Game constants and configuration // Added assets for new human units // Placeholder ID // Placeholder ID // Ensure melee sound exists var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; var PLAYER_SPAWN_X = 150; var ENEMY_SPAWN_X = GAME_WIDTH - 150; var SPAWN_Y_START = GAME_HEIGHT * 0.3; // Start spawning units lower down var SPAWN_Y_END = GAME_HEIGHT * 0.8; // End spawning units lower down var SPAWN_Y_STEP = 100; // Vertical distance between spawned units // Research configuration var MILITIA_RESEARCH_COST = 50; var SLINGER_RESEARCH_COST = 75; var militiaResearchComplete = false; var slingerResearchComplete = false; // Unit stats lookup table var UNIT_STATS = { skeletonMilitia: { hp: 50, attackPower: 5, attackRange: 80, speed: 1.5, assetId: 'skeletonMilitia', cost: 10, // Cost to recruit base militia attackSoundId: 'attackMelee' }, // Note: skeletonRookie stats are defined below, its cost is for recruitment *after* research. skeletonSlinger: { hp: 30, attackPower: 7, attackRange: 400, speed: 1.2, assetId: 'skeletonSlinger', cost: 15, // Cost to recruit base slinger attackCooldownTime: 90, attackSoundId: 'attackRanged' }, // Note: skeletonTraineeArcher stats are defined below, its cost is for recruitment *after* research. humanFarmer: { hp: 40, attackPower: 4, attackRange: 70, speed: 1.3, assetId: 'humanFarmer', attackSoundId: 'attackMelee' }, humanSlinger: { hp: 25, attackPower: 6, attackRange: 380, speed: 1.1, assetId: 'humanSlinger', attackCooldownTime: 90, attackSoundId: 'attackRanged' }, // --- New Human Units --- humanGuard: { hp: 80, // Tougher than Farmer attackPower: 7, attackRange: 85, // Slightly longer reach than militia speed: 1.2, assetId: 'humanGuard', attackSoundId: 'attackMelee' }, humanMilitiaArcher: { hp: 35, // Similar to Slinger attackPower: 8, // Slightly more damage than Slinger attackRange: 420, // Slightly longer range speed: 1.3, assetId: 'humanMilitiaArcher', attackCooldownTime: 80, // Slightly faster fire rate attackSoundId: 'attackRanged' }, // --- Researched Player Units --- skeletonSwordsman: { // Renamed from skeletonRookie hp: 70, // Kept stats from Rookie for now attackPower: 8, attackRange: 90, speed: 1.8, assetId: 'skeletonSwordsman', // Use the Swordsman asset cost: 20, attackSoundId: 'attackMelee' }, skeletonArcher: { // Renamed from skeletonTraineeArcher hp: 40, //{1z} // Kept stats from Trainee Archer for now attackPower: 10, attackRange: 450, speed: 1.5, assetId: 'skeletonArcher', // Use the Archer asset cost: 25, attackCooldownTime: 70, attackSoundId: 'attackRanged' } }; // Game state variables - Defined globally for access within functions and update loop var gold = 10; var playerUnits = []; var enemyUnits = []; var isBattling = false; var battleCounter = 0; // Tracks the number of battles won var currentEnemyConfig = {}; // Store the config for the current battle // Initial player army composition (can be modified by recruitment) // Using an object for easier modification by type var playerArmyRoster = { skeletonMilitia: 2, skeletonSlinger: 1 }; // --- UI Elements --- // Gold Display var goldTxt = new Text2('Gold: 0', { size: 60, fill: 0xFFD700 }); goldTxt.anchor.set(1.0, 0); // Anchor top-right goldTxt.x = -50; // Offset from the right edge goldTxt.y = 20; // Offset from the top edge // Avoid top-left (reserved for LK menu), top-right is fine. LK.gui.topRight.addChild(goldTxt); // Buttons Container (for organizing buttons at the bottom) var buttonContainer = new Container(); buttonContainer.x = GAME_WIDTH / 2; buttonContainer.y = GAME_HEIGHT - 200; // Position buttons lower game.addChild(buttonContainer); // Add to the main game scene // Start Battle Button var startButton = new Button('Start Battle', { width: 400, height: 120, textSize: 50, bgColor: 0x008000 }); // Greenish button startButton.y = -80; // Position above the recruitment buttons buttonContainer.addChild(startButton); // Recruitment Buttons (Example: Militia) var recruitMilitiaButton = new Button('Recruit Militia (10g)', { width: 400, height: 100, textSize: 40, bgColor: 0x404040 }); recruitMilitiaButton.x = -220; // Position left of center recruitMilitiaButton.y = 80; // Position below start button buttonContainer.addChild(recruitMilitiaButton); // Example: Slinger recruitment button var recruitSlingerButton = new Button('Recruit Slinger (15g)', { width: 400, height: 100, textSize: 40, bgColor: 0x404040 }); recruitSlingerButton.x = 220; // Position right of center recruitSlingerButton.y = 80; // Position below start button buttonContainer.addChild(recruitSlingerButton); // Research Buttons var researchMilitiaButton = new Button('Research Rookie (50g)', { width: 400, height: 100, textSize: 40, bgColor: 0x004080 // Blueish button }); researchMilitiaButton.x = -220; // Position left, above recruitment researchMilitiaButton.y = -220; // Position above recruitment buttons buttonContainer.addChild(researchMilitiaButton); var researchSlingerButton = new Button('Research Trainee (75g)', { width: 400, height: 100, textSize: 40, bgColor: 0x004080 // Blueish button }); researchSlingerButton.x = 220; // Position right, above recruitment researchSlingerButton.y = -220; // Position above recruitment buttons buttonContainer.addChild(researchSlingerButton); // Adjust Start Button position to accommodate research buttons startButton.y = -350; // Move start button further up // --- Game Logic Functions --- function updateGoldDisplay() { goldTxt.setText('Gold: ' + gold); // --- Update Research Buttons --- // Militia Research Button (Now Swordsman) if (militiaResearchComplete) { researchMilitiaButton.visible = false; // Hide button once researched researchMilitiaButton.setEnabled(false); } else { researchMilitiaButton.visible = true; researchMilitiaButton.setText('Research Swordsman (' + MILITIA_RESEARCH_COST + 'g)'); // Update text researchMilitiaButton.setEnabled(gold >= MILITIA_RESEARCH_COST && !isBattling); } // Slinger Research Button (Now Archer) if (slingerResearchComplete) { researchSlingerButton.visible = false; // Hide button once researched researchSlingerButton.setEnabled(false); } else { researchSlingerButton.visible = true; researchSlingerButton.setText('Research Archer (' + SLINGER_RESEARCH_COST + 'g)'); // Update text researchSlingerButton.setEnabled(gold >= SLINGER_RESEARCH_COST && !isBattling); } // --- Update Recruitment Buttons --- // Militia Recruitment Button var currentMilitiaType = militiaResearchComplete ? 'skeletonSwordsman' : 'skeletonMilitia'; // Use Swordsman if researched var currentMilitiaStats = UNIT_STATS[currentMilitiaType]; var militiaButtonText = militiaResearchComplete ? 'Recruit Swordsman' : 'Recruit Militia'; // Update text recruitMilitiaButton.setText(militiaButtonText + ' (' + currentMilitiaStats.cost + 'g)'); recruitMilitiaButton.setEnabled(gold >= currentMilitiaStats.cost && !isBattling); // Slinger Recruitment Button var currentSlingerType = slingerResearchComplete ? 'skeletonArcher' : 'skeletonSlinger'; // Use Archer if researched var currentSlingerStats = UNIT_STATS[currentSlingerType]; var slingerButtonText = slingerResearchComplete ? 'Recruit Archer' : 'Recruit Slinger'; // Update text recruitSlingerButton.setText(slingerButtonText + ' (' + currentSlingerStats.cost + 'g)'); recruitSlingerButton.setEnabled(gold >= currentSlingerStats.cost && !isBattling); } function recruitUnit(unitType) { if (isBattling) { return; } // Cannot recruit during battle var stats = UNIT_STATS[unitType]; if (!stats) { console.error("Attempted to recruit unknown unit type:", unitType); return; } if (gold >= stats.cost) { gold -= stats.cost; try { // Defensive coding for sound playback LK.getSound('recruit').play(); } catch (e) { console.log("Sound error playing recruit: " + e); } // Safely increment the count in the roster playerArmyRoster[unitType] = (playerArmyRoster[unitType] || 0) + 1; console.log("Recruited " + unitType + ". Roster:", playerArmyRoster); updateGoldDisplay(); // Update UI after recruitment } else { console.log("Not enough gold to recruit " + unitType); // Optional: Show feedback to the player (e.g., flashing the gold red) LK.effects.flashObject(goldTxt, 0xFF0000, 300); } } function researchUnit(researchType) { if (isBattling) { return; } // Cannot research during battle var cost = 0; var researchFlag = false; var researchName = ''; if (researchType === 'militia' && !militiaResearchComplete) { cost = MILITIA_RESEARCH_COST; researchName = 'Swordsman'; // Update name } else if (researchType === 'slinger' && !slingerResearchComplete) { cost = SLINGER_RESEARCH_COST; researchName = 'Archer'; // Update name } else { console.log("Research already complete or invalid type:", researchType); return; // Invalid type or already researched } if (gold >= cost) { gold -= cost; if (researchType === 'militia') { militiaResearchComplete = true; } else if (researchType === 'slinger') { slingerResearchComplete = true; } try { // Reuse upgrade sound, or create a specific 'research' sound LK.getSound('upgradeUnit').play(); // Assuming 'upgradeUnit' sound is suitable } catch (e) { console.log("Sound error playing research sound: " + e); } console.log("Research complete for:", researchName); updateGoldDisplay(); // Update UI immediately } else { console.log("Not enough gold to research " + researchName); LK.effects.flashObject(goldTxt, 0xFF0000, 300); } } function clearBattlefield() { // Remove all existing units from the game and arrays for (var i = playerUnits.length - 1; i >= 0; i--) { if (playerUnits[i] && playerUnits[i].destroy) { // Check if destroy exists playerUnits[i].destroy(); } } playerUnits = []; // Reset array for (var i = enemyUnits.length - 1; i >= 0; i--) { if (enemyUnits[i] && enemyUnits[i].destroy) { // Check if destroy exists enemyUnits[i].destroy(); } } enemyUnits = []; // Reset array } function spawnUnit(unitType, isEnemy) { var stats = UNIT_STATS[unitType]; if (!stats) { console.error("Cannot spawn unknown unit type:", unitType); return null; } // Prepare config for the Unit class constructor var config = { isEnemy: isEnemy, hp: stats.hp, attackPower: stats.attackPower, attackRange: stats.attackRange, speed: stats.speed, assetId: stats.assetId, attackCooldownTime: stats.attackCooldownTime, attackSoundId: stats.attackSoundId, unitType: unitType // Pass the unit type }; // Create new instance of Unit var unit = new Unit(config); // Determine spawn side and array var armyArray = isEnemy ? enemyUnits : playerUnits; var spawnX = isEnemy ? ENEMY_SPAWN_X : PLAYER_SPAWN_X; // Calculate staggered Y position var currentY = SPAWN_Y_START + armyArray.length * SPAWN_Y_STEP; // Wrap Y position if it goes beyond the designated spawn area end if (currentY > SPAWN_Y_END) { var yRange = SPAWN_Y_END - SPAWN_Y_START; if (yRange <= 0) { yRange = SPAWN_Y_STEP; } // Avoid division by zero or negative range currentY = SPAWN_Y_START + armyArray.length * SPAWN_Y_STEP % yRange; } // Set initial position and state unit.x = spawnX; unit.y = currentY; unit.lastX = unit.x; // Initialize last state correctly // Add the unit to the game display and the logical array game.addChild(unit); armyArray.push(unit); return unit; // Return the created unit if needed elsewhere } function startBattle() { if (isBattling) { return; } // Prevent starting multiple battles console.log("Starting battle..."); isBattling = true; clearBattlefield(); // Remove any units from a previous battle // Define enemy forces for this battle (can be made dynamic later) if (Object.keys(currentEnemyConfig).length === 0) { // Only initialize on first battle currentEnemyConfig = { humanFarmer: 3, humanSlinger: 1 }; } // Otherwise use the already incremented config from previous win // Spawn Player Units based on the current roster console.log("Spawning player units:", playerArmyRoster); for (var unitType in playerArmyRoster) { if (playerArmyRoster.hasOwnProperty(unitType)) { // Good practice for iterating objects var count = playerArmyRoster[unitType]; for (var i = 0; i < count; i++) { spawnUnit(unitType, false); // Spawn player unit (not enemy) } } } // Spawn Enemy Units based on the current configuration console.log("Spawning enemy units:", currentEnemyConfig); for (var unitType in currentEnemyConfig) { if (currentEnemyConfig.hasOwnProperty(unitType)) { var count = currentEnemyConfig[unitType]; for (var i = 0; i < count; i++) { spawnUnit(unitType, true); // Spawn enemy unit } } } // Hide UI elements not relevant during battle buttonContainer.visible = false; // Ensure units arrays are populated before the first update runs console.log("Battle started. Player units:", playerUnits.length, "Enemy units:", enemyUnits.length); } function endBattle(playerWon) { // Check if the battle hasn't already ended to prevent multiple triggers if (!isBattling) { return; } console.log("Battle ended. Player won:", playerWon); isBattling = false; // Set state before doing anything else // Show UI elements again buttonContainer.visible = true; if (playerWon) { var goldEarned = 50; // Example gold reward gold += goldEarned; try { // Defensive coding for sound playback LK.getSound('battleWon').play(); } catch (e) { console.log("Sound error playing battleWon: " + e); } console.log("Earned " + goldEarned + " gold. Total gold:", gold); // Update display immediately after awarding gold updateGoldDisplay(); battleCounter++; // Increment the battle counter on win console.log("Battle won! Proceeding to village: " + (battleCounter + 1)); // Humans gain more units when player wins battles for (var unitType in currentEnemyConfig) { if (currentEnemyConfig.hasOwnProperty(unitType)) { // Increase each existing unit type by 1-2 for the next battle currentEnemyConfig[unitType] += Math.floor(Math.random() * 2) + 1; } } // Introduce new units starting from the 5th battle (after winning the 4th) if (battleCounter >= 4) { // 4 wins means the next battle is the 5th // Add Guard if not present, or increase count if (!currentEnemyConfig.hasOwnProperty('humanGuard')) { currentEnemyConfig['humanGuard'] = 1; // Start with 1 } else { currentEnemyConfig['humanGuard'] += 1; // Add one more each subsequent win } // Add Militia Archer if not present, or increase count if (!currentEnemyConfig.hasOwnProperty('humanMilitiaArcher')) { currentEnemyConfig['humanMilitiaArcher'] = 1; // Start with 1 } else { currentEnemyConfig['humanMilitiaArcher'] += 1; // Add one more each subsequent win } console.log("Stronger human reinforcements (Guards, Militia Archers) have arrived!"); } console.log("Enemy forces have grown stronger for the next battle:", currentEnemyConfig); // Game continues, player can choose to recruit or battle again } else { console.log("Player lost the battle."); // Game over is handled by LK engine which resets the game state LK.showGameOver(); // Note: Gold and roster will reset unless storage plugin is used. } // Update button states based on new gold amount AFTER battle outcome is decided // This is especially important if the player lost, as LK.showGameOver might reset things // before this line runs if not careful. updateGoldDisplay() called inside win condition is safer. // If player loses, the game resets anyway, so updating buttons might not be needed here. if (playerWon) { updateGoldDisplay(); // Ensure buttons reflect new gold total after winning } } // --- Initialize Game State --- // Assign onClick handlers to buttons *after* they are created startButton.onClick = startBattle; // Assign the function reference recruitMilitiaButton.onClick = function () { var unitToRecruit = militiaResearchComplete ? 'skeletonSwordsman' : 'skeletonMilitia'; // Recruit Swordsman if researched recruitUnit(unitToRecruit); }; recruitSlingerButton.onClick = function () { var unitToRecruit = slingerResearchComplete ? 'skeletonArcher' : 'skeletonSlinger'; // Recruit Archer if researched recruitUnit(unitToRecruit); }; researchMilitiaButton.onClick = function () { researchUnit('militia'); }; researchSlingerButton.onClick = function () { researchUnit('slinger'); }; updateGoldDisplay(); // Set initial gold text and button states based on starting gold (10) and research state // --- Game Update Loop --- // This function is called by the LK engine automatically 60 times per second. game.update = function () { if (!isBattling) { // Game is idle, waiting for player action (recruit/start battle) // No unit updates needed. return; } // --- Battle Logic --- // Update Player Units (Iterate backwards for safe removal) for (var i = playerUnits.length - 1; i >= 0; i--) { var pUnit = playerUnits[i]; // Ensure unit exists and has methods before calling them if (!pUnit) { continue; } if (pUnit.isDead) { if (pUnit.destroy) { pUnit.destroy(); } // Remove graphics from game playerUnits.splice(i, 1); // Remove from logical array continue; // Move to the next unit } // Pass the array of enemies for target finding if (pUnit.update) { pUnit.update(enemyUnits); } } // Update Enemy Units (Iterate backwards for safe removal) for (var i = enemyUnits.length - 1; i >= 0; i--) { var eUnit = enemyUnits[i]; if (!eUnit) { continue; } if (eUnit.isDead) { if (eUnit.destroy) { eUnit.destroy(); } enemyUnits.splice(i, 1); continue; } // Pass the array of player units for target finding if (eUnit.update) { eUnit.update(playerUnits); } } // Check Win/Loss Conditions *after* all units have been updated for the tick // Only check if the battle is currently marked as active if (isBattling) { if (enemyUnits.length === 0 && playerUnits.length > 0) { // Player wins: All enemies are defeated, player still has units. endBattle(true); } else if (playerUnits.length === 0) { // Player loses: All player units are defeated. (Enemy count doesn't matter here) // We might get here even if enemies also died in the same tick. endBattle(false); } } }; // --- Input Handling --- // Button clicks are managed by the Button class instances via their `up` method. // No global game-level mouse/touch handlers needed for this MVP's interaction model.
===================================================================
--- original.js
+++ change.js
@@ -203,20 +203,20 @@
/****
* Game Code
****/
-// Ensure melee sound exists
+// Button background
+// Blue ellipse
+// Green box
+// Gray ellipse
+// White box
+// Define logical assets for units and UI.
+// LK Engine automatically creates assets based on usage.
+// Game constants and configuration
+// Added assets for new human units
// Placeholder ID
// Placeholder ID
-// Added assets for new human units
-// Game constants and configuration
-// LK Engine automatically creates assets based on usage.
-// Define logical assets for units and UI.
-// White box
-// Gray ellipse
-// Green box
-// Blue ellipse
-// Button background
+// Ensure melee sound exists
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var PLAYER_SPAWN_X = 150;
var ENEMY_SPAWN_X = GAME_WIDTH - 150;
@@ -294,26 +294,29 @@
// Slightly faster fire rate
attackSoundId: 'attackRanged'
},
// --- Researched Player Units ---
- skeletonRookie: {
+ skeletonSwordsman: {
+ // Renamed from skeletonRookie
hp: 70,
+ // Kept stats from Rookie for now
attackPower: 8,
attackRange: 90,
speed: 1.8,
- assetId: 'skeletonRookie',
- // Use the dedicated rookie asset
+ assetId: 'skeletonSwordsman',
+ // Use the Swordsman asset
cost: 20,
attackSoundId: 'attackMelee'
},
- // Reusing militia asset for now
- skeletonTraineeArcher: {
+ skeletonArcher: {
+ // Renamed from skeletonTraineeArcher
hp: 40,
+ //{1z} // Kept stats from Trainee Archer for now
attackPower: 10,
attackRange: 450,
speed: 1.5,
- assetId: 'skeletonTraineearcher',
- // Use the dedicated trainee archer asset
+ assetId: 'skeletonArcher',
+ // Use the Archer asset
cost: 25,
attackCooldownTime: 70,
attackSoundId: 'attackRanged'
}
@@ -400,37 +403,37 @@
// --- Game Logic Functions ---
function updateGoldDisplay() {
goldTxt.setText('Gold: ' + gold);
// --- Update Research Buttons ---
- // Militia Research Button
+ // Militia Research Button (Now Swordsman)
if (militiaResearchComplete) {
researchMilitiaButton.visible = false; // Hide button once researched
researchMilitiaButton.setEnabled(false);
} else {
researchMilitiaButton.visible = true;
- researchMilitiaButton.setText('Research Rookie (' + MILITIA_RESEARCH_COST + 'g)');
+ researchMilitiaButton.setText('Research Swordsman (' + MILITIA_RESEARCH_COST + 'g)'); // Update text
researchMilitiaButton.setEnabled(gold >= MILITIA_RESEARCH_COST && !isBattling);
}
- // Slinger Research Button
+ // Slinger Research Button (Now Archer)
if (slingerResearchComplete) {
researchSlingerButton.visible = false; // Hide button once researched
researchSlingerButton.setEnabled(false);
} else {
researchSlingerButton.visible = true;
- researchSlingerButton.setText('Research Trainee (' + SLINGER_RESEARCH_COST + 'g)');
+ researchSlingerButton.setText('Research Archer (' + SLINGER_RESEARCH_COST + 'g)'); // Update text
researchSlingerButton.setEnabled(gold >= SLINGER_RESEARCH_COST && !isBattling);
}
// --- Update Recruitment Buttons ---
// Militia Recruitment Button
- var currentMilitiaType = militiaResearchComplete ? 'skeletonRookie' : 'skeletonMilitia';
+ var currentMilitiaType = militiaResearchComplete ? 'skeletonSwordsman' : 'skeletonMilitia'; // Use Swordsman if researched
var currentMilitiaStats = UNIT_STATS[currentMilitiaType];
- var militiaButtonText = militiaResearchComplete ? 'Recruit Rookie' : 'Recruit Militia';
+ var militiaButtonText = militiaResearchComplete ? 'Recruit Swordsman' : 'Recruit Militia'; // Update text
recruitMilitiaButton.setText(militiaButtonText + ' (' + currentMilitiaStats.cost + 'g)');
recruitMilitiaButton.setEnabled(gold >= currentMilitiaStats.cost && !isBattling);
// Slinger Recruitment Button
- var currentSlingerType = slingerResearchComplete ? 'skeletonTraineeArcher' : 'skeletonSlinger';
+ var currentSlingerType = slingerResearchComplete ? 'skeletonArcher' : 'skeletonSlinger'; // Use Archer if researched
var currentSlingerStats = UNIT_STATS[currentSlingerType];
- var slingerButtonText = slingerResearchComplete ? 'Recruit Trainee' : 'Recruit Slinger';
+ var slingerButtonText = slingerResearchComplete ? 'Recruit Archer' : 'Recruit Slinger'; // Update text
recruitSlingerButton.setText(slingerButtonText + ' (' + currentSlingerStats.cost + 'g)');
recruitSlingerButton.setEnabled(gold >= currentSlingerStats.cost && !isBattling);
}
function recruitUnit(unitType) {
@@ -468,12 +471,12 @@
var researchFlag = false;
var researchName = '';
if (researchType === 'militia' && !militiaResearchComplete) {
cost = MILITIA_RESEARCH_COST;
- researchName = 'Rookie';
+ researchName = 'Swordsman'; // Update name
} else if (researchType === 'slinger' && !slingerResearchComplete) {
cost = SLINGER_RESEARCH_COST;
- researchName = 'Trainee Archer';
+ researchName = 'Archer'; // Update name
} else {
console.log("Research already complete or invalid type:", researchType);
return; // Invalid type or already researched
}
@@ -663,13 +666,13 @@
// --- Initialize Game State ---
// Assign onClick handlers to buttons *after* they are created
startButton.onClick = startBattle; // Assign the function reference
recruitMilitiaButton.onClick = function () {
- var unitToRecruit = militiaResearchComplete ? 'skeletonRookie' : 'skeletonMilitia';
+ var unitToRecruit = militiaResearchComplete ? 'skeletonSwordsman' : 'skeletonMilitia'; // Recruit Swordsman if researched
recruitUnit(unitToRecruit);
};
recruitSlingerButton.onClick = function () {
- var unitToRecruit = slingerResearchComplete ? 'skeletonTraineeArcher' : 'skeletonSlinger';
+ var unitToRecruit = slingerResearchComplete ? 'skeletonArcher' : 'skeletonSlinger'; // Recruit Archer if researched
recruitUnit(unitToRecruit);
};
researchMilitiaButton.onClick = function () {
researchUnit('militia');
Skeleton with a wooden sword. In-Game asset. 2d. High contrast. No shadows
Skeleton facing left with a slingshot. In-Game asset. 2d. High contrast. No shadows
Farmer with a pitchfork. In-Game asset. 2d. High contrast. No shadows
Farmer with a slingshot. In-Game asset. 2d. High contrast. No shadows
Skeleton with copper sword, copper helmet and copper shield. In-Game asset. 2d. High contrast. No shadows
A skeleton with a bow. In-Game asset. 2d. High contrast. No shadows
Skeleton with Iron sword, iron helmet and a Reinforced wooden shield. In-Game asset. 2d. High contrast. No shadows
Skeleton with a reinforced bow and a hood. In-Game asset. 2d. High contrast. No shadows
Human with copper Armor, a copper sword and shield. In-Game asset. 2d. High contrast. No shadows
A human with a bow and a quiver. In-Game asset. 2d. High contrast. No shadows
Human with Iron Armor, iron sword and iron shield. In-Game asset. 2d. High contrast. No shadows
A human with a reinforced bow, iron helmet and a bucket full of arrows. In-Game asset. 2d. High contrast. No shadows
Skeleton holding a big copper shield with both hands. In-Game asset. 2d. High contrast. No shadows
Skeleton with an Iron helmet, Iron spear and a big iron shield. In-Game asset. 2d. High contrast. No shadows
A golden pig. In-Game asset. 2d. High contrast. No shadows
Human with a big iron shield. In-Game asset. 2d. High contrast. No shadows
Human with a big Copper shield. In-Game asset. 2d. High contrast. No shadows
Skeleton with a blue hoodie and cape, golden sword and a reinforced iron shield. In-Game asset. 2d. High contrast. No shadows
Skeleton with a dead wolf's head as a hood, a Crossbow and a big quiver. In-Game asset. 2d. High contrast. No shadows
Skeleton with it's skull covered with a dark helmet, a dark axe and a shield with a red skull in the middle. In-Game asset. 2d. High contrast. No shadows
Skeleton with a bomb. In-Game asset. 2d. High contrast. No shadows
Pumpkin catapult made out of bones. In-Game asset. 2d. High contrast. No shadows
Skeleton with a blue hoodie and cape riding a skeleton dragon. In-Game asset. 2d. High contrast. No shadows
A knight with golden armour, golden shield and golden sword. In-Game asset. 2d. High contrast. No shadows
A human with a golden helmet, crossbow and a big bucket full of arrows in his back. In-Game asset. 2d. High contrast. No shadows
Human with a BIG golden shield and shining reinforced golden armor. In-Game asset. 2d. High contrast. No shadows
Human with a bomb. In-Game asset. 2d. High contrast. No shadows
Cannon. In-Game asset. 2d. High contrast. No shadows
Trebuchet on wheels. In-Game asset. 2d. High contrast. No shadows