/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Boss enemy class
var Boss = Container.expand(function () {
var self = Container.call(this);
self.isBoss = true;
self.state = 0; // 0: alive, 1: fading
self.bossAsset = self.attachAsset('enemy_shielded', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = CENTER_X;
self.y = CENTER_Y - 700;
self.speed = 12;
self.targetAngle = 0;
self.moveTimer = 0;
self.fadeTimer = 0;
self.lastLaserHit = false;
self.escapeDir = 1; // 1: right, -1: left
// Set boss to a new random direction (left or right of laser)
self.setNewDirection = function () {
// Pick a side to escape: left or right of laser
self.escapeDir = Math.random() < 0.5 ? 1 : -1;
// Offset angle from laser
self.targetAngle = laserAngle + Math.PI / 2 * self.escapeDir;
// Clamp to [-PI, PI]
if (self.targetAngle > Math.PI) self.targetAngle -= 2 * Math.PI;
if (self.targetAngle < -Math.PI) self.targetAngle += 2 * Math.PI;
// Set moveTimer for how long to move in this direction
self.moveTimer = 30 + Math.floor(Math.random() * 30);
};
self.setNewDirection();
self.update = function () {
if (self.state === 1) {
self.fadeTimer += 1;
self.alpha = 1 - self.fadeTimer / 60;
if (self.fadeTimer > 60) {
self.destroy();
}
return;
}
// Move away from laser direction
var moveAngle = self.targetAngle;
var moveDist = self.speed;
self.x += Math.cos(moveAngle) * moveDist;
self.y += Math.sin(moveAngle) * moveDist;
// Clamp boss inside game area
if (self.x < 200) self.x = 200;
if (self.x > 1848) self.x = 1848;
if (self.y < 200) self.y = 200;
if (self.y > 2532) self.y = 2532;
self.moveTimer -= 1;
if (self.moveTimer <= 0) {
self.setNewDirection();
}
};
// Boss takes hit
self.hitByLaser = function () {
if (self.state === 0) {
self.state = 1;
self.fadeTimer = 0;
LK.getSound('enemy_down').play();
}
};
return self;
});
// ENEMY STATES
// 0: normal
// 1: shielded (red)
// 2: fading (about to die)
var Enemy = Container.expand(function () {
var self = Container.call(this);
// Default: normal enemy
self.state = 0; // 0: normal, 1: shielded, 2: fading
self.isWizard = false;
self.isProtected = false; // If protected by wizard
self.protector = null; // Wizard protecting this enemy
// Asset
self.enemyAsset = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.shieldAsset = null; // For shield effect
// For movement
self.speed = 2 + Math.random() * 1.5; // Slightly random speed
self.angle = 0; // Will be set on spawn
self.radius = 0; // Distance from center
// For fade-out
self.fadeTimer = 0;
// For collision
self.lastLaserHit = false;
// For wizard
self.isWizard = false;
self.shieldedEnemy = null; // For wizard: the enemy it protects
// Set state (change color/asset)
self.setState = function (state) {
self.state = state;
if (self.isWizard) {
// Always wizard asset
if (self.enemyAsset) self.enemyAsset.destroy();
self.enemyAsset = self.attachAsset('wizard', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (state === 0) {
if (self.enemyAsset) self.enemyAsset.destroy();
self.enemyAsset = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (state === 1) {
if (self.enemyAsset) self.enemyAsset.destroy();
self.enemyAsset = self.attachAsset('enemy_shielded', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (state === 2) {
if (self.enemyAsset) self.enemyAsset.destroy();
self.enemyAsset = self.attachAsset('enemy_fading', {
anchorX: 0.5,
anchorY: 0.5
});
}
};
// Show/hide shield
self.setShield = function (on) {
if (on && !self.shieldAsset) {
self.shieldAsset = self.attachAsset('shield', {
anchorX: 0.5,
anchorY: 0.5
});
self.shieldAsset.alpha = 0.35;
} else if (!on && self.shieldAsset) {
self.shieldAsset.destroy();
self.shieldAsset = null;
}
};
// Called every tick
self.update = function () {
// Move towards center
var dx = 1024 - self.x;
var dy = 1366 - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
var moveDist = Math.min(self.speed, dist);
self.x += dx / dist * moveDist;
self.y += dy / dist * moveDist;
}
// Fading out
if (self.state === 2) {
self.fadeTimer += 1;
if (self.fadeTimer > 30) {
// Remove after fade
self.destroy();
} else {
self.alpha = 1 - self.fadeTimer / 30;
}
}
};
// For wizard: assign protected enemy
self.setShieldedEnemy = function (enemy) {
self.shieldedEnemy = enemy;
if (enemy) {
enemy.isProtected = true;
enemy.protector = self;
enemy.setShield(true);
}
};
// For wizard: remove shield from protected enemy
self.removeShieldedEnemy = function () {
if (self.shieldedEnemy) {
self.shieldedEnemy.isProtected = false;
self.shieldedEnemy.protector = null;
self.shieldedEnemy.setShield(false);
self.shieldedEnemy = null;
}
};
// For protected enemy: called if wizard dies
self.removeProtection = function () {
self.isProtected = false;
self.protector = null;
self.setShield(false);
};
return self;
});
// --- Dangerous Enemy Types ---
// Fast enemy: moves faster, dies in 1 hit, red color
// Tank enemy: moves slow, takes 3 hits, purple color
// Exploder enemy: explodes on death, orange color
// Tank Enemy
var TankEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
self.tankHits = 0; // 0,1,2,3 (3rd is death)
self.speed = 1.2 + Math.random() * 0.5;
self.setState = function (state) {
self.state = state;
if (self.enemyAsset) self.enemyAsset.destroy();
if (self.tankHits === 0) {
self.enemyAsset = self.attachAsset('enemy_tank', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.tankHits === 1) {
self.enemyAsset = self.attachAsset('enemy_tank_dmg1', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.tankHits === 2) {
self.enemyAsset = self.attachAsset('enemy_tank_dmg2', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
self.enemyAsset = self.attachAsset('enemy_fading', {
anchorX: 0.5,
anchorY: 0.5
});
}
};
self.update = function () {
var dx = 1024 - self.x;
var dy = 1366 - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
var moveDist = Math.min(self.speed, dist);
self.x += dx / dist * moveDist;
self.y += dy / dist * moveDist;
}
if (self.state === 2) {
self.fadeTimer += 1;
if (self.fadeTimer > 40) {
self.destroy();
} else {
self.alpha = 1 - self.fadeTimer / 40;
}
}
};
return self;
});
// --- Dangerous Enemy Classes ---
// Fast Enemy
var FastEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
self.speed = 5 + Math.random() * 2;
self.setState = function (state) {
self.state = state;
if (self.enemyAsset) self.enemyAsset.destroy();
self.enemyAsset = self.attachAsset('enemy_fast', {
anchorX: 0.5,
anchorY: 0.5
});
};
// Only 1 hit to die
self.update = function () {
var dx = 1024 - self.x;
var dy = 1366 - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
var moveDist = Math.min(self.speed, dist);
self.x += dx / dist * moveDist;
self.y += dy / dist * moveDist;
}
if (self.state === 2) {
self.fadeTimer += 1;
if (self.fadeTimer > 18) {
self.destroy();
} else {
self.alpha = 1 - self.fadeTimer / 18;
}
}
};
return self;
});
// Exploder Enemy
var ExploderEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
self.speed = 2.5 + Math.random() * 1.2;
self.setState = function (state) {
self.state = state;
if (self.enemyAsset) self.enemyAsset.destroy();
self.enemyAsset = self.attachAsset('enemy_exploder', {
anchorX: 0.5,
anchorY: 0.5
});
};
self.update = function () {
var dx = 1024 - self.x;
var dy = 1366 - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
var moveDist = Math.min(self.speed, dist);
self.x += dx / dist * moveDist;
self.y += dy / dist * moveDist;
}
if (self.state === 2) {
self.fadeTimer += 1;
if (self.fadeTimer > 22) {
self.destroy();
} else {
self.alpha = 1 - self.fadeTimer / 22;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181a1b
});
/****
* Game Code
****/
// Exploder enemy: explodes on death, orange color
// Tank enemy: moves slow, takes 3 hits, purple color
// Fast enemy: moves faster, dies in 1 hit, red color
// --- Dangerous Enemy Types ---
// Center of screen
// Main character (center)
// Lazer beam (thin, long rectangle)
// Enemy: normal
// Enemy: shielded (first hit)
// Enemy: fading (second hit)
// Enemy: wizard
// Shield effect (for protected enemies)
// Sound for enemy destroyed
var CENTER_X = 1024;
var CENTER_Y = 1366;
// Main character
var hero = new Container();
var heroAsset = hero.attachAsset('hero', {
anchorX: 0.5,
anchorY: 0.5
});
hero.x = CENTER_X;
hero.y = CENTER_Y;
game.addChild(hero);
// Laser
var laser = new Container();
var laserAsset = laser.attachAsset('laser', {
anchorX: 0.5,
anchorY: 0
});
laser.x = CENTER_X;
laser.y = CENTER_Y;
laser.rotation = 0;
game.addChild(laser);
// Laser direction (in radians)
var laserAngle = 0; // 0 = up
var laserTargetAngle = 0; // For smooth tweening
// Touch drag control
// Remove dragging logic, always follow last touch/mouse position
var lastPointer = {
x: CENTER_X,
y: CENTER_Y
};
var enemies = [];
var spawnWaveTimer = 0;
var waveNumber = 1;
// Boss
var boss = null;
var bossActive = false;
var bossDefeated = false;
// Score
var score = 0;
// Import storage plugin for persistent high score
// Retrieve high score from storage, or 0 if not set
var highScore = storage.highScore || 0;
// Score text
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// High score text
var highScoreTxt = new Text2('En Yüksek: ' + highScore, {
size: 60,
fill: 0xFFD700
});
highScoreTxt.anchor.set(0.5, 0);
highScoreTxt.y = 110; // Place below the main score
LK.gui.top.addChild(highScoreTxt);
// Helper: spawn a single enemy at random angle/distance
function spawnEnemy(type) {
var enemy;
if (type === 'wizard') {
enemy = new Enemy();
enemy.isWizard = true;
enemy.setState(0);
} else if (type === 'fast') {
enemy = new FastEnemy();
enemy.setState(0);
} else if (type === 'tank') {
enemy = new TankEnemy();
enemy.setState(0);
} else if (type === 'exploder') {
enemy = new ExploderEnemy();
enemy.setState(0);
} else {
enemy = new Enemy();
enemy.setState(0);
}
// Spawn at random angle, at edge of screen
var angle = Math.random() * Math.PI * 2;
var dist = 1200 + Math.random() * 400;
enemy.x = CENTER_X + Math.cos(angle) * dist;
enemy.y = CENTER_Y + Math.sin(angle) * dist;
enemy.angle = angle;
enemy.radius = dist;
// Set speed for normal/wizard
if (type === 'wizard') {
enemy.speed = 2.5 + Math.random() * 1.2;
} else if (type === 'normal') {
enemy.speed = 2 + Math.random() * 1.5;
}
game.addChild(enemy);
enemies.push(enemy);
return enemy;
}
// Helper: assign wizards to protect nearest enemy
function assignWizards() {
// Get all wizards
var wizards = [];
var normalEnemies = [];
for (var i = 0; i < enemies.length; ++i) {
var e = enemies[i];
if (e.isWizard) wizards.push(e);else if (!e.isProtected) normalEnemies.push(e);
}
// For each wizard, find nearest unprotected normal enemy (not wizard, not protected, not fading)
for (var j = 0; j < wizards.length; ++j) {
var wiz = wizards[j];
// Remove old shield
wiz.removeShieldedEnemy();
// Find nearest normal enemy (not wizard, not protected, not fading)
var minDist = 99999;
var nearest = null;
for (var k = 0; k < enemies.length; ++k) {
var e2 = enemies[k];
if (e2 === wiz) continue;
if (e2.isWizard) continue; // Only shield normal enemies
if (e2.isProtected) continue;
if (e2.state === 2) continue; // Fading out
var dx = wiz.x - e2.x;
var dy = wiz.y - e2.y;
var d = dx * dx + dy * dy;
if (d < minDist) {
minDist = d;
nearest = e2;
}
}
if (nearest) {
wiz.setShieldedEnemy(nearest);
}
}
}
// Helper: spawn a wave of enemies
function spawnWave() {
// After boss is defeated, scale up difficulty and spawn more dangerous enemies
var postBoss = bossDefeated && score >= 500;
var baseEnemies = 3 + Math.floor(waveNumber * 0.7);
var numEnemies = postBoss ? Math.floor(baseEnemies * (1.2 + (score - 500) / 1000)) : baseEnemies;
var numWizards = postBoss ? Math.floor(numEnemies * 0.08) : Math.min(1 + Math.floor(waveNumber / 3), Math.floor(numEnemies / 3));
// Dangerous enemy ratios
var fastRatio = postBoss ? Math.min(0.25 + (score - 500) / 2000, 0.45) : 0;
var tankRatio = postBoss ? Math.min(0.18 + (score - 500) / 3000, 0.32) : 0;
var exploderRatio = postBoss ? Math.min(0.12 + (score - 500) / 4000, 0.22) : 0;
// Normal enemies become rare
var normalRatio = 1 - (fastRatio + tankRatio + exploderRatio + numWizards / numEnemies);
// Clamp
if (normalRatio < 0.08) normalRatio = 0.08;
// Build enemy type pool
var pool = [];
for (var i = 0; i < Math.floor(numEnemies * fastRatio); ++i) pool.push('fast');
for (var i = 0; i < Math.floor(numEnemies * tankRatio); ++i) pool.push('tank');
for (var i = 0; i < Math.floor(numEnemies * exploderRatio); ++i) pool.push('exploder');
for (var i = 0; i < Math.floor(numEnemies * normalRatio); ++i) pool.push('normal');
// Fill up to numEnemies
while (pool.length < numEnemies) pool.push('normal');
// Shuffle pool
for (var i = pool.length - 1; i > 0; --i) {
var ri = Math.floor(Math.random() * (i + 1));
var tmp = pool[i];
pool[i] = pool[ri];
pool[ri] = tmp;
}
// Spawn wizards
for (var i = 0; i < numWizards; ++i) {
spawnEnemy('wizard');
}
// Spawn from pool
for (var j = 0; j < pool.length; ++j) {
spawnEnemy(pool[j]);
}
assignWizards();
waveNumber += 1;
}
// Helper: check if a point is inside the laser beam
function pointInLaser(px, py) {
// Laser is a rectangle from (CENTER_X, CENTER_Y) in direction laserAngle, length = laserAsset.height
var dx = px - CENTER_X;
var dy = py - CENTER_Y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len < 60) return false; // Don't hit at center
var angleToPoint = Math.atan2(dy, dx);
var diff = Math.abs(angleToPoint - laserAngle);
// Normalize diff to [0, PI]
while (diff > Math.PI) diff = Math.abs(diff - 2 * Math.PI);
// If within 0.13 rad (~7.5 deg) of laser direction, and within laser length
if (diff < 0.13 && len < laserAsset.height) {
return true;
}
return false;
}
// Helper: remove enemy from array and game
function removeEnemy(enemy) {
// Remove shield if wizard
if (enemy.isWizard) {
enemy.removeShieldedEnemy();
}
// Remove protection if protected
if (enemy.isProtected && enemy.protector) {
enemy.protector.removeShieldedEnemy();
}
for (var i = enemies.length - 1; i >= 0; --i) {
if (enemies[i] === enemy) {
enemies.splice(i, 1);
break;
}
}
enemy.destroy();
}
// Touch/drag controls
game.down = function (x, y, obj) {
// Ignore top left 100x100 for menu
if (x < 100 && y < 100) return;
// Always update last pointer position and angle
lastPointer.x = x;
lastPointer.y = y;
var dx = x - CENTER_X;
var dy = y - CENTER_Y;
var angle = Math.atan2(dy, dx);
if (angle > Math.PI) angle -= 2 * Math.PI;
if (angle < -Math.PI) angle += 2 * Math.PI;
laserTargetAngle = angle;
};
game.move = function (x, y, obj) {
// Always update last pointer position
lastPointer.x = x;
lastPointer.y = y;
// Calculate angle from center to pointer
var dx = x - CENTER_X;
var dy = y - CENTER_Y;
var angle = Math.atan2(dy, dx);
// Clamp to [-PI, PI]
if (angle > Math.PI) angle -= 2 * Math.PI;
if (angle < -Math.PI) angle += 2 * Math.PI;
laserTargetAngle = angle;
};
game.up = function (x, y, obj) {
// No drag state to reset, but update pointer and angle for consistency
lastPointer.x = x;
lastPointer.y = y;
var dx = x - CENTER_X;
var dy = y - CENTER_Y;
var angle = Math.atan2(dy, dx);
if (angle > Math.PI) angle -= 2 * Math.PI;
if (angle < -Math.PI) angle += 2 * Math.PI;
laserTargetAngle = angle;
};
// Main update loop
game.update = function () {
// Smoothly rotate laser towards target angle
var diff = laserTargetAngle - laserAngle;
while (diff > Math.PI) diff -= 2 * Math.PI;
while (diff < -Math.PI) diff += 2 * Math.PI;
laserAngle += diff * 0.18; // Smooth
// Clamp
if (laserAngle > Math.PI) laserAngle -= 2 * Math.PI;
if (laserAngle < -Math.PI) laserAngle += 2 * Math.PI;
laser.rotation = laserAngle - Math.PI / 2; // Because asset is vertical
// Boss phase logic
if (!bossActive && score >= 500 && !bossDefeated) {
// Remove all enemies
for (var i = enemies.length - 1; i >= 0; --i) {
enemies[i].destroy();
}
enemies = [];
// Spawn boss
boss = new Boss();
boss.x = CENTER_X;
boss.y = CENTER_Y - 700;
game.addChild(boss);
bossActive = true;
}
if (bossActive && boss && !bossDefeated) {
boss.update();
// Boss collision with laser
if (boss.state === 0 && pointInLaser(boss.x, boss.y)) {
boss.hitByLaser();
score += 20;
scoreTxt.setText(score);
// Update high score if needed
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('En Yüksek: ' + highScore);
}
}
// Remove boss if faded out
if (boss.state === 1 && boss.fadeTimer > 60) {
boss.destroy();
boss = null;
bossActive = false;
bossDefeated = true;
// No win popup, continue game
// Resume spawning waves
waveNumber += 1;
spawnWaveTimer = 0;
// Optionally, you can spawn a new wave immediately or let the normal wave logic handle it
// spawnWave();
// No return here, let the game continue
}
// Boss game over if reaches center
if (boss) {
var bdx = boss.x - CENTER_X;
var bdy = boss.y - CENTER_Y;
var bdist = Math.sqrt(bdx * bdx + bdy * bdy);
if (bdist < 120 && boss.state === 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return;
}
}
return; // Only boss is active, skip rest
}
// Update all enemies
for (var i = enemies.length - 1; i >= 0; --i) {
var e = enemies[i];
e.update();
// Check if reached center (game over)
var dx = e.x - CENTER_X;
var dy = e.y - CENTER_Y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 100 && e.state !== 2) {
// Game over
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return;
}
}
// Laser collision
var anyLaserHit = false;
for (var j = enemies.length - 1; j >= 0; --j) {
var e2 = enemies[j];
if (e2.state === 2) continue; // Already dying
if (pointInLaser(e2.x, e2.y)) {
// If protected, can't be hit
if (e2.isProtected) {
// Show shield effect
e2.setShield(true);
continue;
}
// Wizards: die in one hit
if (e2.isWizard) {
e2.setState(2);
e2.fadeTimer = 0;
e2.removeShieldedEnemy();
LK.getSound('wizard_down').play();
score += 5;
scoreTxt.setText(score);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('En Yüksek: ' + highScore);
}
anyLaserHit = true;
continue;
}
// Fast enemy: 1 hit to die
if (e2 instanceof FastEnemy) {
e2.setState(2);
e2.fadeTimer = 0;
LK.getSound('enemy_down').play();
score += 2;
scoreTxt.setText(score);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('En Yüksek: ' + highScore);
}
anyLaserHit = true;
continue;
}
// Tank enemy: 3 hits to die
if (e2 instanceof TankEnemy) {
if (e2.state !== 2) {
e2.tankHits += 1;
if (e2.tankHits < 3) {
e2.setState(0);
LK.getSound('laser_hit').play();
} else {
e2.setState(2);
e2.fadeTimer = 0;
LK.getSound('enemy_down').play();
score += 4;
scoreTxt.setText(score);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('En Yüksek: ' + highScore);
}
}
anyLaserHit = true;
continue;
}
}
// Exploder enemy: 1 hit to die, but will explode (handled in removeEnemy)
if (e2 instanceof ExploderEnemy) {
e2.setState(2);
e2.fadeTimer = 0;
LK.getSound('enemy_down').play();
score += 3;
scoreTxt.setText(score);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('En Yüksek: ' + highScore);
}
anyLaserHit = true;
continue;
}
// Normal enemy: 2 hits to die
if (e2.state === 0) {
e2.setState(1);
LK.getSound('laser_hit').play();
anyLaserHit = true;
} else if (e2.state === 1) {
e2.setState(2);
e2.fadeTimer = 0;
LK.getSound('enemy_down').play();
score += 1;
scoreTxt.setText(score);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('En Yüksek: ' + highScore);
}
anyLaserHit = true;
}
} else {
// Not hit, remove shield effect if any
if (!e2.isWizard && !e2.isProtected) {
e2.setShield(false);
}
}
}
// Remove dead enemies
for (var k = enemies.length - 1; k >= 0; --k) {
var e3 = enemies[k];
// Exploder enemy: explode on death
if (e3 instanceof ExploderEnemy && e3.state === 2 && e3.fadeTimer === 22) {
// Check if player is close to explosion
var dx = e3.x - CENTER_X;
var dy = e3.y - CENTER_Y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 220) {
LK.effects.flashScreen(0xff6600, 1000);
LK.showGameOver();
return;
}
// Optionally, damage other enemies in radius (not implemented for simplicity)
}
if (e3.state === 2 && (e3 instanceof FastEnemy && e3.fadeTimer > 18 || e3 instanceof TankEnemy && e3.fadeTimer > 40 || e3 instanceof ExploderEnemy && e3.fadeTimer > 22 || !(e3 instanceof FastEnemy) && !(e3 instanceof TankEnemy) && !(e3 instanceof ExploderEnemy) && e3.fadeTimer > 30)) {
removeEnemy(e3);
}
}
// If all enemies gone, spawn next wave
var living = 0;
for (var m = 0; m < enemies.length; ++m) {
if (enemies[m].state !== 2) living += 1;
}
if (living === 0) {
spawnWaveTimer += 1;
if (spawnWaveTimer > 40) {
spawnWave();
spawnWaveTimer = 0;
}
} else {
spawnWaveTimer = 0;
}
// (Removed wave-based win condition so game continues indefinitely)
};
// Start first wave
spawnWave();
scoreTxt.setText(score);
// Play action-packed background music
LK.playMusic('action_bgmusic'); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Boss enemy class
var Boss = Container.expand(function () {
var self = Container.call(this);
self.isBoss = true;
self.state = 0; // 0: alive, 1: fading
self.bossAsset = self.attachAsset('enemy_shielded', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = CENTER_X;
self.y = CENTER_Y - 700;
self.speed = 12;
self.targetAngle = 0;
self.moveTimer = 0;
self.fadeTimer = 0;
self.lastLaserHit = false;
self.escapeDir = 1; // 1: right, -1: left
// Set boss to a new random direction (left or right of laser)
self.setNewDirection = function () {
// Pick a side to escape: left or right of laser
self.escapeDir = Math.random() < 0.5 ? 1 : -1;
// Offset angle from laser
self.targetAngle = laserAngle + Math.PI / 2 * self.escapeDir;
// Clamp to [-PI, PI]
if (self.targetAngle > Math.PI) self.targetAngle -= 2 * Math.PI;
if (self.targetAngle < -Math.PI) self.targetAngle += 2 * Math.PI;
// Set moveTimer for how long to move in this direction
self.moveTimer = 30 + Math.floor(Math.random() * 30);
};
self.setNewDirection();
self.update = function () {
if (self.state === 1) {
self.fadeTimer += 1;
self.alpha = 1 - self.fadeTimer / 60;
if (self.fadeTimer > 60) {
self.destroy();
}
return;
}
// Move away from laser direction
var moveAngle = self.targetAngle;
var moveDist = self.speed;
self.x += Math.cos(moveAngle) * moveDist;
self.y += Math.sin(moveAngle) * moveDist;
// Clamp boss inside game area
if (self.x < 200) self.x = 200;
if (self.x > 1848) self.x = 1848;
if (self.y < 200) self.y = 200;
if (self.y > 2532) self.y = 2532;
self.moveTimer -= 1;
if (self.moveTimer <= 0) {
self.setNewDirection();
}
};
// Boss takes hit
self.hitByLaser = function () {
if (self.state === 0) {
self.state = 1;
self.fadeTimer = 0;
LK.getSound('enemy_down').play();
}
};
return self;
});
// ENEMY STATES
// 0: normal
// 1: shielded (red)
// 2: fading (about to die)
var Enemy = Container.expand(function () {
var self = Container.call(this);
// Default: normal enemy
self.state = 0; // 0: normal, 1: shielded, 2: fading
self.isWizard = false;
self.isProtected = false; // If protected by wizard
self.protector = null; // Wizard protecting this enemy
// Asset
self.enemyAsset = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.shieldAsset = null; // For shield effect
// For movement
self.speed = 2 + Math.random() * 1.5; // Slightly random speed
self.angle = 0; // Will be set on spawn
self.radius = 0; // Distance from center
// For fade-out
self.fadeTimer = 0;
// For collision
self.lastLaserHit = false;
// For wizard
self.isWizard = false;
self.shieldedEnemy = null; // For wizard: the enemy it protects
// Set state (change color/asset)
self.setState = function (state) {
self.state = state;
if (self.isWizard) {
// Always wizard asset
if (self.enemyAsset) self.enemyAsset.destroy();
self.enemyAsset = self.attachAsset('wizard', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (state === 0) {
if (self.enemyAsset) self.enemyAsset.destroy();
self.enemyAsset = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (state === 1) {
if (self.enemyAsset) self.enemyAsset.destroy();
self.enemyAsset = self.attachAsset('enemy_shielded', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (state === 2) {
if (self.enemyAsset) self.enemyAsset.destroy();
self.enemyAsset = self.attachAsset('enemy_fading', {
anchorX: 0.5,
anchorY: 0.5
});
}
};
// Show/hide shield
self.setShield = function (on) {
if (on && !self.shieldAsset) {
self.shieldAsset = self.attachAsset('shield', {
anchorX: 0.5,
anchorY: 0.5
});
self.shieldAsset.alpha = 0.35;
} else if (!on && self.shieldAsset) {
self.shieldAsset.destroy();
self.shieldAsset = null;
}
};
// Called every tick
self.update = function () {
// Move towards center
var dx = 1024 - self.x;
var dy = 1366 - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
var moveDist = Math.min(self.speed, dist);
self.x += dx / dist * moveDist;
self.y += dy / dist * moveDist;
}
// Fading out
if (self.state === 2) {
self.fadeTimer += 1;
if (self.fadeTimer > 30) {
// Remove after fade
self.destroy();
} else {
self.alpha = 1 - self.fadeTimer / 30;
}
}
};
// For wizard: assign protected enemy
self.setShieldedEnemy = function (enemy) {
self.shieldedEnemy = enemy;
if (enemy) {
enemy.isProtected = true;
enemy.protector = self;
enemy.setShield(true);
}
};
// For wizard: remove shield from protected enemy
self.removeShieldedEnemy = function () {
if (self.shieldedEnemy) {
self.shieldedEnemy.isProtected = false;
self.shieldedEnemy.protector = null;
self.shieldedEnemy.setShield(false);
self.shieldedEnemy = null;
}
};
// For protected enemy: called if wizard dies
self.removeProtection = function () {
self.isProtected = false;
self.protector = null;
self.setShield(false);
};
return self;
});
// --- Dangerous Enemy Types ---
// Fast enemy: moves faster, dies in 1 hit, red color
// Tank enemy: moves slow, takes 3 hits, purple color
// Exploder enemy: explodes on death, orange color
// Tank Enemy
var TankEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
self.tankHits = 0; // 0,1,2,3 (3rd is death)
self.speed = 1.2 + Math.random() * 0.5;
self.setState = function (state) {
self.state = state;
if (self.enemyAsset) self.enemyAsset.destroy();
if (self.tankHits === 0) {
self.enemyAsset = self.attachAsset('enemy_tank', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.tankHits === 1) {
self.enemyAsset = self.attachAsset('enemy_tank_dmg1', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.tankHits === 2) {
self.enemyAsset = self.attachAsset('enemy_tank_dmg2', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
self.enemyAsset = self.attachAsset('enemy_fading', {
anchorX: 0.5,
anchorY: 0.5
});
}
};
self.update = function () {
var dx = 1024 - self.x;
var dy = 1366 - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
var moveDist = Math.min(self.speed, dist);
self.x += dx / dist * moveDist;
self.y += dy / dist * moveDist;
}
if (self.state === 2) {
self.fadeTimer += 1;
if (self.fadeTimer > 40) {
self.destroy();
} else {
self.alpha = 1 - self.fadeTimer / 40;
}
}
};
return self;
});
// --- Dangerous Enemy Classes ---
// Fast Enemy
var FastEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
self.speed = 5 + Math.random() * 2;
self.setState = function (state) {
self.state = state;
if (self.enemyAsset) self.enemyAsset.destroy();
self.enemyAsset = self.attachAsset('enemy_fast', {
anchorX: 0.5,
anchorY: 0.5
});
};
// Only 1 hit to die
self.update = function () {
var dx = 1024 - self.x;
var dy = 1366 - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
var moveDist = Math.min(self.speed, dist);
self.x += dx / dist * moveDist;
self.y += dy / dist * moveDist;
}
if (self.state === 2) {
self.fadeTimer += 1;
if (self.fadeTimer > 18) {
self.destroy();
} else {
self.alpha = 1 - self.fadeTimer / 18;
}
}
};
return self;
});
// Exploder Enemy
var ExploderEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
self.speed = 2.5 + Math.random() * 1.2;
self.setState = function (state) {
self.state = state;
if (self.enemyAsset) self.enemyAsset.destroy();
self.enemyAsset = self.attachAsset('enemy_exploder', {
anchorX: 0.5,
anchorY: 0.5
});
};
self.update = function () {
var dx = 1024 - self.x;
var dy = 1366 - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
var moveDist = Math.min(self.speed, dist);
self.x += dx / dist * moveDist;
self.y += dy / dist * moveDist;
}
if (self.state === 2) {
self.fadeTimer += 1;
if (self.fadeTimer > 22) {
self.destroy();
} else {
self.alpha = 1 - self.fadeTimer / 22;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181a1b
});
/****
* Game Code
****/
// Exploder enemy: explodes on death, orange color
// Tank enemy: moves slow, takes 3 hits, purple color
// Fast enemy: moves faster, dies in 1 hit, red color
// --- Dangerous Enemy Types ---
// Center of screen
// Main character (center)
// Lazer beam (thin, long rectangle)
// Enemy: normal
// Enemy: shielded (first hit)
// Enemy: fading (second hit)
// Enemy: wizard
// Shield effect (for protected enemies)
// Sound for enemy destroyed
var CENTER_X = 1024;
var CENTER_Y = 1366;
// Main character
var hero = new Container();
var heroAsset = hero.attachAsset('hero', {
anchorX: 0.5,
anchorY: 0.5
});
hero.x = CENTER_X;
hero.y = CENTER_Y;
game.addChild(hero);
// Laser
var laser = new Container();
var laserAsset = laser.attachAsset('laser', {
anchorX: 0.5,
anchorY: 0
});
laser.x = CENTER_X;
laser.y = CENTER_Y;
laser.rotation = 0;
game.addChild(laser);
// Laser direction (in radians)
var laserAngle = 0; // 0 = up
var laserTargetAngle = 0; // For smooth tweening
// Touch drag control
// Remove dragging logic, always follow last touch/mouse position
var lastPointer = {
x: CENTER_X,
y: CENTER_Y
};
var enemies = [];
var spawnWaveTimer = 0;
var waveNumber = 1;
// Boss
var boss = null;
var bossActive = false;
var bossDefeated = false;
// Score
var score = 0;
// Import storage plugin for persistent high score
// Retrieve high score from storage, or 0 if not set
var highScore = storage.highScore || 0;
// Score text
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// High score text
var highScoreTxt = new Text2('En Yüksek: ' + highScore, {
size: 60,
fill: 0xFFD700
});
highScoreTxt.anchor.set(0.5, 0);
highScoreTxt.y = 110; // Place below the main score
LK.gui.top.addChild(highScoreTxt);
// Helper: spawn a single enemy at random angle/distance
function spawnEnemy(type) {
var enemy;
if (type === 'wizard') {
enemy = new Enemy();
enemy.isWizard = true;
enemy.setState(0);
} else if (type === 'fast') {
enemy = new FastEnemy();
enemy.setState(0);
} else if (type === 'tank') {
enemy = new TankEnemy();
enemy.setState(0);
} else if (type === 'exploder') {
enemy = new ExploderEnemy();
enemy.setState(0);
} else {
enemy = new Enemy();
enemy.setState(0);
}
// Spawn at random angle, at edge of screen
var angle = Math.random() * Math.PI * 2;
var dist = 1200 + Math.random() * 400;
enemy.x = CENTER_X + Math.cos(angle) * dist;
enemy.y = CENTER_Y + Math.sin(angle) * dist;
enemy.angle = angle;
enemy.radius = dist;
// Set speed for normal/wizard
if (type === 'wizard') {
enemy.speed = 2.5 + Math.random() * 1.2;
} else if (type === 'normal') {
enemy.speed = 2 + Math.random() * 1.5;
}
game.addChild(enemy);
enemies.push(enemy);
return enemy;
}
// Helper: assign wizards to protect nearest enemy
function assignWizards() {
// Get all wizards
var wizards = [];
var normalEnemies = [];
for (var i = 0; i < enemies.length; ++i) {
var e = enemies[i];
if (e.isWizard) wizards.push(e);else if (!e.isProtected) normalEnemies.push(e);
}
// For each wizard, find nearest unprotected normal enemy (not wizard, not protected, not fading)
for (var j = 0; j < wizards.length; ++j) {
var wiz = wizards[j];
// Remove old shield
wiz.removeShieldedEnemy();
// Find nearest normal enemy (not wizard, not protected, not fading)
var minDist = 99999;
var nearest = null;
for (var k = 0; k < enemies.length; ++k) {
var e2 = enemies[k];
if (e2 === wiz) continue;
if (e2.isWizard) continue; // Only shield normal enemies
if (e2.isProtected) continue;
if (e2.state === 2) continue; // Fading out
var dx = wiz.x - e2.x;
var dy = wiz.y - e2.y;
var d = dx * dx + dy * dy;
if (d < minDist) {
minDist = d;
nearest = e2;
}
}
if (nearest) {
wiz.setShieldedEnemy(nearest);
}
}
}
// Helper: spawn a wave of enemies
function spawnWave() {
// After boss is defeated, scale up difficulty and spawn more dangerous enemies
var postBoss = bossDefeated && score >= 500;
var baseEnemies = 3 + Math.floor(waveNumber * 0.7);
var numEnemies = postBoss ? Math.floor(baseEnemies * (1.2 + (score - 500) / 1000)) : baseEnemies;
var numWizards = postBoss ? Math.floor(numEnemies * 0.08) : Math.min(1 + Math.floor(waveNumber / 3), Math.floor(numEnemies / 3));
// Dangerous enemy ratios
var fastRatio = postBoss ? Math.min(0.25 + (score - 500) / 2000, 0.45) : 0;
var tankRatio = postBoss ? Math.min(0.18 + (score - 500) / 3000, 0.32) : 0;
var exploderRatio = postBoss ? Math.min(0.12 + (score - 500) / 4000, 0.22) : 0;
// Normal enemies become rare
var normalRatio = 1 - (fastRatio + tankRatio + exploderRatio + numWizards / numEnemies);
// Clamp
if (normalRatio < 0.08) normalRatio = 0.08;
// Build enemy type pool
var pool = [];
for (var i = 0; i < Math.floor(numEnemies * fastRatio); ++i) pool.push('fast');
for (var i = 0; i < Math.floor(numEnemies * tankRatio); ++i) pool.push('tank');
for (var i = 0; i < Math.floor(numEnemies * exploderRatio); ++i) pool.push('exploder');
for (var i = 0; i < Math.floor(numEnemies * normalRatio); ++i) pool.push('normal');
// Fill up to numEnemies
while (pool.length < numEnemies) pool.push('normal');
// Shuffle pool
for (var i = pool.length - 1; i > 0; --i) {
var ri = Math.floor(Math.random() * (i + 1));
var tmp = pool[i];
pool[i] = pool[ri];
pool[ri] = tmp;
}
// Spawn wizards
for (var i = 0; i < numWizards; ++i) {
spawnEnemy('wizard');
}
// Spawn from pool
for (var j = 0; j < pool.length; ++j) {
spawnEnemy(pool[j]);
}
assignWizards();
waveNumber += 1;
}
// Helper: check if a point is inside the laser beam
function pointInLaser(px, py) {
// Laser is a rectangle from (CENTER_X, CENTER_Y) in direction laserAngle, length = laserAsset.height
var dx = px - CENTER_X;
var dy = py - CENTER_Y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len < 60) return false; // Don't hit at center
var angleToPoint = Math.atan2(dy, dx);
var diff = Math.abs(angleToPoint - laserAngle);
// Normalize diff to [0, PI]
while (diff > Math.PI) diff = Math.abs(diff - 2 * Math.PI);
// If within 0.13 rad (~7.5 deg) of laser direction, and within laser length
if (diff < 0.13 && len < laserAsset.height) {
return true;
}
return false;
}
// Helper: remove enemy from array and game
function removeEnemy(enemy) {
// Remove shield if wizard
if (enemy.isWizard) {
enemy.removeShieldedEnemy();
}
// Remove protection if protected
if (enemy.isProtected && enemy.protector) {
enemy.protector.removeShieldedEnemy();
}
for (var i = enemies.length - 1; i >= 0; --i) {
if (enemies[i] === enemy) {
enemies.splice(i, 1);
break;
}
}
enemy.destroy();
}
// Touch/drag controls
game.down = function (x, y, obj) {
// Ignore top left 100x100 for menu
if (x < 100 && y < 100) return;
// Always update last pointer position and angle
lastPointer.x = x;
lastPointer.y = y;
var dx = x - CENTER_X;
var dy = y - CENTER_Y;
var angle = Math.atan2(dy, dx);
if (angle > Math.PI) angle -= 2 * Math.PI;
if (angle < -Math.PI) angle += 2 * Math.PI;
laserTargetAngle = angle;
};
game.move = function (x, y, obj) {
// Always update last pointer position
lastPointer.x = x;
lastPointer.y = y;
// Calculate angle from center to pointer
var dx = x - CENTER_X;
var dy = y - CENTER_Y;
var angle = Math.atan2(dy, dx);
// Clamp to [-PI, PI]
if (angle > Math.PI) angle -= 2 * Math.PI;
if (angle < -Math.PI) angle += 2 * Math.PI;
laserTargetAngle = angle;
};
game.up = function (x, y, obj) {
// No drag state to reset, but update pointer and angle for consistency
lastPointer.x = x;
lastPointer.y = y;
var dx = x - CENTER_X;
var dy = y - CENTER_Y;
var angle = Math.atan2(dy, dx);
if (angle > Math.PI) angle -= 2 * Math.PI;
if (angle < -Math.PI) angle += 2 * Math.PI;
laserTargetAngle = angle;
};
// Main update loop
game.update = function () {
// Smoothly rotate laser towards target angle
var diff = laserTargetAngle - laserAngle;
while (diff > Math.PI) diff -= 2 * Math.PI;
while (diff < -Math.PI) diff += 2 * Math.PI;
laserAngle += diff * 0.18; // Smooth
// Clamp
if (laserAngle > Math.PI) laserAngle -= 2 * Math.PI;
if (laserAngle < -Math.PI) laserAngle += 2 * Math.PI;
laser.rotation = laserAngle - Math.PI / 2; // Because asset is vertical
// Boss phase logic
if (!bossActive && score >= 500 && !bossDefeated) {
// Remove all enemies
for (var i = enemies.length - 1; i >= 0; --i) {
enemies[i].destroy();
}
enemies = [];
// Spawn boss
boss = new Boss();
boss.x = CENTER_X;
boss.y = CENTER_Y - 700;
game.addChild(boss);
bossActive = true;
}
if (bossActive && boss && !bossDefeated) {
boss.update();
// Boss collision with laser
if (boss.state === 0 && pointInLaser(boss.x, boss.y)) {
boss.hitByLaser();
score += 20;
scoreTxt.setText(score);
// Update high score if needed
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('En Yüksek: ' + highScore);
}
}
// Remove boss if faded out
if (boss.state === 1 && boss.fadeTimer > 60) {
boss.destroy();
boss = null;
bossActive = false;
bossDefeated = true;
// No win popup, continue game
// Resume spawning waves
waveNumber += 1;
spawnWaveTimer = 0;
// Optionally, you can spawn a new wave immediately or let the normal wave logic handle it
// spawnWave();
// No return here, let the game continue
}
// Boss game over if reaches center
if (boss) {
var bdx = boss.x - CENTER_X;
var bdy = boss.y - CENTER_Y;
var bdist = Math.sqrt(bdx * bdx + bdy * bdy);
if (bdist < 120 && boss.state === 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return;
}
}
return; // Only boss is active, skip rest
}
// Update all enemies
for (var i = enemies.length - 1; i >= 0; --i) {
var e = enemies[i];
e.update();
// Check if reached center (game over)
var dx = e.x - CENTER_X;
var dy = e.y - CENTER_Y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 100 && e.state !== 2) {
// Game over
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return;
}
}
// Laser collision
var anyLaserHit = false;
for (var j = enemies.length - 1; j >= 0; --j) {
var e2 = enemies[j];
if (e2.state === 2) continue; // Already dying
if (pointInLaser(e2.x, e2.y)) {
// If protected, can't be hit
if (e2.isProtected) {
// Show shield effect
e2.setShield(true);
continue;
}
// Wizards: die in one hit
if (e2.isWizard) {
e2.setState(2);
e2.fadeTimer = 0;
e2.removeShieldedEnemy();
LK.getSound('wizard_down').play();
score += 5;
scoreTxt.setText(score);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('En Yüksek: ' + highScore);
}
anyLaserHit = true;
continue;
}
// Fast enemy: 1 hit to die
if (e2 instanceof FastEnemy) {
e2.setState(2);
e2.fadeTimer = 0;
LK.getSound('enemy_down').play();
score += 2;
scoreTxt.setText(score);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('En Yüksek: ' + highScore);
}
anyLaserHit = true;
continue;
}
// Tank enemy: 3 hits to die
if (e2 instanceof TankEnemy) {
if (e2.state !== 2) {
e2.tankHits += 1;
if (e2.tankHits < 3) {
e2.setState(0);
LK.getSound('laser_hit').play();
} else {
e2.setState(2);
e2.fadeTimer = 0;
LK.getSound('enemy_down').play();
score += 4;
scoreTxt.setText(score);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('En Yüksek: ' + highScore);
}
}
anyLaserHit = true;
continue;
}
}
// Exploder enemy: 1 hit to die, but will explode (handled in removeEnemy)
if (e2 instanceof ExploderEnemy) {
e2.setState(2);
e2.fadeTimer = 0;
LK.getSound('enemy_down').play();
score += 3;
scoreTxt.setText(score);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('En Yüksek: ' + highScore);
}
anyLaserHit = true;
continue;
}
// Normal enemy: 2 hits to die
if (e2.state === 0) {
e2.setState(1);
LK.getSound('laser_hit').play();
anyLaserHit = true;
} else if (e2.state === 1) {
e2.setState(2);
e2.fadeTimer = 0;
LK.getSound('enemy_down').play();
score += 1;
scoreTxt.setText(score);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('En Yüksek: ' + highScore);
}
anyLaserHit = true;
}
} else {
// Not hit, remove shield effect if any
if (!e2.isWizard && !e2.isProtected) {
e2.setShield(false);
}
}
}
// Remove dead enemies
for (var k = enemies.length - 1; k >= 0; --k) {
var e3 = enemies[k];
// Exploder enemy: explode on death
if (e3 instanceof ExploderEnemy && e3.state === 2 && e3.fadeTimer === 22) {
// Check if player is close to explosion
var dx = e3.x - CENTER_X;
var dy = e3.y - CENTER_Y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 220) {
LK.effects.flashScreen(0xff6600, 1000);
LK.showGameOver();
return;
}
// Optionally, damage other enemies in radius (not implemented for simplicity)
}
if (e3.state === 2 && (e3 instanceof FastEnemy && e3.fadeTimer > 18 || e3 instanceof TankEnemy && e3.fadeTimer > 40 || e3 instanceof ExploderEnemy && e3.fadeTimer > 22 || !(e3 instanceof FastEnemy) && !(e3 instanceof TankEnemy) && !(e3 instanceof ExploderEnemy) && e3.fadeTimer > 30)) {
removeEnemy(e3);
}
}
// If all enemies gone, spawn next wave
var living = 0;
for (var m = 0; m < enemies.length; ++m) {
if (enemies[m].state !== 2) living += 1;
}
if (living === 0) {
spawnWaveTimer += 1;
if (spawnWaveTimer > 40) {
spawnWave();
spawnWaveTimer = 0;
}
} else {
spawnWaveTimer = 0;
}
// (Removed wave-based win condition so game continues indefinitely)
};
// Start first wave
spawnWave();
scoreTxt.setText(score);
// Play action-packed background music
LK.playMusic('action_bgmusic');