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 to use entire screen area while maintaining small safety margins
var screenWidth = 2048;
var screenHeight = 2732;
// Use small margins: 8% from all sides to use most of the screen while preventing edge clipping
var marginPercent = 0.08; // 8% margin from all sides for safety
var minX = Math.floor(screenWidth * marginPercent); // 8% from left (164 px)
var maxX = screenWidth - Math.floor(screenWidth * marginPercent); // 8% from right (1884 px)
var minY = Math.floor(screenHeight * marginPercent); // 8% from top (219 px)
var maxY = screenHeight - Math.floor(screenHeight * marginPercent); // 8% from bottom (2513 px)
// Set path length based on wave - Wave 1 is 3000px, others use previous wave length + 300px
var targetPathLength;
if (currentWave === 1) {
targetPathLength = 3000;
} else {
// For other waves, use the actual path length from previous wave plus 300px
var prevWaveLength = wavePathLengths.length >= currentWave - 1 ? wavePathLengths[currentWave - 2] : previousWavePathLength;
targetPathLength = prevWaveLength + 300;
// Special enforcement for Wave 6 to ensure it's exactly 3300 pixels (like Wave 1's 3000px requirement)
if (currentWave === 6) {
targetPathLength = 3300; // Wave 6 is always exactly 3300 pixels
}
}
// Function to calculate distance from point to line segment
function distanceToLineSegment(point, lineStart, lineEnd) {
var A = point.x - lineStart.x;
var B = point.y - lineStart.y;
var C = lineEnd.x - lineStart.x;
var D = lineEnd.y - lineStart.y;
var dot = A * C + B * D;
var lenSq = C * C + D * D;
if (lenSq === 0) return Math.sqrt(A * A + B * B);
var param = dot / lenSq;
var xx, yy;
if (param < 0) {
xx = lineStart.x;
yy = lineStart.y;
} else if (param > 1) {
xx = lineEnd.x;
yy = lineEnd.y;
} else {
xx = lineStart.x + param * C;
yy = lineStart.y + param * D;
}
var dx = point.x - xx;
var dy = point.y - yy;
return Math.sqrt(dx * dx + dy * dy);
}
// Function to check if two line segments intersect
function doLinesIntersect(p1, q1, p2, q2) {
function orientation(p, q, r) {
var val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
if (val === 0) return 0; // collinear
return val > 0 ? 1 : 2; // clockwise or counterclockwise
}
function onSegment(p, q, r) {
return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y);
}
var o1 = orientation(p1, q1, p2);
var o2 = orientation(p1, q1, q2);
var o3 = orientation(p2, q2, p1);
var o4 = orientation(p2, q2, q1);
// General case
if (o1 !== o2 && o3 !== o4) return true;
// Special cases
if (o1 === 0 && onSegment(p1, p2, q1)) return true;
if (o2 === 0 && onSegment(p1, q2, q1)) return true;
if (o3 === 0 && onSegment(p2, p1, q2)) return true;
if (o4 === 0 && onSegment(p2, q1, q2)) return true;
return false;
}
// Function to check if proposed segment would intersect or get too close to existing path
function wouldIntersectPath(newStart, newEnd, existingPath) {
// Strict minimum distance to prevent any crossing or touching
var minDistance = 350; // Increased minimum distance significantly
var minSafeDistance = 200; // Minimum safe distance for any part of path
// Primary check: Direct line intersection with ANY existing segment (except immediate connection)
for (var i = 0; i < existingPath.length - 1; i++) {
var segStart = existingPath[i];
var segEnd = existingPath[i + 1];
// Skip only the immediate previous segment that we're connecting from
if (i === existingPath.length - 2) continue;
// Check for any intersection between the new segment and existing segments
if (doLinesIntersect(newStart, newEnd, segStart, segEnd)) {
return true;
}
}
// Secondary check: Ensure new segment maintains safe distance from ALL existing segments
for (var i = 0; i < existingPath.length - 1; i++) {
var segStart = existingPath[i];
var segEnd = existingPath[i + 1];
// Skip only the immediate previous segment
if (i === existingPath.length - 2) continue;
// Calculate minimum distance between the new segment and existing segment
var dist1 = distanceToLineSegment(newStart, segStart, segEnd);
var dist2 = distanceToLineSegment(newEnd, segStart, segEnd);
var dist3 = distanceToLineSegment(segStart, newStart, newEnd);
var dist4 = distanceToLineSegment(segEnd, newStart, newEnd);
var minimumSegmentDistance = Math.min(dist1, dist2, dist3, dist4);
// Reject if any part of the segments get too close
if (minimumSegmentDistance < minSafeDistance) {
return true;
}
}
// Tertiary check: Ensure new endpoint doesn't get too close to any existing path points
for (var i = 0; i < existingPath.length; i++) {
var pathPoint = existingPath[i];
// Skip only the immediate previous point we're connecting from
if (i === existingPath.length - 1) continue;
// Calculate distance from new endpoint to existing path points
var distToEnd = Math.sqrt((newEnd.x - pathPoint.x) * (newEnd.x - pathPoint.x) + (newEnd.y - pathPoint.y) * (newEnd.y - pathPoint.y));
// Use increasingly strict distance requirements for older path points
var ageMultiplier = 1.0 + (existingPath.length - 1 - i) * 0.2; // Older points need more distance
var requiredDistance = minDistance * ageMultiplier;
if (distToEnd < requiredDistance) {
return true;
}
}
// Quaternary check: Prevent sharp reversals that could lead to future self-intersection
if (existingPath.length >= 2) {
var prevPoint = existingPath[existingPath.length - 1];
var prevPrevPoint = existingPath[existingPath.length - 2];
// Calculate direction vectors
var prevDirX = prevPoint.x - prevPrevPoint.x;
var prevDirY = prevPoint.y - prevPrevPoint.y;
var newDirX = newEnd.x - newStart.x;
var newDirY = newEnd.y - newStart.y;
// Calculate angle between directions using dot product
var dotProduct = prevDirX * newDirX + prevDirY * newDirY;
var prevLength = Math.sqrt(prevDirX * prevDirX + prevDirY * prevDirY);
var newLength = Math.sqrt(newDirX * newDirX + newDirY * newDirY);
if (prevLength > 0 && newLength > 0) {
var cosAngle = dotProduct / (prevLength * newLength);
// Prevent sharp reversals (angles greater than 140 degrees)
if (cosAngle < -0.64) {
// cos(140°) ≈ -0.64
return true;
}
}
}
// Final comprehensive check: Ensure the new segment doesn't create potential for future loops
// by checking if it would "enclose" any existing path segments
if (existingPath.length >= 3) {
// Check if the new segment would create a potential enclosed area with any non-adjacent path segments
for (var i = 0; i < existingPath.length - 3; i++) {
// Check against segments that are not adjacent
var oldSegStart = existingPath[i];
var oldSegEnd = existingPath[i + 1];
// Calculate if new segment and old segment could form an enclosed area
// Check if segments are oriented in a way that could trap path points between them
var cross1 = (newEnd.x - newStart.x) * (oldSegStart.y - newStart.y) - (newEnd.y - newStart.y) * (oldSegStart.x - newStart.x);
var cross2 = (newEnd.x - newStart.x) * (oldSegEnd.y - newStart.y) - (newEnd.y - newStart.y) * (oldSegEnd.x - newStart.x);
var cross3 = (oldSegEnd.x - oldSegStart.x) * (newStart.y - oldSegStart.y) - (oldSegEnd.y - oldSegStart.y) * (newStart.x - oldSegStart.x);
var cross4 = (oldSegEnd.x - oldSegStart.x) * (newEnd.y - oldSegStart.y) - (oldSegEnd.y - oldSegStart.y) * (newEnd.x - oldSegStart.x);
// If segments create opposing orientations, they might enclose an area - reject this
if (cross1 > 0 !== cross2 > 0 && cross3 > 0 !== cross4 > 0) {
return true;
}
}
}
return false;
}
// Create varied path shapes while maintaining characteristics
gamePath = [];
var baseSegmentLength = currentWave === 1 ? 300 : Math.floor(250 * pathExtensionFactor); // Shorter base segments for more curves
// For Wave 1, calculate exact segment lengths to total exactly 3000 pixels with more segments for better curves
var numSegments = currentWave === 1 ? 10 : Math.max(8, Math.floor(7 + currentWave * 0.6)); // More segments for better curving
var turnCount = 0; // Track number of turns
// Starting position - randomized across full usable screen area
var startX, startY;
// All waves get full randomization within the entire usable screen area
startX = minX + 50 + Math.random() * (maxX - minX - 100);
startY = minY + 50 + Math.random() * (maxY - minY - 100);
gamePath.push({
x: startX,
y: startY
});
var currentX = startX;
var currentY = startY;
// Balance all directions to create more varied paths across the full screen
var possibleStartDirections = [];
// Add all valid directions with balanced weighting
if (startY + 300 <= maxY) {
possibleStartDirections.push(1, 1); // down (double weight)
}
if (startY - 300 >= minY) {
possibleStartDirections.push(3, 3); // up (double weight)
}
// Add horizontal directions with good safety margins but allow full screen usage
if (startX + 300 <= maxX - 100) possibleStartDirections.push(0, 0); // right (double weight with reasonable margin)
if (startX - 300 >= minX + 100) possibleStartDirections.push(2, 2); // left (double weight with reasonable margin)
var direction = possibleStartDirections.length > 0 ? possibleStartDirections[Math.floor(Math.random() * possibleStartDirections.length)] : 1; // default to down
var totalPathLength = 0; // Track total path length built so far
var segmentLengths = []; // Pre-calculate segment lengths to total exactly 3000 pixels
// Calculate exact segment lengths to achieve target path length
if (currentWave === 1) {
// For Wave 1, use shorter segments that create better curves and total exactly 3000 pixels
segmentLengths = [300, 300, 300, 300, 300, 300, 300, 300, 300, 300]; // 10 segments of 300 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 = [];
// Prioritize vertical directions heavily over horizontal directions
var possibleDirections = [];
// Add all directions with balanced weighting to use full screen area
if (direction !== 3 && currentY + segmentLength <= maxY) {
possibleDirections.push(1, 1); // down (double weight)
}
if (direction !== 1 && currentY - segmentLength >= minY) {
possibleDirections.push(3, 3); // up (double weight)
}
// Add horizontal directions with reasonable margins to use full screen width
if (direction !== 2 && currentX + segmentLength <= maxX - 100) possibleDirections.push(0, 0); // right (double weight with reasonable margin)
if (direction !== 0 && currentX - segmentLength >= minX + 100) possibleDirections.push(2, 2); // left (double weight with reasonable margin)
// Remove the current direction to force an actual turn
var filteredDirections = [];
for (var d = 0; d < possibleDirections.length; d++) {
if (possibleDirections[d] !== direction) {
filteredDirections.push(possibleDirections[d]);
}
}
if (filteredDirections.length > 0) {
direction = filteredDirections[Math.floor(Math.random() * filteredDirections.length)];
turnCount++;
} else if (possibleDirections.length > 0) {
// Fallback to any valid direction if no turn is possible
direction = possibleDirections[Math.floor(Math.random() * possibleDirections.length)];
}
}
// Calculate next position based on direction with boundary checking
var nextX = currentX;
var nextY = currentY;
var wouldExceedBounds = false;
if (direction === 0) {
// right
nextX = currentX + segmentLength;
if (nextX > maxX) wouldExceedBounds = true;
} else if (direction === 1) {
// down
nextY = currentY + segmentLength;
if (nextY > maxY) wouldExceedBounds = true;
} else if (direction === 2) {
// left
nextX = currentX - segmentLength;
if (nextX < minX) wouldExceedBounds = true;
} else if (direction === 3) {
// up
nextY = currentY - segmentLength;
if (nextY < minY) wouldExceedBounds = true;
}
// If path would go off-screen, increment turn count instead
if (wouldExceedBounds) {
turnCount++;
// Try a different direction that stays within bounds, heavily prioritizing vertical movements
var safeDirections = [];
// Add vertical directions first with maximum priority (octuple weight)
if (currentY + segmentLength <= maxY && direction !== 3) {
safeDirections.push(1, 1, 1, 1, 1, 1, 1, 1); // down (octuple weight)
}
if (currentY - segmentLength >= minY && direction !== 1) {
safeDirections.push(3, 3, 3, 3, 3, 3, 3, 3); // up (octuple weight)
}
// Add horizontal directions only if they're extremely safe with enormous margins
if (currentX + segmentLength <= maxX - 600 && direction !== 2) safeDirections.push(0); // right (with enormous extra margin)
if (currentX - segmentLength >= minX + 600 && direction !== 0) safeDirections.push(2); // left (with enormous extra margin)
if (safeDirections.length > 0) {
direction = safeDirections[Math.floor(Math.random() * safeDirections.length)];
// Recalculate position with new direction
if (direction === 0) {
nextX = currentX + segmentLength;
} else if (direction === 1) {
nextY = currentY + segmentLength;
} else if (direction === 2) {
nextX = currentX - segmentLength;
} else if (direction === 3) {
nextY = currentY - segmentLength;
}
} else {
// No safe direction available, reduce segment length
segmentLength = Math.min(segmentLength, Math.min(maxX - currentX, currentX - minX, maxY - currentY, currentY - minY));
nextX = currentX + (direction === 0 ? segmentLength : direction === 2 ? -segmentLength : 0);
nextY = currentY + (direction === 1 ? segmentLength : direction === 3 ? -segmentLength : 0);
}
}
// Final boundary enforcement with the 35% margins - no additional margin needed
nextX = Math.max(minX, Math.min(maxX, nextX));
nextY = Math.max(minY, Math.min(maxY, nextY));
// Double-check boundaries are respected with the 35% base margins
if (nextX < minX || nextX > maxX || nextY < minY || nextY > maxY) {
// Force position to safe center area if still problematic
nextX = minX + (maxX - minX) * 0.5;
nextY = minY + (maxY - minY) * 0.5;
}
// Ensure we're moving towards the 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 the strict base margins
var finalX = Math.max(minX, Math.min(maxX, nextX));
var finalY = Math.max(minY, Math.min(maxY, nextY));
// Ensure path point is completely within the strict base bounds
var isWithinBounds = finalX >= minX && finalX <= maxX && finalY >= minY && finalY <= maxY;
// Additional validation: ensure minimum distance from actual screen edges using 8% margins
var distFromLeft = finalX - 0;
var distFromRight = screenWidth - finalX;
var distFromTop = finalY - 0;
var distFromBottom = screenHeight - finalY;
var minScreenDistance = Math.floor(screenWidth * marginPercent); // Use same 8% margin as boundary calculation
var isSafeFromEdges = distFromLeft >= minScreenDistance && distFromRight >= minScreenDistance && distFromTop >= minScreenDistance && distFromBottom >= minScreenDistance;
isWithinBounds = isWithinBounds && isSafeFromEdges;
if (isValidSegment && isWithinBounds) {
gamePath.push({
x: finalX,
y: finalY
});
currentX = finalX;
currentY = finalY;
validSegmentFound = true;
} else {
// Segment would intersect - try different approach
attempts++;
// Reduce segment length for intersection avoidance (except for pre-calculated lengths)
if (seg >= segmentLengths.length || currentWave === 1) {
// Keep original length for pre-calculated segments
} else {
segmentLength = Math.max(150, segmentLength * 0.7);
}
// After several attempts, try completely different directions
if (attempts > 10) {
var availableDirections = [];
// Check all directions for validity, prioritizing vertical movements
if (currentWave === 1) {
// For Wave 1, use original segment length for direction checking, prioritize vertical
var checkLength = segmentLengths[seg];
if (currentY + checkLength <= maxY && direction !== 3) availableDirections.push(1); // down (first priority)
if (currentY - checkLength >= minY && direction !== 1) availableDirections.push(3); // up (second priority)
if (currentX + checkLength <= maxX - 100 && direction !== 2) availableDirections.push(0); // right (with margin)
if (currentX - checkLength >= minX + 100 && direction !== 0) availableDirections.push(2); // left (with margin)
} else {
// Prioritize vertical directions for other waves too
if (currentY + 200 <= maxY && direction !== 3) availableDirections.push(1); // down (first priority)
if (currentY - 200 >= minY && direction !== 1) availableDirections.push(3); // up (second priority)
if (currentX + 200 <= maxX - 100 && direction !== 2) availableDirections.push(0); // right (with margin)
if (currentX - 200 >= minX + 100 && direction !== 0) availableDirections.push(2); // left (with margin)
}
if (availableDirections.length > 0) {
// Prefer vertical directions even in fallback
var verticalDirections = [];
var horizontalDirections = [];
for (var ad = 0; ad < availableDirections.length; ad++) {
if (availableDirections[ad] === 1 || availableDirections[ad] === 3) {
verticalDirections.push(availableDirections[ad]);
} else {
horizontalDirections.push(availableDirections[ad]);
}
}
// Choose vertical direction if available, otherwise horizontal
if (verticalDirections.length > 0) {
direction = verticalDirections[Math.floor(Math.random() * verticalDirections.length)];
} else {
direction = horizontalDirections[Math.floor(Math.random() * horizontalDirections.length)];
}
if (currentWave !== 1) {
segmentLength = 200; // Reset to smaller safe length
}
}
}
}
}
if (!validSegmentFound) {
// Force a valid segment if we can't find one - try multiple directions, prioritizing vertical
var fallbackFound = false;
var fallbackDirections = [1, 3, 0, 2]; // down, up, right, left - vertical first
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 the strict base boundaries
var safeFallbackX = Math.max(minX, Math.min(maxX, fallbackX));
var safeFallbackY = Math.max(minY, Math.min(maxY, fallbackY));
// Comprehensive fallback validation using the strict base boundaries
var fallbackWithinBounds = safeFallbackX >= minX && safeFallbackX <= maxX && safeFallbackY >= minY && safeFallbackY <= maxY;
// Additional fallback validation: check distance from all screen edges using consistent 8% margin
var fallbackDistFromLeft = safeFallbackX - 0;
var fallbackDistFromRight = screenWidth - safeFallbackX;
var fallbackDistFromTop = safeFallbackY - 0;
var fallbackDistFromBottom = screenHeight - safeFallbackY;
var minFallbackDistance = Math.floor(screenWidth * marginPercent); // Use consistent 8% margin
var fallbackSafeFromEdges = fallbackDistFromLeft >= minFallbackDistance && fallbackDistFromRight >= minFallbackDistance && fallbackDistFromTop >= minFallbackDistance && fallbackDistFromBottom >= minFallbackDistance;
fallbackWithinBounds = fallbackWithinBounds && fallbackSafeFromEdges;
if (fallbackWithinBounds && (gamePath.length < 2 || !wouldIntersectPath(fallbackStart, {
x: safeFallbackX,
y: safeFallbackY
}, gamePath))) {
gamePath.push({
x: safeFallbackX,
y: safeFallbackY
});
currentX = safeFallbackX;
currentY = safeFallbackY;
direction = fallbackDir;
fallbackFound = true;
}
}
// If still no valid segment found, break the loop to prevent infinite generation
if (!fallbackFound) {
break;
}
}
}
// Always ensure path connects to the base position as final endpoint
// Calculate safe base position within the strict boundaries
var safeBaseX = Math.max(minX, Math.min(maxX, base.x));
var safeBaseY = Math.max(minY, Math.min(maxY, 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 the strict base boundaries
intermediateX = Math.max(minX, Math.min(maxX, intermediateX));
intermediateY = Math.max(minY, Math.min(maxY, intermediateY));
// Check if intermediate point would intersect existing path
var intermediateStart = {
x: finalCurrentX,
y: finalCurrentY
};
var intermediateEnd = {
x: intermediateX,
y: intermediateY
};
if (gamePath.length < 2 || !wouldIntersectPath(intermediateStart, intermediateEnd, gamePath)) {
gamePath.push({
x: intermediateX,
y: intermediateY
});
finalCurrentX = intermediateX;
finalCurrentY = intermediateY;
}
}
}
// Always add base position as final endpoint
gamePath.push({
x: safeBaseX,
y: safeBaseY
});
// Redraw path tiles using circles for smoother appearance
for (var i = 0; i < gamePath.length; i++) {
var pathTile = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.75,
scaleY: 3.75
});
pathTile.x = gamePath[i].x;
pathTile.y = gamePath[i].y;
pathTile.alpha = 0.9;
pathTile.tint = 0x8B4513;
game.addChild(pathTile);
}
// Draw connecting segments with circles for smoother road
for (var i = 0; i < gamePath.length - 1; i++) {
var startPoint = gamePath[i];
var endPoint = gamePath[i + 1];
var dx = endPoint.x - startPoint.x;
var dy = endPoint.y - startPoint.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var segments = Math.ceil(distance / 20);
for (var j = 1; j < segments; j++) {
var t = j / segments;
var segmentX = startPoint.x + dx * t;
var segmentY = startPoint.y + dy * t;
var segmentTile = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.75,
scaleY: 3.75
});
segmentTile.x = segmentX;
segmentTile.y = segmentY;
segmentTile.alpha = 0.8;
segmentTile.tint = 0x8B4513;
game.addChild(segmentTile);
}
}
// Ensure gamePath has valid elements
if (!gamePath || gamePath.length === 0) {
// Fallback path if generation failed
gamePath = [{
x: 100,
y: 400
}, {
x: 1800,
y: 1600
}];
}
// Update base position to match final path endpoint exactly
if (base && gamePath.length > 0) {
// Set base position to exactly match the final path point
base.x = gamePath[gamePath.length - 1].x;
base.y = gamePath[gamePath.length - 1].y;
}
// Update baslangic position to new start position
if (baslangicImage) {
// Ensure baslangic stays within screen bounds with safety margin
var baslangicSafetyMargin = 100;
baslangicImage.x = Math.max(baslangicSafetyMargin, Math.min(2048 - baslangicSafetyMargin, gamePath[0].x));
baslangicImage.y = Math.max(baslangicSafetyMargin, Math.min(2732 - baslangicSafetyMargin, gamePath[0].y));
}
// Ensure base and baslangic stay in foreground
if (base) game.addChild(base);
if (baslangicImage) game.addChild(baslangicImage);
// 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();
}; ===================================================================
--- original.js
+++ change.js
@@ -411,13 +411,13 @@
/****
* Game Code
****/
-// Game variables
-// Tower assets
-// Enemy assets
-// Game elements
// Sounds
+// Game elements
+// Enemy assets
+// Tower assets
+// Game variables
var gamePath = [{
x: 100,
y: 400
}, {
@@ -1038,17 +1038,17 @@
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
+ // Calculate safe boundaries to use entire screen area while maintaining small safety margins
var screenWidth = 2048;
var screenHeight = 2732;
- // Use even stricter margins: 35% from all sides to absolutely guarantee complete visibility with generous spacing from edges
- var marginPercent = 0.35; // 35% margin from all sides
- var minX = Math.floor(screenWidth * marginPercent); // 35% from left (717 px)
- var maxX = screenWidth - Math.floor(screenWidth * marginPercent); // 35% from right (1331 px)
- var minY = Math.floor(screenHeight * marginPercent); // 35% from top (956 px)
- var maxY = screenHeight - Math.floor(screenHeight * marginPercent); // 35% from bottom (1776 px)
+ // Use small margins: 8% from all sides to use most of the screen while preventing edge clipping
+ var marginPercent = 0.08; // 8% margin from all sides for safety
+ var minX = Math.floor(screenWidth * marginPercent); // 8% from left (164 px)
+ var maxX = screenWidth - Math.floor(screenWidth * marginPercent); // 8% from right (1884 px)
+ var minY = Math.floor(screenHeight * marginPercent); // 8% from top (219 px)
+ var maxY = screenHeight - Math.floor(screenHeight * marginPercent); // 8% from bottom (2513 px)
// Set path length based on wave - Wave 1 is 3000px, others use previous wave length + 300px
var targetPathLength;
if (currentWave === 1) {
targetPathLength = 3000;
@@ -1205,37 +1205,31 @@
var baseSegmentLength = currentWave === 1 ? 300 : Math.floor(250 * pathExtensionFactor); // Shorter base segments for more curves
// For Wave 1, calculate exact segment lengths to total exactly 3000 pixels with more segments for better curves
var numSegments = currentWave === 1 ? 10 : Math.max(8, Math.floor(7 + currentWave * 0.6)); // More segments for better curving
var turnCount = 0; // Track number of turns
- // Starting position - more randomized for all waves within safe boundaries
+ // Starting position - randomized across full usable screen area
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);
- }
+ // All waves get full randomization within the entire usable screen area
+ startX = minX + 50 + Math.random() * (maxX - minX - 100);
+ startY = minY + 50 + Math.random() * (maxY - minY - 100);
gamePath.push({
x: startX,
y: startY
});
var currentX = startX;
var currentY = startY;
- // Prioritize vertical directions (up/down) over horizontal to prevent going off-screen
+ // Balance all directions to create more varied paths across the full screen
var possibleStartDirections = [];
- // Add vertical directions first with much higher weight (quintuple entries for maximum priority)
+ // Add all valid directions with balanced weighting
if (startY + 300 <= maxY) {
- possibleStartDirections.push(1, 1, 1, 1, 1); // down (quintuple weight)
+ possibleStartDirections.push(1, 1); // down (double weight)
}
if (startY - 300 >= minY) {
- possibleStartDirections.push(3, 3, 3, 3, 3); // up (quintuple weight)
+ possibleStartDirections.push(3, 3); // up (double weight)
}
- // Add horizontal directions only if they're extremely safe with very large margins
- if (startX + 300 <= maxX - 400) possibleStartDirections.push(0); // right (with very large extra margin)
- if (startX - 300 >= minX + 400) possibleStartDirections.push(2); // left (with very large extra margin)
+ // Add horizontal directions with good safety margins but allow full screen usage
+ if (startX + 300 <= maxX - 100) possibleStartDirections.push(0, 0); // right (double weight with reasonable margin)
+ if (startX - 300 >= minX + 100) possibleStartDirections.push(2, 2); // left (double weight with reasonable margin)
var direction = possibleStartDirections.length > 0 ? possibleStartDirections[Math.floor(Math.random() * possibleStartDirections.length)] : 1; // default to down
var totalPathLength = 0; // Track total path length built so far
var segmentLengths = []; // Pre-calculate segment lengths to total exactly 3000 pixels
// Calculate exact segment lengths to achieve target path length
@@ -1319,18 +1313,18 @@
var oldDirection = direction;
var possibleDirections = [];
// Prioritize vertical directions heavily over horizontal directions
var possibleDirections = [];
- // Add vertical directions first with maximum priority (sextuple weight)
+ // Add all directions with balanced weighting to use full screen area
if (direction !== 3 && currentY + segmentLength <= maxY) {
- possibleDirections.push(1, 1, 1, 1, 1, 1); // down (sextuple weight)
+ possibleDirections.push(1, 1); // down (double weight)
}
if (direction !== 1 && currentY - segmentLength >= minY) {
- possibleDirections.push(3, 3, 3, 3, 3, 3); // up (sextuple weight)
+ possibleDirections.push(3, 3); // up (double weight)
}
- // Add horizontal directions only if they're extremely safe with massive margins
- if (direction !== 2 && currentX + segmentLength <= maxX - 500) possibleDirections.push(0); // right (with massive extra margin)
- if (direction !== 0 && currentX - segmentLength >= minX + 500) possibleDirections.push(2); // left (with massive extra margin)
+ // Add horizontal directions with reasonable margins to use full screen width
+ if (direction !== 2 && currentX + segmentLength <= maxX - 100) possibleDirections.push(0, 0); // right (double weight with reasonable margin)
+ if (direction !== 0 && currentX - segmentLength >= minX + 100) possibleDirections.push(2, 2); // left (double weight with reasonable margin)
// Remove the current direction to force an actual turn
var filteredDirections = [];
for (var d = 0; d < possibleDirections.length; d++) {
if (possibleDirections[d] !== direction) {
@@ -1439,14 +1433,14 @@
var finalX = Math.max(minX, Math.min(maxX, nextX));
var finalY = Math.max(minY, Math.min(maxY, nextY));
// Ensure path point is completely within the strict base bounds
var isWithinBounds = finalX >= minX && finalX <= maxX && finalY >= minY && finalY <= maxY;
- // Additional validation: ensure minimum distance from actual screen edges using 35% margins
+ // Additional validation: ensure minimum distance from actual screen edges using 8% margins
var distFromLeft = finalX - 0;
var distFromRight = screenWidth - finalX;
var distFromTop = finalY - 0;
var distFromBottom = screenHeight - finalY;
- var minScreenDistance = Math.floor(screenWidth * marginPercent); // Use same 35% margin as boundary calculation
+ var minScreenDistance = Math.floor(screenWidth * marginPercent); // Use same 8% margin as boundary calculation
var isSafeFromEdges = distFromLeft >= minScreenDistance && distFromRight >= minScreenDistance && distFromTop >= minScreenDistance && distFromBottom >= minScreenDistance;
isWithinBounds = isWithinBounds && isSafeFromEdges;
if (isValidSegment && isWithinBounds) {
gamePath.push({
@@ -1544,14 +1538,14 @@
var safeFallbackX = Math.max(minX, Math.min(maxX, fallbackX));
var safeFallbackY = Math.max(minY, Math.min(maxY, fallbackY));
// Comprehensive fallback validation using the strict base boundaries
var fallbackWithinBounds = safeFallbackX >= minX && safeFallbackX <= maxX && safeFallbackY >= minY && safeFallbackY <= maxY;
- // Additional fallback validation: check distance from all screen edges using consistent 35% margin
+ // Additional fallback validation: check distance from all screen edges using consistent 8% margin
var fallbackDistFromLeft = safeFallbackX - 0;
var fallbackDistFromRight = screenWidth - safeFallbackX;
var fallbackDistFromTop = safeFallbackY - 0;
var fallbackDistFromBottom = screenHeight - safeFallbackY;
- var minFallbackDistance = Math.floor(screenWidth * marginPercent); // Use consistent 35% margin
+ var minFallbackDistance = Math.floor(screenWidth * marginPercent); // Use consistent 8% margin
var fallbackSafeFromEdges = fallbackDistFromLeft >= minFallbackDistance && fallbackDistFromRight >= minFallbackDistance && fallbackDistFromTop >= minFallbackDistance && fallbackDistFromBottom >= minFallbackDistance;
fallbackWithinBounds = fallbackWithinBounds && fallbackSafeFromEdges;
if (fallbackWithinBounds && (gamePath.length < 2 || !wouldIntersectPath(fallbackStart, {
x: safeFallbackX,