User prompt
Currently, StaplerTower calls initializeTowerFromData in its constructor. BureaucracyBlockerTower has an init method that calls it, but this init method isn't explicitly called when a new BureaucracyBlockerTower is created in buildSelectedTower. Suggestion: Let's standardize this. In both StaplerTower and BureaucracyBlockerTower constructors: Remove the lines that directly do self.graphic = self.attachAsset(...). Call initializeTowerFromData(self, 'towerTypeKey', 0); at the end of the constructor (e.g., initializeTowerFromData(self, 'stapler', 0); for StaplerTower). Modify initializeTowerFromData to reliably handle creating the graphic if it doesn't exist, or updating it if it does. // Inside initializeTowerFromData function: // ... (towerTypeData, levelData checks) ... towerInstance.towerType = towerTypeKey; towerInstance.currentLevel = levelIndex; // Graphic handling: if (towerInstance.graphic && towerInstance.graphic.parent) { towerInstance.graphic.setAsset(levelData.asset); } else { if (towerInstance.graphic) { towerInstance.graphic.destroy(); // Clean up old if it existed but was detached } towerInstance.graphic = towerInstance.attachAsset(levelData.asset, { anchorX: 0.5, anchorY: 0.5 }); } // ... (set stats) ... // Tower-specific re-initialization logic if (towerTypeKey === 'stapler' && towerInstance.hasOwnProperty('lastFired')) { towerInstance.lastFired = 0; // Reset fire cooldown on init/upgrade } if (towerTypeKey === 'blocker' && typeof towerInstance.clearAllSlows === 'function') { towerInstance.clearAllSlows(); // Ensure slows are reset based on new stats } Remove the separate self.init function from BureaucracyBlockerTower as its job will be done by the constructor calling initializeTowerFromData. In BuildSpot.buildSelectedTower, you would no longer need a special newTower.init(0) call for the blocker.
User prompt
Ava, please copy the update and takeDamage methods from the Enemy class into the RedTapeWorm class, ensuring RedTapeWorm instances can move along the path and react to damage.
User prompt
Please fix the bug: 'Uncaught ReferenceError: initializeTowerFromData is not defined' in or related to this line: 'initializeTowerFromData(self, 'stapler', 0); // Set initial stats & correct L0 asset' Line Number: 690
User prompt
Duplicate Definitions: You have TOWER_DATA defined twice. The second definition (with detailed levels, costs, assets, etc.) is the one that seems to be in use and is more complete. The first one should be removed. Similarly, the function initializeTowerFromData is defined twice. The first, shorter one should be removed, and we should ensure the more complete second one is used everywhere. The game.up function is defined twice with identical code. One of them can be removed.
Code edit (7 edits merged)
Please save this source code
User prompt
Stop dog movement when clicking on buildspot
User prompt
Please fix the bug: 'Uncaught ReferenceError: clickedOnDoge is not defined' in or related to this line: 'if (!clickedOnDoge) if (dragDoge && doge) {' Line Number: 1245
Code edit (5 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'stopPropagation')' in or related to this line: 'obj.event.stopPropagation();' Line Number: 250
User prompt
Doge shouldn't move towards build spots when they are clicked.
User prompt
Please fix the bug: 'ReferenceError: globalPos is not defined' in or related to this line: 'var dx = enemy.x - globalPos.x; // Use global tower X' Line Number: 660
Code edit (1 edits merged)
Please save this source code
Code edit (12 edits merged)
Please save this source code
User prompt
Erase all Tower Menu code
User prompt
Erase all debug code
User prompt
Please fix the bug: 'Uncaught ReferenceError: worldPos is not defined' in or related to this line: 'var dx = worldPos.x - doge.x;' Line Number: 1287
Code edit (4 edits merged)
Please save this source code
User prompt
Please fix the bug: 'TypeError: enemy.takeDamage is not a function' in or related to this line: 'enemy.takeDamage(self.damage);' Line Number: 69
Code edit (1 edits merged)
Please save this source code
Code edit (20 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of null (reading 'length')' in or related to this line: 'self.graphic = self.attachAsset(null, {}); // Start with no asset, will be set by init' Line Number: 472
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of null (reading 'length')' in or related to this line: 'self.graphic = self.attachAsset(null, {}); // Start with no asset' Line Number: 119
User prompt
Please fix the bug: 'Uncaught TypeError: LK.Rectangle is not a constructor' in or related to this line: 'var background = new LK.Rectangle({' Line Number: 559
Code edit (1 edits merged)
Please save this source code
Code edit (2 edits merged)
Please save this source code
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScore: 0 }); /**** * Classes ****/ var BarkWave = Container.expand(function () { var self = Container.call(this); var graphic = self.attachAsset('barkWave', { anchorX: 0.5, anchorY: 0.5, alpha: 0.5 }); self.damage = 3; // Slightly increased damage for manual ability self.duration = 30; // frames self.radius = 100; self.maxRadius = 350; // Slightly larger radius for manual ability self.update = function () { self.radius += 15; // Expand slightly faster graphic.scaleX = self.radius / 100; graphic.scaleY = self.radius / 100; graphic.alpha -= 0.017; // Check for enemies in range ONCE per enemy per wave activation // To avoid hitting the same enemy multiple times with one wave if (!self.enemiesHit) { self.enemiesHit = []; } // Initialize if needed for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; // Only check if enemy hasn't been hit by this wave yet if (self.enemiesHit.indexOf(enemy) === -1) { var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < self.radius) { enemy.takeDamage(self.damage); self.enemiesHit.push(enemy); // Mark enemy as hit } } } self.duration--; if (self.duration <= 0 || self.radius >= self.maxRadius) { self.destroy(); } }; return self; }); var BuildSpot = Container.expand(function () { var self = Container.call(this); var graphic = self.attachAsset('buildSpot', { anchorX: 0.5, anchorY: 0.5, alpha: 0.5 }); self.hasTower = false; self.tower = null; self.down = function (x, y, obj) { if (!self.hasTower && currency >= TOWER_COST) { self.buildTower(); } }; self.buildTower = function () { var tower = new StaplerTower(); // Tower position is relative to the build spot container tower.x = 0; tower.y = 0; self.addChild(tower); self.tower = tower; self.hasTower = true; graphic.alpha = 0.1; // Make spot less visible when tower is built currency -= TOWER_COST; currencyText.setText("$: " + currency); LK.getSound('buildTower').play(); }; return self; }); var DogeHero = Container.expand(function () { var self = Container.call(this); var graphic = self.attachAsset('dogeHero', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 5; self.targetX = self.x; self.targetY = self.y; // Auto Attack Stats /**** MODIFIED ****/ self.autoAttackRange = 180; self.autoAttackDamage = 2; self.autoAttackCooldownTime = 45; // frames between auto-attacks (faster than bark) self.currentAutoAttackCooldown = 0; // Manual Bark Ability Stats /**** MODIFIED ****/ self.manualBarkCooldownTime = 300; // frames between manual barks (5 seconds at 60fps) self.currentManualBarkCooldown = 0; self.update = function () { // --- Movement --- var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > self.speed) { // Only move if further than speed to prevent jitter dx = dx / distance * self.speed; dy = dy / distance * self.speed; self.x += dx; self.y += dy; } else if (distance > 0) { // Snap to position if very close self.x = self.targetX; self.y = self.targetY; } // --- Cooldowns --- /**** MODIFIED ****/ if (self.currentAutoAttackCooldown > 0) { self.currentAutoAttackCooldown--; } if (self.currentManualBarkCooldown > 0) { self.currentManualBarkCooldown--; } // --- Auto Attack Logic --- /**** NEW ****/ if (self.currentAutoAttackCooldown <= 0) { var closestEnemy = null; var minDistanceSq = self.autoAttackRange * self.autoAttackRange; // Use squared distance for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var ex = enemy.x - self.x; var ey = enemy.y - self.y; var distSq = ex * ex + ey * ey; if (distSq < minDistanceSq) { minDistanceSq = distSq; closestEnemy = enemy; } } if (closestEnemy) { // Perform the attack closestEnemy.takeDamage(self.autoAttackDamage); LK.getSound('dogeAutoAttack').play(); // Play sound // Optional: Add a small visual effect like a quick flash or tiny projectile later // LK.effects.flashObject(self, 0xffff00, 100); self.currentAutoAttackCooldown = self.autoAttackCooldownTime; // Reset cooldown } } }; self.setTarget = function (x, y) { self.targetX = x; self.targetY = y; }; // Manual Bark Trigger - Called from UI button /**** NEW ****/ self.manualBark = function () { if (self.currentManualBarkCooldown <= 0) { var wave = new BarkWave(); wave.x = self.x; wave.y = self.y; game.addChild(wave); // Add wave to the main game container LK.getSound('dogeBark').play(); self.currentManualBarkCooldown = self.manualBarkCooldownTime; // Reset cooldown return true; // Indicate success } return false; // Indicate cooldown active }; // Removed the old bark() method which created the wave directly // self.bark = function () { ... } return self; }); // Enemy class remains largely the same var Enemy = Container.expand(function () { var self = Container.call(this); var graphic = self.attachAsset('enemyPaper', { anchorX: 0.5, anchorY: 0.5 }); self.health = 3; self.speed = 2; self.value = 10; // Currency earned when killed self.currentPathIndex = 0; self.update = function () { // Check if pathPoints is loaded and valid if (!pathPoints || pathPoints.length === 0) { return; } if (self.currentPathIndex < pathPoints.length) { var target = pathPoints[self.currentPathIndex]; if (!target) { // Safety check self.currentPathIndex++; return; } var dx = target.x - self.x; var dy = target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Use a slightly larger threshold for path progression if (distance < self.speed * 1.5) { // Adjusted threshold self.currentPathIndex++; // Check if enemy reached the goal if (self.currentPathIndex >= pathPoints.length) { playerLives--; livesText.setText("Lives: " + playerLives); LK.getSound('enemyReachGoal').play(); if (playerLives <= 0) { // Game over if (LK.getScore() > storage.highScore) { storage.highScore = LK.getScore(); } LK.showGameOver(); } self.destroy(); return; } } else { // Move towards the target point dx = dx / distance * self.speed; dy = dy / distance * self.speed; self.x += dx; self.y += dy; } } else { // If somehow past the end of path but not destroyed, remove it console.log("Enemy past end of path, destroying."); self.destroy(); } }; self.takeDamage = function (amount) { self.health -= amount; // Flash red when taking damage LK.effects.flashObject(self, 0xff0000, 200); if (self.health <= 0 && self.parent) { // Added check for self.parent before accessing currency currency += self.value; currencyText.setText("$: " + currency); LK.setScore(LK.getScore() + self.value); scoreText.setText("Score: " + LK.getScore()); LK.getSound('enemyDeath').play(); self.destroy(); } }; return self; }); // GameLevel class remains the same var GameLevel = Container.expand(function () { var self = Container.call(this); var pathGraphics = []; // Store path visuals var buildSpotGraphics = []; // Store build spot visuals self.createPath = function (pathData) { // Clear previous path graphics pathGraphics.forEach(function (tile) { tile.destroy(); }); pathGraphics = []; // Assume pathData is the pathPoints array for (var i = 0; i < pathData.length - 1; i++) { var start = pathData[i]; var end = pathData[i + 1]; if (!start || !end) { continue; } // Safety check // Calculate direction and distance var dx = end.x - start.x; var dy = end.y - start.y; var distance = Math.sqrt(dx * dx + dy * dy); // Adjust tile spacing slightly var steps = Math.ceil(distance / 90); // Slightly closer tiles for (var j = 0; j < steps; j++) { var ratio = j / steps; var x = start.x + dx * ratio; var y = start.y + dy * ratio; var tile = LK.getAsset('pathTile', { x: x, y: y, alpha: 0.3, // Make path fainter anchorX: 0.5, anchorY: 0.5 }); self.addChild(tile); pathGraphics.push(tile); // Store reference } } }; self.createBuildSpots = function (spotsData) { // Clear previous build spots buildSpotGraphics.forEach(function (spot) { spot.destroy(); }); buildSpotGraphics = []; self.buildSpots = []; // Clear the logical array too for (var i = 0; i < spotsData.length; i++) { if (!spotsData[i]) { continue; } // Safety check var spot = new BuildSpot(); spot.x = spotsData[i].x; spot.y = spotsData[i].y; self.addChild(spot); self.buildSpots.push(spot); // Store logical spot buildSpotGraphics.push(spot); // Store graphical spot } }; return self; }); // Goal class remains the same var Goal = Container.expand(function () { var self = Container.call(this); var graphic = self.attachAsset('goal', { anchorX: 0.5, anchorY: 0.5, alpha: 0.6 }); return self; }); // StaplerTower class remains the same var StaplerTower = Container.expand(function () { var self = Container.call(this); var graphic = self.attachAsset('stapler', { anchorX: 0.5, anchorY: 0.5 }); self.fireRate = 60; // frames between shots self.range = 300; self.damage = 1; self.lastFired = 0; // Renamed from fireCooldown for clarity self.update = function () { self.lastFired++; // Increment timer if (self.lastFired >= self.fireRate) { // Find closest enemy var closestEnemy = null; // Use squared distance for efficiency var minDistanceSq = self.range * self.range; // Need global position of the tower for range check var globalPos = self.parent.toGlobal(self.position); for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = enemy.x - globalPos.x; // Use global tower X var dy = enemy.y - globalPos.y; // Use global tower Y var distanceSq = dx * dx + dy * dy; // Squared distance check if (distanceSq < minDistanceSq) { minDistanceSq = distanceSq; closestEnemy = enemy; } } if (closestEnemy) { self.shoot(closestEnemy); self.lastFired = 0; // Reset timer } } }; self.shoot = function (target) { var bullet = new TowerBullet(); // Use global position for bullet starting point var globalPos = self.parent.toGlobal(self.position); bullet.x = globalPos.x; bullet.y = globalPos.y; bullet.target = target; bullet.damage = self.damage; // Add bullet to the main game container, not the tower itself game.addChild(bullet); bullets.push(bullet); LK.getSound('shoot').play(); }; return self; }); // TowerBullet class remains the same var TowerBullet = Container.expand(function () { var self = Container.call(this); var graphic = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 10; self.damage = 1; self.target = null; self.update = function () { // Check if target exists and is still in the game if (!self.target || !self.target.parent) { self.destroy(); return; } var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Use speed as collision threshold if (distance < self.speed) { self.target.takeDamage(self.damage); self.destroy(); return; } // Normalize and multiply by speed dx = dx / distance * self.speed; dy = dy / distance * self.speed; self.x += dx; self.y += dy; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x558855 // Darker green background }); /**** * Game Code ****/ // Added sound for auto-attack // Game constants var TOWER_COST = 50; var WAVE_DELAY = 300; // frames between waves var MAX_WAVES = 10; // Access screen dimensions directly from LK var SCREEN_HEIGHT = 2732; // Standard iPad Pro height (portrait mode) var SCREEN_WIDTH = 2048; // Standard iPad Pro width (portrait mode) var PAN_THRESHOLD = SCREEN_HEIGHT * 0.25; // Start panning when Doge is in top/bottom 25% /**** NEW ****/ var MAP_HEIGHT = 3000; // Define total map height /**** NEW - ADJUST AS NEEDED ****/ // Game variables var currency = 100; var playerLives = 5; var currentWave = 0; var waveTimer = 0; var isWaveActive = false; var enemies = []; var bullets = []; var pathPoints = []; var level; var doge; var goal; // UI elements var scoreText = new Text2("Score: 0", { size: 50, fill: 0xFFFFFF }); // White text scoreText.anchor.set(0.5, 0); LK.gui.top.addChild(scoreText); var waveText = new Text2("Wave: 0/" + MAX_WAVES, { size: 50, fill: 0xFFFFFF }); waveText.anchor.set(1, 0); // Anchor top-right waveText.x = -100; // Position from right edge LK.gui.topRight.addChild(waveText); var currencyText = new Text2("$: " + currency, { size: 50, fill: 0xFFFFFF }); currencyText.anchor.set(0.5, 0); currencyText.y = 60; LK.gui.top.addChild(currencyText); var livesText = new Text2("Lives: " + playerLives, { size: 50, fill: 0xFFFFFF }); livesText.anchor.set(1, 0); // Anchor top-right livesText.x = -100; livesText.y = 60; LK.gui.topRight.addChild(livesText); // Removed old info text, replaced by Bark button // var infoText = new Text2(...) // Bark Button /**** NEW ****/ var barkButton = new Text2("BARK!", { size: 60, fill: 0xffcc00, // Doge color stroke: 0x000000, strokeThickness: 4 }); barkButton.anchor.set(0.5, 1); // Anchor bottom-center barkButton.y = -50; // Position from bottom edge barkButton.interactive = true; // Make it clickable barkButton.down = function () { if (doge) { var success = doge.manualBark(); if (success) { // Optional: visual feedback on button press barkButton.scale.set(1.1); LK.setTimeout(function () { barkButton.scale.set(1.0); }, 100); } } }; LK.gui.bottom.addChild(barkButton); // Initialize game level and path function initializeGame() { // Ensure game container starts at y=0 game.y = 0; level = new GameLevel(); game.addChild(level); // Create path points (Ensure Y values extend significantly for taller map) pathPoints = [{ x: SCREEN_WIDTH * 0.1, y: 100 }, // Start near top-left { x: SCREEN_WIDTH * 0.1, y: 500 }, { x: SCREEN_WIDTH * 0.4, y: 500 }, { x: SCREEN_WIDTH * 0.4, y: 1000 }, { x: SCREEN_WIDTH * 0.7, y: 1000 }, { x: SCREEN_WIDTH * 0.7, y: 1500 }, { x: SCREEN_WIDTH * 0.3, y: 1500 }, { x: SCREEN_WIDTH * 0.3, y: 2000 }, { x: SCREEN_WIDTH * 0.8, y: 2000 }, { x: SCREEN_WIDTH * 0.8, y: 2800 } // End near bottom-right (within MAP_HEIGHT) ]; level.createPath(pathPoints); // Create build spots (Distribute along the taller path) var buildSpots = [{ x: SCREEN_WIDTH * 0.25, y: 300 }, { x: SCREEN_WIDTH * 0.25, y: 750 }, { x: SCREEN_WIDTH * 0.55, y: 750 }, { x: SCREEN_WIDTH * 0.55, y: 1250 }, { x: SCREEN_WIDTH * 0.85, y: 1250 }, { x: SCREEN_WIDTH * 0.50, y: 1750 }, { x: SCREEN_WIDTH * 0.15, y: 1750 }, { x: SCREEN_WIDTH * 0.50, y: 2400 }, { x: SCREEN_WIDTH * 0.90, y: 2400 }]; level.createBuildSpots(buildSpots); // Create goal at the end of the path goal = new Goal(); if (pathPoints.length > 0) { goal.x = pathPoints[pathPoints.length - 1].x; goal.y = pathPoints[pathPoints.length - 1].y; game.addChild(goal); // Add goal to the game container } // Create Doge hero (Start somewhere sensible, like near the middle screen Y) doge = new DogeHero(); doge.x = SCREEN_WIDTH / 2; doge.y = SCREEN_HEIGHT / 2; // Start doge relative to initial screen view doge.targetX = doge.x; // Initialize target position doge.targetY = doge.y; game.addChild(doge); // Add doge to the game container // Reset game variables currency = 100; playerLives = 5; currentWave = 0; waveTimer = 0; isWaveActive = false; // Clear existing enemies/bullets if re-initializing enemies.forEach(function (e) { if (e.parent) { e.destroy(); } }); enemies = []; bullets.forEach(function (b) { if (b.parent) { b.destroy(); } }); bullets = []; // Update UI currencyText.setText("$: " + currency); livesText.setText("Lives: " + playerLives); waveText.setText("Wave: " + currentWave + "/" + MAX_WAVES); scoreText.setText("Score: " + LK.getScore()); // Reset score text barkButton.setText("BARK!"); // Reset bark button text // Start background music LK.playMusic('bgmusic'); } function spawnWave() { currentWave++; waveText.setText("Wave: " + currentWave + "/" + MAX_WAVES); var enemyCount = 5 + currentWave * 2; var spawnInterval = 60; // frames between enemy spawns function spawnEnemy(count) { if (count <= 0 || !pathPoints || pathPoints.length === 0) { // Added check for pathPoints isWaveActive = true; // Mark wave active even if spawning failed/finished return; } var enemy = new Enemy(); enemy.x = pathPoints[0].x; enemy.y = pathPoints[0].y; // Increase difficulty with each wave enemy.health = 2 + Math.floor(currentWave / 2); if (currentWave > 5) { enemy.speed = 2.5; } if (currentWave > 8) { enemy.speed = 3; } game.addChild(enemy); // Add enemy to the main game container enemies.push(enemy); // Use LK.setTimeout for delays LK.setTimeout(function () { spawnEnemy(count - 1); }, spawnInterval * 16.67); // Use ~16.67ms for 60fps } isWaveActive = false; // Mark wave as starting (will be set true after first spawn attempt or completion) spawnEnemy(enemyCount); } function checkWaveComplete() { if (isWaveActive && enemies.length === 0) { // Double check if we really spawned everything for the wave before proceeding // This basic check assumes spawning finishes before all enemies are killed isWaveActive = false; waveTimer = 0; // Reset timer for next wave delay if (currentWave >= MAX_WAVES) { // Player wins if (LK.getScore() > storage.highScore) { storage.highScore = LK.getScore(); } LK.showYouWin(); } } } // Event Handlers /**** MODIFIED ****/ var dragDoge = false; game.down = function (x, y, obj) { // Convert click coordinates to game world coordinates var worldPos = game.toLocal({ x: x, y: y }); // <-- CRITICAL CONVERSION // Check if clicking on Doge to start drag (using world coordinates for check) var dx = worldPos.x - doge.x; var dy = worldPos.y - doge.y; var distance = Math.sqrt(dx * dx + dy * dy); if (doge && distance < doge.width / 2) { // Check if doge exists before accessing width dragDoge = true; // Set target to current pos while dragging to stop auto-movement doge.setTarget(doge.x, doge.y); } else if (doge) { // Check if doge exists before setting target // Otherwise, set movement target using WORLD coordinates dragDoge = false; // Ensure drag is off if clicking elsewhere doge.setTarget(worldPos.x, worldPos.y); // <-- USES CONVERTED worldPos } }; game.move = function (x, y, obj) { if (dragDoge && doge) { // Check if doge exists // Convert move coordinates to game world coordinates var worldPos = game.toLocal({ x: x, y: y }); // <-- CRITICAL CONVERSION doge.x = worldPos.x; // <-- Update using world coords doge.y = worldPos.y; // <-- Update using world coords // Keep target updated while dragging to prevent auto-move after drop doge.setTarget(doge.x, doge.y); } }; game.up = function (x, y, obj) { if (dragDoge) { dragDoge = false; // Optional: after dropping, set target to final drop position if needed // if (doge) { // doge.setTarget(doge.x, doge.y); // } } }; // Main game loop /**** MODIFIED ****/ game.update = function () { // --- Wave Management --- if (!isWaveActive && currentWave < MAX_WAVES) { // Only increment timer if not won yet waveTimer++; if (waveTimer >= WAVE_DELAY) { waveTimer = 0; // Reset timer immediately spawnWave(); } } // --- Update Bullets --- (Remove destroyed ones) for (var i = bullets.length - 1; i >= 0; i--) { if (!bullets[i].parent) { bullets.splice(i, 1); } } // --- Update Enemies --- (Remove destroyed ones) for (var i = enemies.length - 1; i >= 0; i--) { if (!enemies[i].parent) { enemies.splice(i, 1); } } // --- Update Doge (handles its own cooldowns and attacks) --- if (doge && doge.parent) { // Check if doge exists doge.update(); // Make sure Doge's update runs } // --- Check Wave Completion --- checkWaveComplete(); // --- Update Bark Button UI --- /**** NEW ****/ if (doge) { // Check if doge exists if (doge.currentManualBarkCooldown > 0) { var secondsLeft = Math.ceil(doge.currentManualBarkCooldown / 60); // Approx seconds barkButton.setText("WAIT (" + secondsLeft + ")"); barkButton.setText("WAIT (" + secondsLeft + ")", { fill: 0x888888 }); // Grey out text } else { barkButton.setText("BARK!"); barkButton.setText("BARK!", { fill: 0xffcc00 }); // Restore color } } // --- Camera Panning Logic --- /**** NEW ****/ if (doge) { // Check if doge exists var dogeScreenY = doge.y + game.y; // Doge Y relative to screen top var targetGameY = game.y; // Start with current camera position if (dogeScreenY < PAN_THRESHOLD) { // Calculate desired camera position to keep Doge at the threshold targetGameY = -(doge.y - PAN_THRESHOLD); } else if (dogeScreenY > SCREEN_HEIGHT - PAN_THRESHOLD) { // Calculate desired camera position targetGameY = -(doge.y - (SCREEN_HEIGHT - PAN_THRESHOLD)); } // Clamp the target camera position to map boundaries var minGameY = -(MAP_HEIGHT - SCREEN_HEIGHT); var maxGameY = 0; targetGameY = Math.max(minGameY, Math.min(maxGameY, targetGameY)); // Smoothly move the camera towards the target position (optional, adds smoothness) var camSmoothFactor = 0.1; game.y += (targetGameY - game.y) * camSmoothFactor; // Or apply directly for instant panning: // game.y = targetGameY; } }; // Initialize the game LK.setScore(0); // Reset score on start initializeGame();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
/****
* Classes
****/
var BarkWave = Container.expand(function () {
var self = Container.call(this);
var graphic = self.attachAsset('barkWave', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.5
});
self.damage = 3; // Slightly increased damage for manual ability
self.duration = 30; // frames
self.radius = 100;
self.maxRadius = 350; // Slightly larger radius for manual ability
self.update = function () {
self.radius += 15; // Expand slightly faster
graphic.scaleX = self.radius / 100;
graphic.scaleY = self.radius / 100;
graphic.alpha -= 0.017;
// Check for enemies in range ONCE per enemy per wave activation
// To avoid hitting the same enemy multiple times with one wave
if (!self.enemiesHit) {
self.enemiesHit = [];
} // Initialize if needed
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
// Only check if enemy hasn't been hit by this wave yet
if (self.enemiesHit.indexOf(enemy) === -1) {
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < self.radius) {
enemy.takeDamage(self.damage);
self.enemiesHit.push(enemy); // Mark enemy as hit
}
}
}
self.duration--;
if (self.duration <= 0 || self.radius >= self.maxRadius) {
self.destroy();
}
};
return self;
});
var BuildSpot = Container.expand(function () {
var self = Container.call(this);
var graphic = self.attachAsset('buildSpot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.5
});
self.hasTower = false;
self.tower = null;
self.down = function (x, y, obj) {
if (!self.hasTower && currency >= TOWER_COST) {
self.buildTower();
}
};
self.buildTower = function () {
var tower = new StaplerTower();
// Tower position is relative to the build spot container
tower.x = 0;
tower.y = 0;
self.addChild(tower);
self.tower = tower;
self.hasTower = true;
graphic.alpha = 0.1; // Make spot less visible when tower is built
currency -= TOWER_COST;
currencyText.setText("$: " + currency);
LK.getSound('buildTower').play();
};
return self;
});
var DogeHero = Container.expand(function () {
var self = Container.call(this);
var graphic = self.attachAsset('dogeHero', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 5;
self.targetX = self.x;
self.targetY = self.y;
// Auto Attack Stats /**** MODIFIED ****/
self.autoAttackRange = 180;
self.autoAttackDamage = 2;
self.autoAttackCooldownTime = 45; // frames between auto-attacks (faster than bark)
self.currentAutoAttackCooldown = 0;
// Manual Bark Ability Stats /**** MODIFIED ****/
self.manualBarkCooldownTime = 300; // frames between manual barks (5 seconds at 60fps)
self.currentManualBarkCooldown = 0;
self.update = function () {
// --- Movement ---
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > self.speed) {
// Only move if further than speed to prevent jitter
dx = dx / distance * self.speed;
dy = dy / distance * self.speed;
self.x += dx;
self.y += dy;
} else if (distance > 0) {
// Snap to position if very close
self.x = self.targetX;
self.y = self.targetY;
}
// --- Cooldowns --- /**** MODIFIED ****/
if (self.currentAutoAttackCooldown > 0) {
self.currentAutoAttackCooldown--;
}
if (self.currentManualBarkCooldown > 0) {
self.currentManualBarkCooldown--;
}
// --- Auto Attack Logic --- /**** NEW ****/
if (self.currentAutoAttackCooldown <= 0) {
var closestEnemy = null;
var minDistanceSq = self.autoAttackRange * self.autoAttackRange; // Use squared distance
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var ex = enemy.x - self.x;
var ey = enemy.y - self.y;
var distSq = ex * ex + ey * ey;
if (distSq < minDistanceSq) {
minDistanceSq = distSq;
closestEnemy = enemy;
}
}
if (closestEnemy) {
// Perform the attack
closestEnemy.takeDamage(self.autoAttackDamage);
LK.getSound('dogeAutoAttack').play(); // Play sound
// Optional: Add a small visual effect like a quick flash or tiny projectile later
// LK.effects.flashObject(self, 0xffff00, 100);
self.currentAutoAttackCooldown = self.autoAttackCooldownTime; // Reset cooldown
}
}
};
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
};
// Manual Bark Trigger - Called from UI button /**** NEW ****/
self.manualBark = function () {
if (self.currentManualBarkCooldown <= 0) {
var wave = new BarkWave();
wave.x = self.x;
wave.y = self.y;
game.addChild(wave); // Add wave to the main game container
LK.getSound('dogeBark').play();
self.currentManualBarkCooldown = self.manualBarkCooldownTime; // Reset cooldown
return true; // Indicate success
}
return false; // Indicate cooldown active
};
// Removed the old bark() method which created the wave directly
// self.bark = function () { ... }
return self;
});
// Enemy class remains largely the same
var Enemy = Container.expand(function () {
var self = Container.call(this);
var graphic = self.attachAsset('enemyPaper', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 3;
self.speed = 2;
self.value = 10; // Currency earned when killed
self.currentPathIndex = 0;
self.update = function () {
// Check if pathPoints is loaded and valid
if (!pathPoints || pathPoints.length === 0) {
return;
}
if (self.currentPathIndex < pathPoints.length) {
var target = pathPoints[self.currentPathIndex];
if (!target) {
// Safety check
self.currentPathIndex++;
return;
}
var dx = target.x - self.x;
var dy = target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Use a slightly larger threshold for path progression
if (distance < self.speed * 1.5) {
// Adjusted threshold
self.currentPathIndex++;
// Check if enemy reached the goal
if (self.currentPathIndex >= pathPoints.length) {
playerLives--;
livesText.setText("Lives: " + playerLives);
LK.getSound('enemyReachGoal').play();
if (playerLives <= 0) {
// Game over
if (LK.getScore() > storage.highScore) {
storage.highScore = LK.getScore();
}
LK.showGameOver();
}
self.destroy();
return;
}
} else {
// Move towards the target point
dx = dx / distance * self.speed;
dy = dy / distance * self.speed;
self.x += dx;
self.y += dy;
}
} else {
// If somehow past the end of path but not destroyed, remove it
console.log("Enemy past end of path, destroying.");
self.destroy();
}
};
self.takeDamage = function (amount) {
self.health -= amount;
// Flash red when taking damage
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0 && self.parent) {
// Added check for self.parent before accessing currency
currency += self.value;
currencyText.setText("$: " + currency);
LK.setScore(LK.getScore() + self.value);
scoreText.setText("Score: " + LK.getScore());
LK.getSound('enemyDeath').play();
self.destroy();
}
};
return self;
});
// GameLevel class remains the same
var GameLevel = Container.expand(function () {
var self = Container.call(this);
var pathGraphics = []; // Store path visuals
var buildSpotGraphics = []; // Store build spot visuals
self.createPath = function (pathData) {
// Clear previous path graphics
pathGraphics.forEach(function (tile) {
tile.destroy();
});
pathGraphics = [];
// Assume pathData is the pathPoints array
for (var i = 0; i < pathData.length - 1; i++) {
var start = pathData[i];
var end = pathData[i + 1];
if (!start || !end) {
continue;
} // Safety check
// Calculate direction and distance
var dx = end.x - start.x;
var dy = end.y - start.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Adjust tile spacing slightly
var steps = Math.ceil(distance / 90); // Slightly closer tiles
for (var j = 0; j < steps; j++) {
var ratio = j / steps;
var x = start.x + dx * ratio;
var y = start.y + dy * ratio;
var tile = LK.getAsset('pathTile', {
x: x,
y: y,
alpha: 0.3,
// Make path fainter
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(tile);
pathGraphics.push(tile); // Store reference
}
}
};
self.createBuildSpots = function (spotsData) {
// Clear previous build spots
buildSpotGraphics.forEach(function (spot) {
spot.destroy();
});
buildSpotGraphics = [];
self.buildSpots = []; // Clear the logical array too
for (var i = 0; i < spotsData.length; i++) {
if (!spotsData[i]) {
continue;
} // Safety check
var spot = new BuildSpot();
spot.x = spotsData[i].x;
spot.y = spotsData[i].y;
self.addChild(spot);
self.buildSpots.push(spot); // Store logical spot
buildSpotGraphics.push(spot); // Store graphical spot
}
};
return self;
});
// Goal class remains the same
var Goal = Container.expand(function () {
var self = Container.call(this);
var graphic = self.attachAsset('goal', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6
});
return self;
});
// StaplerTower class remains the same
var StaplerTower = Container.expand(function () {
var self = Container.call(this);
var graphic = self.attachAsset('stapler', {
anchorX: 0.5,
anchorY: 0.5
});
self.fireRate = 60; // frames between shots
self.range = 300;
self.damage = 1;
self.lastFired = 0; // Renamed from fireCooldown for clarity
self.update = function () {
self.lastFired++; // Increment timer
if (self.lastFired >= self.fireRate) {
// Find closest enemy
var closestEnemy = null;
// Use squared distance for efficiency
var minDistanceSq = self.range * self.range;
// Need global position of the tower for range check
var globalPos = self.parent.toGlobal(self.position);
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - globalPos.x; // Use global tower X
var dy = enemy.y - globalPos.y; // Use global tower Y
var distanceSq = dx * dx + dy * dy; // Squared distance check
if (distanceSq < minDistanceSq) {
minDistanceSq = distanceSq;
closestEnemy = enemy;
}
}
if (closestEnemy) {
self.shoot(closestEnemy);
self.lastFired = 0; // Reset timer
}
}
};
self.shoot = function (target) {
var bullet = new TowerBullet();
// Use global position for bullet starting point
var globalPos = self.parent.toGlobal(self.position);
bullet.x = globalPos.x;
bullet.y = globalPos.y;
bullet.target = target;
bullet.damage = self.damage;
// Add bullet to the main game container, not the tower itself
game.addChild(bullet);
bullets.push(bullet);
LK.getSound('shoot').play();
};
return self;
});
// TowerBullet class remains the same
var TowerBullet = Container.expand(function () {
var self = Container.call(this);
var graphic = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 10;
self.damage = 1;
self.target = null;
self.update = function () {
// Check if target exists and is still in the game
if (!self.target || !self.target.parent) {
self.destroy();
return;
}
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Use speed as collision threshold
if (distance < self.speed) {
self.target.takeDamage(self.damage);
self.destroy();
return;
}
// Normalize and multiply by speed
dx = dx / distance * self.speed;
dy = dy / distance * self.speed;
self.x += dx;
self.y += dy;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x558855 // Darker green background
});
/****
* Game Code
****/
// Added sound for auto-attack
// Game constants
var TOWER_COST = 50;
var WAVE_DELAY = 300; // frames between waves
var MAX_WAVES = 10;
// Access screen dimensions directly from LK
var SCREEN_HEIGHT = 2732; // Standard iPad Pro height (portrait mode)
var SCREEN_WIDTH = 2048; // Standard iPad Pro width (portrait mode)
var PAN_THRESHOLD = SCREEN_HEIGHT * 0.25; // Start panning when Doge is in top/bottom 25% /**** NEW ****/
var MAP_HEIGHT = 3000; // Define total map height /**** NEW - ADJUST AS NEEDED ****/
// Game variables
var currency = 100;
var playerLives = 5;
var currentWave = 0;
var waveTimer = 0;
var isWaveActive = false;
var enemies = [];
var bullets = [];
var pathPoints = [];
var level;
var doge;
var goal;
// UI elements
var scoreText = new Text2("Score: 0", {
size: 50,
fill: 0xFFFFFF
}); // White text
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
var waveText = new Text2("Wave: 0/" + MAX_WAVES, {
size: 50,
fill: 0xFFFFFF
});
waveText.anchor.set(1, 0); // Anchor top-right
waveText.x = -100; // Position from right edge
LK.gui.topRight.addChild(waveText);
var currencyText = new Text2("$: " + currency, {
size: 50,
fill: 0xFFFFFF
});
currencyText.anchor.set(0.5, 0);
currencyText.y = 60;
LK.gui.top.addChild(currencyText);
var livesText = new Text2("Lives: " + playerLives, {
size: 50,
fill: 0xFFFFFF
});
livesText.anchor.set(1, 0); // Anchor top-right
livesText.x = -100;
livesText.y = 60;
LK.gui.topRight.addChild(livesText);
// Removed old info text, replaced by Bark button
// var infoText = new Text2(...)
// Bark Button /**** NEW ****/
var barkButton = new Text2("BARK!", {
size: 60,
fill: 0xffcc00,
// Doge color
stroke: 0x000000,
strokeThickness: 4
});
barkButton.anchor.set(0.5, 1); // Anchor bottom-center
barkButton.y = -50; // Position from bottom edge
barkButton.interactive = true; // Make it clickable
barkButton.down = function () {
if (doge) {
var success = doge.manualBark();
if (success) {
// Optional: visual feedback on button press
barkButton.scale.set(1.1);
LK.setTimeout(function () {
barkButton.scale.set(1.0);
}, 100);
}
}
};
LK.gui.bottom.addChild(barkButton);
// Initialize game level and path
function initializeGame() {
// Ensure game container starts at y=0
game.y = 0;
level = new GameLevel();
game.addChild(level);
// Create path points (Ensure Y values extend significantly for taller map)
pathPoints = [{
x: SCREEN_WIDTH * 0.1,
y: 100
},
// Start near top-left
{
x: SCREEN_WIDTH * 0.1,
y: 500
}, {
x: SCREEN_WIDTH * 0.4,
y: 500
}, {
x: SCREEN_WIDTH * 0.4,
y: 1000
}, {
x: SCREEN_WIDTH * 0.7,
y: 1000
}, {
x: SCREEN_WIDTH * 0.7,
y: 1500
}, {
x: SCREEN_WIDTH * 0.3,
y: 1500
}, {
x: SCREEN_WIDTH * 0.3,
y: 2000
}, {
x: SCREEN_WIDTH * 0.8,
y: 2000
}, {
x: SCREEN_WIDTH * 0.8,
y: 2800
} // End near bottom-right (within MAP_HEIGHT)
];
level.createPath(pathPoints);
// Create build spots (Distribute along the taller path)
var buildSpots = [{
x: SCREEN_WIDTH * 0.25,
y: 300
}, {
x: SCREEN_WIDTH * 0.25,
y: 750
}, {
x: SCREEN_WIDTH * 0.55,
y: 750
}, {
x: SCREEN_WIDTH * 0.55,
y: 1250
}, {
x: SCREEN_WIDTH * 0.85,
y: 1250
}, {
x: SCREEN_WIDTH * 0.50,
y: 1750
}, {
x: SCREEN_WIDTH * 0.15,
y: 1750
}, {
x: SCREEN_WIDTH * 0.50,
y: 2400
}, {
x: SCREEN_WIDTH * 0.90,
y: 2400
}];
level.createBuildSpots(buildSpots);
// Create goal at the end of the path
goal = new Goal();
if (pathPoints.length > 0) {
goal.x = pathPoints[pathPoints.length - 1].x;
goal.y = pathPoints[pathPoints.length - 1].y;
game.addChild(goal); // Add goal to the game container
}
// Create Doge hero (Start somewhere sensible, like near the middle screen Y)
doge = new DogeHero();
doge.x = SCREEN_WIDTH / 2;
doge.y = SCREEN_HEIGHT / 2; // Start doge relative to initial screen view
doge.targetX = doge.x; // Initialize target position
doge.targetY = doge.y;
game.addChild(doge); // Add doge to the game container
// Reset game variables
currency = 100;
playerLives = 5;
currentWave = 0;
waveTimer = 0;
isWaveActive = false;
// Clear existing enemies/bullets if re-initializing
enemies.forEach(function (e) {
if (e.parent) {
e.destroy();
}
});
enemies = [];
bullets.forEach(function (b) {
if (b.parent) {
b.destroy();
}
});
bullets = [];
// Update UI
currencyText.setText("$: " + currency);
livesText.setText("Lives: " + playerLives);
waveText.setText("Wave: " + currentWave + "/" + MAX_WAVES);
scoreText.setText("Score: " + LK.getScore()); // Reset score text
barkButton.setText("BARK!"); // Reset bark button text
// Start background music
LK.playMusic('bgmusic');
}
function spawnWave() {
currentWave++;
waveText.setText("Wave: " + currentWave + "/" + MAX_WAVES);
var enemyCount = 5 + currentWave * 2;
var spawnInterval = 60; // frames between enemy spawns
function spawnEnemy(count) {
if (count <= 0 || !pathPoints || pathPoints.length === 0) {
// Added check for pathPoints
isWaveActive = true; // Mark wave active even if spawning failed/finished
return;
}
var enemy = new Enemy();
enemy.x = pathPoints[0].x;
enemy.y = pathPoints[0].y;
// Increase difficulty with each wave
enemy.health = 2 + Math.floor(currentWave / 2);
if (currentWave > 5) {
enemy.speed = 2.5;
}
if (currentWave > 8) {
enemy.speed = 3;
}
game.addChild(enemy); // Add enemy to the main game container
enemies.push(enemy);
// Use LK.setTimeout for delays
LK.setTimeout(function () {
spawnEnemy(count - 1);
}, spawnInterval * 16.67); // Use ~16.67ms for 60fps
}
isWaveActive = false; // Mark wave as starting (will be set true after first spawn attempt or completion)
spawnEnemy(enemyCount);
}
function checkWaveComplete() {
if (isWaveActive && enemies.length === 0) {
// Double check if we really spawned everything for the wave before proceeding
// This basic check assumes spawning finishes before all enemies are killed
isWaveActive = false;
waveTimer = 0; // Reset timer for next wave delay
if (currentWave >= MAX_WAVES) {
// Player wins
if (LK.getScore() > storage.highScore) {
storage.highScore = LK.getScore();
}
LK.showYouWin();
}
}
}
// Event Handlers /**** MODIFIED ****/
var dragDoge = false;
game.down = function (x, y, obj) {
// Convert click coordinates to game world coordinates
var worldPos = game.toLocal({
x: x,
y: y
}); // <-- CRITICAL CONVERSION
// Check if clicking on Doge to start drag (using world coordinates for check)
var dx = worldPos.x - doge.x;
var dy = worldPos.y - doge.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (doge && distance < doge.width / 2) {
// Check if doge exists before accessing width
dragDoge = true;
// Set target to current pos while dragging to stop auto-movement
doge.setTarget(doge.x, doge.y);
} else if (doge) {
// Check if doge exists before setting target
// Otherwise, set movement target using WORLD coordinates
dragDoge = false; // Ensure drag is off if clicking elsewhere
doge.setTarget(worldPos.x, worldPos.y); // <-- USES CONVERTED worldPos
}
};
game.move = function (x, y, obj) {
if (dragDoge && doge) {
// Check if doge exists
// Convert move coordinates to game world coordinates
var worldPos = game.toLocal({
x: x,
y: y
}); // <-- CRITICAL CONVERSION
doge.x = worldPos.x; // <-- Update using world coords
doge.y = worldPos.y; // <-- Update using world coords
// Keep target updated while dragging to prevent auto-move after drop
doge.setTarget(doge.x, doge.y);
}
};
game.up = function (x, y, obj) {
if (dragDoge) {
dragDoge = false;
// Optional: after dropping, set target to final drop position if needed
// if (doge) {
// doge.setTarget(doge.x, doge.y);
// }
}
};
// Main game loop /**** MODIFIED ****/
game.update = function () {
// --- Wave Management ---
if (!isWaveActive && currentWave < MAX_WAVES) {
// Only increment timer if not won yet
waveTimer++;
if (waveTimer >= WAVE_DELAY) {
waveTimer = 0; // Reset timer immediately
spawnWave();
}
}
// --- Update Bullets --- (Remove destroyed ones)
for (var i = bullets.length - 1; i >= 0; i--) {
if (!bullets[i].parent) {
bullets.splice(i, 1);
}
}
// --- Update Enemies --- (Remove destroyed ones)
for (var i = enemies.length - 1; i >= 0; i--) {
if (!enemies[i].parent) {
enemies.splice(i, 1);
}
}
// --- Update Doge (handles its own cooldowns and attacks) ---
if (doge && doge.parent) {
// Check if doge exists
doge.update(); // Make sure Doge's update runs
}
// --- Check Wave Completion ---
checkWaveComplete();
// --- Update Bark Button UI --- /**** NEW ****/
if (doge) {
// Check if doge exists
if (doge.currentManualBarkCooldown > 0) {
var secondsLeft = Math.ceil(doge.currentManualBarkCooldown / 60); // Approx seconds
barkButton.setText("WAIT (" + secondsLeft + ")");
barkButton.setText("WAIT (" + secondsLeft + ")", {
fill: 0x888888
}); // Grey out text
} else {
barkButton.setText("BARK!");
barkButton.setText("BARK!", {
fill: 0xffcc00
}); // Restore color
}
}
// --- Camera Panning Logic --- /**** NEW ****/
if (doge) {
// Check if doge exists
var dogeScreenY = doge.y + game.y; // Doge Y relative to screen top
var targetGameY = game.y; // Start with current camera position
if (dogeScreenY < PAN_THRESHOLD) {
// Calculate desired camera position to keep Doge at the threshold
targetGameY = -(doge.y - PAN_THRESHOLD);
} else if (dogeScreenY > SCREEN_HEIGHT - PAN_THRESHOLD) {
// Calculate desired camera position
targetGameY = -(doge.y - (SCREEN_HEIGHT - PAN_THRESHOLD));
}
// Clamp the target camera position to map boundaries
var minGameY = -(MAP_HEIGHT - SCREEN_HEIGHT);
var maxGameY = 0;
targetGameY = Math.max(minGameY, Math.min(maxGameY, targetGameY));
// Smoothly move the camera towards the target position (optional, adds smoothness)
var camSmoothFactor = 0.1;
game.y += (targetGameY - game.y) * camSmoothFactor;
// Or apply directly for instant panning:
// game.y = targetGameY;
}
};
// Initialize the game
LK.setScore(0); // Reset score on start
initializeGame();
Stapler Turret Sprite Sheet: An office stapler mounted on a simple rotating base images show it opening and closing.. In-Game asset. 2d. High contrast. No shadows
Stapler bullet. In-Game asset. 2d. High contrast. No shadows
Remove the background
A stylized golden fire hydrant labeled "Free Speech" OR a glowing server rack labeled "Meme Archive".. In-Game asset. 2d. High contrast. No shadows
Paperclip. In-Game asset. 2d. High contrast. No shadows
A simple, slightly glowing circular outline indicating where towers can be placed.. In-Game asset. 2d. High contrast. No shadows
More cabinet, More Files
black circle. In-Game asset. 2d. High contrast. No shadows
DOGE Enemy Auditor. In-Game asset. 2d. High contrast. No shadows
grow the image and have papers fall from the folders
Squish the image like the cabinet is squeezing in on itself
Red Tape enemy extends as if bouncing while moving
Envelope flying through the air with wings. In-Game asset. 2d. High contrast. No shadows
"Laser Cat Perch": A cat with laser eyes that "targets" and zaps high-priority enemies with precision. (Internet loves cats).. In-Game asset. 2d. High contrast. No shadows
"Rickroller": A RickAstley tower holding a mic. In-Game asset. 2d. High contrast. No shadows
"'This Is Fine' Fire Pit": A tower resembling the "This is Fine" dog meme.. In-Game asset. 2d. High contrast. No shadows
Sell icon with a money symbol. In-Game asset. 2d. High contrast. No shadows
DOGE Coin. In-Game asset. 2d. High contrast. No shadows
Realistic MEME of Rick Astley dancing with mic. In-Game asset. 2d. High contrast. No shadows
Range Circle. In-Game asset. 2d. High contrast. No shadows
Shape: A tall, sleek, perhaps slightly intimidating rectangular or obelisk-like structure. Think modern skyscraper aesthetics scaled down. Material/Color: Polished chrome, brushed aluminum, dark grey, or a very clean white. Minimalist. Details: Maybe a single, subtly glowing slit or a small, focused lens near the top where the "restructuring energy" will eventually be directed from (though the actual effect happens on the target). Very clean lines, sharp edges. A small, almost unnoticeable corporate logo (maybe a stylized "R" or an abstract "efficiency" symbol). No visible moving parts when idle. It's about quiet, decisive power. Meme Angle: Evokes the feeling of an unapproachable, all-powerful corporate entity or a consultant's "black box" solution.. In-Game asset. 2d. High contrast. No shadows
Beam of disintegration. In-Game asset. 2d. High contrast. No shadows
Intern holding a coffee cup running 3 frames. In-Game asset. 2d. High contrast. No shadows