User prompt
düşmanlar kulelerin menziline girdiğinde kuleler düşmanları hedef alarak ateş etsin
User prompt
bazı hatalar var kontrol edip düzeltirmisin
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'x')' in or related to this line: 'var localPos = towerInfoPanel.toLocal(obj.position);' Line Number: 623
User prompt
Please fix the bug: 'TypeError: Cannot set properties of undefined (setting 'fill')' in or related to this line: 'basicTowerBtn.style.fill = coins >= 50 ? "#4444FF" : "#666666";' Line Number: 508
User prompt
Generate the first version of the source code of my game: Tower Defense Remix. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Tower Defense Remix
Initial prompt
#TowerDefenseRemix
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Bullet = Container.expand(function (tower, target, towerType) {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.tower = tower;
self.target = target;
self.towerType = towerType;
self.speed = 8;
// Calculate direction from tower to target
var dx = target.x - tower.x;
var dy = target.y - tower.y;
var distance = Math.sqrt(dx * dx + dy * dy);
self.vx = dx / distance * self.speed;
self.vy = dy / distance * self.speed;
self.update = function () {
self.x += self.vx;
self.y += self.vy;
// Check collision with intended target only
if (self.target && self.target.parent && self.intersects(self.target)) {
self.hit();
return;
}
// Remove bullet if target is destroyed
if (!self.target || !self.target.parent) {
self.destroy();
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i] === self) {
bullets.splice(i, 1);
break;
}
}
return;
}
// Remove if off screen with stricter bounds to prevent visual issues
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.destroy();
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i] === self) {
bullets.splice(i, 1);
break;
}
}
}
};
self.hit = function () {
if (soundEnabled) LK.getSound('hit').play();
// Calculate level-based damage multiplier (25% damage increase per level above 1)
var levelDamageMultiplier = 1 + (self.tower.level - 1) * 0.25;
var actualDamage = Math.floor(self.tower.damage * levelDamageMultiplier);
if (self.towerType === 'splash') {
// Splash damage
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
if (enemy && enemy.parent) {
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.tower.splashRadius) {
var killed = enemy.takeDamage(actualDamage);
if (killed) {
coins += enemy.reward;
enemy.destroy();
enemies.splice(i, 1);
}
}
}
}
// Create explosion effect
var explosion = LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5
});
explosion.x = self.x;
explosion.y = self.y;
explosion.alpha = 0.7;
game.addChild(explosion);
tween(explosion, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
explosion.destroy();
}
});
} else {
// Regular damage
if (self.target && self.target.parent) {
var killed = self.target.takeDamage(actualDamage);
if (killed) {
coins += self.target.reward;
self.target.destroy();
var targetIndex = enemies.indexOf(self.target);
if (targetIndex !== -1) {
enemies.splice(targetIndex, 1);
}
}
// Apply slow effect if slow tower
if (self.towerType === 'slow' && !killed) {
self.target.applySlow(self.tower.slowFactor, self.tower.slowDuration);
}
}
}
// Remove bullet
self.destroy();
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i] === self) {
bullets.splice(i, 1);
break;
}
}
};
return self;
});
// Game variables
var Enemy = Container.expand(function (type, pathIndex) {
var self = Container.call(this);
// Enemy properties based on type
var enemyConfig = {
basic: {
asset: 'basicEnemy',
health: 100,
speed: 2,
reward: 10
},
fast: {
asset: 'fastEnemy',
health: 60,
speed: 4,
reward: 15
},
tank: {
asset: 'tankEnemy',
health: 300,
speed: 1,
reward: 25
},
boss: {
asset: 'boss',
health: 1000,
speed: 1.5,
reward: 100
}
};
var config = enemyConfig[type] || enemyConfig.basic;
var enemyGraphics = self.attachAsset(config.asset, {
anchorX: 0.5,
anchorY: 0.5
});
self.maxHealth = config.health;
self.health = config.health;
self.speed = config.speed;
self.reward = config.reward;
self.pathIndex = pathIndex || 0;
self.slowEffect = 1;
self.slowDuration = 0;
// Health bar
var healthBarBg = LK.getAsset('pathTile', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.1,
y: -40
});
healthBarBg.tint = 0x333333;
self.addChild(healthBarBg);
var healthBar = LK.getAsset('pathTile', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.1,
y: -40
});
healthBar.tint = 0x00FF00;
self.addChild(healthBar);
self.healthBar = healthBar;
self.takeDamage = function (damage) {
self.health -= damage;
var healthPercent = Math.max(0, self.health / self.maxHealth);
self.healthBar.scaleX = 0.6 * healthPercent;
// Health bar color changes
if (healthPercent > 0.6) {
self.healthBar.tint = 0x00FF00;
} else if (healthPercent > 0.3) {
self.healthBar.tint = 0xFFFF00;
} else {
self.healthBar.tint = 0xFF0000;
}
// Flash effect
tween(enemyGraphics, {
tint: 0xFF0000
}, {
duration: 100,
onFinish: function onFinish() {
tween(enemyGraphics, {
tint: 0xFFFFFF
}, {
duration: 100
});
}
});
// Update boss health display if this is a boss during boss wave
if (bossWave && bossSpawned && bossHealthText.visible) {
bossHealthText.setText('Boss Health: ' + Math.max(0, self.health));
}
return self.health <= 0;
};
self.applySlow = function (factor, duration) {
self.slowEffect = Math.min(self.slowEffect, factor);
self.slowDuration = Math.max(self.slowDuration, duration);
};
self.update = function () {
// Update slow effect
if (self.slowDuration > 0) {
self.slowDuration--;
if (self.slowDuration <= 0) {
self.slowEffect = 1;
}
}
// Move along path
if (self.pathIndex < gamePath.length - 1 && gamePath[self.pathIndex + 1]) {
var currentTarget = gamePath[self.pathIndex + 1];
var dx = currentTarget.x - self.x;
var dy = currentTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 20) {
self.pathIndex++;
if (self.pathIndex >= gamePath.length - 1) {
// Reached base
baseHealth--;
return;
}
} else {
var moveSpeed = self.speed * self.slowEffect;
var newX = self.x + dx / distance * moveSpeed;
var newY = self.y + dy / distance * moveSpeed;
// Ensure enemy stays within screen bounds with safety margin
var enemySafetyMargin = 50;
self.x = Math.max(enemySafetyMargin, Math.min(2048 - enemySafetyMargin, newX));
self.y = Math.max(enemySafetyMargin, Math.min(2732 - enemySafetyMargin, newY));
}
} else if (self.pathIndex >= gamePath.length - 1) {
// Reached base
baseHealth--;
return;
}
};
return self;
});
var Tower = Container.expand(function (type) {
var self = Container.call(this);
// Tower configurations
var towerConfig = {
basic: {
asset: 'basicTower',
damage: 25,
range: 180,
fireRate: 60,
cost: 100,
upgradeCost: 75
},
splash: {
asset: 'splashTower',
damage: 40,
range: 150,
fireRate: 90,
cost: 200,
upgradeCost: 150,
splashRadius: 80
},
slow: {
asset: 'slowTower',
damage: 15,
range: 200,
fireRate: 45,
cost: 150,
upgradeCost: 100,
slowFactor: 0.5,
slowDuration: 120
}
};
var config = towerConfig[type] || towerConfig.basic;
var towerGraphics = self.attachAsset(config.asset, {
anchorX: 0.5,
anchorY: 0.5
});
self.type = type;
// Use upgraded values from towerInfoStats if available, otherwise use base config
self.damage = towerInfoStats[type] ? towerInfoStats[type].damage : config.damage;
self.range = config.range;
self.fireRate = config.fireRate;
self.cost = config.cost;
self.upgradeCost = towerInfoStats[type] ? towerInfoStats[type].upgradeCost : config.upgradeCost;
self.splashRadius = config.splashRadius;
self.slowFactor = config.slowFactor;
self.slowDuration = config.slowDuration;
self.lastShot = 0;
self.level = towerInfoStats[type] ? towerInfoStats[type].level : 1;
// Range indicator (hidden by default)
var rangeIndicator = LK.getAsset('pathTile', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: self.range / 50,
scaleY: self.range / 50
});
rangeIndicator.tint = 0x00FF00;
rangeIndicator.alpha = 0.2;
rangeIndicator.visible = false;
self.addChild(rangeIndicator);
self.rangeIndicator = rangeIndicator;
self.showRange = function () {
self.rangeIndicator.visible = true;
};
self.hideRange = function () {
self.rangeIndicator.visible = false;
};
self.canShoot = function () {
return LK.ticks - self.lastShot >= self.fireRate;
};
self.findTarget = function () {
var closestEnemy = null;
var closestDistance = self.range;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy && enemy.parent) {
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range && distance < closestDistance) {
closestEnemy = enemy;
closestDistance = distance;
}
}
}
return closestEnemy;
};
self.shoot = function (target) {
if (!self.canShoot()) return;
self.lastShot = LK.ticks;
// Create bullet
var bullet = new Bullet(self, target, self.type);
bullet.x = self.x;
bullet.y = self.y;
bullets.push(bullet);
game.addChild(bullet);
if (soundEnabled) LK.getSound('shoot').play();
};
self.upgrade = function () {
if (coins >= self.upgradeCost) {
coins -= self.upgradeCost;
self.level++;
self.damage = Math.floor(self.damage * 1.5);
self.range = Math.floor(self.range * 1.1);
self.upgradeCost = Math.floor(self.upgradeCost * 1.8);
// Update global tower info stats
towerInfoStats[self.type].level = Math.max(towerInfoStats[self.type].level, self.level);
towerInfoStats[self.type].damage = Math.max(towerInfoStats[self.type].damage, self.damage);
towerInfoStats[self.type].upgradeCost = Math.max(towerInfoStats[self.type].upgradeCost, self.upgradeCost);
// Visual upgrade effect
tween(towerGraphics, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
tween(towerGraphics, {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
}
});
// Update range indicator
self.rangeIndicator.scaleX = self.range / 50;
self.rangeIndicator.scaleY = self.range / 50;
return true;
}
return false;
};
self.down = function (x, y, obj) {
selectedTower = self;
self.showRange();
};
self.update = function () {
// Find target in range
var target = self.findTarget();
if (target) {
// Rotate tower towards target
var angle = Math.atan2(target.y - self.y, target.x - self.x);
towerGraphics.rotation = angle;
// Shoot if ready
if (self.canShoot()) {
self.shoot(target);
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x4a6b3a
});
/****
* Game Code
****/
// Sounds
// Game elements
// Enemy assets
// Tower assets
// Game variables
var gamePath = [{
x: 100,
y: 400
}, {
x: 500,
y: 400
}, {
x: 500,
y: 800
}, {
x: 900,
y: 800
}, {
x: 900,
y: 1200
}, {
x: 1400,
y: 1200
}, {
x: 1400,
y: 1600
}, {
x: 1800,
y: 1600
}];
var enemies = [];
var towers = [];
var bullets = [];
var coins = 300;
var baseHealth = 50;
var currentWave = 1;
var enemiesSpawned = 0;
var enemiesPerWave = 5;
var waveDelay = 0;
var selectedTower = null;
var towerInfoPanel = null;
var bossWave = false;
var bossSpawned = false;
var pathExtensionFactor = 1.0;
var isInitialPath = true;
var wavePathLengths = storage.wavePathLengths || []; // Track path length for each wave - load from database
var previousWavePathLength = storage.previousWavePathLength || 3000; // Track path length from previous wave - load from database
// Initialize empty path - will be generated when wave starts
// Create base and baslangic placeholders - will be positioned when path is generated
var base = LK.getAsset('base', {
anchorX: 0.5,
anchorY: 0.5
});
base.x = 1800;
base.y = 1600;
game.addChild(base);
// Create baslangic image at spawn point
var baslangicImage = LK.getAsset('baslangic', {
anchorX: 0.5,
anchorY: 0.5
});
baslangicImage.x = 200;
baslangicImage.y = 1000;
game.addChild(baslangicImage);
// UI Elements
var coinsText = new Text2('Coins: ' + coins, {
size: 50,
fill: 0xFFD700
});
coinsText.anchor.set(0, 0);
coinsText.x = 150;
coinsText.y = 20;
LK.gui.topLeft.addChild(coinsText);
var healthText = new Text2('Base Health: ' + baseHealth, {
size: 50,
fill: 0xFF0000
});
healthText.anchor.set(1, 0);
healthText.y = 20;
LK.gui.topRight.addChild(healthText);
var waveText = new Text2('Wave: ' + currentWave, {
size: 50,
fill: 0xFFFFFF
});
waveText.anchor.set(0.5, 0);
waveText.y = 20;
LK.gui.top.addChild(waveText);
var bossHealthText = new Text2('', {
size: 45,
fill: 0xFF0000
});
bossHealthText.anchor.set(0.5, 0);
bossHealthText.y = 80;
bossHealthText.visible = false;
LK.gui.top.addChild(bossHealthText);
// Sound toggle button
var soundEnabled = true;
var soundToggleBtn = new Text2('🔊', {
size: 60,
fill: 0xFFFFFF
});
soundToggleBtn.anchor.set(1, 0);
soundToggleBtn.x = -20;
soundToggleBtn.y = 90;
LK.gui.topRight.addChild(soundToggleBtn);
// Tower selection buttons
var basicTowerImage = LK.getAsset('basicTower', {
anchorX: 0.5,
anchorY: 1,
scaleX: 0.8,
scaleY: 0.8
});
basicTowerImage.x = 60;
basicTowerImage.y = -20;
LK.gui.bottomLeft.addChild(basicTowerImage);
var basicTowerBtn = new Text2('Basic (100)', {
size: 40,
fill: 0x4444FF
});
basicTowerBtn.anchor.set(0, 1);
basicTowerBtn.x = 100;
basicTowerBtn.y = -20;
LK.gui.bottomLeft.addChild(basicTowerBtn);
var basicTowerInfo = new Text2('Level: 1 | DMG: 25 | Upgrade: 75', {
size: 25,
fill: 0xFFFFFF
});
basicTowerInfo.anchor.set(0, 1);
basicTowerInfo.x = 350;
basicTowerInfo.y = -20;
LK.gui.bottomLeft.addChild(basicTowerInfo);
var splashTowerImage = LK.getAsset('splashTower', {
anchorX: 0.5,
anchorY: 1,
scaleX: 0.8,
scaleY: 0.8
});
splashTowerImage.x = 60;
splashTowerImage.y = -80;
LK.gui.bottomLeft.addChild(splashTowerImage);
var splashTowerBtn = new Text2('Splash (200)', {
size: 40,
fill: 0xFF4444
});
splashTowerBtn.anchor.set(0, 1);
splashTowerBtn.x = 100;
splashTowerBtn.y = -80;
LK.gui.bottomLeft.addChild(splashTowerBtn);
var splashTowerInfo = new Text2('Level: 1 | DMG: 40 | Upgrade: 150', {
size: 25,
fill: 0xFFFFFF
});
splashTowerInfo.anchor.set(0, 1);
splashTowerInfo.x = 350;
splashTowerInfo.y = -80;
LK.gui.bottomLeft.addChild(splashTowerInfo);
var slowTowerImage = LK.getAsset('slowTower', {
anchorX: 0.5,
anchorY: 1,
scaleX: 0.8,
scaleY: 0.8
});
slowTowerImage.x = 60;
slowTowerImage.y = -140;
LK.gui.bottomLeft.addChild(slowTowerImage);
var slowTowerBtn = new Text2('Slow (150)', {
size: 40,
fill: 0x44FF44
});
slowTowerBtn.anchor.set(0, 1);
slowTowerBtn.x = 100;
slowTowerBtn.y = -140;
LK.gui.bottomLeft.addChild(slowTowerBtn);
var slowTowerInfo = new Text2('Level: 1 | DMG: 15 | Upgrade: 100', {
size: 25,
fill: 0xFFFFFF
});
slowTowerInfo.anchor.set(0, 1);
slowTowerInfo.x = 350;
slowTowerInfo.y = -140;
LK.gui.bottomLeft.addChild(slowTowerInfo);
// Add levelatla upgrade buttons
var basicLevelatlaBtn = LK.getAsset('levelatla', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
basicLevelatlaBtn.x = 770;
basicLevelatlaBtn.y = -40;
LK.gui.bottomLeft.addChild(basicLevelatlaBtn);
var splashLevelatlaBtn = LK.getAsset('levelatla', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
splashLevelatlaBtn.x = 770;
splashLevelatlaBtn.y = -100;
LK.gui.bottomLeft.addChild(splashLevelatlaBtn);
var slowLevelatlaBtn = LK.getAsset('levelatla', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
slowLevelatlaBtn.x = 770;
slowLevelatlaBtn.y = -160;
LK.gui.bottomLeft.addChild(slowLevelatlaBtn);
// Selected tower type
var selectedTowerType = 'basic';
// Tower info tracking
var towerInfoStats = {
basic: {
level: 1,
damage: 25,
upgradeCost: 75
},
splash: {
level: 1,
damage: 40,
upgradeCost: 150
},
slow: {
level: 1,
damage: 15,
upgradeCost: 100
}
};
// Add background highlights for tower selection
var basicTowerBg = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0.5,
scaleX: 8.0,
scaleY: 0.8
});
basicTowerBg.tint = 0x00FF00;
basicTowerBg.alpha = 0.3;
basicTowerBg.x = 10;
basicTowerBg.y = -40;
LK.gui.bottomLeft.addChild(basicTowerBg);
var splashTowerBg = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0.5,
scaleX: 8.0,
scaleY: 0.8
});
splashTowerBg.tint = 0x00FF00;
splashTowerBg.alpha = 0;
splashTowerBg.x = 10;
splashTowerBg.y = -100;
LK.gui.bottomLeft.addChild(splashTowerBg);
var slowTowerBg = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0.5,
scaleX: 8.0,
scaleY: 0.8
});
slowTowerBg.tint = 0x00FF00;
slowTowerBg.alpha = 0;
slowTowerBg.x = 10;
slowTowerBg.y = -160;
LK.gui.bottomLeft.addChild(slowTowerBg);
// Helper functions
function calculatePathLength() {
if (!gamePath || gamePath.length < 2) return 0;
var totalLength = 0;
for (var i = 0; i < gamePath.length - 1; i++) {
var dx = gamePath[i + 1].x - gamePath[i].x;
var dy = gamePath[i + 1].y - gamePath[i].y;
totalLength += Math.sqrt(dx * dx + dy * dy);
}
return Math.round(totalLength);
}
function updateUI() {
coinsText.setText('Coins: ' + coins);
healthText.setText('Base Health: ' + baseHealth);
waveText.setText('Wave: ' + currentWave + (bossWave ? ' (BOSS)' : ''));
// "6th wave" text display removed per request
// Update button colors based on affordability
basicTowerBtn.tint = coins >= 100 ? 0x4444FF : 0x666666;
splashTowerBtn.tint = coins >= 200 ? 0xFF4444 : 0x666666;
slowTowerBtn.tint = coins >= 150 ? 0x44FF44 : 0x666666;
// Update tower info displays
basicTowerInfo.setText('Level: ' + towerInfoStats.basic.level + ' | DMG: ' + towerInfoStats.basic.damage + ' | Upgrade: ' + towerInfoStats.basic.upgradeCost);
splashTowerInfo.setText('Level: ' + towerInfoStats.splash.level + ' | DMG: ' + towerInfoStats.splash.damage + ' | Upgrade: ' + towerInfoStats.splash.upgradeCost);
slowTowerInfo.setText('Level: ' + towerInfoStats.slow.level + ' | DMG: ' + towerInfoStats.slow.damage + ' | Upgrade: ' + towerInfoStats.slow.upgradeCost);
// Update tower selection highlighting
basicTowerBg.alpha = selectedTowerType === 'basic' ? 0.3 : 0;
splashTowerBg.alpha = selectedTowerType === 'splash' ? 0.3 : 0;
slowTowerBg.alpha = selectedTowerType === 'slow' ? 0.3 : 0;
// Add pulsing animation to selected tower
var selectedBg = selectedTowerType === 'basic' ? basicTowerBg : selectedTowerType === 'splash' ? splashTowerBg : slowTowerBg;
if (selectedBg.alpha > 0) {
var pulseAlpha = 0.3 + Math.sin(LK.ticks * 0.1) * 0.1;
selectedBg.alpha = pulseAlpha;
}
// Show/hide levelatla buttons based on available coins
basicLevelatlaBtn.visible = coins >= towerInfoStats.basic.upgradeCost;
splashLevelatlaBtn.visible = coins >= towerInfoStats.splash.upgradeCost;
slowLevelatlaBtn.visible = coins >= towerInfoStats.slow.upgradeCost;
}
function canPlaceTower(x, y) {
// First check if position is within the blue frame boundaries
var screenWidth = 2048;
var screenHeight = 2732;
var marginPercent = 0.08; // 8% margin from sides and top
var bottomMarginPercent = 0.13; // 13% margin from bottom (8% + 5% increase)
var pathMinX = Math.floor(screenWidth * marginPercent); // Blue frame left boundary
var pathMaxX = screenWidth - Math.floor(screenWidth * marginPercent); // Blue frame right boundary
var pathMinY = Math.floor(screenHeight * marginPercent); // Blue frame top boundary
var pathMaxY = screenHeight - Math.floor(screenHeight * bottomMarginPercent); // Blue frame bottom boundary
// Check if tower position is within blue frame boundaries
if (x < pathMinX || x > pathMaxX || y < pathMinY || y > pathMaxY) {
return false; // Outside blue frame area
}
// Check if position is not on path points
for (var i = 0; i < gamePath.length; i++) {
var pathPoint = gamePath[i];
var distance = Math.sqrt((x - pathPoint.x) * (x - pathPoint.x) + (y - pathPoint.y) * (y - pathPoint.y));
if (distance < 80) {
return false;
}
}
// Check if position is not on path segments
for (var i = 0; i < gamePath.length - 1; i++) {
var startPoint = gamePath[i];
var endPoint = gamePath[i + 1];
// Calculate distance from point to line segment
var A = x - startPoint.x;
var B = y - startPoint.y;
var C = endPoint.x - startPoint.x;
var D = endPoint.y - startPoint.y;
var dot = A * C + B * D;
var lenSq = C * C + D * D;
var param = lenSq != 0 ? dot / lenSq : -1;
var xx, yy;
if (param < 0) {
xx = startPoint.x;
yy = startPoint.y;
} else if (param > 1) {
xx = endPoint.x;
yy = endPoint.y;
} else {
xx = startPoint.x + param * C;
yy = startPoint.y + param * D;
}
var dx = x - xx;
var dy = y - yy;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 80) {
return false;
}
}
// Check if position is not too close to other towers
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var distance = Math.sqrt((x - tower.x) * (x - tower.x) + (y - tower.y) * (y - tower.y));
if (distance < 100) {
return false;
}
}
return true;
}
function spawnEnemy() {
var enemyTypes = ['basic', 'fast', 'tank'];
var waveMultiplier = Math.floor((currentWave - 1) / 3);
// Choose enemy type based on wave
var enemyType = 'basic';
if (currentWave > 3) {
enemyType = enemyTypes[Math.floor(Math.random() * 2)]; // basic or fast
}
if (currentWave > 6) {
enemyType = enemyTypes[Math.floor(Math.random() * 3)]; // all types
}
var enemy = new Enemy(enemyType, 0);
enemy.x = gamePath[0].x;
enemy.y = gamePath[0].y;
// Scale health for later waves
enemy.maxHealth *= 1 + waveMultiplier * 0.5;
enemy.health = enemy.maxHealth;
enemies.push(enemy);
game.addChild(enemy);
enemiesSpawned++;
}
function generateNewPath() {
// Clear existing path tiles
for (var i = 0; i < game.children.length; i++) {
var child = game.children[i];
if (child.tint === 0x8B4513) {
child.destroy();
i--;
}
}
// Use blue frame boundaries for path generation (same as tower placement restrictions)
var screenWidth = 2048;
var screenHeight = 2732;
var marginPercent = 0.08; // 8% margin from sides and top
var bottomMarginPercent = 0.13; // 13% margin from bottom (8% + 5% increase)
var minX = Math.floor(screenWidth * marginPercent); // Blue frame left boundary
var maxX = screenWidth - Math.floor(screenWidth * marginPercent); // Blue frame right boundary
var minY = Math.floor(screenHeight * marginPercent); // Blue frame top boundary
var maxY = screenHeight - Math.floor(screenHeight * bottomMarginPercent); // Blue frame bottom boundary
// Set path length based on wave with specific requirements
var targetPathLength;
if (currentWave === 1) {
targetPathLength = 3000; // Wave 1 is exactly 3000 pixels
} else if (currentWave === 6) {
targetPathLength = 3500; // Wave 6 is exactly 3500 pixels
} else if (currentWave === 11) {
targetPathLength = 4000; // Wave 11 is exactly 4000 pixels
} else if (currentWave === 16) {
targetPathLength = 5000; // Wave 16 is exactly 5000 pixels
} else {
// For other waves, use the actual path length from previous wave plus 300px
var prevWaveLength = wavePathLengths.length >= currentWave - 1 ? wavePathLengths[currentWave - 2] : previousWavePathLength;
targetPathLength = prevWaveLength + 300;
}
// New completely random path generation system
gamePath = [];
// Random starting position on the edges of blue frame for better space utilization
var edge = Math.floor(Math.random() * 4); // 0=top, 1=right, 2=bottom, 3=left
var startX, startY;
if (edge === 0) {
// top edge
startX = minX + 100 + Math.random() * (maxX - minX - 200);
startY = minY + 100;
} else if (edge === 1) {
// right edge
startX = maxX - 100;
startY = minY + 100 + Math.random() * (maxY - minY - 200);
} else if (edge === 2) {
// bottom edge
startX = minX + 100 + Math.random() * (maxX - minX - 200);
startY = maxY - 100;
} else {
// left edge
startX = minX + 100;
startY = minY + 100 + Math.random() * (maxY - minY - 200);
}
gamePath.push({
x: startX,
y: startY
});
var currentX = startX;
var currentY = startY;
var totalLength = 0;
var lastDirection = -1; // Track last direction to encourage turns
// Helper function to check if two line segments intersect
function segmentsIntersect(x1, y1, x2, y2, x3, y3, x4, y4) {
var denominator = (x2 - x1) * (y4 - y3) - (y2 - y1) * (x4 - x3);
if (Math.abs(denominator) < 0.0001) return false; // Parallel lines
var t1 = ((x3 - x1) * (y4 - y3) - (y3 - y1) * (x4 - x3)) / denominator;
var t2 = ((x3 - x1) * (y2 - y1) - (y3 - y1) * (x2 - x1)) / denominator;
return t1 > 0.01 && t1 < 0.99 && t2 > 0.01 && t2 < 0.99; // Exclude endpoints
}
// Helper function to check if a new segment would intersect with existing path
function wouldIntersectPath(fromX, fromY, toX, toY, currentPath) {
// Check against all existing segments in the path
for (var i = 0; i < currentPath.length - 1; i++) {
var segX1 = currentPath[i].x;
var segY1 = currentPath[i].y;
var segX2 = currentPath[i + 1].x;
var segY2 = currentPath[i + 1].y;
// Skip if checking against the last segment (which connects to current position)
if (i === currentPath.length - 2) continue;
if (segmentsIntersect(fromX, fromY, toX, toY, segX1, segY1, segX2, segY2)) {
return true;
}
}
return false;
}
// Generate random path segments until we reach target length
var maxAttempts = 200; // Increase attempts for more complex paths
while (totalLength < targetPathLength - 200 && maxAttempts > 0) {
var validSegmentFound = false;
var attempts = 0;
while (!validSegmentFound && attempts < 30) {
// Prefer directions that create turns (different from last direction)
var direction;
if (lastDirection >= 0 && Math.random() < 0.7) {
// 70% chance to turn
// Choose a different direction than last
var availableDirections = [0, 1, 2, 3].filter(function (d) {
return d !== lastDirection && d !== (lastDirection + 2) % 4;
});
direction = availableDirections[Math.floor(Math.random() * availableDirections.length)];
} else {
direction = Math.floor(Math.random() * 4);
}
// Vary segment lengths more - shorter segments for more curves
var segmentLength = 100 + Math.random() * 300;
// Make segments shorter near edges to allow more maneuvering
var edgeDistance = Math.min(currentX - minX, maxX - currentX, currentY - minY, maxY - currentY);
if (edgeDistance < 300) {
segmentLength = Math.min(segmentLength, 100 + Math.random() * 150);
}
// Make sure we don't exceed remaining target length
var remainingLength = targetPathLength - totalLength;
if (segmentLength > remainingLength - 100) {
segmentLength = Math.max(100, remainingLength - 100);
}
var nextX = currentX;
var nextY = currentY;
// Calculate next position based on random direction
if (direction === 0) {
// right
nextX = currentX + segmentLength;
} else if (direction === 1) {
// down
nextY = currentY + segmentLength;
} else if (direction === 2) {
// left
nextX = currentX - segmentLength;
} else if (direction === 3) {
// up
nextY = currentY - segmentLength;
}
// Ensure path stays within blue frame boundaries with minimum distance from edges
var minDistanceFromEdge = 50;
nextX = Math.max(minX + minDistanceFromEdge, Math.min(maxX - minDistanceFromEdge, nextX));
nextY = Math.max(minY + minDistanceFromEdge, Math.min(maxY - minDistanceFromEdge, nextY));
// Check minimum distance between path points (not too close to existing points)
var tooClose = false;
for (var i = 0; i < gamePath.length; i++) {
var dist = Math.sqrt(Math.pow(nextX - gamePath[i].x, 2) + Math.pow(nextY - gamePath[i].y, 2));
if (dist < 200) {
// Minimum 200px between path points
tooClose = true;
break;
}
}
// Check if this segment would intersect with existing path and not too close
if (!tooClose && !wouldIntersectPath(currentX, currentY, nextX, nextY, gamePath)) {
validSegmentFound = true;
// Add the new point to path
gamePath.push({
x: nextX,
y: nextY
});
// Update current position and total length
var segmentDx = nextX - currentX;
var segmentDy = nextY - currentY;
var actualSegmentLength = Math.sqrt(segmentDx * segmentDx + segmentDy * segmentDy);
totalLength += actualSegmentLength;
currentX = nextX;
currentY = nextY;
lastDirection = direction;
}
attempts++;
}
if (!validSegmentFound) {
// If we can't find a valid segment, try backtracking
if (gamePath.length > 2) {
// Remove last point and try again
gamePath.pop();
var prevPoint = gamePath[gamePath.length - 1];
currentX = prevPoint.x;
currentY = prevPoint.y;
// Recalculate total length
totalLength = 0;
for (var i = 0; i < gamePath.length - 1; i++) {
var dx = gamePath[i + 1].x - gamePath[i].x;
var dy = gamePath[i + 1].y - gamePath[i].y;
totalLength += Math.sqrt(dx * dx + dy * dy);
}
} else {
break;
}
}
maxAttempts--;
// Safety check to prevent infinite loops
if (gamePath.length > 80) break; // Allow more segments for complex paths
}
// Add final segment to reach target length and preferably opposite side
if (totalLength < targetPathLength - 50 && gamePath.length > 1) {
var finalAttempts = 0;
var finalSegmentAdded = false;
// Try to end on opposite edge from start
var startEdge = -1;
if (gamePath[0].y < minY + 200) startEdge = 0; // started at top
else if (gamePath[0].x > maxX - 200) startEdge = 1; // started at right
else if (gamePath[0].y > maxY - 200) startEdge = 2; // started at bottom
else if (gamePath[0].x < minX + 200) startEdge = 3; // started at left
while (!finalSegmentAdded && finalAttempts < 20) {
var finalX = currentX;
var finalY = currentY;
var remainingLength = targetPathLength - totalLength;
// Try to reach opposite edge
if (startEdge >= 0 && finalAttempts < 10) {
var targetEdge = (startEdge + 2) % 4; // Opposite edge
if (targetEdge === 0) {
// top edge
finalY = minY + 100;
finalX = currentX + (Math.random() - 0.5) * 400;
} else if (targetEdge === 1) {
// right edge
finalX = maxX - 100;
finalY = currentY + (Math.random() - 0.5) * 400;
} else if (targetEdge === 2) {
// bottom edge
finalY = maxY - 100;
finalX = currentX + (Math.random() - 0.5) * 400;
} else {
// left edge
finalX = minX + 100;
finalY = currentY + (Math.random() - 0.5) * 400;
}
} else {
// Random direction if opposite edge fails
var finalDirection = Math.floor(Math.random() * 4);
if (finalDirection === 0) {
finalX = Math.min(maxX - 100, currentX + remainingLength);
} else if (finalDirection === 1) {
finalY = Math.min(maxY - 100, currentY + remainingLength);
} else if (finalDirection === 2) {
finalX = Math.max(minX + 100, currentX - remainingLength);
} else {
finalY = Math.max(minY + 100, currentY - remainingLength);
}
}
// Ensure final position is within bounds
finalX = Math.max(minX + 100, Math.min(maxX - 100, finalX));
finalY = Math.max(minY + 100, Math.min(maxY - 100, finalY));
// Check if final segment would intersect with existing path
if (!wouldIntersectPath(currentX, currentY, finalX, finalY, gamePath)) {
gamePath.push({
x: finalX,
y: finalY
});
finalSegmentAdded = true;
}
finalAttempts++;
}
}
// Ensure gamePath has valid elements
if (!gamePath || gamePath.length === 0) {
// Fallback path if generation failed
gamePath = [{
x: 100,
y: 400
}, {
x: 1800,
y: 1600
}];
}
// Draw path tiles using circles for smoother appearance
for (var i = 0; i < gamePath.length; i++) {
var pathTile = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.75,
scaleY: 3.75
});
pathTile.x = gamePath[i].x;
pathTile.y = gamePath[i].y;
pathTile.alpha = 0.9;
pathTile.tint = 0x8B4513;
game.addChild(pathTile);
}
// Draw connecting segments with circles for smoother road
for (var i = 0; i < gamePath.length - 1; i++) {
var startPoint = gamePath[i];
var endPoint = gamePath[i + 1];
var dx = endPoint.x - startPoint.x;
var dy = endPoint.y - startPoint.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var segments = Math.ceil(distance / 20);
for (var j = 1; j < segments; j++) {
var t = j / segments;
var segmentX = startPoint.x + dx * t;
var segmentY = startPoint.y + dy * t;
var segmentTile = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.75,
scaleY: 3.75
});
segmentTile.x = segmentX;
segmentTile.y = segmentY;
segmentTile.alpha = 0.8;
segmentTile.tint = 0x8B4513;
game.addChild(segmentTile);
}
}
// Update base position to match final path endpoint exactly
if (base && gamePath.length > 0) {
// Set base position to exactly match the final path point
base.x = gamePath[gamePath.length - 1].x;
base.y = gamePath[gamePath.length - 1].y;
}
// Update baslangic position to new start position
if (baslangicImage) {
// Ensure baslangic stays within screen bounds with safety margin
var baslangicSafetyMargin = 100;
baslangicImage.x = Math.max(baslangicSafetyMargin, Math.min(2048 - baslangicSafetyMargin, gamePath[0].x));
baslangicImage.y = Math.max(baslangicSafetyMargin, Math.min(2732 - baslangicSafetyMargin, gamePath[0].y));
}
// Ensure base and baslangic stay in foreground
if (base) game.addChild(base);
if (baslangicImage) game.addChild(baslangicImage);
// Create path area border frame (10px thickness, blue color, in foreground)
// Calculate path area boundaries with 8% margin from sides/top, 13% margin from bottom (same as path generation)
var pathAreaMinX = Math.floor(screenWidth * marginPercent); // 8% from left
var pathAreaMaxX = screenWidth - Math.floor(screenWidth * marginPercent); // 8% from right
var pathAreaMinY = Math.floor(screenHeight * marginPercent); // 8% from top
var pathAreaMaxY = screenHeight - Math.floor(screenHeight * bottomMarginPercent); // 13% from bottom
// Top path area border
var pathAreaTopBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: (pathAreaMaxX - pathAreaMinX) / 100,
scaleY: 0.1
});
pathAreaTopBorder.x = pathAreaMinX;
pathAreaTopBorder.y = pathAreaMinY;
pathAreaTopBorder.tint = 0x0000FF;
game.addChild(pathAreaTopBorder);
// Bottom path area border
var pathAreaBottomBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: (pathAreaMaxX - pathAreaMinX) / 100,
scaleY: 0.1
});
pathAreaBottomBorder.x = pathAreaMinX;
pathAreaBottomBorder.y = pathAreaMaxY - 10;
pathAreaBottomBorder.tint = 0x0000FF;
game.addChild(pathAreaBottomBorder);
// Left path area border
var pathAreaLeftBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: 0.1,
scaleY: (pathAreaMaxY - pathAreaMinY) / 100
});
pathAreaLeftBorder.x = pathAreaMinX;
pathAreaLeftBorder.y = pathAreaMinY;
pathAreaLeftBorder.tint = 0x0000FF;
game.addChild(pathAreaLeftBorder);
// Right path area border
var pathAreaRightBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: 0.1,
scaleY: (pathAreaMaxY - pathAreaMinY) / 100
});
pathAreaRightBorder.x = pathAreaMaxX - 10;
pathAreaRightBorder.y = pathAreaMinY;
pathAreaRightBorder.tint = 0x0000FF;
game.addChild(pathAreaRightBorder);
// Update previous wave path length for waves after Wave 1
if (currentWave > 1) {
var actualPathLength = calculatePathLength();
if (actualPathLength > 0) {
previousWavePathLength = actualPathLength;
wavePathLengths[currentWave - 1] = actualPathLength; // Store current wave length
// Save to database
storage.previousWavePathLength = previousWavePathLength;
storage.wavePathLengths = wavePathLengths;
}
}
}
function startNextWave() {
// Generate new path when wave starts
generateNewPath();
// Ensure gamePath has valid elements after generation
if (!gamePath || gamePath.length === 0) {
// Fallback path if generation failed
gamePath = [{
x: 100,
y: 400
}, {
x: 1800,
y: 1600
}];
}
// Update base position to match path endpoint exactly
if (base && gamePath.length > 0) {
// Set base position to exactly match the final path point
base.x = gamePath[gamePath.length - 1].x;
base.y = gamePath[gamePath.length - 1].y;
}
if (baslangicImage) {
// Ensure baslangic stays within screen bounds with safety margin
var baslangicSafetyMargin = 100;
baslangicImage.x = Math.max(baslangicSafetyMargin, Math.min(2048 - baslangicSafetyMargin, gamePath[0].x));
baslangicImage.y = Math.max(baslangicSafetyMargin, Math.min(2732 - baslangicSafetyMargin, gamePath[0].y));
}
}
function clearAllTowers() {
for (var i = towers.length - 1; i >= 0; i--) {
towers[i].destroy();
}
towers = [];
}
function spawnBossEnemy() {
var waveMultiplier = Math.floor((currentWave - 1) / 3);
var totalEnemyHealth = 0;
var enemyCount = enemiesPerWave * currentWave;
// Calculate total health of all enemies that would spawn in wave 5
for (var i = 0; i < enemyCount; i++) {
var baseHealth = 100; // Basic enemy health
if (currentWave > 3) baseHealth = 80; // Mix of basic and fast
if (currentWave > 6) baseHealth = 120; // Mix of all types
totalEnemyHealth += baseHealth * (1 + waveMultiplier * 0.5);
}
// Reduce boss health by 90% (keep only 10% of original health) then multiply by 6 (3*2), then reduce by 20%, then reduce by another 10%
var bossHealth = Math.floor(totalEnemyHealth * 0.1) * 6 * 0.8 * 0.9;
var boss = new Enemy('boss', 0);
boss.x = gamePath[0].x;
boss.y = gamePath[0].y;
boss.maxHealth = bossHealth;
boss.health = bossHealth;
boss.reward = 200;
enemies.push(boss);
game.addChild(boss);
bossSpawned = true;
// Show boss health display
bossHealthText.setText('Boss Health: ' + boss.health);
bossHealthText.visible = true;
}
function showTowerInfo(tower) {
if (!tower) return;
if (towerInfoPanel) {
towerInfoPanel.destroy();
}
var panel = LK.getAsset('base', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 1.5
});
panel.tint = 0x333333;
panel.alpha = 0.8;
// Ensure panel stays completely within screen bounds
var panelWidth = 120 * 2; // base width * scaleX
var panelHeight = 120 * 1.5; // base height * scaleY
var panelMargin = 50;
panel.x = Math.max(panelWidth / 2 + panelMargin, Math.min(2048 - panelWidth / 2 - panelMargin, tower.x));
panel.y = Math.max(panelHeight / 2 + panelMargin, Math.min(2732 - panelHeight / 2 - panelMargin, Math.max(150, tower.y - 150)));
game.addChild(panel);
towerInfoPanel = panel;
var infoText = new Text2('Level: ' + tower.level + '\nDamage: ' + tower.damage + '\nUpgrade: ' + tower.upgradeCost, {
size: 25,
fill: 0xFFFFFF
});
infoText.anchor.set(0.5, 0.5);
infoText.x = 0;
infoText.y = -25;
panel.addChild(infoText);
if (coins >= tower.upgradeCost) {
var upgradeBtn = new Text2('UPGRADE', {
size: 30,
fill: 0x00FF00
});
upgradeBtn.anchor.set(0.5, 0.5);
upgradeBtn.y = 35;
panel.addChild(upgradeBtn);
}
}
// Function to jump to specific wave
function jumpToWave(targetWave) {
if (targetWave < 1) targetWave = 1;
// Clear current enemies and bullets
for (var i = enemies.length - 1; i >= 0; i--) {
enemies[i].destroy();
}
enemies = [];
for (var i = bullets.length - 1; i >= 0; i--) {
bullets[i].destroy();
}
bullets = [];
// Clear all towers
clearAllTowers();
// Set wave variables
currentWave = targetWave;
enemiesSpawned = 0;
bossWave = currentWave % 5 === 0;
bossSpawned = false;
waveDelay = 60;
// Reset base health and give appropriate coins for the wave
baseHealth = 100;
coins = 300 + (targetWave - 1) * 100; // Give coins based on wave
// Update path lengths array for target wave
while (wavePathLengths.length < targetWave) {
var baseLength = wavePathLengths.length > 0 ? wavePathLengths[wavePathLengths.length - 1] : 3000;
wavePathLengths.push(baseLength + 300);
}
if (targetWave > 1) {
previousWavePathLength = wavePathLengths[targetWave - 2];
} else {
previousWavePathLength = 3000;
}
// Save to database
storage.wavePathLengths = wavePathLengths;
storage.previousWavePathLength = previousWavePathLength;
// Generate new path for target wave
startNextWave();
}
// Sound toggle handler
soundToggleBtn.down = function () {
soundEnabled = !soundEnabled;
if (soundEnabled) {
soundToggleBtn.setText('🔊');
LK.playMusic('muzik', {
fade: {
start: 0,
end: 0.5,
duration: 100
}
});
} else {
soundToggleBtn.setText('🔇');
LK.stopMusic();
}
};
// Event handlers
basicTowerBtn.down = function () {
selectedTowerType = 'basic';
if (selectedTower) {
selectedTower.hideRange();
selectedTower = null;
}
};
splashTowerBtn.down = function () {
selectedTowerType = 'splash';
if (selectedTower) {
selectedTower.hideRange();
selectedTower = null;
}
};
slowTowerBtn.down = function () {
selectedTowerType = 'slow';
if (selectedTower) {
selectedTower.hideRange();
selectedTower = null;
}
};
// Add click handlers for levelatla upgrade buttons
basicLevelatlaBtn.down = function () {
if (coins >= towerInfoStats.basic.upgradeCost) {
coins -= towerInfoStats.basic.upgradeCost;
towerInfoStats.basic.level++;
towerInfoStats.basic.damage = Math.floor(towerInfoStats.basic.damage * 1.5);
towerInfoStats.basic.upgradeCost = Math.floor(towerInfoStats.basic.upgradeCost * 1.8);
}
};
splashLevelatlaBtn.down = function () {
if (coins >= towerInfoStats.splash.upgradeCost) {
coins -= towerInfoStats.splash.upgradeCost;
towerInfoStats.splash.level++;
towerInfoStats.splash.damage = Math.floor(towerInfoStats.splash.damage * 1.5);
towerInfoStats.splash.upgradeCost = Math.floor(towerInfoStats.splash.upgradeCost * 1.8);
}
};
slowLevelatlaBtn.down = function () {
if (coins >= towerInfoStats.slow.upgradeCost) {
coins -= towerInfoStats.slow.upgradeCost;
towerInfoStats.slow.level++;
towerInfoStats.slow.damage = Math.floor(towerInfoStats.slow.damage * 1.5);
towerInfoStats.slow.upgradeCost = Math.floor(towerInfoStats.slow.upgradeCost * 1.8);
}
};
game.down = function (x, y, obj) {
// Hide tower range if clicking elsewhere
if (selectedTower) {
selectedTower.hideRange();
selectedTower = null;
}
// Try to place tower
var towerCost = selectedTowerType === 'basic' ? 100 : selectedTowerType === 'splash' ? 200 : 150;
if (coins >= towerCost && canPlaceTower(x, y)) {
var tower = new Tower(selectedTowerType);
tower.x = x;
tower.y = y;
towers.push(tower);
game.addChild(tower);
coins -= towerCost;
// Update tower info stats
towerInfoStats[selectedTowerType].level = Math.max(towerInfoStats[selectedTowerType].level, tower.level);
towerInfoStats[selectedTowerType].damage = Math.max(towerInfoStats[selectedTowerType].damage, tower.damage);
towerInfoStats[selectedTowerType].upgradeCost = Math.max(towerInfoStats[selectedTowerType].upgradeCost, tower.upgradeCost);
if (soundEnabled) LK.getSound('place').play();
}
};
// Create black border frame around the game area (10px thickness)
// Top border
var topBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: 20.48,
scaleY: 0.1
});
topBorder.x = 0;
topBorder.y = 0;
topBorder.tint = 0x000000;
game.addChild(topBorder);
// Bottom border
var bottomBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: 20.48,
scaleY: 0.1
});
bottomBorder.x = 0;
bottomBorder.y = 2722;
bottomBorder.tint = 0x000000;
game.addChild(bottomBorder);
// Left border
var leftBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: 0.1,
scaleY: 27.32
});
leftBorder.x = 0;
leftBorder.y = 0;
leftBorder.tint = 0x000000;
game.addChild(leftBorder);
// Right border
var rightBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: 0.1,
scaleY: 27.32
});
rightBorder.x = 2038;
rightBorder.y = 0;
rightBorder.tint = 0x000000;
game.addChild(rightBorder);
// Start background music
LK.playMusic('muzik', {
fade: {
start: 0,
end: 0.5,
duration: 100
}
});
// Start the first wave
startNextWave();
// Main game loop
game.update = function () {
// Spawn enemies
if (bossWave) {
// Boss wave spawning
if (waveDelay <= 0 && !bossSpawned) {
spawnBossEnemy();
waveDelay = 0;
} else {
waveDelay--;
}
} else {
// Normal wave spawning
if (waveDelay <= 0 && enemiesSpawned < enemiesPerWave * currentWave) {
spawnEnemy();
waveDelay = 60; // 1 second between spawns
} else {
waveDelay--;
}
}
// Check wave completion
if (bossWave) {
// Boss wave completion
if (bossSpawned && enemies.length === 0) {
// Store current boss wave path length before generating new path
var bossWavePathLength = calculatePathLength();
wavePathLengths[currentWave - 1] = bossWavePathLength;
// Set previous wave path length to the boss wave path length
// so the new path will be boss wave length + 300px
previousWavePathLength = bossWavePathLength;
// Save to database
storage.wavePathLengths = wavePathLengths;
storage.previousWavePathLength = previousWavePathLength;
// Clear all towers when boss is killed
clearAllTowers();
// Reset path extension factor for new wave
pathExtensionFactor = 1.0;
currentWave++;
enemiesSpawned = 0;
// Generate new path with 300px increase when boss is killed
startNextWave();
coins += 100 * currentWave; // Bonus coins for completing boss wave
waveDelay = 300; // 5 second break after boss
bossWave = false;
bossSpawned = false;
// Hide boss health display
bossHealthText.visible = false;
}
} else {
// Normal wave completion - keep same path and towers
if (enemiesSpawned >= enemiesPerWave * currentWave && enemies.length === 0) {
// Check if this completes a set of 5 waves
if (currentWave % 5 === 0) {
// Start boss wave - don't regenerate path yet, wait for boss kill
bossWave = true;
bossSpawned = false;
waveDelay = 180; // 3 second break before boss
} else {
// Normal wave progression - no path regeneration, no tower clearing
currentWave++;
enemiesSpawned = 0;
coins += 25 * currentWave; // Bonus coins for completing wave
waveDelay = 180; // 3 second break between waves
}
}
}
// Update enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
if (enemy.pathIndex >= gamePath.length - 1) {
// Enemy reached base
// Check if this is a boss enemy - if so, game over immediately
if (bossWave && enemy.health > 0) {
// Boss reached base - game over
LK.setScore(currentWave - 1);
LK.showGameOver();
return;
}
baseHealth--;
enemy.destroy();
enemies.splice(i, 1);
}
}
// Update towers
for (var i = 0; i < towers.length; i++) {
towers[i].update();
}
// Update bullets
for (var i = 0; i < bullets.length; i++) {
bullets[i].update();
}
// Check game over
if (baseHealth <= 0) {
LK.setScore(currentWave - 1);
LK.showGameOver();
}
// Update UI
updateUI();
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Bullet = Container.expand(function (tower, target, towerType) {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.tower = tower;
self.target = target;
self.towerType = towerType;
self.speed = 8;
// Calculate direction from tower to target
var dx = target.x - tower.x;
var dy = target.y - tower.y;
var distance = Math.sqrt(dx * dx + dy * dy);
self.vx = dx / distance * self.speed;
self.vy = dy / distance * self.speed;
self.update = function () {
self.x += self.vx;
self.y += self.vy;
// Check collision with intended target only
if (self.target && self.target.parent && self.intersects(self.target)) {
self.hit();
return;
}
// Remove bullet if target is destroyed
if (!self.target || !self.target.parent) {
self.destroy();
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i] === self) {
bullets.splice(i, 1);
break;
}
}
return;
}
// Remove if off screen with stricter bounds to prevent visual issues
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.destroy();
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i] === self) {
bullets.splice(i, 1);
break;
}
}
}
};
self.hit = function () {
if (soundEnabled) LK.getSound('hit').play();
// Calculate level-based damage multiplier (25% damage increase per level above 1)
var levelDamageMultiplier = 1 + (self.tower.level - 1) * 0.25;
var actualDamage = Math.floor(self.tower.damage * levelDamageMultiplier);
if (self.towerType === 'splash') {
// Splash damage
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
if (enemy && enemy.parent) {
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.tower.splashRadius) {
var killed = enemy.takeDamage(actualDamage);
if (killed) {
coins += enemy.reward;
enemy.destroy();
enemies.splice(i, 1);
}
}
}
}
// Create explosion effect
var explosion = LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5
});
explosion.x = self.x;
explosion.y = self.y;
explosion.alpha = 0.7;
game.addChild(explosion);
tween(explosion, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
explosion.destroy();
}
});
} else {
// Regular damage
if (self.target && self.target.parent) {
var killed = self.target.takeDamage(actualDamage);
if (killed) {
coins += self.target.reward;
self.target.destroy();
var targetIndex = enemies.indexOf(self.target);
if (targetIndex !== -1) {
enemies.splice(targetIndex, 1);
}
}
// Apply slow effect if slow tower
if (self.towerType === 'slow' && !killed) {
self.target.applySlow(self.tower.slowFactor, self.tower.slowDuration);
}
}
}
// Remove bullet
self.destroy();
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i] === self) {
bullets.splice(i, 1);
break;
}
}
};
return self;
});
// Game variables
var Enemy = Container.expand(function (type, pathIndex) {
var self = Container.call(this);
// Enemy properties based on type
var enemyConfig = {
basic: {
asset: 'basicEnemy',
health: 100,
speed: 2,
reward: 10
},
fast: {
asset: 'fastEnemy',
health: 60,
speed: 4,
reward: 15
},
tank: {
asset: 'tankEnemy',
health: 300,
speed: 1,
reward: 25
},
boss: {
asset: 'boss',
health: 1000,
speed: 1.5,
reward: 100
}
};
var config = enemyConfig[type] || enemyConfig.basic;
var enemyGraphics = self.attachAsset(config.asset, {
anchorX: 0.5,
anchorY: 0.5
});
self.maxHealth = config.health;
self.health = config.health;
self.speed = config.speed;
self.reward = config.reward;
self.pathIndex = pathIndex || 0;
self.slowEffect = 1;
self.slowDuration = 0;
// Health bar
var healthBarBg = LK.getAsset('pathTile', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.1,
y: -40
});
healthBarBg.tint = 0x333333;
self.addChild(healthBarBg);
var healthBar = LK.getAsset('pathTile', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.1,
y: -40
});
healthBar.tint = 0x00FF00;
self.addChild(healthBar);
self.healthBar = healthBar;
self.takeDamage = function (damage) {
self.health -= damage;
var healthPercent = Math.max(0, self.health / self.maxHealth);
self.healthBar.scaleX = 0.6 * healthPercent;
// Health bar color changes
if (healthPercent > 0.6) {
self.healthBar.tint = 0x00FF00;
} else if (healthPercent > 0.3) {
self.healthBar.tint = 0xFFFF00;
} else {
self.healthBar.tint = 0xFF0000;
}
// Flash effect
tween(enemyGraphics, {
tint: 0xFF0000
}, {
duration: 100,
onFinish: function onFinish() {
tween(enemyGraphics, {
tint: 0xFFFFFF
}, {
duration: 100
});
}
});
// Update boss health display if this is a boss during boss wave
if (bossWave && bossSpawned && bossHealthText.visible) {
bossHealthText.setText('Boss Health: ' + Math.max(0, self.health));
}
return self.health <= 0;
};
self.applySlow = function (factor, duration) {
self.slowEffect = Math.min(self.slowEffect, factor);
self.slowDuration = Math.max(self.slowDuration, duration);
};
self.update = function () {
// Update slow effect
if (self.slowDuration > 0) {
self.slowDuration--;
if (self.slowDuration <= 0) {
self.slowEffect = 1;
}
}
// Move along path
if (self.pathIndex < gamePath.length - 1 && gamePath[self.pathIndex + 1]) {
var currentTarget = gamePath[self.pathIndex + 1];
var dx = currentTarget.x - self.x;
var dy = currentTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 20) {
self.pathIndex++;
if (self.pathIndex >= gamePath.length - 1) {
// Reached base
baseHealth--;
return;
}
} else {
var moveSpeed = self.speed * self.slowEffect;
var newX = self.x + dx / distance * moveSpeed;
var newY = self.y + dy / distance * moveSpeed;
// Ensure enemy stays within screen bounds with safety margin
var enemySafetyMargin = 50;
self.x = Math.max(enemySafetyMargin, Math.min(2048 - enemySafetyMargin, newX));
self.y = Math.max(enemySafetyMargin, Math.min(2732 - enemySafetyMargin, newY));
}
} else if (self.pathIndex >= gamePath.length - 1) {
// Reached base
baseHealth--;
return;
}
};
return self;
});
var Tower = Container.expand(function (type) {
var self = Container.call(this);
// Tower configurations
var towerConfig = {
basic: {
asset: 'basicTower',
damage: 25,
range: 180,
fireRate: 60,
cost: 100,
upgradeCost: 75
},
splash: {
asset: 'splashTower',
damage: 40,
range: 150,
fireRate: 90,
cost: 200,
upgradeCost: 150,
splashRadius: 80
},
slow: {
asset: 'slowTower',
damage: 15,
range: 200,
fireRate: 45,
cost: 150,
upgradeCost: 100,
slowFactor: 0.5,
slowDuration: 120
}
};
var config = towerConfig[type] || towerConfig.basic;
var towerGraphics = self.attachAsset(config.asset, {
anchorX: 0.5,
anchorY: 0.5
});
self.type = type;
// Use upgraded values from towerInfoStats if available, otherwise use base config
self.damage = towerInfoStats[type] ? towerInfoStats[type].damage : config.damage;
self.range = config.range;
self.fireRate = config.fireRate;
self.cost = config.cost;
self.upgradeCost = towerInfoStats[type] ? towerInfoStats[type].upgradeCost : config.upgradeCost;
self.splashRadius = config.splashRadius;
self.slowFactor = config.slowFactor;
self.slowDuration = config.slowDuration;
self.lastShot = 0;
self.level = towerInfoStats[type] ? towerInfoStats[type].level : 1;
// Range indicator (hidden by default)
var rangeIndicator = LK.getAsset('pathTile', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: self.range / 50,
scaleY: self.range / 50
});
rangeIndicator.tint = 0x00FF00;
rangeIndicator.alpha = 0.2;
rangeIndicator.visible = false;
self.addChild(rangeIndicator);
self.rangeIndicator = rangeIndicator;
self.showRange = function () {
self.rangeIndicator.visible = true;
};
self.hideRange = function () {
self.rangeIndicator.visible = false;
};
self.canShoot = function () {
return LK.ticks - self.lastShot >= self.fireRate;
};
self.findTarget = function () {
var closestEnemy = null;
var closestDistance = self.range;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy && enemy.parent) {
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range && distance < closestDistance) {
closestEnemy = enemy;
closestDistance = distance;
}
}
}
return closestEnemy;
};
self.shoot = function (target) {
if (!self.canShoot()) return;
self.lastShot = LK.ticks;
// Create bullet
var bullet = new Bullet(self, target, self.type);
bullet.x = self.x;
bullet.y = self.y;
bullets.push(bullet);
game.addChild(bullet);
if (soundEnabled) LK.getSound('shoot').play();
};
self.upgrade = function () {
if (coins >= self.upgradeCost) {
coins -= self.upgradeCost;
self.level++;
self.damage = Math.floor(self.damage * 1.5);
self.range = Math.floor(self.range * 1.1);
self.upgradeCost = Math.floor(self.upgradeCost * 1.8);
// Update global tower info stats
towerInfoStats[self.type].level = Math.max(towerInfoStats[self.type].level, self.level);
towerInfoStats[self.type].damage = Math.max(towerInfoStats[self.type].damage, self.damage);
towerInfoStats[self.type].upgradeCost = Math.max(towerInfoStats[self.type].upgradeCost, self.upgradeCost);
// Visual upgrade effect
tween(towerGraphics, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
tween(towerGraphics, {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
}
});
// Update range indicator
self.rangeIndicator.scaleX = self.range / 50;
self.rangeIndicator.scaleY = self.range / 50;
return true;
}
return false;
};
self.down = function (x, y, obj) {
selectedTower = self;
self.showRange();
};
self.update = function () {
// Find target in range
var target = self.findTarget();
if (target) {
// Rotate tower towards target
var angle = Math.atan2(target.y - self.y, target.x - self.x);
towerGraphics.rotation = angle;
// Shoot if ready
if (self.canShoot()) {
self.shoot(target);
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x4a6b3a
});
/****
* Game Code
****/
// Sounds
// Game elements
// Enemy assets
// Tower assets
// Game variables
var gamePath = [{
x: 100,
y: 400
}, {
x: 500,
y: 400
}, {
x: 500,
y: 800
}, {
x: 900,
y: 800
}, {
x: 900,
y: 1200
}, {
x: 1400,
y: 1200
}, {
x: 1400,
y: 1600
}, {
x: 1800,
y: 1600
}];
var enemies = [];
var towers = [];
var bullets = [];
var coins = 300;
var baseHealth = 50;
var currentWave = 1;
var enemiesSpawned = 0;
var enemiesPerWave = 5;
var waveDelay = 0;
var selectedTower = null;
var towerInfoPanel = null;
var bossWave = false;
var bossSpawned = false;
var pathExtensionFactor = 1.0;
var isInitialPath = true;
var wavePathLengths = storage.wavePathLengths || []; // Track path length for each wave - load from database
var previousWavePathLength = storage.previousWavePathLength || 3000; // Track path length from previous wave - load from database
// Initialize empty path - will be generated when wave starts
// Create base and baslangic placeholders - will be positioned when path is generated
var base = LK.getAsset('base', {
anchorX: 0.5,
anchorY: 0.5
});
base.x = 1800;
base.y = 1600;
game.addChild(base);
// Create baslangic image at spawn point
var baslangicImage = LK.getAsset('baslangic', {
anchorX: 0.5,
anchorY: 0.5
});
baslangicImage.x = 200;
baslangicImage.y = 1000;
game.addChild(baslangicImage);
// UI Elements
var coinsText = new Text2('Coins: ' + coins, {
size: 50,
fill: 0xFFD700
});
coinsText.anchor.set(0, 0);
coinsText.x = 150;
coinsText.y = 20;
LK.gui.topLeft.addChild(coinsText);
var healthText = new Text2('Base Health: ' + baseHealth, {
size: 50,
fill: 0xFF0000
});
healthText.anchor.set(1, 0);
healthText.y = 20;
LK.gui.topRight.addChild(healthText);
var waveText = new Text2('Wave: ' + currentWave, {
size: 50,
fill: 0xFFFFFF
});
waveText.anchor.set(0.5, 0);
waveText.y = 20;
LK.gui.top.addChild(waveText);
var bossHealthText = new Text2('', {
size: 45,
fill: 0xFF0000
});
bossHealthText.anchor.set(0.5, 0);
bossHealthText.y = 80;
bossHealthText.visible = false;
LK.gui.top.addChild(bossHealthText);
// Sound toggle button
var soundEnabled = true;
var soundToggleBtn = new Text2('🔊', {
size: 60,
fill: 0xFFFFFF
});
soundToggleBtn.anchor.set(1, 0);
soundToggleBtn.x = -20;
soundToggleBtn.y = 90;
LK.gui.topRight.addChild(soundToggleBtn);
// Tower selection buttons
var basicTowerImage = LK.getAsset('basicTower', {
anchorX: 0.5,
anchorY: 1,
scaleX: 0.8,
scaleY: 0.8
});
basicTowerImage.x = 60;
basicTowerImage.y = -20;
LK.gui.bottomLeft.addChild(basicTowerImage);
var basicTowerBtn = new Text2('Basic (100)', {
size: 40,
fill: 0x4444FF
});
basicTowerBtn.anchor.set(0, 1);
basicTowerBtn.x = 100;
basicTowerBtn.y = -20;
LK.gui.bottomLeft.addChild(basicTowerBtn);
var basicTowerInfo = new Text2('Level: 1 | DMG: 25 | Upgrade: 75', {
size: 25,
fill: 0xFFFFFF
});
basicTowerInfo.anchor.set(0, 1);
basicTowerInfo.x = 350;
basicTowerInfo.y = -20;
LK.gui.bottomLeft.addChild(basicTowerInfo);
var splashTowerImage = LK.getAsset('splashTower', {
anchorX: 0.5,
anchorY: 1,
scaleX: 0.8,
scaleY: 0.8
});
splashTowerImage.x = 60;
splashTowerImage.y = -80;
LK.gui.bottomLeft.addChild(splashTowerImage);
var splashTowerBtn = new Text2('Splash (200)', {
size: 40,
fill: 0xFF4444
});
splashTowerBtn.anchor.set(0, 1);
splashTowerBtn.x = 100;
splashTowerBtn.y = -80;
LK.gui.bottomLeft.addChild(splashTowerBtn);
var splashTowerInfo = new Text2('Level: 1 | DMG: 40 | Upgrade: 150', {
size: 25,
fill: 0xFFFFFF
});
splashTowerInfo.anchor.set(0, 1);
splashTowerInfo.x = 350;
splashTowerInfo.y = -80;
LK.gui.bottomLeft.addChild(splashTowerInfo);
var slowTowerImage = LK.getAsset('slowTower', {
anchorX: 0.5,
anchorY: 1,
scaleX: 0.8,
scaleY: 0.8
});
slowTowerImage.x = 60;
slowTowerImage.y = -140;
LK.gui.bottomLeft.addChild(slowTowerImage);
var slowTowerBtn = new Text2('Slow (150)', {
size: 40,
fill: 0x44FF44
});
slowTowerBtn.anchor.set(0, 1);
slowTowerBtn.x = 100;
slowTowerBtn.y = -140;
LK.gui.bottomLeft.addChild(slowTowerBtn);
var slowTowerInfo = new Text2('Level: 1 | DMG: 15 | Upgrade: 100', {
size: 25,
fill: 0xFFFFFF
});
slowTowerInfo.anchor.set(0, 1);
slowTowerInfo.x = 350;
slowTowerInfo.y = -140;
LK.gui.bottomLeft.addChild(slowTowerInfo);
// Add levelatla upgrade buttons
var basicLevelatlaBtn = LK.getAsset('levelatla', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
basicLevelatlaBtn.x = 770;
basicLevelatlaBtn.y = -40;
LK.gui.bottomLeft.addChild(basicLevelatlaBtn);
var splashLevelatlaBtn = LK.getAsset('levelatla', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
splashLevelatlaBtn.x = 770;
splashLevelatlaBtn.y = -100;
LK.gui.bottomLeft.addChild(splashLevelatlaBtn);
var slowLevelatlaBtn = LK.getAsset('levelatla', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
slowLevelatlaBtn.x = 770;
slowLevelatlaBtn.y = -160;
LK.gui.bottomLeft.addChild(slowLevelatlaBtn);
// Selected tower type
var selectedTowerType = 'basic';
// Tower info tracking
var towerInfoStats = {
basic: {
level: 1,
damage: 25,
upgradeCost: 75
},
splash: {
level: 1,
damage: 40,
upgradeCost: 150
},
slow: {
level: 1,
damage: 15,
upgradeCost: 100
}
};
// Add background highlights for tower selection
var basicTowerBg = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0.5,
scaleX: 8.0,
scaleY: 0.8
});
basicTowerBg.tint = 0x00FF00;
basicTowerBg.alpha = 0.3;
basicTowerBg.x = 10;
basicTowerBg.y = -40;
LK.gui.bottomLeft.addChild(basicTowerBg);
var splashTowerBg = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0.5,
scaleX: 8.0,
scaleY: 0.8
});
splashTowerBg.tint = 0x00FF00;
splashTowerBg.alpha = 0;
splashTowerBg.x = 10;
splashTowerBg.y = -100;
LK.gui.bottomLeft.addChild(splashTowerBg);
var slowTowerBg = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0.5,
scaleX: 8.0,
scaleY: 0.8
});
slowTowerBg.tint = 0x00FF00;
slowTowerBg.alpha = 0;
slowTowerBg.x = 10;
slowTowerBg.y = -160;
LK.gui.bottomLeft.addChild(slowTowerBg);
// Helper functions
function calculatePathLength() {
if (!gamePath || gamePath.length < 2) return 0;
var totalLength = 0;
for (var i = 0; i < gamePath.length - 1; i++) {
var dx = gamePath[i + 1].x - gamePath[i].x;
var dy = gamePath[i + 1].y - gamePath[i].y;
totalLength += Math.sqrt(dx * dx + dy * dy);
}
return Math.round(totalLength);
}
function updateUI() {
coinsText.setText('Coins: ' + coins);
healthText.setText('Base Health: ' + baseHealth);
waveText.setText('Wave: ' + currentWave + (bossWave ? ' (BOSS)' : ''));
// "6th wave" text display removed per request
// Update button colors based on affordability
basicTowerBtn.tint = coins >= 100 ? 0x4444FF : 0x666666;
splashTowerBtn.tint = coins >= 200 ? 0xFF4444 : 0x666666;
slowTowerBtn.tint = coins >= 150 ? 0x44FF44 : 0x666666;
// Update tower info displays
basicTowerInfo.setText('Level: ' + towerInfoStats.basic.level + ' | DMG: ' + towerInfoStats.basic.damage + ' | Upgrade: ' + towerInfoStats.basic.upgradeCost);
splashTowerInfo.setText('Level: ' + towerInfoStats.splash.level + ' | DMG: ' + towerInfoStats.splash.damage + ' | Upgrade: ' + towerInfoStats.splash.upgradeCost);
slowTowerInfo.setText('Level: ' + towerInfoStats.slow.level + ' | DMG: ' + towerInfoStats.slow.damage + ' | Upgrade: ' + towerInfoStats.slow.upgradeCost);
// Update tower selection highlighting
basicTowerBg.alpha = selectedTowerType === 'basic' ? 0.3 : 0;
splashTowerBg.alpha = selectedTowerType === 'splash' ? 0.3 : 0;
slowTowerBg.alpha = selectedTowerType === 'slow' ? 0.3 : 0;
// Add pulsing animation to selected tower
var selectedBg = selectedTowerType === 'basic' ? basicTowerBg : selectedTowerType === 'splash' ? splashTowerBg : slowTowerBg;
if (selectedBg.alpha > 0) {
var pulseAlpha = 0.3 + Math.sin(LK.ticks * 0.1) * 0.1;
selectedBg.alpha = pulseAlpha;
}
// Show/hide levelatla buttons based on available coins
basicLevelatlaBtn.visible = coins >= towerInfoStats.basic.upgradeCost;
splashLevelatlaBtn.visible = coins >= towerInfoStats.splash.upgradeCost;
slowLevelatlaBtn.visible = coins >= towerInfoStats.slow.upgradeCost;
}
function canPlaceTower(x, y) {
// First check if position is within the blue frame boundaries
var screenWidth = 2048;
var screenHeight = 2732;
var marginPercent = 0.08; // 8% margin from sides and top
var bottomMarginPercent = 0.13; // 13% margin from bottom (8% + 5% increase)
var pathMinX = Math.floor(screenWidth * marginPercent); // Blue frame left boundary
var pathMaxX = screenWidth - Math.floor(screenWidth * marginPercent); // Blue frame right boundary
var pathMinY = Math.floor(screenHeight * marginPercent); // Blue frame top boundary
var pathMaxY = screenHeight - Math.floor(screenHeight * bottomMarginPercent); // Blue frame bottom boundary
// Check if tower position is within blue frame boundaries
if (x < pathMinX || x > pathMaxX || y < pathMinY || y > pathMaxY) {
return false; // Outside blue frame area
}
// Check if position is not on path points
for (var i = 0; i < gamePath.length; i++) {
var pathPoint = gamePath[i];
var distance = Math.sqrt((x - pathPoint.x) * (x - pathPoint.x) + (y - pathPoint.y) * (y - pathPoint.y));
if (distance < 80) {
return false;
}
}
// Check if position is not on path segments
for (var i = 0; i < gamePath.length - 1; i++) {
var startPoint = gamePath[i];
var endPoint = gamePath[i + 1];
// Calculate distance from point to line segment
var A = x - startPoint.x;
var B = y - startPoint.y;
var C = endPoint.x - startPoint.x;
var D = endPoint.y - startPoint.y;
var dot = A * C + B * D;
var lenSq = C * C + D * D;
var param = lenSq != 0 ? dot / lenSq : -1;
var xx, yy;
if (param < 0) {
xx = startPoint.x;
yy = startPoint.y;
} else if (param > 1) {
xx = endPoint.x;
yy = endPoint.y;
} else {
xx = startPoint.x + param * C;
yy = startPoint.y + param * D;
}
var dx = x - xx;
var dy = y - yy;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 80) {
return false;
}
}
// Check if position is not too close to other towers
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var distance = Math.sqrt((x - tower.x) * (x - tower.x) + (y - tower.y) * (y - tower.y));
if (distance < 100) {
return false;
}
}
return true;
}
function spawnEnemy() {
var enemyTypes = ['basic', 'fast', 'tank'];
var waveMultiplier = Math.floor((currentWave - 1) / 3);
// Choose enemy type based on wave
var enemyType = 'basic';
if (currentWave > 3) {
enemyType = enemyTypes[Math.floor(Math.random() * 2)]; // basic or fast
}
if (currentWave > 6) {
enemyType = enemyTypes[Math.floor(Math.random() * 3)]; // all types
}
var enemy = new Enemy(enemyType, 0);
enemy.x = gamePath[0].x;
enemy.y = gamePath[0].y;
// Scale health for later waves
enemy.maxHealth *= 1 + waveMultiplier * 0.5;
enemy.health = enemy.maxHealth;
enemies.push(enemy);
game.addChild(enemy);
enemiesSpawned++;
}
function generateNewPath() {
// Clear existing path tiles
for (var i = 0; i < game.children.length; i++) {
var child = game.children[i];
if (child.tint === 0x8B4513) {
child.destroy();
i--;
}
}
// Use blue frame boundaries for path generation (same as tower placement restrictions)
var screenWidth = 2048;
var screenHeight = 2732;
var marginPercent = 0.08; // 8% margin from sides and top
var bottomMarginPercent = 0.13; // 13% margin from bottom (8% + 5% increase)
var minX = Math.floor(screenWidth * marginPercent); // Blue frame left boundary
var maxX = screenWidth - Math.floor(screenWidth * marginPercent); // Blue frame right boundary
var minY = Math.floor(screenHeight * marginPercent); // Blue frame top boundary
var maxY = screenHeight - Math.floor(screenHeight * bottomMarginPercent); // Blue frame bottom boundary
// Set path length based on wave with specific requirements
var targetPathLength;
if (currentWave === 1) {
targetPathLength = 3000; // Wave 1 is exactly 3000 pixels
} else if (currentWave === 6) {
targetPathLength = 3500; // Wave 6 is exactly 3500 pixels
} else if (currentWave === 11) {
targetPathLength = 4000; // Wave 11 is exactly 4000 pixels
} else if (currentWave === 16) {
targetPathLength = 5000; // Wave 16 is exactly 5000 pixels
} else {
// For other waves, use the actual path length from previous wave plus 300px
var prevWaveLength = wavePathLengths.length >= currentWave - 1 ? wavePathLengths[currentWave - 2] : previousWavePathLength;
targetPathLength = prevWaveLength + 300;
}
// New completely random path generation system
gamePath = [];
// Random starting position on the edges of blue frame for better space utilization
var edge = Math.floor(Math.random() * 4); // 0=top, 1=right, 2=bottom, 3=left
var startX, startY;
if (edge === 0) {
// top edge
startX = minX + 100 + Math.random() * (maxX - minX - 200);
startY = minY + 100;
} else if (edge === 1) {
// right edge
startX = maxX - 100;
startY = minY + 100 + Math.random() * (maxY - minY - 200);
} else if (edge === 2) {
// bottom edge
startX = minX + 100 + Math.random() * (maxX - minX - 200);
startY = maxY - 100;
} else {
// left edge
startX = minX + 100;
startY = minY + 100 + Math.random() * (maxY - minY - 200);
}
gamePath.push({
x: startX,
y: startY
});
var currentX = startX;
var currentY = startY;
var totalLength = 0;
var lastDirection = -1; // Track last direction to encourage turns
// Helper function to check if two line segments intersect
function segmentsIntersect(x1, y1, x2, y2, x3, y3, x4, y4) {
var denominator = (x2 - x1) * (y4 - y3) - (y2 - y1) * (x4 - x3);
if (Math.abs(denominator) < 0.0001) return false; // Parallel lines
var t1 = ((x3 - x1) * (y4 - y3) - (y3 - y1) * (x4 - x3)) / denominator;
var t2 = ((x3 - x1) * (y2 - y1) - (y3 - y1) * (x2 - x1)) / denominator;
return t1 > 0.01 && t1 < 0.99 && t2 > 0.01 && t2 < 0.99; // Exclude endpoints
}
// Helper function to check if a new segment would intersect with existing path
function wouldIntersectPath(fromX, fromY, toX, toY, currentPath) {
// Check against all existing segments in the path
for (var i = 0; i < currentPath.length - 1; i++) {
var segX1 = currentPath[i].x;
var segY1 = currentPath[i].y;
var segX2 = currentPath[i + 1].x;
var segY2 = currentPath[i + 1].y;
// Skip if checking against the last segment (which connects to current position)
if (i === currentPath.length - 2) continue;
if (segmentsIntersect(fromX, fromY, toX, toY, segX1, segY1, segX2, segY2)) {
return true;
}
}
return false;
}
// Generate random path segments until we reach target length
var maxAttempts = 200; // Increase attempts for more complex paths
while (totalLength < targetPathLength - 200 && maxAttempts > 0) {
var validSegmentFound = false;
var attempts = 0;
while (!validSegmentFound && attempts < 30) {
// Prefer directions that create turns (different from last direction)
var direction;
if (lastDirection >= 0 && Math.random() < 0.7) {
// 70% chance to turn
// Choose a different direction than last
var availableDirections = [0, 1, 2, 3].filter(function (d) {
return d !== lastDirection && d !== (lastDirection + 2) % 4;
});
direction = availableDirections[Math.floor(Math.random() * availableDirections.length)];
} else {
direction = Math.floor(Math.random() * 4);
}
// Vary segment lengths more - shorter segments for more curves
var segmentLength = 100 + Math.random() * 300;
// Make segments shorter near edges to allow more maneuvering
var edgeDistance = Math.min(currentX - minX, maxX - currentX, currentY - minY, maxY - currentY);
if (edgeDistance < 300) {
segmentLength = Math.min(segmentLength, 100 + Math.random() * 150);
}
// Make sure we don't exceed remaining target length
var remainingLength = targetPathLength - totalLength;
if (segmentLength > remainingLength - 100) {
segmentLength = Math.max(100, remainingLength - 100);
}
var nextX = currentX;
var nextY = currentY;
// Calculate next position based on random direction
if (direction === 0) {
// right
nextX = currentX + segmentLength;
} else if (direction === 1) {
// down
nextY = currentY + segmentLength;
} else if (direction === 2) {
// left
nextX = currentX - segmentLength;
} else if (direction === 3) {
// up
nextY = currentY - segmentLength;
}
// Ensure path stays within blue frame boundaries with minimum distance from edges
var minDistanceFromEdge = 50;
nextX = Math.max(minX + minDistanceFromEdge, Math.min(maxX - minDistanceFromEdge, nextX));
nextY = Math.max(minY + minDistanceFromEdge, Math.min(maxY - minDistanceFromEdge, nextY));
// Check minimum distance between path points (not too close to existing points)
var tooClose = false;
for (var i = 0; i < gamePath.length; i++) {
var dist = Math.sqrt(Math.pow(nextX - gamePath[i].x, 2) + Math.pow(nextY - gamePath[i].y, 2));
if (dist < 200) {
// Minimum 200px between path points
tooClose = true;
break;
}
}
// Check if this segment would intersect with existing path and not too close
if (!tooClose && !wouldIntersectPath(currentX, currentY, nextX, nextY, gamePath)) {
validSegmentFound = true;
// Add the new point to path
gamePath.push({
x: nextX,
y: nextY
});
// Update current position and total length
var segmentDx = nextX - currentX;
var segmentDy = nextY - currentY;
var actualSegmentLength = Math.sqrt(segmentDx * segmentDx + segmentDy * segmentDy);
totalLength += actualSegmentLength;
currentX = nextX;
currentY = nextY;
lastDirection = direction;
}
attempts++;
}
if (!validSegmentFound) {
// If we can't find a valid segment, try backtracking
if (gamePath.length > 2) {
// Remove last point and try again
gamePath.pop();
var prevPoint = gamePath[gamePath.length - 1];
currentX = prevPoint.x;
currentY = prevPoint.y;
// Recalculate total length
totalLength = 0;
for (var i = 0; i < gamePath.length - 1; i++) {
var dx = gamePath[i + 1].x - gamePath[i].x;
var dy = gamePath[i + 1].y - gamePath[i].y;
totalLength += Math.sqrt(dx * dx + dy * dy);
}
} else {
break;
}
}
maxAttempts--;
// Safety check to prevent infinite loops
if (gamePath.length > 80) break; // Allow more segments for complex paths
}
// Add final segment to reach target length and preferably opposite side
if (totalLength < targetPathLength - 50 && gamePath.length > 1) {
var finalAttempts = 0;
var finalSegmentAdded = false;
// Try to end on opposite edge from start
var startEdge = -1;
if (gamePath[0].y < minY + 200) startEdge = 0; // started at top
else if (gamePath[0].x > maxX - 200) startEdge = 1; // started at right
else if (gamePath[0].y > maxY - 200) startEdge = 2; // started at bottom
else if (gamePath[0].x < minX + 200) startEdge = 3; // started at left
while (!finalSegmentAdded && finalAttempts < 20) {
var finalX = currentX;
var finalY = currentY;
var remainingLength = targetPathLength - totalLength;
// Try to reach opposite edge
if (startEdge >= 0 && finalAttempts < 10) {
var targetEdge = (startEdge + 2) % 4; // Opposite edge
if (targetEdge === 0) {
// top edge
finalY = minY + 100;
finalX = currentX + (Math.random() - 0.5) * 400;
} else if (targetEdge === 1) {
// right edge
finalX = maxX - 100;
finalY = currentY + (Math.random() - 0.5) * 400;
} else if (targetEdge === 2) {
// bottom edge
finalY = maxY - 100;
finalX = currentX + (Math.random() - 0.5) * 400;
} else {
// left edge
finalX = minX + 100;
finalY = currentY + (Math.random() - 0.5) * 400;
}
} else {
// Random direction if opposite edge fails
var finalDirection = Math.floor(Math.random() * 4);
if (finalDirection === 0) {
finalX = Math.min(maxX - 100, currentX + remainingLength);
} else if (finalDirection === 1) {
finalY = Math.min(maxY - 100, currentY + remainingLength);
} else if (finalDirection === 2) {
finalX = Math.max(minX + 100, currentX - remainingLength);
} else {
finalY = Math.max(minY + 100, currentY - remainingLength);
}
}
// Ensure final position is within bounds
finalX = Math.max(minX + 100, Math.min(maxX - 100, finalX));
finalY = Math.max(minY + 100, Math.min(maxY - 100, finalY));
// Check if final segment would intersect with existing path
if (!wouldIntersectPath(currentX, currentY, finalX, finalY, gamePath)) {
gamePath.push({
x: finalX,
y: finalY
});
finalSegmentAdded = true;
}
finalAttempts++;
}
}
// Ensure gamePath has valid elements
if (!gamePath || gamePath.length === 0) {
// Fallback path if generation failed
gamePath = [{
x: 100,
y: 400
}, {
x: 1800,
y: 1600
}];
}
// Draw path tiles using circles for smoother appearance
for (var i = 0; i < gamePath.length; i++) {
var pathTile = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.75,
scaleY: 3.75
});
pathTile.x = gamePath[i].x;
pathTile.y = gamePath[i].y;
pathTile.alpha = 0.9;
pathTile.tint = 0x8B4513;
game.addChild(pathTile);
}
// Draw connecting segments with circles for smoother road
for (var i = 0; i < gamePath.length - 1; i++) {
var startPoint = gamePath[i];
var endPoint = gamePath[i + 1];
var dx = endPoint.x - startPoint.x;
var dy = endPoint.y - startPoint.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var segments = Math.ceil(distance / 20);
for (var j = 1; j < segments; j++) {
var t = j / segments;
var segmentX = startPoint.x + dx * t;
var segmentY = startPoint.y + dy * t;
var segmentTile = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.75,
scaleY: 3.75
});
segmentTile.x = segmentX;
segmentTile.y = segmentY;
segmentTile.alpha = 0.8;
segmentTile.tint = 0x8B4513;
game.addChild(segmentTile);
}
}
// Update base position to match final path endpoint exactly
if (base && gamePath.length > 0) {
// Set base position to exactly match the final path point
base.x = gamePath[gamePath.length - 1].x;
base.y = gamePath[gamePath.length - 1].y;
}
// Update baslangic position to new start position
if (baslangicImage) {
// Ensure baslangic stays within screen bounds with safety margin
var baslangicSafetyMargin = 100;
baslangicImage.x = Math.max(baslangicSafetyMargin, Math.min(2048 - baslangicSafetyMargin, gamePath[0].x));
baslangicImage.y = Math.max(baslangicSafetyMargin, Math.min(2732 - baslangicSafetyMargin, gamePath[0].y));
}
// Ensure base and baslangic stay in foreground
if (base) game.addChild(base);
if (baslangicImage) game.addChild(baslangicImage);
// Create path area border frame (10px thickness, blue color, in foreground)
// Calculate path area boundaries with 8% margin from sides/top, 13% margin from bottom (same as path generation)
var pathAreaMinX = Math.floor(screenWidth * marginPercent); // 8% from left
var pathAreaMaxX = screenWidth - Math.floor(screenWidth * marginPercent); // 8% from right
var pathAreaMinY = Math.floor(screenHeight * marginPercent); // 8% from top
var pathAreaMaxY = screenHeight - Math.floor(screenHeight * bottomMarginPercent); // 13% from bottom
// Top path area border
var pathAreaTopBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: (pathAreaMaxX - pathAreaMinX) / 100,
scaleY: 0.1
});
pathAreaTopBorder.x = pathAreaMinX;
pathAreaTopBorder.y = pathAreaMinY;
pathAreaTopBorder.tint = 0x0000FF;
game.addChild(pathAreaTopBorder);
// Bottom path area border
var pathAreaBottomBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: (pathAreaMaxX - pathAreaMinX) / 100,
scaleY: 0.1
});
pathAreaBottomBorder.x = pathAreaMinX;
pathAreaBottomBorder.y = pathAreaMaxY - 10;
pathAreaBottomBorder.tint = 0x0000FF;
game.addChild(pathAreaBottomBorder);
// Left path area border
var pathAreaLeftBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: 0.1,
scaleY: (pathAreaMaxY - pathAreaMinY) / 100
});
pathAreaLeftBorder.x = pathAreaMinX;
pathAreaLeftBorder.y = pathAreaMinY;
pathAreaLeftBorder.tint = 0x0000FF;
game.addChild(pathAreaLeftBorder);
// Right path area border
var pathAreaRightBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: 0.1,
scaleY: (pathAreaMaxY - pathAreaMinY) / 100
});
pathAreaRightBorder.x = pathAreaMaxX - 10;
pathAreaRightBorder.y = pathAreaMinY;
pathAreaRightBorder.tint = 0x0000FF;
game.addChild(pathAreaRightBorder);
// Update previous wave path length for waves after Wave 1
if (currentWave > 1) {
var actualPathLength = calculatePathLength();
if (actualPathLength > 0) {
previousWavePathLength = actualPathLength;
wavePathLengths[currentWave - 1] = actualPathLength; // Store current wave length
// Save to database
storage.previousWavePathLength = previousWavePathLength;
storage.wavePathLengths = wavePathLengths;
}
}
}
function startNextWave() {
// Generate new path when wave starts
generateNewPath();
// Ensure gamePath has valid elements after generation
if (!gamePath || gamePath.length === 0) {
// Fallback path if generation failed
gamePath = [{
x: 100,
y: 400
}, {
x: 1800,
y: 1600
}];
}
// Update base position to match path endpoint exactly
if (base && gamePath.length > 0) {
// Set base position to exactly match the final path point
base.x = gamePath[gamePath.length - 1].x;
base.y = gamePath[gamePath.length - 1].y;
}
if (baslangicImage) {
// Ensure baslangic stays within screen bounds with safety margin
var baslangicSafetyMargin = 100;
baslangicImage.x = Math.max(baslangicSafetyMargin, Math.min(2048 - baslangicSafetyMargin, gamePath[0].x));
baslangicImage.y = Math.max(baslangicSafetyMargin, Math.min(2732 - baslangicSafetyMargin, gamePath[0].y));
}
}
function clearAllTowers() {
for (var i = towers.length - 1; i >= 0; i--) {
towers[i].destroy();
}
towers = [];
}
function spawnBossEnemy() {
var waveMultiplier = Math.floor((currentWave - 1) / 3);
var totalEnemyHealth = 0;
var enemyCount = enemiesPerWave * currentWave;
// Calculate total health of all enemies that would spawn in wave 5
for (var i = 0; i < enemyCount; i++) {
var baseHealth = 100; // Basic enemy health
if (currentWave > 3) baseHealth = 80; // Mix of basic and fast
if (currentWave > 6) baseHealth = 120; // Mix of all types
totalEnemyHealth += baseHealth * (1 + waveMultiplier * 0.5);
}
// Reduce boss health by 90% (keep only 10% of original health) then multiply by 6 (3*2), then reduce by 20%, then reduce by another 10%
var bossHealth = Math.floor(totalEnemyHealth * 0.1) * 6 * 0.8 * 0.9;
var boss = new Enemy('boss', 0);
boss.x = gamePath[0].x;
boss.y = gamePath[0].y;
boss.maxHealth = bossHealth;
boss.health = bossHealth;
boss.reward = 200;
enemies.push(boss);
game.addChild(boss);
bossSpawned = true;
// Show boss health display
bossHealthText.setText('Boss Health: ' + boss.health);
bossHealthText.visible = true;
}
function showTowerInfo(tower) {
if (!tower) return;
if (towerInfoPanel) {
towerInfoPanel.destroy();
}
var panel = LK.getAsset('base', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 1.5
});
panel.tint = 0x333333;
panel.alpha = 0.8;
// Ensure panel stays completely within screen bounds
var panelWidth = 120 * 2; // base width * scaleX
var panelHeight = 120 * 1.5; // base height * scaleY
var panelMargin = 50;
panel.x = Math.max(panelWidth / 2 + panelMargin, Math.min(2048 - panelWidth / 2 - panelMargin, tower.x));
panel.y = Math.max(panelHeight / 2 + panelMargin, Math.min(2732 - panelHeight / 2 - panelMargin, Math.max(150, tower.y - 150)));
game.addChild(panel);
towerInfoPanel = panel;
var infoText = new Text2('Level: ' + tower.level + '\nDamage: ' + tower.damage + '\nUpgrade: ' + tower.upgradeCost, {
size: 25,
fill: 0xFFFFFF
});
infoText.anchor.set(0.5, 0.5);
infoText.x = 0;
infoText.y = -25;
panel.addChild(infoText);
if (coins >= tower.upgradeCost) {
var upgradeBtn = new Text2('UPGRADE', {
size: 30,
fill: 0x00FF00
});
upgradeBtn.anchor.set(0.5, 0.5);
upgradeBtn.y = 35;
panel.addChild(upgradeBtn);
}
}
// Function to jump to specific wave
function jumpToWave(targetWave) {
if (targetWave < 1) targetWave = 1;
// Clear current enemies and bullets
for (var i = enemies.length - 1; i >= 0; i--) {
enemies[i].destroy();
}
enemies = [];
for (var i = bullets.length - 1; i >= 0; i--) {
bullets[i].destroy();
}
bullets = [];
// Clear all towers
clearAllTowers();
// Set wave variables
currentWave = targetWave;
enemiesSpawned = 0;
bossWave = currentWave % 5 === 0;
bossSpawned = false;
waveDelay = 60;
// Reset base health and give appropriate coins for the wave
baseHealth = 100;
coins = 300 + (targetWave - 1) * 100; // Give coins based on wave
// Update path lengths array for target wave
while (wavePathLengths.length < targetWave) {
var baseLength = wavePathLengths.length > 0 ? wavePathLengths[wavePathLengths.length - 1] : 3000;
wavePathLengths.push(baseLength + 300);
}
if (targetWave > 1) {
previousWavePathLength = wavePathLengths[targetWave - 2];
} else {
previousWavePathLength = 3000;
}
// Save to database
storage.wavePathLengths = wavePathLengths;
storage.previousWavePathLength = previousWavePathLength;
// Generate new path for target wave
startNextWave();
}
// Sound toggle handler
soundToggleBtn.down = function () {
soundEnabled = !soundEnabled;
if (soundEnabled) {
soundToggleBtn.setText('🔊');
LK.playMusic('muzik', {
fade: {
start: 0,
end: 0.5,
duration: 100
}
});
} else {
soundToggleBtn.setText('🔇');
LK.stopMusic();
}
};
// Event handlers
basicTowerBtn.down = function () {
selectedTowerType = 'basic';
if (selectedTower) {
selectedTower.hideRange();
selectedTower = null;
}
};
splashTowerBtn.down = function () {
selectedTowerType = 'splash';
if (selectedTower) {
selectedTower.hideRange();
selectedTower = null;
}
};
slowTowerBtn.down = function () {
selectedTowerType = 'slow';
if (selectedTower) {
selectedTower.hideRange();
selectedTower = null;
}
};
// Add click handlers for levelatla upgrade buttons
basicLevelatlaBtn.down = function () {
if (coins >= towerInfoStats.basic.upgradeCost) {
coins -= towerInfoStats.basic.upgradeCost;
towerInfoStats.basic.level++;
towerInfoStats.basic.damage = Math.floor(towerInfoStats.basic.damage * 1.5);
towerInfoStats.basic.upgradeCost = Math.floor(towerInfoStats.basic.upgradeCost * 1.8);
}
};
splashLevelatlaBtn.down = function () {
if (coins >= towerInfoStats.splash.upgradeCost) {
coins -= towerInfoStats.splash.upgradeCost;
towerInfoStats.splash.level++;
towerInfoStats.splash.damage = Math.floor(towerInfoStats.splash.damage * 1.5);
towerInfoStats.splash.upgradeCost = Math.floor(towerInfoStats.splash.upgradeCost * 1.8);
}
};
slowLevelatlaBtn.down = function () {
if (coins >= towerInfoStats.slow.upgradeCost) {
coins -= towerInfoStats.slow.upgradeCost;
towerInfoStats.slow.level++;
towerInfoStats.slow.damage = Math.floor(towerInfoStats.slow.damage * 1.5);
towerInfoStats.slow.upgradeCost = Math.floor(towerInfoStats.slow.upgradeCost * 1.8);
}
};
game.down = function (x, y, obj) {
// Hide tower range if clicking elsewhere
if (selectedTower) {
selectedTower.hideRange();
selectedTower = null;
}
// Try to place tower
var towerCost = selectedTowerType === 'basic' ? 100 : selectedTowerType === 'splash' ? 200 : 150;
if (coins >= towerCost && canPlaceTower(x, y)) {
var tower = new Tower(selectedTowerType);
tower.x = x;
tower.y = y;
towers.push(tower);
game.addChild(tower);
coins -= towerCost;
// Update tower info stats
towerInfoStats[selectedTowerType].level = Math.max(towerInfoStats[selectedTowerType].level, tower.level);
towerInfoStats[selectedTowerType].damage = Math.max(towerInfoStats[selectedTowerType].damage, tower.damage);
towerInfoStats[selectedTowerType].upgradeCost = Math.max(towerInfoStats[selectedTowerType].upgradeCost, tower.upgradeCost);
if (soundEnabled) LK.getSound('place').play();
}
};
// Create black border frame around the game area (10px thickness)
// Top border
var topBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: 20.48,
scaleY: 0.1
});
topBorder.x = 0;
topBorder.y = 0;
topBorder.tint = 0x000000;
game.addChild(topBorder);
// Bottom border
var bottomBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: 20.48,
scaleY: 0.1
});
bottomBorder.x = 0;
bottomBorder.y = 2722;
bottomBorder.tint = 0x000000;
game.addChild(bottomBorder);
// Left border
var leftBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: 0.1,
scaleY: 27.32
});
leftBorder.x = 0;
leftBorder.y = 0;
leftBorder.tint = 0x000000;
game.addChild(leftBorder);
// Right border
var rightBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: 0.1,
scaleY: 27.32
});
rightBorder.x = 2038;
rightBorder.y = 0;
rightBorder.tint = 0x000000;
game.addChild(rightBorder);
// Start background music
LK.playMusic('muzik', {
fade: {
start: 0,
end: 0.5,
duration: 100
}
});
// Start the first wave
startNextWave();
// Main game loop
game.update = function () {
// Spawn enemies
if (bossWave) {
// Boss wave spawning
if (waveDelay <= 0 && !bossSpawned) {
spawnBossEnemy();
waveDelay = 0;
} else {
waveDelay--;
}
} else {
// Normal wave spawning
if (waveDelay <= 0 && enemiesSpawned < enemiesPerWave * currentWave) {
spawnEnemy();
waveDelay = 60; // 1 second between spawns
} else {
waveDelay--;
}
}
// Check wave completion
if (bossWave) {
// Boss wave completion
if (bossSpawned && enemies.length === 0) {
// Store current boss wave path length before generating new path
var bossWavePathLength = calculatePathLength();
wavePathLengths[currentWave - 1] = bossWavePathLength;
// Set previous wave path length to the boss wave path length
// so the new path will be boss wave length + 300px
previousWavePathLength = bossWavePathLength;
// Save to database
storage.wavePathLengths = wavePathLengths;
storage.previousWavePathLength = previousWavePathLength;
// Clear all towers when boss is killed
clearAllTowers();
// Reset path extension factor for new wave
pathExtensionFactor = 1.0;
currentWave++;
enemiesSpawned = 0;
// Generate new path with 300px increase when boss is killed
startNextWave();
coins += 100 * currentWave; // Bonus coins for completing boss wave
waveDelay = 300; // 5 second break after boss
bossWave = false;
bossSpawned = false;
// Hide boss health display
bossHealthText.visible = false;
}
} else {
// Normal wave completion - keep same path and towers
if (enemiesSpawned >= enemiesPerWave * currentWave && enemies.length === 0) {
// Check if this completes a set of 5 waves
if (currentWave % 5 === 0) {
// Start boss wave - don't regenerate path yet, wait for boss kill
bossWave = true;
bossSpawned = false;
waveDelay = 180; // 3 second break before boss
} else {
// Normal wave progression - no path regeneration, no tower clearing
currentWave++;
enemiesSpawned = 0;
coins += 25 * currentWave; // Bonus coins for completing wave
waveDelay = 180; // 3 second break between waves
}
}
}
// Update enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
if (enemy.pathIndex >= gamePath.length - 1) {
// Enemy reached base
// Check if this is a boss enemy - if so, game over immediately
if (bossWave && enemy.health > 0) {
// Boss reached base - game over
LK.setScore(currentWave - 1);
LK.showGameOver();
return;
}
baseHealth--;
enemy.destroy();
enemies.splice(i, 1);
}
}
// Update towers
for (var i = 0; i < towers.length; i++) {
towers[i].update();
}
// Update bullets
for (var i = 0; i < bullets.length; i++) {
bullets[i].update();
}
// Check game over
if (baseHealth <= 0) {
LK.setScore(currentWave - 1);
LK.showGameOver();
}
// Update UI
updateUI();
};