User prompt
yollar oluşturulurken mavi çerçevenin içi kullanılsın yol kendine değmesin uzasın kıvrılsın
User prompt
yol oluşturulurken aynı yol birbirine değmeyecek ve arkasından veya önünden geçmeyecek
User prompt
When creating paths, the path will not intersect itself.
User prompt
11. dalgadaki yolun uzunluğu 3600 px olsun. 16. dalgadaki yolun uzunluğu 4000 px olsun
User prompt
yolun oluşumu ve virajları hakkında kökten değişiklik yapmak istiyorum. yolu mavi çerçevenin dışına taşmayacak şekilde rastgele istediğin şekilde oluştur
User prompt
yolu oluştuturken yollar arasında en az 200px boşluk olması yeterlidir
User prompt
oyunda değişiklik yapmak istiyorum. yollar oluşturulurken dönüş sayısı istenilen kadar olabilir.
User prompt
yollar oluşturulurken dönüş sayısı sınırsız olabilir.
User prompt
I want to make changes to the game. When roads are created, they will be in a random direction within the frame that determines the area where the road will be created.
User prompt
I want to make changes to the game. When roads are created, they will be in a random direction within the frame that determines the area where the road will be created.
User prompt
oyunda değişiklik yapmak istiyorum. oluşturulan yollar daima bu mavi çerçevenin içerisinde olsun.
User prompt
oyunda değişiklik yapmak istiyorum. yol oluşurken başlangıç ve bitiş noktaları bu mavi çerçevenin içerisinde herhangi bir konumda olabilir. yol da ona göre herhangi bir şekilde oluşturulabilir
User prompt
oytunda düzenleme yapmak istiyorum. yolun başlangıç ve bitiş noktaları daima bu mavi çerçevenin içerisinde olsun.
User prompt
oyunda değişiklik yapmak istiyorum. oluşan yollar kesinlikle bu mavi çerçevenin içinde oluşsun
User prompt
oyunda değişiklik yapmak istiyorum. yol sadece bu mavi çerçevenin içine rastgele oluşsun.
User prompt
bu oluşturulan yol en önde olsun gözüksün
User prompt
oyunda değişiklik yapmak istiyorum. yol sadece bu mavi çerçevenin içine oluşsun. rastgele açılarda ve kıvrımlarda olabilir. bu çerçeve içerisinde rastgele oluşsun
User prompt
oyunda değişiklik yapmak istiyorum. yol uluşturulurken oluşturulan yol bu mavi çerçevenin içinde oluşsun hiç bir zaman için dışına taşmasın. ayrıca başlangıç ve bitiş noktalarıda bu çerçevenin içerisinde olsun
User prompt
yeni yol oluşturulurken yol hiçbir zaman bu mavi çerçeveye değmesin.
User prompt
kaleler sadece bu mavi çerçevenin içerisine yerleştirilebilsin
User prompt
oyunda değişiklik yapmak istiyorum. yolun oluşacağı alanın alttan boşluğu %5 arttırmak istiyorum. çerçeveyide ona göre güncelle
User prompt
yolun oluşacağı alanı belirleyen çerçevenin alt çizgisi 100px yukarıdan başlasın
User prompt
bu çerçevenin alt tarafı slow yazısının yukarısına gelsin
User prompt
yolun oluşacağı alanı belirleyen çerçevenin rengi mavi olsun
User prompt
oyun alanı çerçevesinin rengi mavi olsun
/****
* 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 () {
LK.getSound('hit').play();
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(self.tower.damage);
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(self.tower.damage);
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: 'tankEnemy',
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
});
}
});
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;
self.damage = config.damage;
self.range = config.range;
self.fireRate = config.fireRate;
self.cost = config.cost;
self.upgradeCost = config.upgradeCost;
self.splashRadius = config.splashRadius;
self.slowFactor = config.slowFactor;
self.slowDuration = config.slowDuration;
self.lastShot = 0;
self.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);
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 = 100;
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 pathLengthText = new Text2('Path Length: 0', {
size: 40,
fill: 0x00FFFF
});
pathLengthText.anchor.set(0.5, 0);
pathLengthText.y = 80;
LK.gui.top.addChild(pathLengthText);
// Wave selector dropdown
var waveDropdownBtn = new Text2('Select Wave ▼', {
size: 40,
fill: 0x00FF00
});
waveDropdownBtn.anchor.set(0.5, 0);
waveDropdownBtn.x = 0;
waveDropdownBtn.y = 170;
LK.gui.top.addChild(waveDropdownBtn);
// Dropdown container (initially hidden)
var dropdownContainer = new Container();
dropdownContainer.visible = false;
dropdownContainer.x = 0;
dropdownContainer.y = 220;
LK.gui.top.addChild(dropdownContainer);
// Dropdown background
var dropdownBg = LK.getAsset('pathTile', {
anchorX: 0.5,
anchorY: 0,
scaleX: 4,
scaleY: 8
});
dropdownBg.tint = 0x333333;
dropdownBg.alpha = 0.9;
dropdownContainer.addChild(dropdownBg);
// Wave selection buttons (create 20 wave options)
var waveButtons = [];
for (var i = 1; i <= 20; i++) {
var waveBtn = new Text2('Wave ' + i, {
size: 35,
fill: 0xFFFFFF
});
waveBtn.anchor.set(0.5, 0);
waveBtn.x = 0;
waveBtn.y = (i - 1) * 45 + 10;
waveBtn.waveNumber = i;
dropdownContainer.addChild(waveBtn);
waveButtons.push(waveBtn);
}
var dropdownVisible = false;
// 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)' : ''));
pathLengthText.setText('Path Length: ' + calculatePathLength() + ' px');
// "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) {
// 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--;
}
}
// For Wave 1, Wave 6, Wave 11, and Wave 16, generate varied paths with strict boundary validation
if (currentWave === 1 || currentWave === 6 || currentWave === 11 || currentWave === 16) {
// Pre-validate that special waves use safe boundaries
var specialWaveMinX = Math.floor(screenWidth * 0.25); // 25% margin for special waves
var specialWaveMaxX = screenWidth - Math.floor(screenWidth * 0.25); // 25% margin
var specialWaveMinY = Math.floor(screenHeight * 0.35); // 35% margin for special waves
var specialWaveMaxY = screenHeight - Math.floor(screenHeight * 0.35); // 35% margin
// Generate multiple path variations for Wave 1, Wave 6, and Wave 11
var pathVariations = [
// Variation 1: Zigzag pattern
[{
x: 150,
y: 800
}, {
x: 600,
y: 800
}, {
x: 600,
y: 1300
}, {
x: 1050,
y: 1300
}, {
x: 1050,
y: 800
}, {
x: 1500,
y: 800
}, {
x: 1500,
y: 1300
}, {
x: 1800,
y: 1300
}],
// Variation 2: L-shape pattern
[{
x: 200,
y: 1200
}, {
x: 800,
y: 1200
}, {
x: 800,
y: 600
}, {
x: 1400,
y: 600
}, {
x: 1400,
y: 1000
}, {
x: 1700,
y: 1000
}, {
x: 1700,
y: 1400
}],
// Variation 3: S-curve pattern
[{
x: 250,
y: 900
}, {
x: 650,
y: 900
}, {
x: 650,
y: 1400
}, {
x: 1150,
y: 1400
}, {
x: 1150,
y: 700
}, {
x: 1650,
y: 700
}, {
x: 1650,
y: 1200
}],
// Variation 4: Step pattern
[{
x: 180,
y: 1100
}, {
x: 530,
y: 1100
}, {
x: 530,
y: 800
}, {
x: 880,
y: 800
}, {
x: 880,
y: 1500
}, {
x: 1230,
y: 1500
}, {
x: 1230,
y: 1000
}, {
x: 1580,
y: 1000
}, {
x: 1750,
y: 1000
}]];
// Select a random variation
var selectedVariation = pathVariations[Math.floor(Math.random() * pathVariations.length)];
// Scale the selected variation to exactly 3000 pixels for Wave 1, 3300 pixels for Wave 6, 3600 pixels for Wave 11, or 4000 pixels for Wave 16
var targetLength = currentWave === 1 ? 3000 : currentWave === 6 ? 3300 : currentWave === 11 ? 3600 : 4000;
var originalLength = 0;
for (var i = 0; i < selectedVariation.length - 1; i++) {
var dx = selectedVariation[i + 1].x - selectedVariation[i].x;
var dy = selectedVariation[i + 1].y - selectedVariation[i].y;
originalLength += Math.sqrt(dx * dx + dy * dy);
}
// Apply scaling factor to reach exactly target length
var scaleFactor = targetLength / originalLength;
gamePath = [];
for (var i = 0; i < selectedVariation.length; i++) {
if (i === 0) {
// Keep first point as-is
gamePath.push({
x: selectedVariation[i].x,
y: selectedVariation[i].y
});
} else {
// Scale subsequent segments
var prevPoint = gamePath[i - 1];
var originalDx = selectedVariation[i].x - selectedVariation[i - 1].x;
var originalDy = selectedVariation[i].y - selectedVariation[i - 1].y;
var scaledDx = originalDx * scaleFactor;
var scaledDy = originalDy * scaleFactor;
gamePath.push({
x: prevPoint.x + scaledDx,
y: prevPoint.y + scaledDy
});
}
}
// Draw path tiles for Wave 1, Wave 6, Wave 11, and Wave 16
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 for Wave 1, Wave 6, Wave 11, and Wave 16
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 path endpoint exactly for special waves
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));
}
// Ensure base and baslangic stay in foreground
if (base) game.addChild(base);
if (baslangicImage) game.addChild(baslangicImage);
// Create path area border frame (10px thickness, black color, in foreground)
// Calculate path area boundaries with small margin
var pathMinX = Math.floor(2048 * 0.08); // 8% margin from left
var pathMaxX = 2048 - Math.floor(2048 * 0.08); // 8% margin from right
var pathMinY = Math.floor(2732 * 0.08); // 8% margin from top
var pathMaxY = 2732 - Math.floor(2732 * 0.08); // 8% margin from bottom
// Top path border
var pathTopBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: (pathMaxX - pathMinX) / 100,
scaleY: 0.1
});
pathTopBorder.x = pathMinX;
pathTopBorder.y = pathMinY;
pathTopBorder.tint = 0x000000;
game.addChild(pathTopBorder);
// Bottom path border
var pathBottomBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: (pathMaxX - pathMinX) / 100,
scaleY: 0.1
});
pathBottomBorder.x = pathMinX;
pathBottomBorder.y = pathMaxY - 10;
pathBottomBorder.tint = 0x000000;
game.addChild(pathBottomBorder);
// Left path border
var pathLeftBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: 0.1,
scaleY: (pathMaxY - pathMinY) / 100
});
pathLeftBorder.x = pathMinX;
pathLeftBorder.y = pathMinY;
pathLeftBorder.tint = 0x000000;
game.addChild(pathLeftBorder);
// Right path border
var pathRightBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: 0.1,
scaleY: (pathMaxY - pathMinY) / 100
});
pathRightBorder.x = pathMaxX - 10;
pathRightBorder.y = pathMinY;
pathRightBorder.tint = 0x000000;
game.addChild(pathRightBorder);
// Update previous wave path length for Wave 1, Wave 6, Wave 11, and Wave 16
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;
} else {
// Fallback to expected length
var fallbackLength = currentWave === 1 ? 3000 : currentWave === 6 ? 3300 : currentWave === 11 ? 3600 : 4000;
previousWavePathLength = fallbackLength;
wavePathLengths[currentWave - 1] = fallbackLength;
// Save to database
storage.previousWavePathLength = previousWavePathLength;
storage.wavePathLengths = wavePathLengths;
}
return; // Exit early for Wave 1, Wave 6, Wave 11, and Wave 16
}
// Calculate safe boundaries to use entire screen area while maintaining small safety margins
var screenWidth = 2048;
var screenHeight = 2732;
// Use small margins: 8% from all sides to use most of the screen while preventing edge clipping
var marginPercent = 0.08; // 8% margin from all sides for safety
var minX = Math.floor(screenWidth * marginPercent); // 8% from left (164 px)
var maxX = screenWidth - Math.floor(screenWidth * marginPercent); // 8% from right (1884 px)
var minY = Math.floor(screenHeight * marginPercent); // 8% from top (219 px)
var maxY = screenHeight - Math.floor(screenHeight * marginPercent); // 8% from bottom (2513 px)
// Set path length based on wave - Wave 1 is 3000px, others use previous wave length + 300px
var targetPathLength;
if (currentWave === 1) {
targetPathLength = 3000;
} 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;
// Special enforcement for Wave 6 to ensure it's exactly 3300 pixels (like Wave 1's 3000px requirement)
if (currentWave === 6) {
targetPathLength = 3300; // Wave 6 is always exactly 3300 pixels
}
}
// Function to calculate distance from point to line segment
function distanceToLineSegment(point, lineStart, lineEnd) {
var A = point.x - lineStart.x;
var B = point.y - lineStart.y;
var C = lineEnd.x - lineStart.x;
var D = lineEnd.y - lineStart.y;
var dot = A * C + B * D;
var lenSq = C * C + D * D;
if (lenSq === 0) return Math.sqrt(A * A + B * B);
var param = dot / lenSq;
var xx, yy;
if (param < 0) {
xx = lineStart.x;
yy = lineStart.y;
} else if (param > 1) {
xx = lineEnd.x;
yy = lineEnd.y;
} else {
xx = lineStart.x + param * C;
yy = lineStart.y + param * D;
}
var dx = point.x - xx;
var dy = point.y - yy;
return Math.sqrt(dx * dx + dy * dy);
}
// Function to check if two line segments intersect
function doLinesIntersect(p1, q1, p2, q2) {
function orientation(p, q, r) {
var val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
if (val === 0) return 0; // collinear
return val > 0 ? 1 : 2; // clockwise or counterclockwise
}
function onSegment(p, q, r) {
return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y);
}
var o1 = orientation(p1, q1, p2);
var o2 = orientation(p1, q1, q2);
var o3 = orientation(p2, q2, p1);
var o4 = orientation(p2, q2, q1);
// General case
if (o1 !== o2 && o3 !== o4) return true;
// Special cases
if (o1 === 0 && onSegment(p1, p2, q1)) return true;
if (o2 === 0 && onSegment(p1, q2, q1)) return true;
if (o3 === 0 && onSegment(p2, p1, q2)) return true;
if (o4 === 0 && onSegment(p2, q1, q2)) return true;
return false;
}
// Function to check if proposed segment would intersect or get too close to existing path
function wouldIntersectPath(newStart, newEnd, existingPath) {
// Strict minimum distance to prevent any crossing or touching
var minDistance = 350; // Increased minimum distance significantly
var minSafeDistance = 200; // Minimum safe distance for any part of path
// Primary check: Direct line intersection with ANY existing segment (except immediate connection)
for (var i = 0; i < existingPath.length - 1; i++) {
var segStart = existingPath[i];
var segEnd = existingPath[i + 1];
// Skip only the immediate previous segment that we're connecting from
if (i === existingPath.length - 2) continue;
// Check for any intersection between the new segment and existing segments
if (doLinesIntersect(newStart, newEnd, segStart, segEnd)) {
return true;
}
}
// Secondary check: Ensure new segment maintains safe distance from ALL existing segments
for (var i = 0; i < existingPath.length - 1; i++) {
var segStart = existingPath[i];
var segEnd = existingPath[i + 1];
// Skip only the immediate previous segment
if (i === existingPath.length - 2) continue;
// Calculate minimum distance between the new segment and existing segment
var dist1 = distanceToLineSegment(newStart, segStart, segEnd);
var dist2 = distanceToLineSegment(newEnd, segStart, segEnd);
var dist3 = distanceToLineSegment(segStart, newStart, newEnd);
var dist4 = distanceToLineSegment(segEnd, newStart, newEnd);
var minimumSegmentDistance = Math.min(dist1, dist2, dist3, dist4);
// Reject if any part of the segments get too close
if (minimumSegmentDistance < minSafeDistance) {
return true;
}
}
// Tertiary check: Ensure new endpoint doesn't get too close to any existing path points
for (var i = 0; i < existingPath.length; i++) {
var pathPoint = existingPath[i];
// Skip only the immediate previous point we're connecting from
if (i === existingPath.length - 1) continue;
// Calculate distance from new endpoint to existing path points
var distToEnd = Math.sqrt((newEnd.x - pathPoint.x) * (newEnd.x - pathPoint.x) + (newEnd.y - pathPoint.y) * (newEnd.y - pathPoint.y));
// Use increasingly strict distance requirements for older path points
var ageMultiplier = 1.0 + (existingPath.length - 1 - i) * 0.2; // Older points need more distance
var requiredDistance = minDistance * ageMultiplier;
if (distToEnd < requiredDistance) {
return true;
}
}
// Quaternary check: Prevent sharp reversals that could lead to future self-intersection
if (existingPath.length >= 2) {
var prevPoint = existingPath[existingPath.length - 1];
var prevPrevPoint = existingPath[existingPath.length - 2];
// Calculate direction vectors
var prevDirX = prevPoint.x - prevPrevPoint.x;
var prevDirY = prevPoint.y - prevPrevPoint.y;
var newDirX = newEnd.x - newStart.x;
var newDirY = newEnd.y - newStart.y;
// Calculate angle between directions using dot product
var dotProduct = prevDirX * newDirX + prevDirY * newDirY;
var prevLength = Math.sqrt(prevDirX * prevDirX + prevDirY * prevDirY);
var newLength = Math.sqrt(newDirX * newDirX + newDirY * newDirY);
if (prevLength > 0 && newLength > 0) {
var cosAngle = dotProduct / (prevLength * newLength);
// Prevent sharp reversals (angles greater than 140 degrees)
if (cosAngle < -0.64) {
// cos(140°) ≈ -0.64
return true;
}
}
}
// Final comprehensive check: Ensure the new segment doesn't create potential for future loops
// by checking if it would "enclose" any existing path segments
if (existingPath.length >= 3) {
// Check if the new segment would create a potential enclosed area with any non-adjacent path segments
for (var i = 0; i < existingPath.length - 3; i++) {
// Check against segments that are not adjacent
var oldSegStart = existingPath[i];
var oldSegEnd = existingPath[i + 1];
// Calculate if new segment and old segment could form an enclosed area
// Check if segments are oriented in a way that could trap path points between them
var cross1 = (newEnd.x - newStart.x) * (oldSegStart.y - newStart.y) - (newEnd.y - newStart.y) * (oldSegStart.x - newStart.x);
var cross2 = (newEnd.x - newStart.x) * (oldSegEnd.y - newStart.y) - (newEnd.y - newStart.y) * (oldSegEnd.x - newStart.x);
var cross3 = (oldSegEnd.x - oldSegStart.x) * (newStart.y - oldSegStart.y) - (oldSegEnd.y - oldSegStart.y) * (newStart.x - oldSegStart.x);
var cross4 = (oldSegEnd.x - oldSegStart.x) * (newEnd.y - oldSegStart.y) - (oldSegEnd.y - oldSegStart.y) * (newEnd.x - oldSegStart.x);
// If segments create opposing orientations, they might enclose an area - reject this
if (cross1 > 0 !== cross2 > 0 && cross3 > 0 !== cross4 > 0) {
return true;
}
}
}
return false;
}
// Create varied path shapes while maintaining characteristics
gamePath = [];
var baseSegmentLength = currentWave === 1 ? 200 : Math.floor(180 * pathExtensionFactor); // Much shorter base segments for more frequent turns
// For Wave 1, calculate exact segment lengths to total exactly 3000 pixels with more segments for better curves
var numSegments = currentWave === 1 ? 15 : Math.max(12, Math.floor(10 + currentWave * 0.8)); // More segments for much better curving
var turnCount = 0; // Track number of turns
// Starting position - randomized across top portion of screen
var startX, startY;
// All waves start from top portion of screen
startX = minX + 50 + Math.random() * (maxX - minX - 100);
startY = minY + 50 + Math.random() * 200; // Start in top 200px of usable area
gamePath.push({
x: startX,
y: startY
});
var currentX = startX;
var currentY = startY;
// Prioritize downward movement to create top-to-bottom flow
var possibleStartDirections = [];
// Heavily favor downward movement
if (startY + 300 <= maxY) {
possibleStartDirections.push(1, 1, 1, 1, 1, 1); // down (6x weight)
}
// Minimal upward movement only if needed
if (startY - 300 >= minY) {
possibleStartDirections.push(3); // up (single weight)
}
// Limited horizontal directions with reduced priority
if (startX + 300 <= maxX - 100) possibleStartDirections.push(0); // right (single weight)
if (startX - 300 >= minX + 100) possibleStartDirections.push(2); // left (single weight)
var direction = possibleStartDirections.length > 0 ? possibleStartDirections[Math.floor(Math.random() * possibleStartDirections.length)] : 1; // default to down
var totalPathLength = 0; // Track total path length built so far
var segmentLengths = []; // Pre-calculate segment lengths to total exactly 3000 pixels
// Calculate exact segment lengths to achieve target path length
if (currentWave === 1) {
// For Wave 1, use much shorter segments that create better curves and total exactly 3000 pixels
segmentLengths = [200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200]; // 15 segments of 200 pixels each = 3000 total
} else {
// For subsequent waves, calculate segments to achieve exact target length with randomization
var baseSegmentLength = Math.floor(targetPathLength / numSegments);
var remainder = targetPathLength % numSegments;
segmentLengths = [];
// Create randomized segment lengths while maintaining total target length
var totalAllocated = 0;
for (var i = 0; i < numSegments; i++) {
if (i === numSegments - 1) {
// Last segment gets remaining length to ensure exact total
segmentLengths.push(targetPathLength - totalAllocated);
} else {
// Add randomization: ±30% variation from base length
var minLength = Math.max(200, Math.floor(baseSegmentLength * 0.7));
var maxLength = Math.floor(baseSegmentLength * 1.3);
var randomLength = minLength + Math.floor(Math.random() * (maxLength - minLength + 1));
// Add remainder distribution
randomLength += i < remainder ? 1 : 0;
// Ensure we don't exceed target length with remaining segments
var remainingSegments = numSegments - i - 1;
var maxPossibleLength = targetPathLength - totalAllocated - remainingSegments * 200;
randomLength = Math.min(randomLength, maxPossibleLength);
segmentLengths.push(randomLength);
totalAllocated += randomLength;
}
}
}
// Generate varied path with more turns based on path length
// Calculate minimum turns based on target path length - much more turns for shorter segments
var baseMinTurns = Math.max(5, Math.floor(targetPathLength / 600)); // At least 1 turn per 600px (more frequent)
var maxTurns = Math.min(numSegments - 1, baseMinTurns + 4); // Allow more maximum turns
var minTurns = Math.max(5, baseMinTurns);
// Create strategic turn points with randomization
var mandatoryTurnSegments = [];
if (numSegments >= 5) {
// Distribute turns more evenly across the path with some randomization
var turnSpacing = numSegments / (minTurns + 1);
for (var t = 0; t < minTurns; t++) {
var basePosition = Math.floor((t + 1) * turnSpacing);
// Add randomization to turn positions (±20% variation)
var variation = Math.floor(turnSpacing * 0.2);
var randomOffset = Math.floor(Math.random() * (variation * 2 + 1)) - variation;
var turnPosition = Math.max(1, Math.min(numSegments - 2, basePosition + randomOffset));
// Ensure we don't duplicate turn positions
if (mandatoryTurnSegments.indexOf(turnPosition) === -1) {
mandatoryTurnSegments.push(turnPosition);
}
}
}
for (var seg = 0; seg < numSegments; seg++) {
// Use pre-calculated segment length for exact path length control
var segmentLength = segmentLengths[seg] || Math.max(250, baseSegmentLength + Math.random() * 200 - 100);
var attempts = 0;
var validSegmentFound = false;
var forceTurn = mandatoryTurnSegments.indexOf(seg) !== -1; // Force turn at strategic segments
while (!validSegmentFound && attempts < 50) {
// Increased max attempts
var shouldTurn = false;
// Force turn at mandatory segments or if we haven't turned enough
if (forceTurn || turnCount < minTurns && seg >= numSegments - (minTurns - turnCount)) {
shouldTurn = true;
} else if (turnCount < minTurns && seg > 0 && Math.random() < 0.95) {
// Very high chance of turning when we need more turns
shouldTurn = true;
} else if (turnCount >= minTurns && turnCount < maxTurns && seg > 0 && Math.random() < 0.8) {
// High chance for additional turns up to maximum
shouldTurn = true;
} else if (turnCount >= maxTurns && seg > 0 && Math.random() < 0.4) {
// Higher chance even after reaching maximum turns for more curves
shouldTurn = true;
}
if (shouldTurn && seg > 0) {
// Don't turn on first segment
// Change direction (90-degree turn)
var oldDirection = direction;
var possibleDirections = [];
// Prioritize vertical directions heavily over horizontal directions
var possibleDirections = [];
// Heavily prioritize downward movement to maintain top-to-bottom flow
if (direction !== 3 && currentY + segmentLength <= maxY) {
possibleDirections.push(1, 1, 1, 1, 1, 1, 1, 1); // down (8x weight)
}
// Minimal upward movement only when necessary
if (direction !== 1 && currentY - segmentLength >= minY && currentY > maxY * 0.7) {
possibleDirections.push(3); // up (single weight, only in bottom 30% of screen)
}
// Reduced horizontal directions to maintain vertical flow
if (direction !== 2 && currentX + segmentLength <= maxX - 100) possibleDirections.push(0); // right (single weight)
if (direction !== 0 && currentX - segmentLength >= minX + 100) possibleDirections.push(2); // left (single weight)
// Remove the current direction to force an actual turn
var filteredDirections = [];
for (var d = 0; d < possibleDirections.length; d++) {
if (possibleDirections[d] !== direction) {
filteredDirections.push(possibleDirections[d]);
}
}
if (filteredDirections.length > 0) {
direction = filteredDirections[Math.floor(Math.random() * filteredDirections.length)];
turnCount++;
} else if (possibleDirections.length > 0) {
// Fallback to any valid direction if no turn is possible
direction = possibleDirections[Math.floor(Math.random() * possibleDirections.length)];
}
}
// Calculate next position based on direction with boundary checking
var nextX = currentX;
var nextY = currentY;
var wouldExceedBounds = false;
if (direction === 0) {
// right
nextX = currentX + segmentLength;
if (nextX > maxX) wouldExceedBounds = true;
} else if (direction === 1) {
// down
nextY = currentY + segmentLength;
if (nextY > maxY) wouldExceedBounds = true;
} else if (direction === 2) {
// left
nextX = currentX - segmentLength;
if (nextX < minX) wouldExceedBounds = true;
} else if (direction === 3) {
// up
nextY = currentY - segmentLength;
if (nextY < minY) wouldExceedBounds = true;
}
// If path would go off-screen, increment turn count instead
if (wouldExceedBounds) {
turnCount++;
// Try a different direction that stays within bounds, heavily prioritizing downward movement
var safeDirections = [];
// Heavily prioritize downward movement for top-to-bottom flow
if (currentY + segmentLength <= maxY && direction !== 3) {
safeDirections.push(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1); // down (12x weight)
}
// Minimal upward movement only in bottom portion of screen
if (currentY - segmentLength >= minY && direction !== 1 && currentY > maxY * 0.6) {
safeDirections.push(3, 3); // up (double weight, only in bottom 40% of screen)
}
// Limited horizontal directions to maintain vertical flow
if (currentX + segmentLength <= maxX - 300 && direction !== 2) safeDirections.push(0); // right (with margin)
if (currentX - segmentLength >= minX + 300 && direction !== 0) safeDirections.push(2); // left (with margin)
if (safeDirections.length > 0) {
direction = safeDirections[Math.floor(Math.random() * safeDirections.length)];
// Recalculate position with new direction
if (direction === 0) {
nextX = currentX + segmentLength;
} else if (direction === 1) {
nextY = currentY + segmentLength;
} else if (direction === 2) {
nextX = currentX - segmentLength;
} else if (direction === 3) {
nextY = currentY - segmentLength;
}
} else {
// No safe direction available, reduce segment length
segmentLength = Math.min(segmentLength, Math.min(maxX - currentX, currentX - minX, maxY - currentY, currentY - minY));
nextX = currentX + (direction === 0 ? segmentLength : direction === 2 ? -segmentLength : 0);
nextY = currentY + (direction === 1 ? segmentLength : direction === 3 ? -segmentLength : 0);
}
}
// Final boundary enforcement with the 35% margins - no additional margin needed
nextX = Math.max(minX, Math.min(maxX, nextX));
nextY = Math.max(minY, Math.min(maxY, nextY));
// Double-check boundaries are respected with the 35% base margins
if (nextX < minX || nextX > maxX || nextY < minY || nextY > maxY) {
// Force position to safe center area if still problematic
nextX = minX + (maxX - minX) * 0.5;
nextY = minY + (maxY - minY) * 0.5;
}
// Ensure we're moving towards the bottom area in later segments for top-to-bottom flow
if (seg > numSegments * 0.7) {
var targetEndY = maxY - 200; // Target area near bottom but within boundaries
if (nextY < targetEndY && direction !== 3) {
nextY = Math.min(maxY, currentY + Math.abs(segmentLength));
direction = 1; // Force downward movement
}
}
// Check if this segment would intersect existing path
var proposedStart = {
x: currentX,
y: currentY
};
var proposedEnd = {
x: nextX,
y: nextY
};
// Check intersection with much stricter criteria
var isValidSegment = false;
if (gamePath.length < 2) {
// First few segments are always safe
isValidSegment = true;
} else {
// For subsequent segments, apply strict intersection checking
isValidSegment = !wouldIntersectPath(proposedStart, proposedEnd, gamePath);
}
// Comprehensive boundary validation with the strict base margins
var finalX = Math.max(minX, Math.min(maxX, nextX));
var finalY = Math.max(minY, Math.min(maxY, nextY));
// Ensure path point is completely within the strict base bounds
var isWithinBounds = finalX >= minX && finalX <= maxX && finalY >= minY && finalY <= maxY;
// Additional validation: ensure minimum distance from actual screen edges using 8% margins
var distFromLeft = finalX - 0;
var distFromRight = screenWidth - finalX;
var distFromTop = finalY - 0;
var distFromBottom = screenHeight - finalY;
var minScreenDistance = Math.floor(screenWidth * marginPercent); // Use same 8% margin as boundary calculation
var isSafeFromEdges = distFromLeft >= minScreenDistance && distFromRight >= minScreenDistance && distFromTop >= minScreenDistance && distFromBottom >= minScreenDistance;
isWithinBounds = isWithinBounds && isSafeFromEdges;
if (isValidSegment && isWithinBounds) {
gamePath.push({
x: finalX,
y: finalY
});
currentX = finalX;
currentY = finalY;
validSegmentFound = true;
} else {
// Segment would intersect - try different approach
attempts++;
// Reduce segment length for intersection avoidance (except for pre-calculated lengths)
if (seg >= segmentLengths.length || currentWave === 1) {
// Keep original length for pre-calculated segments
} else {
segmentLength = Math.max(120, segmentLength * 0.6); // Shorter fallback segments for tighter turns
}
// After several attempts, try completely different directions
if (attempts > 10) {
var availableDirections = [];
// Check all directions for validity, prioritizing vertical movements
if (currentWave === 1) {
// For Wave 1, use original segment length for direction checking, prioritize vertical
var checkLength = segmentLengths[seg];
if (currentY + checkLength <= maxY && direction !== 3) availableDirections.push(1); // down (first priority)
if (currentY - checkLength >= minY && direction !== 1) availableDirections.push(3); // up (second priority)
if (currentX + checkLength <= maxX - 100 && direction !== 2) availableDirections.push(0); // right (with margin)
if (currentX - checkLength >= minX + 100 && direction !== 0) availableDirections.push(2); // left (with margin)
} else {
// Prioritize vertical directions for other waves too with shorter check distances
if (currentY + 150 <= maxY && direction !== 3) availableDirections.push(1); // down (first priority, shorter check)
if (currentY - 150 >= minY && direction !== 1) availableDirections.push(3); // up (second priority, shorter check)
if (currentX + 150 <= maxX - 100 && direction !== 2) availableDirections.push(0); // right (with margin, shorter check)
if (currentX - 150 >= minX + 100 && direction !== 0) availableDirections.push(2); // left (with margin, shorter check)
}
if (availableDirections.length > 0) {
// Prefer vertical directions even in fallback
var verticalDirections = [];
var horizontalDirections = [];
for (var ad = 0; ad < availableDirections.length; ad++) {
if (availableDirections[ad] === 1 || availableDirections[ad] === 3) {
verticalDirections.push(availableDirections[ad]);
} else {
horizontalDirections.push(availableDirections[ad]);
}
}
// Choose vertical direction if available, otherwise horizontal
if (verticalDirections.length > 0) {
direction = verticalDirections[Math.floor(Math.random() * verticalDirections.length)];
} else {
direction = horizontalDirections[Math.floor(Math.random() * horizontalDirections.length)];
}
if (currentWave !== 1) {
segmentLength = 150; // Reset to much smaller safe length for tighter turns
}
}
}
}
}
if (!validSegmentFound) {
// Force a valid segment if we can't find one - try multiple directions, prioritizing vertical
var fallbackFound = false;
var fallbackDirections = [1, 0, 2, 3]; // down first, then horizontal, up last
for (var fd = 0; fd < fallbackDirections.length && !fallbackFound; fd++) {
var fallbackDir = fallbackDirections[fd];
var fallbackLength = seg < segmentLengths.length ? segmentLengths[seg] : 150; // Shorter fallback for tighter curves
var fallbackX = currentX;
var fallbackY = currentY;
if (fallbackDir === 0 && currentX + fallbackLength <= maxX) {
// right
fallbackX = currentX + fallbackLength;
} else if (fallbackDir === 1 && currentY + fallbackLength <= maxY) {
// down
fallbackY = currentY + fallbackLength;
} else if (fallbackDir === 2 && currentX - fallbackLength >= minX) {
// left
fallbackX = currentX - fallbackLength;
} else if (fallbackDir === 3 && currentY - fallbackLength >= minY) {
// up
fallbackY = currentY - fallbackLength;
} else {
continue; // Try next direction
}
var fallbackStart = {
x: currentX,
y: currentY
};
var fallbackEnd = {
x: fallbackX,
y: fallbackY
};
// Check if this fallback direction works
// Ensure fallback position is within the strict base boundaries
var safeFallbackX = Math.max(minX, Math.min(maxX, fallbackX));
var safeFallbackY = Math.max(minY, Math.min(maxY, fallbackY));
// Comprehensive fallback validation using the strict base boundaries
var fallbackWithinBounds = safeFallbackX >= minX && safeFallbackX <= maxX && safeFallbackY >= minY && safeFallbackY <= maxY;
// Additional fallback validation: check distance from all screen edges using consistent 8% margin
var fallbackDistFromLeft = safeFallbackX - 0;
var fallbackDistFromRight = screenWidth - safeFallbackX;
var fallbackDistFromTop = safeFallbackY - 0;
var fallbackDistFromBottom = screenHeight - safeFallbackY;
var minFallbackDistance = Math.floor(screenWidth * marginPercent); // Use consistent 8% margin
var fallbackSafeFromEdges = fallbackDistFromLeft >= minFallbackDistance && fallbackDistFromRight >= minFallbackDistance && fallbackDistFromTop >= minFallbackDistance && fallbackDistFromBottom >= minFallbackDistance;
fallbackWithinBounds = fallbackWithinBounds && fallbackSafeFromEdges;
if (fallbackWithinBounds && (gamePath.length < 2 || !wouldIntersectPath(fallbackStart, {
x: safeFallbackX,
y: safeFallbackY
}, gamePath))) {
gamePath.push({
x: safeFallbackX,
y: safeFallbackY
});
currentX = safeFallbackX;
currentY = safeFallbackY;
direction = fallbackDir;
fallbackFound = true;
}
}
// If still no valid segment found, break the loop to prevent infinite generation
if (!fallbackFound) {
break;
}
}
}
// Always ensure path connects to the base position as final endpoint
// Calculate safe base position in bottom portion of screen for top-to-bottom flow
var safeBaseX = minX + Math.random() * (maxX - minX); // Random X position across width
var safeBaseY = maxY - 200 + Math.random() * 150; // Bottom portion of screen
safeBaseX = Math.max(minX, Math.min(maxX, safeBaseX));
safeBaseY = Math.max(minY, Math.min(maxY, safeBaseY));
// Check if we need to add intermediate points to connect to base
var finalCurrentX = currentX;
var finalCurrentY = currentY;
var baseConnectionNeeded = true;
// Calculate distance to base from current position
var distanceToBase = Math.sqrt((safeBaseX - finalCurrentX) * (safeBaseX - finalCurrentX) + (safeBaseY - finalCurrentY) * (safeBaseY - finalCurrentY));
// If we're far from base, add intermediate connecting points
if (distanceToBase > 300) {
// Calculate direction to base
var directionToBaseX = safeBaseX - finalCurrentX;
var directionToBaseY = safeBaseY - finalCurrentY;
var normalizedX = directionToBaseX / distanceToBase;
var normalizedY = directionToBaseY / distanceToBase;
// Add intermediate points if needed (maximum 2 intermediate points)
var maxIntermediatePoints = Math.min(2, Math.floor(distanceToBase / 300));
for (var inter = 1; inter <= maxIntermediatePoints; inter++) {
var stepSize = distanceToBase / (maxIntermediatePoints + 1);
var intermediateX = finalCurrentX + normalizedX * stepSize * inter;
var intermediateY = finalCurrentY + normalizedY * stepSize * inter;
// Ensure intermediate point stays within the strict base boundaries
intermediateX = Math.max(minX, Math.min(maxX, intermediateX));
intermediateY = Math.max(minY, Math.min(maxY, intermediateY));
// Check if intermediate point would intersect existing path
var intermediateStart = {
x: finalCurrentX,
y: finalCurrentY
};
var intermediateEnd = {
x: intermediateX,
y: intermediateY
};
if (gamePath.length < 2 || !wouldIntersectPath(intermediateStart, intermediateEnd, gamePath)) {
gamePath.push({
x: intermediateX,
y: intermediateY
});
finalCurrentX = intermediateX;
finalCurrentY = intermediateY;
}
}
}
// Always add base position as final endpoint
gamePath.push({
x: safeBaseX,
y: safeBaseY
});
// Redraw 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);
}
}
// Ensure gamePath has valid elements
if (!gamePath || gamePath.length === 0) {
// Fallback path if generation failed
gamePath = [{
x: 100,
y: 400
}, {
x: 1800,
y: 1600
}];
}
// 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, black color, in foreground)
// Calculate path area boundaries with 8% margin (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 * marginPercent); // 8% 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 = 0x000000;
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 = 0x000000;
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 = 0x000000;
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 = 0x000000;
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;
}
}
// Wave 1 path generation complete - no flag changes needed since we use currentWave condition
}
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)
var bossHealth = Math.floor(totalEnemyHealth * 0.1);
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;
}
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);
}
}
// Dropdown toggle handler
waveDropdownBtn.down = function () {
dropdownVisible = !dropdownVisible;
dropdownContainer.visible = dropdownVisible;
waveDropdownBtn.setText(dropdownVisible ? 'Select Wave ▲' : 'Select Wave ▼');
};
// Wave button handlers
for (var i = 0; i < waveButtons.length; i++) {
waveButtons[i].down = function (waveNum) {
return function () {
// Jump to selected wave
jumpToWave(waveNum);
// Hide dropdown
dropdownVisible = false;
dropdownContainer.visible = false;
waveDropdownBtn.setText('Select Wave ▼');
};
}(waveButtons[i].waveNumber);
}
// 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();
}
// 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 dropdown if clicking on game area
if (dropdownVisible) {
dropdownVisible = false;
dropdownContainer.visible = false;
waveDropdownBtn.setText('Select Wave ▼');
}
// 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);
LK.getSound('place').play();
}
};
// Create blue 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 = 0x0000FF;
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 = 0x0000FF;
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 = 0x0000FF;
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 = 0x0000FF;
game.addChild(rightBorder);
// 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;
}
} 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 () {
LK.getSound('hit').play();
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(self.tower.damage);
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(self.tower.damage);
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: 'tankEnemy',
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
});
}
});
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;
self.damage = config.damage;
self.range = config.range;
self.fireRate = config.fireRate;
self.cost = config.cost;
self.upgradeCost = config.upgradeCost;
self.splashRadius = config.splashRadius;
self.slowFactor = config.slowFactor;
self.slowDuration = config.slowDuration;
self.lastShot = 0;
self.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);
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 = 100;
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 pathLengthText = new Text2('Path Length: 0', {
size: 40,
fill: 0x00FFFF
});
pathLengthText.anchor.set(0.5, 0);
pathLengthText.y = 80;
LK.gui.top.addChild(pathLengthText);
// Wave selector dropdown
var waveDropdownBtn = new Text2('Select Wave ▼', {
size: 40,
fill: 0x00FF00
});
waveDropdownBtn.anchor.set(0.5, 0);
waveDropdownBtn.x = 0;
waveDropdownBtn.y = 170;
LK.gui.top.addChild(waveDropdownBtn);
// Dropdown container (initially hidden)
var dropdownContainer = new Container();
dropdownContainer.visible = false;
dropdownContainer.x = 0;
dropdownContainer.y = 220;
LK.gui.top.addChild(dropdownContainer);
// Dropdown background
var dropdownBg = LK.getAsset('pathTile', {
anchorX: 0.5,
anchorY: 0,
scaleX: 4,
scaleY: 8
});
dropdownBg.tint = 0x333333;
dropdownBg.alpha = 0.9;
dropdownContainer.addChild(dropdownBg);
// Wave selection buttons (create 20 wave options)
var waveButtons = [];
for (var i = 1; i <= 20; i++) {
var waveBtn = new Text2('Wave ' + i, {
size: 35,
fill: 0xFFFFFF
});
waveBtn.anchor.set(0.5, 0);
waveBtn.x = 0;
waveBtn.y = (i - 1) * 45 + 10;
waveBtn.waveNumber = i;
dropdownContainer.addChild(waveBtn);
waveButtons.push(waveBtn);
}
var dropdownVisible = false;
// 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)' : ''));
pathLengthText.setText('Path Length: ' + calculatePathLength() + ' px');
// "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) {
// 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--;
}
}
// For Wave 1, Wave 6, Wave 11, and Wave 16, generate varied paths with strict boundary validation
if (currentWave === 1 || currentWave === 6 || currentWave === 11 || currentWave === 16) {
// Pre-validate that special waves use safe boundaries
var specialWaveMinX = Math.floor(screenWidth * 0.25); // 25% margin for special waves
var specialWaveMaxX = screenWidth - Math.floor(screenWidth * 0.25); // 25% margin
var specialWaveMinY = Math.floor(screenHeight * 0.35); // 35% margin for special waves
var specialWaveMaxY = screenHeight - Math.floor(screenHeight * 0.35); // 35% margin
// Generate multiple path variations for Wave 1, Wave 6, and Wave 11
var pathVariations = [
// Variation 1: Zigzag pattern
[{
x: 150,
y: 800
}, {
x: 600,
y: 800
}, {
x: 600,
y: 1300
}, {
x: 1050,
y: 1300
}, {
x: 1050,
y: 800
}, {
x: 1500,
y: 800
}, {
x: 1500,
y: 1300
}, {
x: 1800,
y: 1300
}],
// Variation 2: L-shape pattern
[{
x: 200,
y: 1200
}, {
x: 800,
y: 1200
}, {
x: 800,
y: 600
}, {
x: 1400,
y: 600
}, {
x: 1400,
y: 1000
}, {
x: 1700,
y: 1000
}, {
x: 1700,
y: 1400
}],
// Variation 3: S-curve pattern
[{
x: 250,
y: 900
}, {
x: 650,
y: 900
}, {
x: 650,
y: 1400
}, {
x: 1150,
y: 1400
}, {
x: 1150,
y: 700
}, {
x: 1650,
y: 700
}, {
x: 1650,
y: 1200
}],
// Variation 4: Step pattern
[{
x: 180,
y: 1100
}, {
x: 530,
y: 1100
}, {
x: 530,
y: 800
}, {
x: 880,
y: 800
}, {
x: 880,
y: 1500
}, {
x: 1230,
y: 1500
}, {
x: 1230,
y: 1000
}, {
x: 1580,
y: 1000
}, {
x: 1750,
y: 1000
}]];
// Select a random variation
var selectedVariation = pathVariations[Math.floor(Math.random() * pathVariations.length)];
// Scale the selected variation to exactly 3000 pixels for Wave 1, 3300 pixels for Wave 6, 3600 pixels for Wave 11, or 4000 pixels for Wave 16
var targetLength = currentWave === 1 ? 3000 : currentWave === 6 ? 3300 : currentWave === 11 ? 3600 : 4000;
var originalLength = 0;
for (var i = 0; i < selectedVariation.length - 1; i++) {
var dx = selectedVariation[i + 1].x - selectedVariation[i].x;
var dy = selectedVariation[i + 1].y - selectedVariation[i].y;
originalLength += Math.sqrt(dx * dx + dy * dy);
}
// Apply scaling factor to reach exactly target length
var scaleFactor = targetLength / originalLength;
gamePath = [];
for (var i = 0; i < selectedVariation.length; i++) {
if (i === 0) {
// Keep first point as-is
gamePath.push({
x: selectedVariation[i].x,
y: selectedVariation[i].y
});
} else {
// Scale subsequent segments
var prevPoint = gamePath[i - 1];
var originalDx = selectedVariation[i].x - selectedVariation[i - 1].x;
var originalDy = selectedVariation[i].y - selectedVariation[i - 1].y;
var scaledDx = originalDx * scaleFactor;
var scaledDy = originalDy * scaleFactor;
gamePath.push({
x: prevPoint.x + scaledDx,
y: prevPoint.y + scaledDy
});
}
}
// Draw path tiles for Wave 1, Wave 6, Wave 11, and Wave 16
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 for Wave 1, Wave 6, Wave 11, and Wave 16
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 path endpoint exactly for special waves
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));
}
// Ensure base and baslangic stay in foreground
if (base) game.addChild(base);
if (baslangicImage) game.addChild(baslangicImage);
// Create path area border frame (10px thickness, black color, in foreground)
// Calculate path area boundaries with small margin
var pathMinX = Math.floor(2048 * 0.08); // 8% margin from left
var pathMaxX = 2048 - Math.floor(2048 * 0.08); // 8% margin from right
var pathMinY = Math.floor(2732 * 0.08); // 8% margin from top
var pathMaxY = 2732 - Math.floor(2732 * 0.08); // 8% margin from bottom
// Top path border
var pathTopBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: (pathMaxX - pathMinX) / 100,
scaleY: 0.1
});
pathTopBorder.x = pathMinX;
pathTopBorder.y = pathMinY;
pathTopBorder.tint = 0x000000;
game.addChild(pathTopBorder);
// Bottom path border
var pathBottomBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: (pathMaxX - pathMinX) / 100,
scaleY: 0.1
});
pathBottomBorder.x = pathMinX;
pathBottomBorder.y = pathMaxY - 10;
pathBottomBorder.tint = 0x000000;
game.addChild(pathBottomBorder);
// Left path border
var pathLeftBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: 0.1,
scaleY: (pathMaxY - pathMinY) / 100
});
pathLeftBorder.x = pathMinX;
pathLeftBorder.y = pathMinY;
pathLeftBorder.tint = 0x000000;
game.addChild(pathLeftBorder);
// Right path border
var pathRightBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: 0.1,
scaleY: (pathMaxY - pathMinY) / 100
});
pathRightBorder.x = pathMaxX - 10;
pathRightBorder.y = pathMinY;
pathRightBorder.tint = 0x000000;
game.addChild(pathRightBorder);
// Update previous wave path length for Wave 1, Wave 6, Wave 11, and Wave 16
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;
} else {
// Fallback to expected length
var fallbackLength = currentWave === 1 ? 3000 : currentWave === 6 ? 3300 : currentWave === 11 ? 3600 : 4000;
previousWavePathLength = fallbackLength;
wavePathLengths[currentWave - 1] = fallbackLength;
// Save to database
storage.previousWavePathLength = previousWavePathLength;
storage.wavePathLengths = wavePathLengths;
}
return; // Exit early for Wave 1, Wave 6, Wave 11, and Wave 16
}
// Calculate safe boundaries to use entire screen area while maintaining small safety margins
var screenWidth = 2048;
var screenHeight = 2732;
// Use small margins: 8% from all sides to use most of the screen while preventing edge clipping
var marginPercent = 0.08; // 8% margin from all sides for safety
var minX = Math.floor(screenWidth * marginPercent); // 8% from left (164 px)
var maxX = screenWidth - Math.floor(screenWidth * marginPercent); // 8% from right (1884 px)
var minY = Math.floor(screenHeight * marginPercent); // 8% from top (219 px)
var maxY = screenHeight - Math.floor(screenHeight * marginPercent); // 8% from bottom (2513 px)
// Set path length based on wave - Wave 1 is 3000px, others use previous wave length + 300px
var targetPathLength;
if (currentWave === 1) {
targetPathLength = 3000;
} 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;
// Special enforcement for Wave 6 to ensure it's exactly 3300 pixels (like Wave 1's 3000px requirement)
if (currentWave === 6) {
targetPathLength = 3300; // Wave 6 is always exactly 3300 pixels
}
}
// Function to calculate distance from point to line segment
function distanceToLineSegment(point, lineStart, lineEnd) {
var A = point.x - lineStart.x;
var B = point.y - lineStart.y;
var C = lineEnd.x - lineStart.x;
var D = lineEnd.y - lineStart.y;
var dot = A * C + B * D;
var lenSq = C * C + D * D;
if (lenSq === 0) return Math.sqrt(A * A + B * B);
var param = dot / lenSq;
var xx, yy;
if (param < 0) {
xx = lineStart.x;
yy = lineStart.y;
} else if (param > 1) {
xx = lineEnd.x;
yy = lineEnd.y;
} else {
xx = lineStart.x + param * C;
yy = lineStart.y + param * D;
}
var dx = point.x - xx;
var dy = point.y - yy;
return Math.sqrt(dx * dx + dy * dy);
}
// Function to check if two line segments intersect
function doLinesIntersect(p1, q1, p2, q2) {
function orientation(p, q, r) {
var val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
if (val === 0) return 0; // collinear
return val > 0 ? 1 : 2; // clockwise or counterclockwise
}
function onSegment(p, q, r) {
return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y);
}
var o1 = orientation(p1, q1, p2);
var o2 = orientation(p1, q1, q2);
var o3 = orientation(p2, q2, p1);
var o4 = orientation(p2, q2, q1);
// General case
if (o1 !== o2 && o3 !== o4) return true;
// Special cases
if (o1 === 0 && onSegment(p1, p2, q1)) return true;
if (o2 === 0 && onSegment(p1, q2, q1)) return true;
if (o3 === 0 && onSegment(p2, p1, q2)) return true;
if (o4 === 0 && onSegment(p2, q1, q2)) return true;
return false;
}
// Function to check if proposed segment would intersect or get too close to existing path
function wouldIntersectPath(newStart, newEnd, existingPath) {
// Strict minimum distance to prevent any crossing or touching
var minDistance = 350; // Increased minimum distance significantly
var minSafeDistance = 200; // Minimum safe distance for any part of path
// Primary check: Direct line intersection with ANY existing segment (except immediate connection)
for (var i = 0; i < existingPath.length - 1; i++) {
var segStart = existingPath[i];
var segEnd = existingPath[i + 1];
// Skip only the immediate previous segment that we're connecting from
if (i === existingPath.length - 2) continue;
// Check for any intersection between the new segment and existing segments
if (doLinesIntersect(newStart, newEnd, segStart, segEnd)) {
return true;
}
}
// Secondary check: Ensure new segment maintains safe distance from ALL existing segments
for (var i = 0; i < existingPath.length - 1; i++) {
var segStart = existingPath[i];
var segEnd = existingPath[i + 1];
// Skip only the immediate previous segment
if (i === existingPath.length - 2) continue;
// Calculate minimum distance between the new segment and existing segment
var dist1 = distanceToLineSegment(newStart, segStart, segEnd);
var dist2 = distanceToLineSegment(newEnd, segStart, segEnd);
var dist3 = distanceToLineSegment(segStart, newStart, newEnd);
var dist4 = distanceToLineSegment(segEnd, newStart, newEnd);
var minimumSegmentDistance = Math.min(dist1, dist2, dist3, dist4);
// Reject if any part of the segments get too close
if (minimumSegmentDistance < minSafeDistance) {
return true;
}
}
// Tertiary check: Ensure new endpoint doesn't get too close to any existing path points
for (var i = 0; i < existingPath.length; i++) {
var pathPoint = existingPath[i];
// Skip only the immediate previous point we're connecting from
if (i === existingPath.length - 1) continue;
// Calculate distance from new endpoint to existing path points
var distToEnd = Math.sqrt((newEnd.x - pathPoint.x) * (newEnd.x - pathPoint.x) + (newEnd.y - pathPoint.y) * (newEnd.y - pathPoint.y));
// Use increasingly strict distance requirements for older path points
var ageMultiplier = 1.0 + (existingPath.length - 1 - i) * 0.2; // Older points need more distance
var requiredDistance = minDistance * ageMultiplier;
if (distToEnd < requiredDistance) {
return true;
}
}
// Quaternary check: Prevent sharp reversals that could lead to future self-intersection
if (existingPath.length >= 2) {
var prevPoint = existingPath[existingPath.length - 1];
var prevPrevPoint = existingPath[existingPath.length - 2];
// Calculate direction vectors
var prevDirX = prevPoint.x - prevPrevPoint.x;
var prevDirY = prevPoint.y - prevPrevPoint.y;
var newDirX = newEnd.x - newStart.x;
var newDirY = newEnd.y - newStart.y;
// Calculate angle between directions using dot product
var dotProduct = prevDirX * newDirX + prevDirY * newDirY;
var prevLength = Math.sqrt(prevDirX * prevDirX + prevDirY * prevDirY);
var newLength = Math.sqrt(newDirX * newDirX + newDirY * newDirY);
if (prevLength > 0 && newLength > 0) {
var cosAngle = dotProduct / (prevLength * newLength);
// Prevent sharp reversals (angles greater than 140 degrees)
if (cosAngle < -0.64) {
// cos(140°) ≈ -0.64
return true;
}
}
}
// Final comprehensive check: Ensure the new segment doesn't create potential for future loops
// by checking if it would "enclose" any existing path segments
if (existingPath.length >= 3) {
// Check if the new segment would create a potential enclosed area with any non-adjacent path segments
for (var i = 0; i < existingPath.length - 3; i++) {
// Check against segments that are not adjacent
var oldSegStart = existingPath[i];
var oldSegEnd = existingPath[i + 1];
// Calculate if new segment and old segment could form an enclosed area
// Check if segments are oriented in a way that could trap path points between them
var cross1 = (newEnd.x - newStart.x) * (oldSegStart.y - newStart.y) - (newEnd.y - newStart.y) * (oldSegStart.x - newStart.x);
var cross2 = (newEnd.x - newStart.x) * (oldSegEnd.y - newStart.y) - (newEnd.y - newStart.y) * (oldSegEnd.x - newStart.x);
var cross3 = (oldSegEnd.x - oldSegStart.x) * (newStart.y - oldSegStart.y) - (oldSegEnd.y - oldSegStart.y) * (newStart.x - oldSegStart.x);
var cross4 = (oldSegEnd.x - oldSegStart.x) * (newEnd.y - oldSegStart.y) - (oldSegEnd.y - oldSegStart.y) * (newEnd.x - oldSegStart.x);
// If segments create opposing orientations, they might enclose an area - reject this
if (cross1 > 0 !== cross2 > 0 && cross3 > 0 !== cross4 > 0) {
return true;
}
}
}
return false;
}
// Create varied path shapes while maintaining characteristics
gamePath = [];
var baseSegmentLength = currentWave === 1 ? 200 : Math.floor(180 * pathExtensionFactor); // Much shorter base segments for more frequent turns
// For Wave 1, calculate exact segment lengths to total exactly 3000 pixels with more segments for better curves
var numSegments = currentWave === 1 ? 15 : Math.max(12, Math.floor(10 + currentWave * 0.8)); // More segments for much better curving
var turnCount = 0; // Track number of turns
// Starting position - randomized across top portion of screen
var startX, startY;
// All waves start from top portion of screen
startX = minX + 50 + Math.random() * (maxX - minX - 100);
startY = minY + 50 + Math.random() * 200; // Start in top 200px of usable area
gamePath.push({
x: startX,
y: startY
});
var currentX = startX;
var currentY = startY;
// Prioritize downward movement to create top-to-bottom flow
var possibleStartDirections = [];
// Heavily favor downward movement
if (startY + 300 <= maxY) {
possibleStartDirections.push(1, 1, 1, 1, 1, 1); // down (6x weight)
}
// Minimal upward movement only if needed
if (startY - 300 >= minY) {
possibleStartDirections.push(3); // up (single weight)
}
// Limited horizontal directions with reduced priority
if (startX + 300 <= maxX - 100) possibleStartDirections.push(0); // right (single weight)
if (startX - 300 >= minX + 100) possibleStartDirections.push(2); // left (single weight)
var direction = possibleStartDirections.length > 0 ? possibleStartDirections[Math.floor(Math.random() * possibleStartDirections.length)] : 1; // default to down
var totalPathLength = 0; // Track total path length built so far
var segmentLengths = []; // Pre-calculate segment lengths to total exactly 3000 pixels
// Calculate exact segment lengths to achieve target path length
if (currentWave === 1) {
// For Wave 1, use much shorter segments that create better curves and total exactly 3000 pixels
segmentLengths = [200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200]; // 15 segments of 200 pixels each = 3000 total
} else {
// For subsequent waves, calculate segments to achieve exact target length with randomization
var baseSegmentLength = Math.floor(targetPathLength / numSegments);
var remainder = targetPathLength % numSegments;
segmentLengths = [];
// Create randomized segment lengths while maintaining total target length
var totalAllocated = 0;
for (var i = 0; i < numSegments; i++) {
if (i === numSegments - 1) {
// Last segment gets remaining length to ensure exact total
segmentLengths.push(targetPathLength - totalAllocated);
} else {
// Add randomization: ±30% variation from base length
var minLength = Math.max(200, Math.floor(baseSegmentLength * 0.7));
var maxLength = Math.floor(baseSegmentLength * 1.3);
var randomLength = minLength + Math.floor(Math.random() * (maxLength - minLength + 1));
// Add remainder distribution
randomLength += i < remainder ? 1 : 0;
// Ensure we don't exceed target length with remaining segments
var remainingSegments = numSegments - i - 1;
var maxPossibleLength = targetPathLength - totalAllocated - remainingSegments * 200;
randomLength = Math.min(randomLength, maxPossibleLength);
segmentLengths.push(randomLength);
totalAllocated += randomLength;
}
}
}
// Generate varied path with more turns based on path length
// Calculate minimum turns based on target path length - much more turns for shorter segments
var baseMinTurns = Math.max(5, Math.floor(targetPathLength / 600)); // At least 1 turn per 600px (more frequent)
var maxTurns = Math.min(numSegments - 1, baseMinTurns + 4); // Allow more maximum turns
var minTurns = Math.max(5, baseMinTurns);
// Create strategic turn points with randomization
var mandatoryTurnSegments = [];
if (numSegments >= 5) {
// Distribute turns more evenly across the path with some randomization
var turnSpacing = numSegments / (minTurns + 1);
for (var t = 0; t < minTurns; t++) {
var basePosition = Math.floor((t + 1) * turnSpacing);
// Add randomization to turn positions (±20% variation)
var variation = Math.floor(turnSpacing * 0.2);
var randomOffset = Math.floor(Math.random() * (variation * 2 + 1)) - variation;
var turnPosition = Math.max(1, Math.min(numSegments - 2, basePosition + randomOffset));
// Ensure we don't duplicate turn positions
if (mandatoryTurnSegments.indexOf(turnPosition) === -1) {
mandatoryTurnSegments.push(turnPosition);
}
}
}
for (var seg = 0; seg < numSegments; seg++) {
// Use pre-calculated segment length for exact path length control
var segmentLength = segmentLengths[seg] || Math.max(250, baseSegmentLength + Math.random() * 200 - 100);
var attempts = 0;
var validSegmentFound = false;
var forceTurn = mandatoryTurnSegments.indexOf(seg) !== -1; // Force turn at strategic segments
while (!validSegmentFound && attempts < 50) {
// Increased max attempts
var shouldTurn = false;
// Force turn at mandatory segments or if we haven't turned enough
if (forceTurn || turnCount < minTurns && seg >= numSegments - (minTurns - turnCount)) {
shouldTurn = true;
} else if (turnCount < minTurns && seg > 0 && Math.random() < 0.95) {
// Very high chance of turning when we need more turns
shouldTurn = true;
} else if (turnCount >= minTurns && turnCount < maxTurns && seg > 0 && Math.random() < 0.8) {
// High chance for additional turns up to maximum
shouldTurn = true;
} else if (turnCount >= maxTurns && seg > 0 && Math.random() < 0.4) {
// Higher chance even after reaching maximum turns for more curves
shouldTurn = true;
}
if (shouldTurn && seg > 0) {
// Don't turn on first segment
// Change direction (90-degree turn)
var oldDirection = direction;
var possibleDirections = [];
// Prioritize vertical directions heavily over horizontal directions
var possibleDirections = [];
// Heavily prioritize downward movement to maintain top-to-bottom flow
if (direction !== 3 && currentY + segmentLength <= maxY) {
possibleDirections.push(1, 1, 1, 1, 1, 1, 1, 1); // down (8x weight)
}
// Minimal upward movement only when necessary
if (direction !== 1 && currentY - segmentLength >= minY && currentY > maxY * 0.7) {
possibleDirections.push(3); // up (single weight, only in bottom 30% of screen)
}
// Reduced horizontal directions to maintain vertical flow
if (direction !== 2 && currentX + segmentLength <= maxX - 100) possibleDirections.push(0); // right (single weight)
if (direction !== 0 && currentX - segmentLength >= minX + 100) possibleDirections.push(2); // left (single weight)
// Remove the current direction to force an actual turn
var filteredDirections = [];
for (var d = 0; d < possibleDirections.length; d++) {
if (possibleDirections[d] !== direction) {
filteredDirections.push(possibleDirections[d]);
}
}
if (filteredDirections.length > 0) {
direction = filteredDirections[Math.floor(Math.random() * filteredDirections.length)];
turnCount++;
} else if (possibleDirections.length > 0) {
// Fallback to any valid direction if no turn is possible
direction = possibleDirections[Math.floor(Math.random() * possibleDirections.length)];
}
}
// Calculate next position based on direction with boundary checking
var nextX = currentX;
var nextY = currentY;
var wouldExceedBounds = false;
if (direction === 0) {
// right
nextX = currentX + segmentLength;
if (nextX > maxX) wouldExceedBounds = true;
} else if (direction === 1) {
// down
nextY = currentY + segmentLength;
if (nextY > maxY) wouldExceedBounds = true;
} else if (direction === 2) {
// left
nextX = currentX - segmentLength;
if (nextX < minX) wouldExceedBounds = true;
} else if (direction === 3) {
// up
nextY = currentY - segmentLength;
if (nextY < minY) wouldExceedBounds = true;
}
// If path would go off-screen, increment turn count instead
if (wouldExceedBounds) {
turnCount++;
// Try a different direction that stays within bounds, heavily prioritizing downward movement
var safeDirections = [];
// Heavily prioritize downward movement for top-to-bottom flow
if (currentY + segmentLength <= maxY && direction !== 3) {
safeDirections.push(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1); // down (12x weight)
}
// Minimal upward movement only in bottom portion of screen
if (currentY - segmentLength >= minY && direction !== 1 && currentY > maxY * 0.6) {
safeDirections.push(3, 3); // up (double weight, only in bottom 40% of screen)
}
// Limited horizontal directions to maintain vertical flow
if (currentX + segmentLength <= maxX - 300 && direction !== 2) safeDirections.push(0); // right (with margin)
if (currentX - segmentLength >= minX + 300 && direction !== 0) safeDirections.push(2); // left (with margin)
if (safeDirections.length > 0) {
direction = safeDirections[Math.floor(Math.random() * safeDirections.length)];
// Recalculate position with new direction
if (direction === 0) {
nextX = currentX + segmentLength;
} else if (direction === 1) {
nextY = currentY + segmentLength;
} else if (direction === 2) {
nextX = currentX - segmentLength;
} else if (direction === 3) {
nextY = currentY - segmentLength;
}
} else {
// No safe direction available, reduce segment length
segmentLength = Math.min(segmentLength, Math.min(maxX - currentX, currentX - minX, maxY - currentY, currentY - minY));
nextX = currentX + (direction === 0 ? segmentLength : direction === 2 ? -segmentLength : 0);
nextY = currentY + (direction === 1 ? segmentLength : direction === 3 ? -segmentLength : 0);
}
}
// Final boundary enforcement with the 35% margins - no additional margin needed
nextX = Math.max(minX, Math.min(maxX, nextX));
nextY = Math.max(minY, Math.min(maxY, nextY));
// Double-check boundaries are respected with the 35% base margins
if (nextX < minX || nextX > maxX || nextY < minY || nextY > maxY) {
// Force position to safe center area if still problematic
nextX = minX + (maxX - minX) * 0.5;
nextY = minY + (maxY - minY) * 0.5;
}
// Ensure we're moving towards the bottom area in later segments for top-to-bottom flow
if (seg > numSegments * 0.7) {
var targetEndY = maxY - 200; // Target area near bottom but within boundaries
if (nextY < targetEndY && direction !== 3) {
nextY = Math.min(maxY, currentY + Math.abs(segmentLength));
direction = 1; // Force downward movement
}
}
// Check if this segment would intersect existing path
var proposedStart = {
x: currentX,
y: currentY
};
var proposedEnd = {
x: nextX,
y: nextY
};
// Check intersection with much stricter criteria
var isValidSegment = false;
if (gamePath.length < 2) {
// First few segments are always safe
isValidSegment = true;
} else {
// For subsequent segments, apply strict intersection checking
isValidSegment = !wouldIntersectPath(proposedStart, proposedEnd, gamePath);
}
// Comprehensive boundary validation with the strict base margins
var finalX = Math.max(minX, Math.min(maxX, nextX));
var finalY = Math.max(minY, Math.min(maxY, nextY));
// Ensure path point is completely within the strict base bounds
var isWithinBounds = finalX >= minX && finalX <= maxX && finalY >= minY && finalY <= maxY;
// Additional validation: ensure minimum distance from actual screen edges using 8% margins
var distFromLeft = finalX - 0;
var distFromRight = screenWidth - finalX;
var distFromTop = finalY - 0;
var distFromBottom = screenHeight - finalY;
var minScreenDistance = Math.floor(screenWidth * marginPercent); // Use same 8% margin as boundary calculation
var isSafeFromEdges = distFromLeft >= minScreenDistance && distFromRight >= minScreenDistance && distFromTop >= minScreenDistance && distFromBottom >= minScreenDistance;
isWithinBounds = isWithinBounds && isSafeFromEdges;
if (isValidSegment && isWithinBounds) {
gamePath.push({
x: finalX,
y: finalY
});
currentX = finalX;
currentY = finalY;
validSegmentFound = true;
} else {
// Segment would intersect - try different approach
attempts++;
// Reduce segment length for intersection avoidance (except for pre-calculated lengths)
if (seg >= segmentLengths.length || currentWave === 1) {
// Keep original length for pre-calculated segments
} else {
segmentLength = Math.max(120, segmentLength * 0.6); // Shorter fallback segments for tighter turns
}
// After several attempts, try completely different directions
if (attempts > 10) {
var availableDirections = [];
// Check all directions for validity, prioritizing vertical movements
if (currentWave === 1) {
// For Wave 1, use original segment length for direction checking, prioritize vertical
var checkLength = segmentLengths[seg];
if (currentY + checkLength <= maxY && direction !== 3) availableDirections.push(1); // down (first priority)
if (currentY - checkLength >= minY && direction !== 1) availableDirections.push(3); // up (second priority)
if (currentX + checkLength <= maxX - 100 && direction !== 2) availableDirections.push(0); // right (with margin)
if (currentX - checkLength >= minX + 100 && direction !== 0) availableDirections.push(2); // left (with margin)
} else {
// Prioritize vertical directions for other waves too with shorter check distances
if (currentY + 150 <= maxY && direction !== 3) availableDirections.push(1); // down (first priority, shorter check)
if (currentY - 150 >= minY && direction !== 1) availableDirections.push(3); // up (second priority, shorter check)
if (currentX + 150 <= maxX - 100 && direction !== 2) availableDirections.push(0); // right (with margin, shorter check)
if (currentX - 150 >= minX + 100 && direction !== 0) availableDirections.push(2); // left (with margin, shorter check)
}
if (availableDirections.length > 0) {
// Prefer vertical directions even in fallback
var verticalDirections = [];
var horizontalDirections = [];
for (var ad = 0; ad < availableDirections.length; ad++) {
if (availableDirections[ad] === 1 || availableDirections[ad] === 3) {
verticalDirections.push(availableDirections[ad]);
} else {
horizontalDirections.push(availableDirections[ad]);
}
}
// Choose vertical direction if available, otherwise horizontal
if (verticalDirections.length > 0) {
direction = verticalDirections[Math.floor(Math.random() * verticalDirections.length)];
} else {
direction = horizontalDirections[Math.floor(Math.random() * horizontalDirections.length)];
}
if (currentWave !== 1) {
segmentLength = 150; // Reset to much smaller safe length for tighter turns
}
}
}
}
}
if (!validSegmentFound) {
// Force a valid segment if we can't find one - try multiple directions, prioritizing vertical
var fallbackFound = false;
var fallbackDirections = [1, 0, 2, 3]; // down first, then horizontal, up last
for (var fd = 0; fd < fallbackDirections.length && !fallbackFound; fd++) {
var fallbackDir = fallbackDirections[fd];
var fallbackLength = seg < segmentLengths.length ? segmentLengths[seg] : 150; // Shorter fallback for tighter curves
var fallbackX = currentX;
var fallbackY = currentY;
if (fallbackDir === 0 && currentX + fallbackLength <= maxX) {
// right
fallbackX = currentX + fallbackLength;
} else if (fallbackDir === 1 && currentY + fallbackLength <= maxY) {
// down
fallbackY = currentY + fallbackLength;
} else if (fallbackDir === 2 && currentX - fallbackLength >= minX) {
// left
fallbackX = currentX - fallbackLength;
} else if (fallbackDir === 3 && currentY - fallbackLength >= minY) {
// up
fallbackY = currentY - fallbackLength;
} else {
continue; // Try next direction
}
var fallbackStart = {
x: currentX,
y: currentY
};
var fallbackEnd = {
x: fallbackX,
y: fallbackY
};
// Check if this fallback direction works
// Ensure fallback position is within the strict base boundaries
var safeFallbackX = Math.max(minX, Math.min(maxX, fallbackX));
var safeFallbackY = Math.max(minY, Math.min(maxY, fallbackY));
// Comprehensive fallback validation using the strict base boundaries
var fallbackWithinBounds = safeFallbackX >= minX && safeFallbackX <= maxX && safeFallbackY >= minY && safeFallbackY <= maxY;
// Additional fallback validation: check distance from all screen edges using consistent 8% margin
var fallbackDistFromLeft = safeFallbackX - 0;
var fallbackDistFromRight = screenWidth - safeFallbackX;
var fallbackDistFromTop = safeFallbackY - 0;
var fallbackDistFromBottom = screenHeight - safeFallbackY;
var minFallbackDistance = Math.floor(screenWidth * marginPercent); // Use consistent 8% margin
var fallbackSafeFromEdges = fallbackDistFromLeft >= minFallbackDistance && fallbackDistFromRight >= minFallbackDistance && fallbackDistFromTop >= minFallbackDistance && fallbackDistFromBottom >= minFallbackDistance;
fallbackWithinBounds = fallbackWithinBounds && fallbackSafeFromEdges;
if (fallbackWithinBounds && (gamePath.length < 2 || !wouldIntersectPath(fallbackStart, {
x: safeFallbackX,
y: safeFallbackY
}, gamePath))) {
gamePath.push({
x: safeFallbackX,
y: safeFallbackY
});
currentX = safeFallbackX;
currentY = safeFallbackY;
direction = fallbackDir;
fallbackFound = true;
}
}
// If still no valid segment found, break the loop to prevent infinite generation
if (!fallbackFound) {
break;
}
}
}
// Always ensure path connects to the base position as final endpoint
// Calculate safe base position in bottom portion of screen for top-to-bottom flow
var safeBaseX = minX + Math.random() * (maxX - minX); // Random X position across width
var safeBaseY = maxY - 200 + Math.random() * 150; // Bottom portion of screen
safeBaseX = Math.max(minX, Math.min(maxX, safeBaseX));
safeBaseY = Math.max(minY, Math.min(maxY, safeBaseY));
// Check if we need to add intermediate points to connect to base
var finalCurrentX = currentX;
var finalCurrentY = currentY;
var baseConnectionNeeded = true;
// Calculate distance to base from current position
var distanceToBase = Math.sqrt((safeBaseX - finalCurrentX) * (safeBaseX - finalCurrentX) + (safeBaseY - finalCurrentY) * (safeBaseY - finalCurrentY));
// If we're far from base, add intermediate connecting points
if (distanceToBase > 300) {
// Calculate direction to base
var directionToBaseX = safeBaseX - finalCurrentX;
var directionToBaseY = safeBaseY - finalCurrentY;
var normalizedX = directionToBaseX / distanceToBase;
var normalizedY = directionToBaseY / distanceToBase;
// Add intermediate points if needed (maximum 2 intermediate points)
var maxIntermediatePoints = Math.min(2, Math.floor(distanceToBase / 300));
for (var inter = 1; inter <= maxIntermediatePoints; inter++) {
var stepSize = distanceToBase / (maxIntermediatePoints + 1);
var intermediateX = finalCurrentX + normalizedX * stepSize * inter;
var intermediateY = finalCurrentY + normalizedY * stepSize * inter;
// Ensure intermediate point stays within the strict base boundaries
intermediateX = Math.max(minX, Math.min(maxX, intermediateX));
intermediateY = Math.max(minY, Math.min(maxY, intermediateY));
// Check if intermediate point would intersect existing path
var intermediateStart = {
x: finalCurrentX,
y: finalCurrentY
};
var intermediateEnd = {
x: intermediateX,
y: intermediateY
};
if (gamePath.length < 2 || !wouldIntersectPath(intermediateStart, intermediateEnd, gamePath)) {
gamePath.push({
x: intermediateX,
y: intermediateY
});
finalCurrentX = intermediateX;
finalCurrentY = intermediateY;
}
}
}
// Always add base position as final endpoint
gamePath.push({
x: safeBaseX,
y: safeBaseY
});
// Redraw 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);
}
}
// Ensure gamePath has valid elements
if (!gamePath || gamePath.length === 0) {
// Fallback path if generation failed
gamePath = [{
x: 100,
y: 400
}, {
x: 1800,
y: 1600
}];
}
// 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, black color, in foreground)
// Calculate path area boundaries with 8% margin (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 * marginPercent); // 8% 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 = 0x000000;
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 = 0x000000;
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 = 0x000000;
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 = 0x000000;
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;
}
}
// Wave 1 path generation complete - no flag changes needed since we use currentWave condition
}
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)
var bossHealth = Math.floor(totalEnemyHealth * 0.1);
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;
}
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);
}
}
// Dropdown toggle handler
waveDropdownBtn.down = function () {
dropdownVisible = !dropdownVisible;
dropdownContainer.visible = dropdownVisible;
waveDropdownBtn.setText(dropdownVisible ? 'Select Wave ▲' : 'Select Wave ▼');
};
// Wave button handlers
for (var i = 0; i < waveButtons.length; i++) {
waveButtons[i].down = function (waveNum) {
return function () {
// Jump to selected wave
jumpToWave(waveNum);
// Hide dropdown
dropdownVisible = false;
dropdownContainer.visible = false;
waveDropdownBtn.setText('Select Wave ▼');
};
}(waveButtons[i].waveNumber);
}
// 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();
}
// 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 dropdown if clicking on game area
if (dropdownVisible) {
dropdownVisible = false;
dropdownContainer.visible = false;
waveDropdownBtn.setText('Select Wave ▼');
}
// 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);
LK.getSound('place').play();
}
};
// Create blue 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 = 0x0000FF;
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 = 0x0000FF;
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 = 0x0000FF;
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 = 0x0000FF;
game.addChild(rightBorder);
// 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;
}
} 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();
};