User prompt
ekraqn çerçevesinin rengi mavi olsun kalınlığı 20 px olsun
User prompt
ekran çerçevenin alt çizgisi Slow yazısının üzt tarafına gelsin
User prompt
bu çerçeveyi elttan 100 px yukarı kaydır
User prompt
yolun oluşturulacağı alanı siyah renkli 10px kalınlığında çizgi ile çerçeve içine alırmısın. bu çerçeve en önde olsun gözüksün
User prompt
daha önce belirlemiş olduğum oyun alanını siyah renkli 10px kalınlığında çizgi ile çerçeve içine alırmısın
User prompt
oyun ekranını belirleyen çizgileri çiz
User prompt
bu çerçeve gözüksün
User prompt
oyunda yolun rastgele oluşturulacağı ekranı çerçeve içine alırmısın
User prompt
yol oluşturulurken yol bitiş noktasıyla birleşsin
User prompt
yol oluşturulurken ekran dışında olmasın
User prompt
yol oluşturulurken yolun bitiş noktası ekran dışında olmasın
User prompt
16. dalgada yol oluşturulurken tekrar baştan oluşturulsun ve ekran dışına taşmasın.
User prompt
oyunda güncelleme yapmak istiyorum.yolu oluştururken dönüşler kısalsın.
User prompt
yolu oluştururken yol ekranın üst tarafında alt tarafına doğru oluşturulsun
User prompt
When creating a path, use the entire screen area. Randomize the path's positions and turns, but never allow it to extend beyond the screen.
User prompt
When creating the road, adjust the turns of the road so that the road does not extend beyond the screen.
User prompt
When creating a path, create it up and down the screen instead of off the screen.
User prompt
yolu oluştururken yolu ekranın dışına doğru değil de yukarı ve aşağı doğru oluştursun
User prompt
yolun tamamı ekranın içinde olsun kesinlikle ekranın kenarına bile değmesin arada boşluk olsun
User prompt
yol oluşturulurken kesinlikle görüntü ekranın dışına taşmasın. yolun kıvrımlarını ona göre ayarla. yok ekranda kıvrılarak dolaşabilir
User prompt
16.dalgada yolu oluştururken lütfen kesinlikle görüntü ekran dışına taşmasın. yolu ona göre ayarla
User prompt
özellikle 11. ve 16. dalgalarda yol ve bitiş noktası ekrandan taşıyor. lütfen bunu düzelt gerekirse yolu dolaştır
User prompt
yol oluşturulurken ekranın dışına taşmaması için istediğigi rastgele oluşturulsun
User prompt
bitişin ve yolun tamamı gözüksün ekranın dışına hiç taşmasın
User prompt
yol oluşturulurken yol mutlaka bitiş noktasına gitsin ona bağlansın ve öyle sona ersin
/****
* 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);
// 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 with extremely strict margins to ensure complete visibility
var screenWidth = 2048;
var screenHeight = 2732;
// Use much stricter margins: 45% from top/bottom, 35% from left/right sides to guarantee complete visibility
var minX = Math.floor(screenWidth * 0.35); // 35% from left (716 px)
var maxX = screenWidth - Math.floor(screenWidth * 0.35); // 35% from right (1332 px)
var minY = Math.floor(screenHeight * 0.45); // 45% from top (1229 px)
var maxY = screenHeight - Math.floor(screenHeight * 0.45); // 45% from bottom (1503 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 ? 500 : Math.floor(300 * pathExtensionFactor); // Base segment length for Wave 1 calculation
// For Wave 1, calculate exact segment lengths to total exactly 3000 pixels
var numSegments = currentWave === 1 ? 6 : Math.max(6, Math.floor(5 + currentWave * 0.5)); // 6 segments for 3000 pixel Wave 1 path
var turnCount = 0; // Track number of turns
// Starting position - more randomized for all waves within safe boundaries
var startX, startY;
if (currentWave === 1) {
// Wave 1 gets some randomization but stays in left area
startX = 150 + Math.random() * 200; // 150-350 range
startY = 800 + Math.random() * 400; // 800-1200 range
} else {
// Other waves get full randomization within safe boundaries
startX = minX + 100 + Math.random() * (maxX - minX - 200);
startY = minY + 100 + Math.random() * (maxY - minY - 200);
}
gamePath.push({
x: startX,
y: startY
});
var currentX = startX;
var currentY = startY;
// Randomize starting direction for more variety
var possibleStartDirections = [];
if (startX + 300 <= maxX) possibleStartDirections.push(0); // right
if (startY + 300 <= maxY) possibleStartDirections.push(1); // down
if (startX - 300 >= minX) possibleStartDirections.push(2); // left
if (startY - 300 >= minY) possibleStartDirections.push(3); // up
var direction = possibleStartDirections.length > 0 ? possibleStartDirections[Math.floor(Math.random() * possibleStartDirections.length)] : 0;
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 simple fixed segments that always total exactly 3000 pixels
segmentLengths = [500, 500, 500, 500, 500, 500]; // 6 segments of 500 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 - more turns for longer paths
var baseMinTurns = Math.max(3, Math.floor(targetPathLength / 1000)); // At least 1 turn per 1000px
var maxTurns = Math.min(numSegments - 1, baseMinTurns + 2); // Cap maximum turns
var minTurns = Math.max(3, 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.85) {
// High chance of turning when we need more turns
shouldTurn = true;
} else if (turnCount >= minTurns && turnCount < maxTurns && seg > 0 && Math.random() < 0.6) {
// Medium chance for additional turns up to maximum
shouldTurn = true;
} else if (turnCount >= maxTurns && seg > 0 && Math.random() < 0.2) {
// Low chance after reaching maximum turns
shouldTurn = true;
}
if (shouldTurn && seg > 0) {
// Don't turn on first segment
// Change direction (90-degree turn)
var oldDirection = direction;
var possibleDirections = [];
// Calculate valid directions based on current position and safe boundaries
if (direction !== 2 && currentX + segmentLength <= maxX) possibleDirections.push(0); // right
if (direction !== 3 && currentY + segmentLength <= maxY) possibleDirections.push(1); // down
if (direction !== 0 && currentX - segmentLength >= minX) possibleDirections.push(2); // left
if (direction !== 1 && currentY - segmentLength >= minY) possibleDirections.push(3); // up
// 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
var safeDirections = [];
if (currentX + segmentLength <= maxX && direction !== 2) safeDirections.push(0); // right
if (currentY + segmentLength <= maxY && direction !== 3) safeDirections.push(1); // down
if (currentX - segmentLength >= minX && direction !== 0) safeDirections.push(2); // left
if (currentY - segmentLength >= minY && direction !== 1) safeDirections.push(3); // up
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 extra strict validation
var extraSafetyMargin = 250; // Additional margin for absolute safety
nextX = Math.max(minX + extraSafetyMargin, Math.min(maxX - extraSafetyMargin, nextX));
nextY = Math.max(minY + extraSafetyMargin, Math.min(maxY - extraSafetyMargin, nextY));
// Double-check boundaries are respected
if (nextX < minX + extraSafetyMargin || nextX > maxX - extraSafetyMargin || nextY < minY + extraSafetyMargin || nextY > maxY - extraSafetyMargin) {
// 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 general end area in later segments
if (seg > numSegments * 0.7) {
var targetEndX = maxX - 200; // Target area near right edge but within boundaries
if (nextX < targetEndX) {
nextX = Math.min(maxX, currentX + Math.abs(segmentLength));
direction = 0;
}
}
// 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 much larger safety margins
var largeSafetyMargin = 600; // Much larger safety margin to guarantee visibility
var finalX = Math.max(minX + largeSafetyMargin, Math.min(maxX - largeSafetyMargin, nextX));
var finalY = Math.max(minY + largeSafetyMargin, Math.min(maxY - largeSafetyMargin, nextY));
// Triple validation - ensure path point is completely within safe bounds with generous margins
var isWithinBounds = finalX >= minX + largeSafetyMargin && finalX <= maxX - largeSafetyMargin && finalY >= minY + largeSafetyMargin && finalY <= maxY - largeSafetyMargin;
// Additional check: ensure the point is not too close to screen edges
var distFromLeft = finalX - 0;
var distFromRight = screenWidth - finalX;
var distFromTop = finalY - 0;
var distFromBottom = screenHeight - finalY;
var minScreenDistance = 700; // Minimum distance from any screen edge
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(150, segmentLength * 0.7);
}
// After several attempts, try completely different directions
if (attempts > 10) {
var availableDirections = [];
// Check all directions for validity
if (currentWave === 1) {
// For Wave 1, use original segment length for direction checking
var checkLength = segmentLengths[seg];
if (currentX + checkLength <= maxX && direction !== 2) availableDirections.push(0); // right
if (currentY + checkLength <= maxY && direction !== 3) availableDirections.push(1); // down
if (currentX - checkLength >= minX && direction !== 0) availableDirections.push(2); // left
if (currentY - checkLength >= minY && direction !== 1) availableDirections.push(3); // up
} else {
if (currentX + 200 <= maxX && direction !== 2) availableDirections.push(0); // right
if (currentY + 200 <= maxY && direction !== 3) availableDirections.push(1); // down
if (currentX - 200 >= minX && direction !== 0) availableDirections.push(2); // left
if (currentY - 200 >= minY && direction !== 1) availableDirections.push(3); // up
}
if (availableDirections.length > 0) {
direction = availableDirections[Math.floor(Math.random() * availableDirections.length)];
if (currentWave !== 1) {
segmentLength = 200; // Reset to smaller safe length
}
}
}
}
}
if (!validSegmentFound) {
// Force a valid segment if we can't find one - try multiple directions
var fallbackFound = false;
var fallbackDirections = [0, 1, 2, 3]; // right, down, left, up
for (var fd = 0; fd < fallbackDirections.length && !fallbackFound; fd++) {
var fallbackDir = fallbackDirections[fd];
var fallbackLength = seg < segmentLengths.length ? segmentLengths[seg] : 200;
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 boundaries with extra safety margin
var safetyMargin = 500; // Additional safety margin for fallback
var safeFallbackX = Math.max(minX + safetyMargin, Math.min(maxX - safetyMargin, fallbackX));
var safeFallbackY = Math.max(minY + safetyMargin, Math.min(maxY - safetyMargin, fallbackY));
// Comprehensive fallback validation with strict boundary checking
var fallbackSafetyMargin = 650; // Even larger margin for fallback positions
var fallbackWithinBounds = safeFallbackX >= minX + fallbackSafetyMargin && safeFallbackX <= maxX - fallbackSafetyMargin && safeFallbackY >= minY + fallbackSafetyMargin && safeFallbackY <= maxY - fallbackSafetyMargin;
// Additional fallback validation: check distance from all screen edges
var fallbackDistFromLeft = safeFallbackX - 0;
var fallbackDistFromRight = screenWidth - safeFallbackX;
var fallbackDistFromTop = safeFallbackY - 0;
var fallbackDistFromBottom = screenHeight - safeFallbackY;
var minFallbackDistance = 750;
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 within boundaries
var baseSafetyMargin = 200;
var safeBaseX = Math.max(minX + baseSafetyMargin, Math.min(maxX - baseSafetyMargin, base.x));
var safeBaseY = Math.max(minY + baseSafetyMargin, Math.min(maxY - baseSafetyMargin, base.y));
// 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 bounds
intermediateX = Math.max(minX + baseSafetyMargin, Math.min(maxX - baseSafetyMargin, intermediateX));
intermediateY = Math.max(minY + baseSafetyMargin, Math.min(maxY - baseSafetyMargin, 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);
// 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();
}
};
// 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);
// 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 with extremely strict margins to ensure complete visibility
var screenWidth = 2048;
var screenHeight = 2732;
// Use much stricter margins: 45% from top/bottom, 35% from left/right sides to guarantee complete visibility
var minX = Math.floor(screenWidth * 0.35); // 35% from left (716 px)
var maxX = screenWidth - Math.floor(screenWidth * 0.35); // 35% from right (1332 px)
var minY = Math.floor(screenHeight * 0.45); // 45% from top (1229 px)
var maxY = screenHeight - Math.floor(screenHeight * 0.45); // 45% from bottom (1503 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 ? 500 : Math.floor(300 * pathExtensionFactor); // Base segment length for Wave 1 calculation
// For Wave 1, calculate exact segment lengths to total exactly 3000 pixels
var numSegments = currentWave === 1 ? 6 : Math.max(6, Math.floor(5 + currentWave * 0.5)); // 6 segments for 3000 pixel Wave 1 path
var turnCount = 0; // Track number of turns
// Starting position - more randomized for all waves within safe boundaries
var startX, startY;
if (currentWave === 1) {
// Wave 1 gets some randomization but stays in left area
startX = 150 + Math.random() * 200; // 150-350 range
startY = 800 + Math.random() * 400; // 800-1200 range
} else {
// Other waves get full randomization within safe boundaries
startX = minX + 100 + Math.random() * (maxX - minX - 200);
startY = minY + 100 + Math.random() * (maxY - minY - 200);
}
gamePath.push({
x: startX,
y: startY
});
var currentX = startX;
var currentY = startY;
// Randomize starting direction for more variety
var possibleStartDirections = [];
if (startX + 300 <= maxX) possibleStartDirections.push(0); // right
if (startY + 300 <= maxY) possibleStartDirections.push(1); // down
if (startX - 300 >= minX) possibleStartDirections.push(2); // left
if (startY - 300 >= minY) possibleStartDirections.push(3); // up
var direction = possibleStartDirections.length > 0 ? possibleStartDirections[Math.floor(Math.random() * possibleStartDirections.length)] : 0;
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 simple fixed segments that always total exactly 3000 pixels
segmentLengths = [500, 500, 500, 500, 500, 500]; // 6 segments of 500 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 - more turns for longer paths
var baseMinTurns = Math.max(3, Math.floor(targetPathLength / 1000)); // At least 1 turn per 1000px
var maxTurns = Math.min(numSegments - 1, baseMinTurns + 2); // Cap maximum turns
var minTurns = Math.max(3, 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.85) {
// High chance of turning when we need more turns
shouldTurn = true;
} else if (turnCount >= minTurns && turnCount < maxTurns && seg > 0 && Math.random() < 0.6) {
// Medium chance for additional turns up to maximum
shouldTurn = true;
} else if (turnCount >= maxTurns && seg > 0 && Math.random() < 0.2) {
// Low chance after reaching maximum turns
shouldTurn = true;
}
if (shouldTurn && seg > 0) {
// Don't turn on first segment
// Change direction (90-degree turn)
var oldDirection = direction;
var possibleDirections = [];
// Calculate valid directions based on current position and safe boundaries
if (direction !== 2 && currentX + segmentLength <= maxX) possibleDirections.push(0); // right
if (direction !== 3 && currentY + segmentLength <= maxY) possibleDirections.push(1); // down
if (direction !== 0 && currentX - segmentLength >= minX) possibleDirections.push(2); // left
if (direction !== 1 && currentY - segmentLength >= minY) possibleDirections.push(3); // up
// 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
var safeDirections = [];
if (currentX + segmentLength <= maxX && direction !== 2) safeDirections.push(0); // right
if (currentY + segmentLength <= maxY && direction !== 3) safeDirections.push(1); // down
if (currentX - segmentLength >= minX && direction !== 0) safeDirections.push(2); // left
if (currentY - segmentLength >= minY && direction !== 1) safeDirections.push(3); // up
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 extra strict validation
var extraSafetyMargin = 250; // Additional margin for absolute safety
nextX = Math.max(minX + extraSafetyMargin, Math.min(maxX - extraSafetyMargin, nextX));
nextY = Math.max(minY + extraSafetyMargin, Math.min(maxY - extraSafetyMargin, nextY));
// Double-check boundaries are respected
if (nextX < minX + extraSafetyMargin || nextX > maxX - extraSafetyMargin || nextY < minY + extraSafetyMargin || nextY > maxY - extraSafetyMargin) {
// 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 general end area in later segments
if (seg > numSegments * 0.7) {
var targetEndX = maxX - 200; // Target area near right edge but within boundaries
if (nextX < targetEndX) {
nextX = Math.min(maxX, currentX + Math.abs(segmentLength));
direction = 0;
}
}
// 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 much larger safety margins
var largeSafetyMargin = 600; // Much larger safety margin to guarantee visibility
var finalX = Math.max(minX + largeSafetyMargin, Math.min(maxX - largeSafetyMargin, nextX));
var finalY = Math.max(minY + largeSafetyMargin, Math.min(maxY - largeSafetyMargin, nextY));
// Triple validation - ensure path point is completely within safe bounds with generous margins
var isWithinBounds = finalX >= minX + largeSafetyMargin && finalX <= maxX - largeSafetyMargin && finalY >= minY + largeSafetyMargin && finalY <= maxY - largeSafetyMargin;
// Additional check: ensure the point is not too close to screen edges
var distFromLeft = finalX - 0;
var distFromRight = screenWidth - finalX;
var distFromTop = finalY - 0;
var distFromBottom = screenHeight - finalY;
var minScreenDistance = 700; // Minimum distance from any screen edge
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(150, segmentLength * 0.7);
}
// After several attempts, try completely different directions
if (attempts > 10) {
var availableDirections = [];
// Check all directions for validity
if (currentWave === 1) {
// For Wave 1, use original segment length for direction checking
var checkLength = segmentLengths[seg];
if (currentX + checkLength <= maxX && direction !== 2) availableDirections.push(0); // right
if (currentY + checkLength <= maxY && direction !== 3) availableDirections.push(1); // down
if (currentX - checkLength >= minX && direction !== 0) availableDirections.push(2); // left
if (currentY - checkLength >= minY && direction !== 1) availableDirections.push(3); // up
} else {
if (currentX + 200 <= maxX && direction !== 2) availableDirections.push(0); // right
if (currentY + 200 <= maxY && direction !== 3) availableDirections.push(1); // down
if (currentX - 200 >= minX && direction !== 0) availableDirections.push(2); // left
if (currentY - 200 >= minY && direction !== 1) availableDirections.push(3); // up
}
if (availableDirections.length > 0) {
direction = availableDirections[Math.floor(Math.random() * availableDirections.length)];
if (currentWave !== 1) {
segmentLength = 200; // Reset to smaller safe length
}
}
}
}
}
if (!validSegmentFound) {
// Force a valid segment if we can't find one - try multiple directions
var fallbackFound = false;
var fallbackDirections = [0, 1, 2, 3]; // right, down, left, up
for (var fd = 0; fd < fallbackDirections.length && !fallbackFound; fd++) {
var fallbackDir = fallbackDirections[fd];
var fallbackLength = seg < segmentLengths.length ? segmentLengths[seg] : 200;
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 boundaries with extra safety margin
var safetyMargin = 500; // Additional safety margin for fallback
var safeFallbackX = Math.max(minX + safetyMargin, Math.min(maxX - safetyMargin, fallbackX));
var safeFallbackY = Math.max(minY + safetyMargin, Math.min(maxY - safetyMargin, fallbackY));
// Comprehensive fallback validation with strict boundary checking
var fallbackSafetyMargin = 650; // Even larger margin for fallback positions
var fallbackWithinBounds = safeFallbackX >= minX + fallbackSafetyMargin && safeFallbackX <= maxX - fallbackSafetyMargin && safeFallbackY >= minY + fallbackSafetyMargin && safeFallbackY <= maxY - fallbackSafetyMargin;
// Additional fallback validation: check distance from all screen edges
var fallbackDistFromLeft = safeFallbackX - 0;
var fallbackDistFromRight = screenWidth - safeFallbackX;
var fallbackDistFromTop = safeFallbackY - 0;
var fallbackDistFromBottom = screenHeight - safeFallbackY;
var minFallbackDistance = 750;
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 within boundaries
var baseSafetyMargin = 200;
var safeBaseX = Math.max(minX + baseSafetyMargin, Math.min(maxX - baseSafetyMargin, base.x));
var safeBaseY = Math.max(minY + baseSafetyMargin, Math.min(maxY - baseSafetyMargin, base.y));
// 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 bounds
intermediateX = Math.max(minX + baseSafetyMargin, Math.min(maxX - baseSafetyMargin, intermediateX));
intermediateY = Math.max(minY + baseSafetyMargin, Math.min(maxY - baseSafetyMargin, 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);
// 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();
}
};
// 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();
};