User prompt
yollar oluşturulurken mavi çerçevenin içi kullanılsın yol kendine değmesin uzasın kıvrılsın
User prompt
yol oluşturulurken aynı yol birbirine değmeyecek ve arkasından veya önünden geçmeyecek
User prompt
When creating paths, the path will not intersect itself.
User prompt
11. dalgadaki yolun uzunluğu 3600 px olsun. 16. dalgadaki yolun uzunluğu 4000 px olsun
User prompt
yolun oluşumu ve virajları hakkında kökten değişiklik yapmak istiyorum. yolu mavi çerçevenin dışına taşmayacak şekilde rastgele istediğin şekilde oluştur
User prompt
yolu oluştuturken yollar arasında en az 200px boşluk olması yeterlidir
User prompt
oyunda değişiklik yapmak istiyorum. yollar oluşturulurken dönüş sayısı istenilen kadar olabilir.
User prompt
yollar oluşturulurken dönüş sayısı sınırsız olabilir.
User prompt
I want to make changes to the game. When roads are created, they will be in a random direction within the frame that determines the area where the road will be created.
User prompt
I want to make changes to the game. When roads are created, they will be in a random direction within the frame that determines the area where the road will be created.
User prompt
oyunda değişiklik yapmak istiyorum. oluşturulan yollar daima bu mavi çerçevenin içerisinde olsun.
User prompt
oyunda değişiklik yapmak istiyorum. yol oluşurken başlangıç ve bitiş noktaları bu mavi çerçevenin içerisinde herhangi bir konumda olabilir. yol da ona göre herhangi bir şekilde oluşturulabilir
User prompt
oytunda düzenleme yapmak istiyorum. yolun başlangıç ve bitiş noktaları daima bu mavi çerçevenin içerisinde olsun.
User prompt
oyunda değişiklik yapmak istiyorum. oluşan yollar kesinlikle bu mavi çerçevenin içinde oluşsun
User prompt
oyunda değişiklik yapmak istiyorum. yol sadece bu mavi çerçevenin içine rastgele oluşsun.
User prompt
bu oluşturulan yol en önde olsun gözüksün
User prompt
oyunda değişiklik yapmak istiyorum. yol sadece bu mavi çerçevenin içine oluşsun. rastgele açılarda ve kıvrımlarda olabilir. bu çerçeve içerisinde rastgele oluşsun
User prompt
oyunda değişiklik yapmak istiyorum. yol uluşturulurken oluşturulan yol bu mavi çerçevenin içinde oluşsun hiç bir zaman için dışına taşmasın. ayrıca başlangıç ve bitiş noktalarıda bu çerçevenin içerisinde olsun
User prompt
yeni yol oluşturulurken yol hiçbir zaman bu mavi çerçeveye değmesin.
User prompt
kaleler sadece bu mavi çerçevenin içerisine yerleştirilebilsin
User prompt
oyunda değişiklik yapmak istiyorum. yolun oluşacağı alanın alttan boşluğu %5 arttırmak istiyorum. çerçeveyide ona göre güncelle
User prompt
yolun oluşacağı alanı belirleyen çerçevenin alt çizgisi 100px yukarıdan başlasın
User prompt
bu çerçevenin alt tarafı slow yazısının yukarısına gelsin
User prompt
yolun oluşacağı alanı belirleyen çerçevenin rengi mavi olsun
User prompt
oyun alanı çerçevesinin rengi mavi olsun
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Bullet = Container.expand(function (tower, target, towerType) {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.tower = tower;
self.target = target;
self.towerType = towerType;
self.speed = 8;
// Calculate direction from tower to target
var dx = target.x - tower.x;
var dy = target.y - tower.y;
var distance = Math.sqrt(dx * dx + dy * dy);
self.vx = dx / distance * self.speed;
self.vy = dy / distance * self.speed;
self.update = function () {
self.x += self.vx;
self.y += self.vy;
// Check collision with intended target only
if (self.target && self.target.parent && self.intersects(self.target)) {
self.hit();
return;
}
// Remove bullet if target is destroyed
if (!self.target || !self.target.parent) {
self.destroy();
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i] === self) {
bullets.splice(i, 1);
break;
}
}
return;
}
// Remove if off screen with stricter bounds to prevent visual issues
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.destroy();
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i] === self) {
bullets.splice(i, 1);
break;
}
}
}
};
self.hit = function () {
LK.getSound('hit').play();
if (self.towerType === 'splash') {
// Splash damage
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
if (enemy && enemy.parent) {
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.tower.splashRadius) {
var killed = enemy.takeDamage(self.tower.damage);
if (killed) {
coins += enemy.reward;
enemy.destroy();
enemies.splice(i, 1);
}
}
}
}
// Create explosion effect
var explosion = LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5
});
explosion.x = self.x;
explosion.y = self.y;
explosion.alpha = 0.7;
game.addChild(explosion);
tween(explosion, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
explosion.destroy();
}
});
} else {
// Regular damage
if (self.target && self.target.parent) {
var killed = self.target.takeDamage(self.tower.damage);
if (killed) {
coins += self.target.reward;
self.target.destroy();
var targetIndex = enemies.indexOf(self.target);
if (targetIndex !== -1) {
enemies.splice(targetIndex, 1);
}
}
// Apply slow effect if slow tower
if (self.towerType === 'slow' && !killed) {
self.target.applySlow(self.tower.slowFactor, self.tower.slowDuration);
}
}
}
// Remove bullet
self.destroy();
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i] === self) {
bullets.splice(i, 1);
break;
}
}
};
return self;
});
// Game variables
var Enemy = Container.expand(function (type, pathIndex) {
var self = Container.call(this);
// Enemy properties based on type
var enemyConfig = {
basic: {
asset: 'basicEnemy',
health: 100,
speed: 2,
reward: 10
},
fast: {
asset: 'fastEnemy',
health: 60,
speed: 4,
reward: 15
},
tank: {
asset: 'tankEnemy',
health: 300,
speed: 1,
reward: 25
},
boss: {
asset: 'tankEnemy',
health: 1000,
speed: 1.5,
reward: 100
}
};
var config = enemyConfig[type] || enemyConfig.basic;
var enemyGraphics = self.attachAsset(config.asset, {
anchorX: 0.5,
anchorY: 0.5
});
self.maxHealth = config.health;
self.health = config.health;
self.speed = config.speed;
self.reward = config.reward;
self.pathIndex = pathIndex || 0;
self.slowEffect = 1;
self.slowDuration = 0;
// Health bar
var healthBarBg = LK.getAsset('pathTile', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.1,
y: -40
});
healthBarBg.tint = 0x333333;
self.addChild(healthBarBg);
var healthBar = LK.getAsset('pathTile', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.1,
y: -40
});
healthBar.tint = 0x00FF00;
self.addChild(healthBar);
self.healthBar = healthBar;
self.takeDamage = function (damage) {
self.health -= damage;
var healthPercent = Math.max(0, self.health / self.maxHealth);
self.healthBar.scaleX = 0.6 * healthPercent;
// Health bar color changes
if (healthPercent > 0.6) {
self.healthBar.tint = 0x00FF00;
} else if (healthPercent > 0.3) {
self.healthBar.tint = 0xFFFF00;
} else {
self.healthBar.tint = 0xFF0000;
}
// Flash effect
tween(enemyGraphics, {
tint: 0xFF0000
}, {
duration: 100,
onFinish: function onFinish() {
tween(enemyGraphics, {
tint: 0xFFFFFF
}, {
duration: 100
});
}
});
return self.health <= 0;
};
self.applySlow = function (factor, duration) {
self.slowEffect = Math.min(self.slowEffect, factor);
self.slowDuration = Math.max(self.slowDuration, duration);
};
self.update = function () {
// Update slow effect
if (self.slowDuration > 0) {
self.slowDuration--;
if (self.slowDuration <= 0) {
self.slowEffect = 1;
}
}
// Move along path
if (self.pathIndex < gamePath.length - 1 && gamePath[self.pathIndex + 1]) {
var currentTarget = gamePath[self.pathIndex + 1];
var dx = currentTarget.x - self.x;
var dy = currentTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 20) {
self.pathIndex++;
if (self.pathIndex >= gamePath.length - 1) {
// Reached base
baseHealth--;
return;
}
} else {
var moveSpeed = self.speed * self.slowEffect;
var newX = self.x + dx / distance * moveSpeed;
var newY = self.y + dy / distance * moveSpeed;
// Ensure enemy stays within screen bounds with safety margin
var enemySafetyMargin = 50;
self.x = Math.max(enemySafetyMargin, Math.min(2048 - enemySafetyMargin, newX));
self.y = Math.max(enemySafetyMargin, Math.min(2732 - enemySafetyMargin, newY));
}
} else if (self.pathIndex >= gamePath.length - 1) {
// Reached base
baseHealth--;
return;
}
};
return self;
});
var Tower = Container.expand(function (type) {
var self = Container.call(this);
// Tower configurations
var towerConfig = {
basic: {
asset: 'basicTower',
damage: 25,
range: 180,
fireRate: 60,
cost: 100,
upgradeCost: 75
},
splash: {
asset: 'splashTower',
damage: 40,
range: 150,
fireRate: 90,
cost: 200,
upgradeCost: 150,
splashRadius: 80
},
slow: {
asset: 'slowTower',
damage: 15,
range: 200,
fireRate: 45,
cost: 150,
upgradeCost: 100,
slowFactor: 0.5,
slowDuration: 120
}
};
var config = towerConfig[type] || towerConfig.basic;
var towerGraphics = self.attachAsset(config.asset, {
anchorX: 0.5,
anchorY: 0.5
});
self.type = type;
self.damage = config.damage;
self.range = config.range;
self.fireRate = config.fireRate;
self.cost = config.cost;
self.upgradeCost = config.upgradeCost;
self.splashRadius = config.splashRadius;
self.slowFactor = config.slowFactor;
self.slowDuration = config.slowDuration;
self.lastShot = 0;
self.level = 1;
// Range indicator (hidden by default)
var rangeIndicator = LK.getAsset('pathTile', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: self.range / 50,
scaleY: self.range / 50
});
rangeIndicator.tint = 0x00FF00;
rangeIndicator.alpha = 0.2;
rangeIndicator.visible = false;
self.addChild(rangeIndicator);
self.rangeIndicator = rangeIndicator;
self.showRange = function () {
self.rangeIndicator.visible = true;
};
self.hideRange = function () {
self.rangeIndicator.visible = false;
};
self.canShoot = function () {
return LK.ticks - self.lastShot >= self.fireRate;
};
self.findTarget = function () {
var closestEnemy = null;
var closestDistance = self.range;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy && enemy.parent) {
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range && distance < closestDistance) {
closestEnemy = enemy;
closestDistance = distance;
}
}
}
return closestEnemy;
};
self.shoot = function (target) {
if (!self.canShoot()) return;
self.lastShot = LK.ticks;
// Create bullet
var bullet = new Bullet(self, target, self.type);
bullet.x = self.x;
bullet.y = self.y;
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
};
self.upgrade = function () {
if (coins >= self.upgradeCost) {
coins -= self.upgradeCost;
self.level++;
self.damage = Math.floor(self.damage * 1.5);
self.range = Math.floor(self.range * 1.1);
self.upgradeCost = Math.floor(self.upgradeCost * 1.8);
// Update global tower info stats
towerInfoStats[self.type].level = Math.max(towerInfoStats[self.type].level, self.level);
towerInfoStats[self.type].damage = Math.max(towerInfoStats[self.type].damage, self.damage);
towerInfoStats[self.type].upgradeCost = Math.max(towerInfoStats[self.type].upgradeCost, self.upgradeCost);
// Visual upgrade effect
tween(towerGraphics, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
tween(towerGraphics, {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
}
});
// Update range indicator
self.rangeIndicator.scaleX = self.range / 50;
self.rangeIndicator.scaleY = self.range / 50;
return true;
}
return false;
};
self.down = function (x, y, obj) {
selectedTower = self;
self.showRange();
};
self.update = function () {
// Find target in range
var target = self.findTarget();
if (target) {
// Rotate tower towards target
var angle = Math.atan2(target.y - self.y, target.x - self.x);
towerGraphics.rotation = angle;
// Shoot if ready
if (self.canShoot()) {
self.shoot(target);
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x4a6b3a
});
/****
* Game Code
****/
// Sounds
// Game elements
// Enemy assets
// Tower assets
// Game variables
var gamePath = [{
x: 100,
y: 400
}, {
x: 500,
y: 400
}, {
x: 500,
y: 800
}, {
x: 900,
y: 800
}, {
x: 900,
y: 1200
}, {
x: 1400,
y: 1200
}, {
x: 1400,
y: 1600
}, {
x: 1800,
y: 1600
}];
var enemies = [];
var towers = [];
var bullets = [];
var coins = 300;
var baseHealth = 100;
var currentWave = 1;
var enemiesSpawned = 0;
var enemiesPerWave = 5;
var waveDelay = 0;
var selectedTower = null;
var towerInfoPanel = null;
var bossWave = false;
var bossSpawned = false;
var pathExtensionFactor = 1.0;
var isInitialPath = true;
var wavePathLengths = storage.wavePathLengths || []; // Track path length for each wave - load from database
var previousWavePathLength = storage.previousWavePathLength || 3000; // Track path length from previous wave - load from database
// Initialize empty path - will be generated when wave starts
// Create base and baslangic placeholders - will be positioned when path is generated
var base = LK.getAsset('base', {
anchorX: 0.5,
anchorY: 0.5
});
base.x = 1800;
base.y = 1600;
game.addChild(base);
// Create baslangic image at spawn point
var baslangicImage = LK.getAsset('baslangic', {
anchorX: 0.5,
anchorY: 0.5
});
baslangicImage.x = 200;
baslangicImage.y = 1000;
game.addChild(baslangicImage);
// UI Elements
var coinsText = new Text2('Coins: ' + coins, {
size: 50,
fill: 0xFFD700
});
coinsText.anchor.set(0, 0);
coinsText.x = 150;
coinsText.y = 20;
LK.gui.topLeft.addChild(coinsText);
var healthText = new Text2('Base Health: ' + baseHealth, {
size: 50,
fill: 0xFF0000
});
healthText.anchor.set(1, 0);
healthText.y = 20;
LK.gui.topRight.addChild(healthText);
var waveText = new Text2('Wave: ' + currentWave, {
size: 50,
fill: 0xFFFFFF
});
waveText.anchor.set(0.5, 0);
waveText.y = 20;
LK.gui.top.addChild(waveText);
var pathLengthText = new Text2('Path Length: 0', {
size: 40,
fill: 0x00FFFF
});
pathLengthText.anchor.set(0.5, 0);
pathLengthText.y = 80;
LK.gui.top.addChild(pathLengthText);
// Wave selector dropdown
var waveDropdownBtn = new Text2('Select Wave ▼', {
size: 40,
fill: 0x00FF00
});
waveDropdownBtn.anchor.set(0.5, 0);
waveDropdownBtn.x = 0;
waveDropdownBtn.y = 170;
LK.gui.top.addChild(waveDropdownBtn);
// Dropdown container (initially hidden)
var dropdownContainer = new Container();
dropdownContainer.visible = false;
dropdownContainer.x = 0;
dropdownContainer.y = 220;
LK.gui.top.addChild(dropdownContainer);
// Dropdown background
var dropdownBg = LK.getAsset('pathTile', {
anchorX: 0.5,
anchorY: 0,
scaleX: 4,
scaleY: 8
});
dropdownBg.tint = 0x333333;
dropdownBg.alpha = 0.9;
dropdownContainer.addChild(dropdownBg);
// Wave selection buttons (create 20 wave options)
var waveButtons = [];
for (var i = 1; i <= 20; i++) {
var waveBtn = new Text2('Wave ' + i, {
size: 35,
fill: 0xFFFFFF
});
waveBtn.anchor.set(0.5, 0);
waveBtn.x = 0;
waveBtn.y = (i - 1) * 45 + 10;
waveBtn.waveNumber = i;
dropdownContainer.addChild(waveBtn);
waveButtons.push(waveBtn);
}
var dropdownVisible = false;
// Tower selection buttons
var basicTowerImage = LK.getAsset('basicTower', {
anchorX: 0.5,
anchorY: 1,
scaleX: 0.8,
scaleY: 0.8
});
basicTowerImage.x = 60;
basicTowerImage.y = -20;
LK.gui.bottomLeft.addChild(basicTowerImage);
var basicTowerBtn = new Text2('Basic (100)', {
size: 40,
fill: 0x4444FF
});
basicTowerBtn.anchor.set(0, 1);
basicTowerBtn.x = 100;
basicTowerBtn.y = -20;
LK.gui.bottomLeft.addChild(basicTowerBtn);
var basicTowerInfo = new Text2('Level: 1 | DMG: 25 | Upgrade: 75', {
size: 25,
fill: 0xFFFFFF
});
basicTowerInfo.anchor.set(0, 1);
basicTowerInfo.x = 350;
basicTowerInfo.y = -20;
LK.gui.bottomLeft.addChild(basicTowerInfo);
var splashTowerImage = LK.getAsset('splashTower', {
anchorX: 0.5,
anchorY: 1,
scaleX: 0.8,
scaleY: 0.8
});
splashTowerImage.x = 60;
splashTowerImage.y = -80;
LK.gui.bottomLeft.addChild(splashTowerImage);
var splashTowerBtn = new Text2('Splash (200)', {
size: 40,
fill: 0xFF4444
});
splashTowerBtn.anchor.set(0, 1);
splashTowerBtn.x = 100;
splashTowerBtn.y = -80;
LK.gui.bottomLeft.addChild(splashTowerBtn);
var splashTowerInfo = new Text2('Level: 1 | DMG: 40 | Upgrade: 150', {
size: 25,
fill: 0xFFFFFF
});
splashTowerInfo.anchor.set(0, 1);
splashTowerInfo.x = 350;
splashTowerInfo.y = -80;
LK.gui.bottomLeft.addChild(splashTowerInfo);
var slowTowerImage = LK.getAsset('slowTower', {
anchorX: 0.5,
anchorY: 1,
scaleX: 0.8,
scaleY: 0.8
});
slowTowerImage.x = 60;
slowTowerImage.y = -140;
LK.gui.bottomLeft.addChild(slowTowerImage);
var slowTowerBtn = new Text2('Slow (150)', {
size: 40,
fill: 0x44FF44
});
slowTowerBtn.anchor.set(0, 1);
slowTowerBtn.x = 100;
slowTowerBtn.y = -140;
LK.gui.bottomLeft.addChild(slowTowerBtn);
var slowTowerInfo = new Text2('Level: 1 | DMG: 15 | Upgrade: 100', {
size: 25,
fill: 0xFFFFFF
});
slowTowerInfo.anchor.set(0, 1);
slowTowerInfo.x = 350;
slowTowerInfo.y = -140;
LK.gui.bottomLeft.addChild(slowTowerInfo);
// Add levelatla upgrade buttons
var basicLevelatlaBtn = LK.getAsset('levelatla', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
basicLevelatlaBtn.x = 770;
basicLevelatlaBtn.y = -40;
LK.gui.bottomLeft.addChild(basicLevelatlaBtn);
var splashLevelatlaBtn = LK.getAsset('levelatla', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
splashLevelatlaBtn.x = 770;
splashLevelatlaBtn.y = -100;
LK.gui.bottomLeft.addChild(splashLevelatlaBtn);
var slowLevelatlaBtn = LK.getAsset('levelatla', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
slowLevelatlaBtn.x = 770;
slowLevelatlaBtn.y = -160;
LK.gui.bottomLeft.addChild(slowLevelatlaBtn);
// Selected tower type
var selectedTowerType = 'basic';
// Tower info tracking
var towerInfoStats = {
basic: {
level: 1,
damage: 25,
upgradeCost: 75
},
splash: {
level: 1,
damage: 40,
upgradeCost: 150
},
slow: {
level: 1,
damage: 15,
upgradeCost: 100
}
};
// Add background highlights for tower selection
var basicTowerBg = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0.5,
scaleX: 8.0,
scaleY: 0.8
});
basicTowerBg.tint = 0x00FF00;
basicTowerBg.alpha = 0.3;
basicTowerBg.x = 10;
basicTowerBg.y = -40;
LK.gui.bottomLeft.addChild(basicTowerBg);
var splashTowerBg = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0.5,
scaleX: 8.0,
scaleY: 0.8
});
splashTowerBg.tint = 0x00FF00;
splashTowerBg.alpha = 0;
splashTowerBg.x = 10;
splashTowerBg.y = -100;
LK.gui.bottomLeft.addChild(splashTowerBg);
var slowTowerBg = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0.5,
scaleX: 8.0,
scaleY: 0.8
});
slowTowerBg.tint = 0x00FF00;
slowTowerBg.alpha = 0;
slowTowerBg.x = 10;
slowTowerBg.y = -160;
LK.gui.bottomLeft.addChild(slowTowerBg);
// Helper functions
function calculatePathLength() {
if (!gamePath || gamePath.length < 2) return 0;
var totalLength = 0;
for (var i = 0; i < gamePath.length - 1; i++) {
var dx = gamePath[i + 1].x - gamePath[i].x;
var dy = gamePath[i + 1].y - gamePath[i].y;
totalLength += Math.sqrt(dx * dx + dy * dy);
}
return Math.round(totalLength);
}
function updateUI() {
coinsText.setText('Coins: ' + coins);
healthText.setText('Base Health: ' + baseHealth);
waveText.setText('Wave: ' + currentWave + (bossWave ? ' (BOSS)' : ''));
pathLengthText.setText('Path Length: ' + calculatePathLength() + ' px');
// "6th wave" text display removed per request
// Update button colors based on affordability
basicTowerBtn.tint = coins >= 100 ? 0x4444FF : 0x666666;
splashTowerBtn.tint = coins >= 200 ? 0xFF4444 : 0x666666;
slowTowerBtn.tint = coins >= 150 ? 0x44FF44 : 0x666666;
// Update tower info displays
basicTowerInfo.setText('Level: ' + towerInfoStats.basic.level + ' | DMG: ' + towerInfoStats.basic.damage + ' | Upgrade: ' + towerInfoStats.basic.upgradeCost);
splashTowerInfo.setText('Level: ' + towerInfoStats.splash.level + ' | DMG: ' + towerInfoStats.splash.damage + ' | Upgrade: ' + towerInfoStats.splash.upgradeCost);
slowTowerInfo.setText('Level: ' + towerInfoStats.slow.level + ' | DMG: ' + towerInfoStats.slow.damage + ' | Upgrade: ' + towerInfoStats.slow.upgradeCost);
// Update tower selection highlighting
basicTowerBg.alpha = selectedTowerType === 'basic' ? 0.3 : 0;
splashTowerBg.alpha = selectedTowerType === 'splash' ? 0.3 : 0;
slowTowerBg.alpha = selectedTowerType === 'slow' ? 0.3 : 0;
// Add pulsing animation to selected tower
var selectedBg = selectedTowerType === 'basic' ? basicTowerBg : selectedTowerType === 'splash' ? splashTowerBg : slowTowerBg;
if (selectedBg.alpha > 0) {
var pulseAlpha = 0.3 + Math.sin(LK.ticks * 0.1) * 0.1;
selectedBg.alpha = pulseAlpha;
}
// Show/hide levelatla buttons based on available coins
basicLevelatlaBtn.visible = coins >= towerInfoStats.basic.upgradeCost;
splashLevelatlaBtn.visible = coins >= towerInfoStats.splash.upgradeCost;
slowLevelatlaBtn.visible = coins >= towerInfoStats.slow.upgradeCost;
}
function canPlaceTower(x, y) {
// First check if position is within the blue frame boundaries
var screenWidth = 2048;
var screenHeight = 2732;
var marginPercent = 0.08; // 8% margin from sides and top
var bottomMarginPercent = 0.13; // 13% margin from bottom (8% + 5% increase)
var pathMinX = Math.floor(screenWidth * marginPercent); // Blue frame left boundary
var pathMaxX = screenWidth - Math.floor(screenWidth * marginPercent); // Blue frame right boundary
var pathMinY = Math.floor(screenHeight * marginPercent); // Blue frame top boundary
var pathMaxY = screenHeight - Math.floor(screenHeight * bottomMarginPercent); // Blue frame bottom boundary
// Check if tower position is within blue frame boundaries
if (x < pathMinX || x > pathMaxX || y < pathMinY || y > pathMaxY) {
return false; // Outside blue frame area
}
// Check if position is not on path points
for (var i = 0; i < gamePath.length; i++) {
var pathPoint = gamePath[i];
var distance = Math.sqrt((x - pathPoint.x) * (x - pathPoint.x) + (y - pathPoint.y) * (y - pathPoint.y));
if (distance < 80) {
return false;
}
}
// Check if position is not on path segments
for (var i = 0; i < gamePath.length - 1; i++) {
var startPoint = gamePath[i];
var endPoint = gamePath[i + 1];
// Calculate distance from point to line segment
var A = x - startPoint.x;
var B = y - startPoint.y;
var C = endPoint.x - startPoint.x;
var D = endPoint.y - startPoint.y;
var dot = A * C + B * D;
var lenSq = C * C + D * D;
var param = lenSq != 0 ? dot / lenSq : -1;
var xx, yy;
if (param < 0) {
xx = startPoint.x;
yy = startPoint.y;
} else if (param > 1) {
xx = endPoint.x;
yy = endPoint.y;
} else {
xx = startPoint.x + param * C;
yy = startPoint.y + param * D;
}
var dx = x - xx;
var dy = y - yy;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 80) {
return false;
}
}
// Check if position is not too close to other towers
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var distance = Math.sqrt((x - tower.x) * (x - tower.x) + (y - tower.y) * (y - tower.y));
if (distance < 100) {
return false;
}
}
return true;
}
function spawnEnemy() {
var enemyTypes = ['basic', 'fast', 'tank'];
var waveMultiplier = Math.floor((currentWave - 1) / 3);
// Choose enemy type based on wave
var enemyType = 'basic';
if (currentWave > 3) {
enemyType = enemyTypes[Math.floor(Math.random() * 2)]; // basic or fast
}
if (currentWave > 6) {
enemyType = enemyTypes[Math.floor(Math.random() * 3)]; // all types
}
var enemy = new Enemy(enemyType, 0);
enemy.x = gamePath[0].x;
enemy.y = gamePath[0].y;
// Scale health for later waves
enemy.maxHealth *= 1 + waveMultiplier * 0.5;
enemy.health = enemy.maxHealth;
enemies.push(enemy);
game.addChild(enemy);
enemiesSpawned++;
}
function generateNewPath() {
// Clear existing path tiles
for (var i = 0; i < game.children.length; i++) {
var child = game.children[i];
if (child.tint === 0x8B4513) {
child.destroy();
i--;
}
}
// Use blue frame boundaries for path generation (same as tower placement restrictions)
var screenWidth = 2048;
var screenHeight = 2732;
var marginPercent = 0.08; // 8% margin from sides and top
var bottomMarginPercent = 0.13; // 13% margin from bottom (8% + 5% increase)
var minX = Math.floor(screenWidth * marginPercent); // Blue frame left boundary
var maxX = screenWidth - Math.floor(screenWidth * marginPercent); // Blue frame right boundary
var minY = Math.floor(screenHeight * marginPercent); // Blue frame top boundary
var maxY = screenHeight - Math.floor(screenHeight * bottomMarginPercent); // Blue frame bottom boundary
// Set path length based on wave - 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
}
}
// New completely random path generation system
gamePath = [];
// Random starting position anywhere within blue frame
var startX = minX + 100 + Math.random() * (maxX - minX - 200);
var startY = minY + 100 + Math.random() * (maxY - minY - 200);
gamePath.push({
x: startX,
y: startY
});
var currentX = startX;
var currentY = startY;
var totalLength = 0;
// Generate random path segments until we reach target length
while (totalLength < targetPathLength - 200) {
// Random direction: 0=right, 1=down, 2=left, 3=up
var direction = Math.floor(Math.random() * 4);
// Random segment length between 150-400 pixels
var segmentLength = 150 + Math.random() * 250;
// Make sure we don't exceed remaining target length
var remainingLength = targetPathLength - totalLength;
if (segmentLength > remainingLength - 100) {
segmentLength = Math.max(100, remainingLength - 100);
}
var nextX = currentX;
var nextY = currentY;
// Calculate next position based on random direction
if (direction === 0) {
// right
nextX = currentX + segmentLength;
} else if (direction === 1) {
// down
nextY = currentY + segmentLength;
} else if (direction === 2) {
// left
nextX = currentX - segmentLength;
} else if (direction === 3) {
// up
nextY = currentY - segmentLength;
}
// Ensure path stays within blue frame boundaries
nextX = Math.max(minX + 100, Math.min(maxX - 100, nextX));
nextY = Math.max(minY + 100, Math.min(maxY - 100, nextY));
// Add the new point to path
gamePath.push({
x: nextX,
y: nextY
});
// Update current position and total length
var segmentDx = nextX - currentX;
var segmentDy = nextY - currentY;
var actualSegmentLength = Math.sqrt(segmentDx * segmentDx + segmentDy * segmentDy);
totalLength += actualSegmentLength;
currentX = nextX;
currentY = nextY;
// Safety check to prevent infinite loops
if (gamePath.length > 50) break;
}
// Add final segment to reach target length if needed
if (totalLength < targetPathLength - 50) {
var finalDirection = Math.floor(Math.random() * 4);
var finalLength = targetPathLength - totalLength;
var finalX = currentX;
var finalY = currentY;
if (finalDirection === 0) {
finalX = Math.min(maxX - 100, currentX + finalLength);
} else if (finalDirection === 1) {
finalY = Math.min(maxY - 100, currentY + finalLength);
} else if (finalDirection === 2) {
finalX = Math.max(minX + 100, currentX - finalLength);
} else {
finalY = Math.max(minY + 100, currentY - finalLength);
}
gamePath.push({
x: finalX,
y: finalY
});
}
// Ensure gamePath has valid elements
if (!gamePath || gamePath.length === 0) {
// Fallback path if generation failed
gamePath = [{
x: 100,
y: 400
}, {
x: 1800,
y: 1600
}];
}
// Draw path tiles using circles for smoother appearance
for (var i = 0; i < gamePath.length; i++) {
var pathTile = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.75,
scaleY: 3.75
});
pathTile.x = gamePath[i].x;
pathTile.y = gamePath[i].y;
pathTile.alpha = 0.9;
pathTile.tint = 0x8B4513;
game.addChild(pathTile);
}
// Draw connecting segments with circles for smoother road
for (var i = 0; i < gamePath.length - 1; i++) {
var startPoint = gamePath[i];
var endPoint = gamePath[i + 1];
var dx = endPoint.x - startPoint.x;
var dy = endPoint.y - startPoint.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var segments = Math.ceil(distance / 20);
for (var j = 1; j < segments; j++) {
var t = j / segments;
var segmentX = startPoint.x + dx * t;
var segmentY = startPoint.y + dy * t;
var segmentTile = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.75,
scaleY: 3.75
});
segmentTile.x = segmentX;
segmentTile.y = segmentY;
segmentTile.alpha = 0.8;
segmentTile.tint = 0x8B4513;
game.addChild(segmentTile);
}
}
// Update base position to match final path endpoint exactly
if (base && gamePath.length > 0) {
// Set base position to exactly match the final path point
base.x = gamePath[gamePath.length - 1].x;
base.y = gamePath[gamePath.length - 1].y;
}
// Update baslangic position to new start position
if (baslangicImage) {
// Ensure baslangic stays within screen bounds with safety margin
var baslangicSafetyMargin = 100;
baslangicImage.x = Math.max(baslangicSafetyMargin, Math.min(2048 - baslangicSafetyMargin, gamePath[0].x));
baslangicImage.y = Math.max(baslangicSafetyMargin, Math.min(2732 - baslangicSafetyMargin, gamePath[0].y));
}
// Ensure base and baslangic stay in foreground
if (base) game.addChild(base);
if (baslangicImage) game.addChild(baslangicImage);
// Create path area border frame (10px thickness, blue color, in foreground)
// Calculate path area boundaries with 8% margin from sides/top, 13% margin from bottom (same as path generation)
var pathAreaMinX = Math.floor(screenWidth * marginPercent); // 8% from left
var pathAreaMaxX = screenWidth - Math.floor(screenWidth * marginPercent); // 8% from right
var pathAreaMinY = Math.floor(screenHeight * marginPercent); // 8% from top
var pathAreaMaxY = screenHeight - Math.floor(screenHeight * bottomMarginPercent); // 13% from bottom
// Top path area border
var pathAreaTopBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: (pathAreaMaxX - pathAreaMinX) / 100,
scaleY: 0.1
});
pathAreaTopBorder.x = pathAreaMinX;
pathAreaTopBorder.y = pathAreaMinY;
pathAreaTopBorder.tint = 0x0000FF;
game.addChild(pathAreaTopBorder);
// Bottom path area border
var pathAreaBottomBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: (pathAreaMaxX - pathAreaMinX) / 100,
scaleY: 0.1
});
pathAreaBottomBorder.x = pathAreaMinX;
pathAreaBottomBorder.y = pathAreaMaxY - 10;
pathAreaBottomBorder.tint = 0x0000FF;
game.addChild(pathAreaBottomBorder);
// Left path area border
var pathAreaLeftBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: 0.1,
scaleY: (pathAreaMaxY - pathAreaMinY) / 100
});
pathAreaLeftBorder.x = pathAreaMinX;
pathAreaLeftBorder.y = pathAreaMinY;
pathAreaLeftBorder.tint = 0x0000FF;
game.addChild(pathAreaLeftBorder);
// Right path area border
var pathAreaRightBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: 0.1,
scaleY: (pathAreaMaxY - pathAreaMinY) / 100
});
pathAreaRightBorder.x = pathAreaMaxX - 10;
pathAreaRightBorder.y = pathAreaMinY;
pathAreaRightBorder.tint = 0x0000FF;
game.addChild(pathAreaRightBorder);
// Update previous wave path length for waves after Wave 1
if (currentWave > 1) {
var actualPathLength = calculatePathLength();
if (actualPathLength > 0) {
previousWavePathLength = actualPathLength;
wavePathLengths[currentWave - 1] = actualPathLength; // Store current wave length
// Save to database
storage.previousWavePathLength = previousWavePathLength;
storage.wavePathLengths = wavePathLengths;
}
}
}
function startNextWave() {
// Generate new path when wave starts
generateNewPath();
// Ensure gamePath has valid elements after generation
if (!gamePath || gamePath.length === 0) {
// Fallback path if generation failed
gamePath = [{
x: 100,
y: 400
}, {
x: 1800,
y: 1600
}];
}
// Update base position to match path endpoint exactly
if (base && gamePath.length > 0) {
// Set base position to exactly match the final path point
base.x = gamePath[gamePath.length - 1].x;
base.y = gamePath[gamePath.length - 1].y;
}
if (baslangicImage) {
// Ensure baslangic stays within screen bounds with safety margin
var baslangicSafetyMargin = 100;
baslangicImage.x = Math.max(baslangicSafetyMargin, Math.min(2048 - baslangicSafetyMargin, gamePath[0].x));
baslangicImage.y = Math.max(baslangicSafetyMargin, Math.min(2732 - baslangicSafetyMargin, gamePath[0].y));
}
}
function clearAllTowers() {
for (var i = towers.length - 1; i >= 0; i--) {
towers[i].destroy();
}
towers = [];
}
function spawnBossEnemy() {
var waveMultiplier = Math.floor((currentWave - 1) / 3);
var totalEnemyHealth = 0;
var enemyCount = enemiesPerWave * currentWave;
// Calculate total health of all enemies that would spawn in wave 5
for (var i = 0; i < enemyCount; i++) {
var baseHealth = 100; // Basic enemy health
if (currentWave > 3) baseHealth = 80; // Mix of basic and fast
if (currentWave > 6) baseHealth = 120; // Mix of all types
totalEnemyHealth += baseHealth * (1 + waveMultiplier * 0.5);
}
// Reduce boss health by 90% (keep only 10% of original health)
var bossHealth = Math.floor(totalEnemyHealth * 0.1);
var boss = new Enemy('boss', 0);
boss.x = gamePath[0].x;
boss.y = gamePath[0].y;
boss.maxHealth = bossHealth;
boss.health = bossHealth;
boss.reward = 200;
enemies.push(boss);
game.addChild(boss);
bossSpawned = true;
}
function showTowerInfo(tower) {
if (!tower) return;
if (towerInfoPanel) {
towerInfoPanel.destroy();
}
var panel = LK.getAsset('base', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 1.5
});
panel.tint = 0x333333;
panel.alpha = 0.8;
// Ensure panel stays completely within screen bounds
var panelWidth = 120 * 2; // base width * scaleX
var panelHeight = 120 * 1.5; // base height * scaleY
var panelMargin = 50;
panel.x = Math.max(panelWidth / 2 + panelMargin, Math.min(2048 - panelWidth / 2 - panelMargin, tower.x));
panel.y = Math.max(panelHeight / 2 + panelMargin, Math.min(2732 - panelHeight / 2 - panelMargin, Math.max(150, tower.y - 150)));
game.addChild(panel);
towerInfoPanel = panel;
var infoText = new Text2('Level: ' + tower.level + '\nDamage: ' + tower.damage + '\nUpgrade: ' + tower.upgradeCost, {
size: 25,
fill: 0xFFFFFF
});
infoText.anchor.set(0.5, 0.5);
infoText.x = 0;
infoText.y = -25;
panel.addChild(infoText);
if (coins >= tower.upgradeCost) {
var upgradeBtn = new Text2('UPGRADE', {
size: 30,
fill: 0x00FF00
});
upgradeBtn.anchor.set(0.5, 0.5);
upgradeBtn.y = 35;
panel.addChild(upgradeBtn);
}
}
// Dropdown toggle handler
waveDropdownBtn.down = function () {
dropdownVisible = !dropdownVisible;
dropdownContainer.visible = dropdownVisible;
waveDropdownBtn.setText(dropdownVisible ? 'Select Wave ▲' : 'Select Wave ▼');
};
// Wave button handlers
for (var i = 0; i < waveButtons.length; i++) {
waveButtons[i].down = function (waveNum) {
return function () {
// Jump to selected wave
jumpToWave(waveNum);
// Hide dropdown
dropdownVisible = false;
dropdownContainer.visible = false;
waveDropdownBtn.setText('Select Wave ▼');
};
}(waveButtons[i].waveNumber);
}
// Function to jump to specific wave
function jumpToWave(targetWave) {
if (targetWave < 1) targetWave = 1;
// Clear current enemies and bullets
for (var i = enemies.length - 1; i >= 0; i--) {
enemies[i].destroy();
}
enemies = [];
for (var i = bullets.length - 1; i >= 0; i--) {
bullets[i].destroy();
}
bullets = [];
// Clear all towers
clearAllTowers();
// Set wave variables
currentWave = targetWave;
enemiesSpawned = 0;
bossWave = currentWave % 5 === 0;
bossSpawned = false;
waveDelay = 60;
// Reset base health and give appropriate coins for the wave
baseHealth = 100;
coins = 300 + (targetWave - 1) * 100; // Give coins based on wave
// Update path lengths array for target wave
while (wavePathLengths.length < targetWave) {
var baseLength = wavePathLengths.length > 0 ? wavePathLengths[wavePathLengths.length - 1] : 3000;
wavePathLengths.push(baseLength + 300);
}
if (targetWave > 1) {
previousWavePathLength = wavePathLengths[targetWave - 2];
} else {
previousWavePathLength = 3000;
}
// Save to database
storage.wavePathLengths = wavePathLengths;
storage.previousWavePathLength = previousWavePathLength;
// Generate new path for target wave
startNextWave();
}
// Event handlers
basicTowerBtn.down = function () {
selectedTowerType = 'basic';
if (selectedTower) {
selectedTower.hideRange();
selectedTower = null;
}
};
splashTowerBtn.down = function () {
selectedTowerType = 'splash';
if (selectedTower) {
selectedTower.hideRange();
selectedTower = null;
}
};
slowTowerBtn.down = function () {
selectedTowerType = 'slow';
if (selectedTower) {
selectedTower.hideRange();
selectedTower = null;
}
};
// Add click handlers for levelatla upgrade buttons
basicLevelatlaBtn.down = function () {
if (coins >= towerInfoStats.basic.upgradeCost) {
coins -= towerInfoStats.basic.upgradeCost;
towerInfoStats.basic.level++;
towerInfoStats.basic.damage = Math.floor(towerInfoStats.basic.damage * 1.5);
towerInfoStats.basic.upgradeCost = Math.floor(towerInfoStats.basic.upgradeCost * 1.8);
}
};
splashLevelatlaBtn.down = function () {
if (coins >= towerInfoStats.splash.upgradeCost) {
coins -= towerInfoStats.splash.upgradeCost;
towerInfoStats.splash.level++;
towerInfoStats.splash.damage = Math.floor(towerInfoStats.splash.damage * 1.5);
towerInfoStats.splash.upgradeCost = Math.floor(towerInfoStats.splash.upgradeCost * 1.8);
}
};
slowLevelatlaBtn.down = function () {
if (coins >= towerInfoStats.slow.upgradeCost) {
coins -= towerInfoStats.slow.upgradeCost;
towerInfoStats.slow.level++;
towerInfoStats.slow.damage = Math.floor(towerInfoStats.slow.damage * 1.5);
towerInfoStats.slow.upgradeCost = Math.floor(towerInfoStats.slow.upgradeCost * 1.8);
}
};
game.down = function (x, y, obj) {
// Hide dropdown if clicking on game area
if (dropdownVisible) {
dropdownVisible = false;
dropdownContainer.visible = false;
waveDropdownBtn.setText('Select Wave ▼');
}
// Hide tower range if clicking elsewhere
if (selectedTower) {
selectedTower.hideRange();
selectedTower = null;
}
// Try to place tower
var towerCost = selectedTowerType === 'basic' ? 100 : selectedTowerType === 'splash' ? 200 : 150;
if (coins >= towerCost && canPlaceTower(x, y)) {
var tower = new Tower(selectedTowerType);
tower.x = x;
tower.y = y;
towers.push(tower);
game.addChild(tower);
coins -= towerCost;
// Update tower info stats
towerInfoStats[selectedTowerType].level = Math.max(towerInfoStats[selectedTowerType].level, tower.level);
towerInfoStats[selectedTowerType].damage = Math.max(towerInfoStats[selectedTowerType].damage, tower.damage);
towerInfoStats[selectedTowerType].upgradeCost = Math.max(towerInfoStats[selectedTowerType].upgradeCost, tower.upgradeCost);
LK.getSound('place').play();
}
};
// Create black border frame around the game area (10px thickness)
// Top border
var topBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: 20.48,
scaleY: 0.1
});
topBorder.x = 0;
topBorder.y = 0;
topBorder.tint = 0x000000;
game.addChild(topBorder);
// Bottom border
var bottomBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: 20.48,
scaleY: 0.1
});
bottomBorder.x = 0;
bottomBorder.y = 2722;
bottomBorder.tint = 0x000000;
game.addChild(bottomBorder);
// Left border
var leftBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: 0.1,
scaleY: 27.32
});
leftBorder.x = 0;
leftBorder.y = 0;
leftBorder.tint = 0x000000;
game.addChild(leftBorder);
// Right border
var rightBorder = LK.getAsset('pathTile', {
anchorX: 0,
anchorY: 0,
scaleX: 0.1,
scaleY: 27.32
});
rightBorder.x = 2038;
rightBorder.y = 0;
rightBorder.tint = 0x000000;
game.addChild(rightBorder);
// Start 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
@@ -835,274 +835,8 @@
child.destroy();
i--;
}
}
- // For Wave 1, Wave 6, Wave 11, and Wave 16, generate varied paths with strict boundary validation
- if (currentWave === 1 || currentWave === 6 || currentWave === 11 || currentWave === 16) {
- // Pre-validate that special waves use safe boundaries
- var specialWaveMinX = Math.floor(screenWidth * 0.25); // 25% margin for special waves
- var specialWaveMaxX = screenWidth - Math.floor(screenWidth * 0.25); // 25% margin
- var specialWaveMinY = Math.floor(screenHeight * 0.35); // 35% margin for special waves
- var specialWaveMaxY = screenHeight - Math.floor(screenHeight * 0.35); // 35% margin
- // Generate multiple path variations for Wave 1, Wave 6, and Wave 11
- var pathVariations = [
- // Variation 1: Zigzag pattern
- [{
- x: 150,
- y: 800
- }, {
- x: 600,
- y: 800
- }, {
- x: 600,
- y: 1300
- }, {
- x: 1050,
- y: 1300
- }, {
- x: 1050,
- y: 800
- }, {
- x: 1500,
- y: 800
- }, {
- x: 1500,
- y: 1300
- }, {
- x: 1800,
- y: 1300
- }],
- // Variation 2: L-shape pattern
- [{
- x: 200,
- y: 1200
- }, {
- x: 800,
- y: 1200
- }, {
- x: 800,
- y: 600
- }, {
- x: 1400,
- y: 600
- }, {
- x: 1400,
- y: 1000
- }, {
- x: 1700,
- y: 1000
- }, {
- x: 1700,
- y: 1400
- }],
- // Variation 3: S-curve pattern
- [{
- x: 250,
- y: 900
- }, {
- x: 650,
- y: 900
- }, {
- x: 650,
- y: 1400
- }, {
- x: 1150,
- y: 1400
- }, {
- x: 1150,
- y: 700
- }, {
- x: 1650,
- y: 700
- }, {
- x: 1650,
- y: 1200
- }],
- // Variation 4: Step pattern
- [{
- x: 180,
- y: 1100
- }, {
- x: 530,
- y: 1100
- }, {
- x: 530,
- y: 800
- }, {
- x: 880,
- y: 800
- }, {
- x: 880,
- y: 1500
- }, {
- x: 1230,
- y: 1500
- }, {
- x: 1230,
- y: 1000
- }, {
- x: 1580,
- y: 1000
- }, {
- x: 1750,
- y: 1000
- }]];
- // Select a random variation
- var selectedVariation = pathVariations[Math.floor(Math.random() * pathVariations.length)];
- // Scale the selected variation to exactly 3000 pixels for Wave 1, 3300 pixels for Wave 6, 3600 pixels for Wave 11, or 4000 pixels for Wave 16
- var targetLength = currentWave === 1 ? 3000 : currentWave === 6 ? 3300 : currentWave === 11 ? 3600 : 4000;
- var originalLength = 0;
- for (var i = 0; i < selectedVariation.length - 1; i++) {
- var dx = selectedVariation[i + 1].x - selectedVariation[i].x;
- var dy = selectedVariation[i + 1].y - selectedVariation[i].y;
- originalLength += Math.sqrt(dx * dx + dy * dy);
- }
- // Apply scaling factor to reach exactly target length
- var scaleFactor = targetLength / originalLength;
- gamePath = [];
- for (var i = 0; i < selectedVariation.length; i++) {
- if (i === 0) {
- // Keep first point as-is
- gamePath.push({
- x: selectedVariation[i].x,
- y: selectedVariation[i].y
- });
- } else {
- // Scale subsequent segments
- var prevPoint = gamePath[i - 1];
- var originalDx = selectedVariation[i].x - selectedVariation[i - 1].x;
- var originalDy = selectedVariation[i].y - selectedVariation[i - 1].y;
- var scaledDx = originalDx * scaleFactor;
- var scaledDy = originalDy * scaleFactor;
- gamePath.push({
- x: prevPoint.x + scaledDx,
- y: prevPoint.y + scaledDy
- });
- }
- }
- // Draw path tiles for Wave 1, Wave 6, Wave 11, and Wave 16
- for (var i = 0; i < gamePath.length; i++) {
- var pathTile = LK.getAsset('bullet', {
- anchorX: 0.5,
- anchorY: 0.5,
- scaleX: 3.75,
- scaleY: 3.75
- });
- pathTile.x = gamePath[i].x;
- pathTile.y = gamePath[i].y;
- pathTile.alpha = 0.9;
- pathTile.tint = 0x8B4513;
- game.addChild(pathTile);
- }
- // Draw connecting segments for Wave 1, Wave 6, Wave 11, and Wave 16
- for (var i = 0; i < gamePath.length - 1; i++) {
- var startPoint = gamePath[i];
- var endPoint = gamePath[i + 1];
- var dx = endPoint.x - startPoint.x;
- var dy = endPoint.y - startPoint.y;
- var distance = Math.sqrt(dx * dx + dy * dy);
- var segments = Math.ceil(distance / 20);
- for (var j = 1; j < segments; j++) {
- var t = j / segments;
- var segmentX = startPoint.x + dx * t;
- var segmentY = startPoint.y + dy * t;
- var segmentTile = LK.getAsset('bullet', {
- anchorX: 0.5,
- anchorY: 0.5,
- scaleX: 3.75,
- scaleY: 3.75
- });
- segmentTile.x = segmentX;
- segmentTile.y = segmentY;
- segmentTile.alpha = 0.8;
- segmentTile.tint = 0x8B4513;
- game.addChild(segmentTile);
- }
- }
- // Update base position to match path endpoint exactly for special waves
- if (base && gamePath.length > 0) {
- // Set base position to exactly match the final path point
- base.x = gamePath[gamePath.length - 1].x;
- base.y = gamePath[gamePath.length - 1].y;
- }
- if (baslangicImage) {
- // Ensure baslangic stays within screen bounds with safety margin
- var baslangicSafetyMargin = 100;
- baslangicImage.x = Math.max(baslangicSafetyMargin, Math.min(2048 - baslangicSafetyMargin, gamePath[0].x));
- baslangicImage.y = Math.max(baslangicSafetyMargin, Math.min(2732 - baslangicSafetyMargin, gamePath[0].y));
- }
- // Ensure base and baslangic stay in foreground
- if (base) game.addChild(base);
- if (baslangicImage) game.addChild(baslangicImage);
- // Create path area border frame (10px thickness, blue color, in foreground)
- // Calculate path area boundaries with 8% margin from sides/top, 13% margin from bottom
- var pathMinX = Math.floor(2048 * 0.08); // 8% margin from left
- var pathMaxX = 2048 - Math.floor(2048 * 0.08); // 8% margin from right
- var pathMinY = Math.floor(2732 * 0.08); // 8% margin from top
- var pathMaxY = 2732 - Math.floor(2732 * 0.13); // 13% margin from bottom (8% + 5% increase)
- // Top path border
- var pathTopBorder = LK.getAsset('pathTile', {
- anchorX: 0,
- anchorY: 0,
- scaleX: (pathMaxX - pathMinX) / 100,
- scaleY: 0.1
- });
- pathTopBorder.x = pathMinX;
- pathTopBorder.y = pathMinY;
- pathTopBorder.tint = 0x0000FF;
- game.addChild(pathTopBorder);
- // Bottom path border
- var pathBottomBorder = LK.getAsset('pathTile', {
- anchorX: 0,
- anchorY: 0,
- scaleX: (pathMaxX - pathMinX) / 100,
- scaleY: 0.1
- });
- pathBottomBorder.x = pathMinX;
- pathBottomBorder.y = pathMaxY - 10;
- pathBottomBorder.tint = 0x0000FF;
- game.addChild(pathBottomBorder);
- // Left path border
- var pathLeftBorder = LK.getAsset('pathTile', {
- anchorX: 0,
- anchorY: 0,
- scaleX: 0.1,
- scaleY: (pathMaxY - pathMinY) / 100
- });
- pathLeftBorder.x = pathMinX;
- pathLeftBorder.y = pathMinY;
- pathLeftBorder.tint = 0x0000FF;
- game.addChild(pathLeftBorder);
- // Right path border
- var pathRightBorder = LK.getAsset('pathTile', {
- anchorX: 0,
- anchorY: 0,
- scaleX: 0.1,
- scaleY: (pathMaxY - pathMinY) / 100
- });
- pathRightBorder.x = pathMaxX - 10;
- pathRightBorder.y = pathMinY;
- pathRightBorder.tint = 0x0000FF;
- game.addChild(pathRightBorder);
- // Update previous wave path length for Wave 1, Wave 6, Wave 11, and Wave 16
- var actualPathLength = calculatePathLength();
- if (actualPathLength > 0) {
- previousWavePathLength = actualPathLength;
- wavePathLengths[currentWave - 1] = actualPathLength; // Store current wave length
- // Save to database
- storage.previousWavePathLength = previousWavePathLength;
- storage.wavePathLengths = wavePathLengths;
- } else {
- // Fallback to expected length
- var fallbackLength = currentWave === 1 ? 3000 : currentWave === 6 ? 3300 : currentWave === 11 ? 3600 : 4000;
- previousWavePathLength = fallbackLength;
- wavePathLengths[currentWave - 1] = fallbackLength;
- // Save to database
- storage.previousWavePathLength = previousWavePathLength;
- storage.wavePathLengths = wavePathLengths;
- }
- return; // Exit early for Wave 1, Wave 6, Wave 11, and Wave 16
- }
// Use blue frame boundaries for path generation (same as tower placement restrictions)
var screenWidth = 2048;
var screenHeight = 2732;
var marginPercent = 0.08; // 8% margin from sides and top
@@ -1123,572 +857,97 @@
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 = 200; // Minimum distance between roads
- 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
+ // New completely random path generation system
gamePath = [];
- var baseSegmentLength = currentWave === 1 ? 200 : Math.floor(180 * pathExtensionFactor); // Much shorter base segments for more frequent turns
- // For Wave 1, calculate exact segment lengths to total exactly 3000 pixels with more segments for better curves
- var numSegments = currentWave === 1 ? 15 : Math.max(12, Math.floor(10 + currentWave * 0.8)); // More segments for much better curving
- var turnCount = 0; // Track number of turns
- // Starting position - randomized across top portion of screen, strictly within blue frame
- var startX, startY;
- // All waves start from top portion of screen with adequate margin from blue frame edges
- startX = minX + 100 + Math.random() * (maxX - minX - 200); // 100px margin from blue frame left/right edges
- startY = minY + 100 + Math.random() * 150; // Start in top portion with 100px margin from blue frame top
+ // Random starting position anywhere within blue frame
+ var startX = minX + 100 + Math.random() * (maxX - minX - 200);
+ var startY = minY + 100 + Math.random() * (maxY - minY - 200);
gamePath.push({
x: startX,
y: startY
});
var currentX = startX;
var currentY = startY;
- // Prioritize downward movement to create top-to-bottom flow
- var possibleStartDirections = [];
- // Heavily favor downward movement
- if (startY + 300 <= maxY) {
- possibleStartDirections.push(1, 1, 1, 1, 1, 1); // down (6x weight)
- }
- // Minimal upward movement only if needed
- if (startY - 300 >= minY) {
- possibleStartDirections.push(3); // up (single weight)
- }
- // Limited horizontal directions with reduced priority
- if (startX + 300 <= maxX - 100) possibleStartDirections.push(0); // right (single weight)
- if (startX - 300 >= minX + 100) possibleStartDirections.push(2); // left (single weight)
- var direction = possibleStartDirections.length > 0 ? possibleStartDirections[Math.floor(Math.random() * possibleStartDirections.length)] : 1; // default to down
- var totalPathLength = 0; // Track total path length built so far
- var segmentLengths = []; // Pre-calculate segment lengths to total exactly 3000 pixels
- // Calculate exact segment lengths to achieve target path length
- if (currentWave === 1) {
- // For Wave 1, use much shorter segments that create better curves and total exactly 3000 pixels
- segmentLengths = [200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200]; // 15 segments of 200 pixels each = 3000 total
- } else {
- // For subsequent waves, calculate segments to achieve exact target length with randomization
- var baseSegmentLength = Math.floor(targetPathLength / numSegments);
- var remainder = targetPathLength % numSegments;
- segmentLengths = [];
- // Create randomized segment lengths while maintaining total target length
- var totalAllocated = 0;
- for (var i = 0; i < numSegments; i++) {
- if (i === numSegments - 1) {
- // Last segment gets remaining length to ensure exact total
- segmentLengths.push(targetPathLength - totalAllocated);
- } else {
- // Add randomization: ±30% variation from base length
- var minLength = Math.max(200, Math.floor(baseSegmentLength * 0.7));
- var maxLength = Math.floor(baseSegmentLength * 1.3);
- var randomLength = minLength + Math.floor(Math.random() * (maxLength - minLength + 1));
- // Add remainder distribution
- randomLength += i < remainder ? 1 : 0;
- // Ensure we don't exceed target length with remaining segments
- var remainingSegments = numSegments - i - 1;
- var maxPossibleLength = targetPathLength - totalAllocated - remainingSegments * 200;
- randomLength = Math.min(randomLength, maxPossibleLength);
- segmentLengths.push(randomLength);
- totalAllocated += randomLength;
- }
+ var totalLength = 0;
+ // Generate random path segments until we reach target length
+ while (totalLength < targetPathLength - 200) {
+ // Random direction: 0=right, 1=down, 2=left, 3=up
+ var direction = Math.floor(Math.random() * 4);
+ // Random segment length between 150-400 pixels
+ var segmentLength = 150 + Math.random() * 250;
+ // Make sure we don't exceed remaining target length
+ var remainingLength = targetPathLength - totalLength;
+ if (segmentLength > remainingLength - 100) {
+ segmentLength = Math.max(100, remainingLength - 100);
}
- }
- // Generate varied path with random directional changes - unlimited turns allowed
- // Remove turn count limitations to allow any number of turns
- var mandatoryTurnSegments = []; // No mandatory turn segments needed
- 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;
- // Allow unlimited turns - turn whenever desired without restrictions
- if (forceTurn) {
- shouldTurn = true;
- } else if (seg > 0 && Math.random() < 0.7) {
- // 70% chance of turning at any segment (allows for very dynamic paths)
- shouldTurn = true;
- }
- if (shouldTurn && seg > 0) {
- // Don't turn on first segment
- // Change direction (90-degree turn)
- var oldDirection = direction;
- var possibleDirections = [];
- // Create random directional priorities for varied path flow
- var possibleDirections = [];
- // Randomize direction weights for more varied paths
- var randomDirectionWeights = {
- down: 2 + Math.floor(Math.random() * 4),
- // 2-5 weight
- up: 1 + Math.floor(Math.random() * 3),
- // 1-3 weight
- right: 1 + Math.floor(Math.random() * 4),
- // 1-4 weight
- left: 1 + Math.floor(Math.random() * 4) // 1-4 weight
- };
- // Apply random weights to each direction
- if (direction !== 3 && currentY + segmentLength <= maxY) {
- for (var dw = 0; dw < randomDirectionWeights.down; dw++) {
- possibleDirections.push(1); // down with random weight
- }
- }
- if (direction !== 1 && currentY - segmentLength >= minY) {
- for (var uw = 0; uw < randomDirectionWeights.up; uw++) {
- possibleDirections.push(3); // up with random weight
- }
- }
- if (direction !== 2 && currentX + segmentLength <= maxX - 100) {
- for (var rw = 0; rw < randomDirectionWeights.right; rw++) {
- possibleDirections.push(0); // right with random weight
- }
- }
- if (direction !== 0 && currentX - segmentLength >= minX + 100) {
- for (var lw = 0; lw < randomDirectionWeights.left; lw++) {
- possibleDirections.push(2); // left with random weight
- }
- }
- // Remove the current direction to force an actual turn
- var filteredDirections = [];
- for (var d = 0; d < possibleDirections.length; d++) {
- if (possibleDirections[d] !== direction) {
- filteredDirections.push(possibleDirections[d]);
- }
- }
- if (filteredDirections.length > 0) {
- direction = filteredDirections[Math.floor(Math.random() * filteredDirections.length)];
- turnCount++;
- } else if (possibleDirections.length > 0) {
- // Fallback to any valid direction if no turn is possible
- direction = possibleDirections[Math.floor(Math.random() * possibleDirections.length)];
- }
- }
- // Calculate next position based on direction with boundary checking
- var nextX = currentX;
- var nextY = currentY;
- var wouldExceedBounds = false;
- if (direction === 0) {
- // right
- nextX = currentX + segmentLength;
- if (nextX > maxX) wouldExceedBounds = true;
- } else if (direction === 1) {
- // down
- nextY = currentY + segmentLength;
- if (nextY > maxY) wouldExceedBounds = true;
- } else if (direction === 2) {
- // left
- nextX = currentX - segmentLength;
- if (nextX < minX) wouldExceedBounds = true;
- } else if (direction === 3) {
- // up
- nextY = currentY - segmentLength;
- if (nextY < minY) wouldExceedBounds = true;
- }
- // If path would go off-screen, increment turn count instead
- if (wouldExceedBounds) {
- turnCount++;
- // Try a different direction that stays within bounds, heavily prioritizing downward movement
- var safeDirections = [];
- // Heavily prioritize downward movement for top-to-bottom flow
- if (currentY + segmentLength <= maxY && direction !== 3) {
- safeDirections.push(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1); // down (12x weight)
- }
- // Minimal upward movement only in bottom portion of screen
- if (currentY - segmentLength >= minY && direction !== 1 && currentY > maxY * 0.6) {
- safeDirections.push(3, 3); // up (double weight, only in bottom 40% of screen)
- }
- // Limited horizontal directions to maintain vertical flow
- if (currentX + segmentLength <= maxX - 300 && direction !== 2) safeDirections.push(0); // right (with margin)
- if (currentX - segmentLength >= minX + 300 && direction !== 0) safeDirections.push(2); // left (with margin)
- if (safeDirections.length > 0) {
- direction = safeDirections[Math.floor(Math.random() * safeDirections.length)];
- // Recalculate position with new direction
- if (direction === 0) {
- nextX = currentX + segmentLength;
- } else if (direction === 1) {
- nextY = currentY + segmentLength;
- } else if (direction === 2) {
- nextX = currentX - segmentLength;
- } else if (direction === 3) {
- nextY = currentY - segmentLength;
- }
- } else {
- // No safe direction available, reduce segment length
- segmentLength = Math.min(segmentLength, Math.min(maxX - currentX, currentX - minX, maxY - currentY, currentY - minY));
- nextX = currentX + (direction === 0 ? segmentLength : direction === 2 ? -segmentLength : 0);
- nextY = currentY + (direction === 1 ? segmentLength : direction === 3 ? -segmentLength : 0);
- }
- }
- // Final boundary enforcement with strict blue frame boundaries
- nextX = Math.max(minX + 50, Math.min(maxX - 50, nextX)); // Add 50px safety margin from blue frame edges
- nextY = Math.max(minY + 50, Math.min(maxY - 50, nextY)); // Add 50px safety margin from blue frame edges
- // Double-check boundaries are respected with strict validation
- if (nextX < minX + 50 || nextX > maxX - 50 || nextY < minY + 50 || nextY > maxY - 50) {
- // Force position to safe area well within blue frame if still problematic
- nextX = minX + Math.max(100, (maxX - minX) * 0.1) + Math.random() * (maxX - minX - 200);
- nextY = minY + Math.max(100, (maxY - minY) * 0.1) + Math.random() * (maxY - minY - 200);
- }
- // Allow random movement in all directions throughout the path generation
- // Remove forced downward bias to enable varied directional flow
- if (seg > numSegments * 0.8) {
- // Only ensure we stay within bounds near the end, but allow any direction
- nextX = Math.max(minX + 100, Math.min(maxX - 100, nextX));
- nextY = Math.max(minY + 100, Math.min(maxY - 100, nextY));
- }
- // Check if this segment would intersect existing path
- var proposedStart = {
- x: currentX,
- y: currentY
- };
- var proposedEnd = {
- x: nextX,
- y: nextY
- };
- // Check intersection with much stricter criteria
- var isValidSegment = false;
- if (gamePath.length < 2) {
- // First few segments are always safe
- isValidSegment = true;
- } else {
- // For subsequent segments, apply strict intersection checking
- isValidSegment = !wouldIntersectPath(proposedStart, proposedEnd, gamePath);
- }
- // Comprehensive boundary validation with the strict base margins
- var finalX = Math.max(minX, Math.min(maxX, nextX));
- var finalY = Math.max(minY, Math.min(maxY, nextY));
- // Ensure path point is completely within the strict base bounds
- var isWithinBounds = finalX >= minX && finalX <= maxX && finalY >= minY && finalY <= maxY;
- // Additional validation: ensure minimum distance from actual screen edges using 8% margins
- var distFromLeft = finalX - 0;
- var distFromRight = screenWidth - finalX;
- var distFromTop = finalY - 0;
- var distFromBottom = screenHeight - finalY;
- var minScreenDistance = Math.floor(screenWidth * marginPercent); // Use same 8% margin as boundary calculation
- var isSafeFromEdges = distFromLeft >= minScreenDistance && distFromRight >= minScreenDistance && distFromTop >= minScreenDistance && distFromBottom >= minScreenDistance;
- isWithinBounds = isWithinBounds && isSafeFromEdges;
- if (isValidSegment && isWithinBounds) {
- gamePath.push({
- x: finalX,
- y: finalY
- });
- currentX = finalX;
- currentY = finalY;
- validSegmentFound = true;
- } else {
- // Segment would intersect - try different approach
- attempts++;
- // Reduce segment length for intersection avoidance (except for pre-calculated lengths)
- if (seg >= segmentLengths.length || currentWave === 1) {
- // Keep original length for pre-calculated segments
- } else {
- segmentLength = Math.max(120, segmentLength * 0.6); // Shorter fallback segments for tighter turns
- }
- // After several attempts, try completely different directions
- if (attempts > 10) {
- var availableDirections = [];
- // Check all directions for validity, prioritizing vertical movements
- if (currentWave === 1) {
- // For Wave 1, use original segment length for direction checking, prioritize vertical
- var checkLength = segmentLengths[seg];
- if (currentY + checkLength <= maxY && direction !== 3) availableDirections.push(1); // down (first priority)
- if (currentY - checkLength >= minY && direction !== 1) availableDirections.push(3); // up (second priority)
- if (currentX + checkLength <= maxX - 100 && direction !== 2) availableDirections.push(0); // right (with margin)
- if (currentX - checkLength >= minX + 100 && direction !== 0) availableDirections.push(2); // left (with margin)
- } else {
- // Prioritize vertical directions for other waves too with shorter check distances
- if (currentY + 150 <= maxY && direction !== 3) availableDirections.push(1); // down (first priority, shorter check)
- if (currentY - 150 >= minY && direction !== 1) availableDirections.push(3); // up (second priority, shorter check)
- if (currentX + 150 <= maxX - 100 && direction !== 2) availableDirections.push(0); // right (with margin, shorter check)
- if (currentX - 150 >= minX + 100 && direction !== 0) availableDirections.push(2); // left (with margin, shorter check)
- }
- if (availableDirections.length > 0) {
- // Prefer vertical directions even in fallback
- var verticalDirections = [];
- var horizontalDirections = [];
- for (var ad = 0; ad < availableDirections.length; ad++) {
- if (availableDirections[ad] === 1 || availableDirections[ad] === 3) {
- verticalDirections.push(availableDirections[ad]);
- } else {
- horizontalDirections.push(availableDirections[ad]);
- }
- }
- // Choose vertical direction if available, otherwise horizontal
- if (verticalDirections.length > 0) {
- direction = verticalDirections[Math.floor(Math.random() * verticalDirections.length)];
- } else {
- direction = horizontalDirections[Math.floor(Math.random() * horizontalDirections.length)];
- }
- if (currentWave !== 1) {
- segmentLength = 150; // Reset to much smaller safe length for tighter turns
- }
- }
- }
- }
+ var nextX = currentX;
+ var nextY = currentY;
+ // Calculate next position based on random direction
+ if (direction === 0) {
+ // right
+ nextX = currentX + segmentLength;
+ } else if (direction === 1) {
+ // down
+ nextY = currentY + segmentLength;
+ } else if (direction === 2) {
+ // left
+ nextX = currentX - segmentLength;
+ } else if (direction === 3) {
+ // up
+ nextY = currentY - segmentLength;
}
- if (!validSegmentFound) {
- // Force a valid segment if we can't find one - try multiple directions, prioritizing vertical
- var fallbackFound = false;
- // Randomize fallback direction order for varied paths
- var fallbackDirections = [0, 1, 2, 3]; // all directions
- // Shuffle the fallback directions randomly
- for (var shuffle = 0; shuffle < fallbackDirections.length; shuffle++) {
- var randomIndex = Math.floor(Math.random() * fallbackDirections.length);
- var temp = fallbackDirections[shuffle];
- fallbackDirections[shuffle] = fallbackDirections[randomIndex];
- fallbackDirections[randomIndex] = temp;
- }
- for (var fd = 0; fd < fallbackDirections.length && !fallbackFound; fd++) {
- var fallbackDir = fallbackDirections[fd];
- var fallbackLength = seg < segmentLengths.length ? segmentLengths[seg] : 150; // Shorter fallback for tighter curves
- var fallbackX = currentX;
- var fallbackY = currentY;
- if (fallbackDir === 0 && currentX + fallbackLength <= maxX) {
- // right
- fallbackX = currentX + fallbackLength;
- } else if (fallbackDir === 1 && currentY + fallbackLength <= maxY) {
- // down
- fallbackY = currentY + fallbackLength;
- } else if (fallbackDir === 2 && currentX - fallbackLength >= minX) {
- // left
- fallbackX = currentX - fallbackLength;
- } else if (fallbackDir === 3 && currentY - fallbackLength >= minY) {
- // up
- fallbackY = currentY - fallbackLength;
- } else {
- continue; // Try next direction
- }
- var fallbackStart = {
- x: currentX,
- y: currentY
- };
- var fallbackEnd = {
- x: fallbackX,
- y: fallbackY
- };
- // Check if this fallback direction works
- // Ensure fallback position is strictly within blue frame boundaries with safety margin
- var safeFallbackX = Math.max(minX + 60, Math.min(maxX - 60, fallbackX)); // 60px margin from blue frame
- var safeFallbackY = Math.max(minY + 60, Math.min(maxY - 60, fallbackY)); // 60px margin from blue frame
- // Comprehensive fallback validation using strict blue frame boundaries with margin
- var fallbackWithinBounds = safeFallbackX >= minX + 60 && safeFallbackX <= maxX - 60 && safeFallbackY >= minY + 60 && safeFallbackY <= maxY - 60;
- // 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;
- }
- }
+ // Ensure path stays within blue frame boundaries
+ nextX = Math.max(minX + 100, Math.min(maxX - 100, nextX));
+ nextY = Math.max(minY + 100, Math.min(maxY - 100, nextY));
+ // Add the new point to path
+ gamePath.push({
+ x: nextX,
+ y: nextY
+ });
+ // Update current position and total length
+ var segmentDx = nextX - currentX;
+ var segmentDy = nextY - currentY;
+ var actualSegmentLength = Math.sqrt(segmentDx * segmentDx + segmentDy * segmentDy);
+ totalLength += actualSegmentLength;
+ currentX = nextX;
+ currentY = nextY;
+ // Safety check to prevent infinite loops
+ if (gamePath.length > 50) break;
}
- // Always ensure path connects to the base position as final endpoint
- // Calculate safe base position in bottom portion of screen for top-to-bottom flow, strictly within blue frame
- var safeBaseX = minX + 100 + Math.random() * (maxX - minX - 200); // Random X position with 100px margin from blue frame edges
- var safeBaseY = maxY - 300 + Math.random() * 150; // Bottom portion of screen with margin from blue frame bottom
- safeBaseX = Math.max(minX + 100, Math.min(maxX - 100, safeBaseX)); // Enforce 100px margin from blue frame
- safeBaseY = Math.max(minY + 100, Math.min(maxY - 100, safeBaseY)); // Enforce 100px margin from blue frame
- // 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 strictly within blue frame boundaries with safety margin
- intermediateX = Math.max(minX + 75, Math.min(maxX - 75, intermediateX)); // 75px margin from blue frame edges
- intermediateY = Math.max(minY + 75, Math.min(maxY - 75, intermediateY)); // 75px margin from blue frame edges
- // 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;
- }
+ // Add final segment to reach target length if needed
+ if (totalLength < targetPathLength - 50) {
+ var finalDirection = Math.floor(Math.random() * 4);
+ var finalLength = targetPathLength - totalLength;
+ var finalX = currentX;
+ var finalY = currentY;
+ if (finalDirection === 0) {
+ finalX = Math.min(maxX - 100, currentX + finalLength);
+ } else if (finalDirection === 1) {
+ finalY = Math.min(maxY - 100, currentY + finalLength);
+ } else if (finalDirection === 2) {
+ finalX = Math.max(minX + 100, currentX - finalLength);
+ } else {
+ finalY = Math.max(minY + 100, currentY - finalLength);
}
+ gamePath.push({
+ x: finalX,
+ y: finalY
+ });
}
- // Always add base position as final endpoint
- gamePath.push({
- x: safeBaseX,
- y: safeBaseY
- });
- // Redraw path tiles using circles for smoother appearance
+ // Ensure gamePath has valid elements
+ if (!gamePath || gamePath.length === 0) {
+ // Fallback path if generation failed
+ gamePath = [{
+ x: 100,
+ y: 400
+ }, {
+ x: 1800,
+ y: 1600
+ }];
+ }
+ // Draw path tiles using circles for smoother appearance
for (var i = 0; i < gamePath.length; i++) {
var pathTile = LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
@@ -1725,19 +984,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;
@@ -1813,9 +1061,8 @@
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();