User prompt
do it
User prompt
do all of that
User prompt
enemies starting before count down. make them wait
User prompt
make a count down before starting the round
Code edit (1 edits merged)
Please save this source code
User prompt
Mini Strike: Top-Down Tactics
Initial prompt
Create a top down counter-strike like game
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Bullet class
var Bullet = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 20;
self.dx = 0;
self.dy = 0;
self.speed = 38;
self.isPlayer = false;
self.lifetime = 60; // frames
self.update = function () {
self.x += self.dx * self.speed;
self.y += self.dy * self.speed;
self.lifetime--;
};
return self;
});
// Enemy class
var Enemy = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 55;
self.speed = 7 + Math.random() * 3;
self.shootCooldown = 0;
self.shootDelay = 60 + Math.floor(Math.random() * 30);
self.targetX = 0;
self.targetY = 0;
self.isAlive = true;
self.aiTimer = 0;
self.update = function () {
if (!self.isAlive) return;
// AI: Move toward player, avoid obstacles
if (self.aiTimer <= 0) {
// Pick a new target near player, with some randomness
var px = player.x + (Math.random() - 0.5) * 300;
var py = player.y + (Math.random() - 0.5) * 300;
// Clamp to arena
px = Math.max(arena.x + 100, Math.min(arena.x + arena.w - 100, px));
py = Math.max(arena.y + 100, Math.min(arena.y + arena.h - 100, py));
self.targetX = px;
self.targetY = py;
self.aiTimer = 30 + Math.floor(Math.random() * 30);
} else {
self.aiTimer--;
}
// Move toward target
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var mag = Math.sqrt(dx * dx + dy * dy);
if (mag > 10) {
var mx = dx / mag * self.speed;
var my = dy / mag * self.speed;
// Try to move, but avoid obstacles
var nx = self.x + mx;
var ny = self.y + my;
var blocked = false;
for (var i = 0; i < obstacles.length; ++i) {
if (rectCircleCollide(obstacles[i], nx, ny, self.radius)) {
blocked = true;
break;
}
}
if (!blocked) {
self.x = nx;
self.y = ny;
}
}
// Shoot at player if in line of sight
if (self.shootCooldown > 0) self.shootCooldown--;else {
if (canSee(self.x, self.y, player.x, player.y)) {
var ddx = player.x - self.x;
var ddy = player.y - self.y;
var dmag = Math.sqrt(ddx * ddx + ddy * ddy);
if (dmag > 0) {
var bullet = new Bullet();
bullet.x = self.x + ddx / dmag * (self.radius + 30);
bullet.y = self.y + ddy / dmag * (self.radius + 30);
bullet.dx = ddx / dmag;
bullet.dy = ddy / dmag;
bullet.isPlayer = false;
bullets.push(bullet);
game.addChild(bullet);
self.shootCooldown = self.shootDelay;
}
}
}
};
// Take damage
self.hit = function () {
if (!self.isAlive) return;
self.isAlive = false;
tween(sprite, {
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
// Obstacle class
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = sprite.width;
self.height = sprite.height;
return self;
});
// Player class
var Player = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 60;
self.speed = 18;
self.shootCooldown = 0;
self.shootDelay = 18; // frames
self.isAlive = true;
self.invincible = 0; // frames left of invincibility
// For touch drag
self.down = function (x, y, obj) {};
self.up = function (x, y, obj) {};
// For powerup
self.powered = false;
self.powerTimer = 0;
self.update = function () {
if (!self.isAlive) return;
if (self.invincible > 0) self.invincible--;
if (self.powered) {
self.powerTimer--;
if (self.powerTimer <= 0) {
self.powered = false;
sprite.tint = 0x2a6cff;
}
}
if (self.shootCooldown > 0) self.shootCooldown--;
};
// Shoot a bullet in direction (dx, dy)
self.shoot = function (dx, dy) {
if (!self.isAlive) return;
if (self.shootCooldown > 0) return;
var mag = Math.sqrt(dx * dx + dy * dy);
if (mag < 0.1) return;
var bullet = new Bullet();
bullet.x = self.x + dx / mag * (self.radius + 30);
bullet.y = self.y + dy / mag * (self.radius + 30);
bullet.dx = dx / mag;
bullet.dy = dy / mag;
bullet.isPlayer = true;
bullets.push(bullet);
game.addChild(bullet);
self.shootCooldown = self.powered ? 5 : self.shootDelay;
};
// Take damage
self.hit = function () {
if (self.invincible > 0 || !self.isAlive) return;
self.isAlive = false;
tween(sprite, {
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
}
});
};
// Powerup
self.powerup = function () {
self.powered = true;
self.powerTimer = 360; // 6 seconds
sprite.tint = 0x3be14a;
};
return self;
});
// Powerup class
var Powerup = Container.expand(function () {
var self = Container.call(this);
var sprite = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 30;
self.type = 'rapid';
self.lifetime = 600; // 10 seconds
self.update = function () {
self.lifetime--;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181818
});
/****
* Game Code
****/
// Powerup: green ellipse
// Obstacle: gray box
// Bullet: yellow ellipse
// Enemy: red box
// Player character: blue box
// Arena bounds
var arena = {
x: 180,
y: 300,
w: 2048 - 360,
h: 2732 - 600
};
// Game state
var player;
var enemies = [];
var bullets = [];
var obstacles = [];
var powerups = [];
var round = 1;
var roundActive = false;
var roundTimer = 0;
var spawnTimer = 0;
var dragNode = null;
var lastTouch = {
x: 0,
y: 0
};
var scoreTxt;
var roundTxt;
var powerupTxt;
var gameOver = false;
// GUI
scoreTxt = new Text2('Score: 0', {
size: 90,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
roundTxt = new Text2('Round 1', {
size: 70,
fill: 0xFFCC00
});
roundTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(roundTxt);
powerupTxt = new Text2('', {
size: 60,
fill: 0x3BE14A
});
powerupTxt.anchor.set(0.5, 0);
LK.gui.bottom.addChild(powerupTxt);
// Place obstacles
function setupObstacles() {
for (var i = 0; i < obstacles.length; ++i) obstacles[i].destroy();
obstacles = [];
// Place 4-6 obstacles randomly
var obsCount = 4 + Math.floor(Math.random() * 3);
for (var i = 0; i < obsCount; ++i) {
var obs = new Obstacle();
obs.x = arena.x + 200 + Math.random() * (arena.w - 400);
obs.y = arena.y + 200 + Math.random() * (arena.h - 400);
if (i % 2 == 0) obs.rotation = Math.PI / 2;
obstacles.push(obs);
game.addChild(obs);
}
}
// Place player
function setupPlayer() {
if (player) player.destroy();
player = new Player();
player.x = arena.x + arena.w / 2;
player.y = arena.y + arena.h - 200;
game.addChild(player);
}
// Place enemies
function setupEnemies(n) {
for (var i = 0; i < enemies.length; ++i) enemies[i].destroy();
enemies = [];
for (var i = 0; i < n; ++i) {
var e = new Enemy();
// Spawn at top or sides
var edge = Math.floor(Math.random() * 4);
if (edge == 0) {
// top
e.x = arena.x + 200 + Math.random() * (arena.w - 400);
e.y = arena.y + 120;
} else if (edge == 1) {
// left
e.x = arena.x + 120;
e.y = arena.y + 200 + Math.random() * (arena.h - 400);
} else if (edge == 2) {
// right
e.x = arena.x + arena.w - 120;
e.y = arena.y + 200 + Math.random() * (arena.h - 400);
} else {
// bottom
e.x = arena.x + 200 + Math.random() * (arena.w - 400);
e.y = arena.y + arena.h - 120;
}
enemies.push(e);
game.addChild(e);
}
}
// Place powerup
function spawnPowerup() {
var p = new Powerup();
p.x = arena.x + 150 + Math.random() * (arena.w - 300);
p.y = arena.y + 150 + Math.random() * (arena.h - 300);
powerups.push(p);
game.addChild(p);
}
// Start round
function startRound() {
roundActive = true;
roundTimer = 0;
spawnTimer = 0;
setupObstacles();
setupPlayer();
setupEnemies(2 + round);
for (var i = 0; i < bullets.length; ++i) bullets[i].destroy();
bullets = [];
for (var i = 0; i < powerups.length; ++i) powerups[i].destroy();
powerups = [];
roundTxt.setText('Round ' + round);
powerupTxt.setText('');
LK.setScore(LK.getScore());
scoreTxt.setText('Score: ' + LK.getScore());
}
// Utility: circle-rectangle collision
function rectCircleCollide(obs, cx, cy, cr) {
var rx = obs.x - obs.width / 2;
var ry = obs.y - obs.height / 2;
var rw = obs.width;
var rh = obs.height;
var closestX = Math.max(rx, Math.min(cx, rx + rw));
var closestY = Math.max(ry, Math.min(cy, ry + rh));
var dx = cx - closestX;
var dy = cy - closestY;
return dx * dx + dy * dy < cr * cr;
}
// Utility: circle-circle collision
function circleCollide(a, b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
var r = (a.radius || 0) + (b.radius || 0);
return dx * dx + dy * dy < r * r;
}
// Utility: line of sight (returns true if no obstacle blocks)
function canSee(x1, y1, x2, y2) {
for (var i = 0; i < obstacles.length; ++i) {
if (lineRectIntersect(x1, y1, x2, y2, obstacles[i])) return false;
}
return true;
}
// Utility: line-rectangle intersection
function lineRectIntersect(x1, y1, x2, y2, obs) {
var rx = obs.x - obs.width / 2;
var ry = obs.y - obs.height / 2;
var rw = obs.width;
var rh = obs.height;
// Check each side
if (lineLineIntersect(x1, y1, x2, y2, rx, ry, rx + rw, ry)) return true;
if (lineLineIntersect(x1, y1, x2, y2, rx + rw, ry, rx + rw, ry + rh)) return true;
if (lineLineIntersect(x1, y1, x2, y2, rx + rw, ry + rh, rx, ry + rh)) return true;
if (lineLineIntersect(x1, y1, x2, y2, rx, ry + rh, rx, ry)) return true;
return false;
}
// Utility: line-line intersection
function lineLineIntersect(x1, y1, x2, y2, x3, y3, x4, y4) {
var denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
if (denom == 0) return false;
var ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denom;
var ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denom;
return ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1;
}
// Clamp player inside arena and avoid obstacles
function clampPlayer(px, py) {
// Clamp to arena
px = Math.max(arena.x + player.radius, Math.min(arena.x + arena.w - player.radius, px));
py = Math.max(arena.y + player.radius, Math.min(arena.y + arena.h - player.radius, py));
// Avoid obstacles
for (var i = 0; i < obstacles.length; ++i) {
if (rectCircleCollide(obstacles[i], px, py, player.radius)) {
// Push out
var ox = obstacles[i].x;
var oy = obstacles[i].y;
var dx = px - ox;
var dy = py - oy;
var mag = Math.sqrt(dx * dx + dy * dy);
if (mag > 0) {
px = ox + dx / mag * (obstacles[i].width / 2 + player.radius + 2);
py = oy + dy / mag * (obstacles[i].height / 2 + player.radius + 2);
}
}
}
return {
x: px,
y: py
};
}
// Touch controls: drag to move, tap to shoot
game.down = function (x, y, obj) {
if (!player.isAlive) return;
// If touch is near player, start drag
var dx = x - player.x;
var dy = y - player.y;
if (dx * dx + dy * dy < player.radius * player.radius * 2) {
dragNode = player;
} else {
// Else, shoot toward touch
var dirx = x - player.x;
var diry = y - player.y;
player.shoot(dirx, diry);
}
lastTouch.x = x;
lastTouch.y = y;
};
game.move = function (x, y, obj) {
if (!player.isAlive) return;
if (dragNode === player) {
// Move player toward touch, but clamp
var dx = x - player.x;
var dy = y - player.y;
var mag = Math.sqrt(dx * dx + dy * dy);
if (mag > 0) {
var step = Math.min(player.speed, mag);
var nx = player.x + dx / mag * step;
var ny = player.y + dy / mag * step;
var clamped = clampPlayer(nx, ny);
player.x = clamped.x;
player.y = clamped.y;
}
lastTouch.x = x;
lastTouch.y = y;
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
// Main update loop
game.update = function () {
if (!roundActive) {
startRound();
return;
}
if (!player.isAlive) return;
// Update player
player.update();
// Update enemies
for (var i = enemies.length - 1; i >= 0; --i) {
var e = enemies[i];
e.update();
// If dead, remove
if (!e.isAlive && e.alpha <= 0) {
e.destroy();
enemies.splice(i, 1);
LK.setScore(LK.getScore() + 1);
scoreTxt.setText('Score: ' + LK.getScore());
}
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; --i) {
var b = bullets[i];
b.update();
// Remove if out of bounds or expired
if (b.x < arena.x - 100 || b.x > arena.x + arena.w + 100 || b.y < arena.y - 100 || b.y > arena.y + arena.h + 100 || b.lifetime <= 0) {
b.destroy();
bullets.splice(i, 1);
continue;
}
// Collide with obstacles
var hitObs = false;
for (var j = 0; j < obstacles.length; ++j) {
if (rectCircleCollide(obstacles[j], b.x, b.y, b.radius)) {
hitObs = true;
break;
}
}
if (hitObs) {
b.destroy();
bullets.splice(i, 1);
continue;
}
// Collide with enemies (if player bullet)
if (b.isPlayer) {
for (var j = 0; j < enemies.length; ++j) {
if (enemies[j].isAlive && circleCollide(b, enemies[j])) {
enemies[j].hit();
b.destroy();
bullets.splice(i, 1);
break;
}
}
} else {
// Collide with player
if (player.isAlive && circleCollide(b, player)) {
if (player.invincible <= 0) {
player.hit();
}
b.destroy();
bullets.splice(i, 1);
}
}
}
// Update powerups
for (var i = powerups.length - 1; i >= 0; --i) {
var p = powerups[i];
p.update();
if (p.lifetime <= 0) {
p.destroy();
powerups.splice(i, 1);
continue;
}
// Collide with player
if (player.isAlive && circleCollide(p, player)) {
player.powerup();
powerupTxt.setText('Rapid Fire!');
p.destroy();
powerups.splice(i, 1);
}
}
// Spawn powerup occasionally
if (LK.ticks % 600 == 0 && powerups.length == 0) {
spawnPowerup();
}
// End round if all enemies dead
if (enemies.length == 0) {
roundActive = false;
round++;
LK.setTimeout(function () {
startRound();
}, 1200);
}
};
// Initial round
startRound(); ===================================================================
--- original.js
+++ change.js
@@ -1,6 +1,557 @@
-/****
+/****
+* Plugins
+****/
+var tween = LK.import("@upit/tween.v1");
+
+/****
+* Classes
+****/
+// Bullet class
+var Bullet = Container.expand(function () {
+ var self = Container.call(this);
+ var sprite = self.attachAsset('bullet', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.radius = 20;
+ self.dx = 0;
+ self.dy = 0;
+ self.speed = 38;
+ self.isPlayer = false;
+ self.lifetime = 60; // frames
+ self.update = function () {
+ self.x += self.dx * self.speed;
+ self.y += self.dy * self.speed;
+ self.lifetime--;
+ };
+ return self;
+});
+// Enemy class
+var Enemy = Container.expand(function () {
+ var self = Container.call(this);
+ var sprite = self.attachAsset('enemy', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.radius = 55;
+ self.speed = 7 + Math.random() * 3;
+ self.shootCooldown = 0;
+ self.shootDelay = 60 + Math.floor(Math.random() * 30);
+ self.targetX = 0;
+ self.targetY = 0;
+ self.isAlive = true;
+ self.aiTimer = 0;
+ self.update = function () {
+ if (!self.isAlive) return;
+ // AI: Move toward player, avoid obstacles
+ if (self.aiTimer <= 0) {
+ // Pick a new target near player, with some randomness
+ var px = player.x + (Math.random() - 0.5) * 300;
+ var py = player.y + (Math.random() - 0.5) * 300;
+ // Clamp to arena
+ px = Math.max(arena.x + 100, Math.min(arena.x + arena.w - 100, px));
+ py = Math.max(arena.y + 100, Math.min(arena.y + arena.h - 100, py));
+ self.targetX = px;
+ self.targetY = py;
+ self.aiTimer = 30 + Math.floor(Math.random() * 30);
+ } else {
+ self.aiTimer--;
+ }
+ // Move toward target
+ var dx = self.targetX - self.x;
+ var dy = self.targetY - self.y;
+ var mag = Math.sqrt(dx * dx + dy * dy);
+ if (mag > 10) {
+ var mx = dx / mag * self.speed;
+ var my = dy / mag * self.speed;
+ // Try to move, but avoid obstacles
+ var nx = self.x + mx;
+ var ny = self.y + my;
+ var blocked = false;
+ for (var i = 0; i < obstacles.length; ++i) {
+ if (rectCircleCollide(obstacles[i], nx, ny, self.radius)) {
+ blocked = true;
+ break;
+ }
+ }
+ if (!blocked) {
+ self.x = nx;
+ self.y = ny;
+ }
+ }
+ // Shoot at player if in line of sight
+ if (self.shootCooldown > 0) self.shootCooldown--;else {
+ if (canSee(self.x, self.y, player.x, player.y)) {
+ var ddx = player.x - self.x;
+ var ddy = player.y - self.y;
+ var dmag = Math.sqrt(ddx * ddx + ddy * ddy);
+ if (dmag > 0) {
+ var bullet = new Bullet();
+ bullet.x = self.x + ddx / dmag * (self.radius + 30);
+ bullet.y = self.y + ddy / dmag * (self.radius + 30);
+ bullet.dx = ddx / dmag;
+ bullet.dy = ddy / dmag;
+ bullet.isPlayer = false;
+ bullets.push(bullet);
+ game.addChild(bullet);
+ self.shootCooldown = self.shootDelay;
+ }
+ }
+ }
+ };
+ // Take damage
+ self.hit = function () {
+ if (!self.isAlive) return;
+ self.isAlive = false;
+ tween(sprite, {
+ alpha: 0
+ }, {
+ duration: 200,
+ onFinish: function onFinish() {
+ self.destroy();
+ }
+ });
+ };
+ return self;
+});
+// Obstacle class
+var Obstacle = Container.expand(function () {
+ var self = Container.call(this);
+ var sprite = self.attachAsset('obstacle', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.width = sprite.width;
+ self.height = sprite.height;
+ return self;
+});
+// Player class
+var Player = Container.expand(function () {
+ var self = Container.call(this);
+ var sprite = self.attachAsset('player', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.radius = 60;
+ self.speed = 18;
+ self.shootCooldown = 0;
+ self.shootDelay = 18; // frames
+ self.isAlive = true;
+ self.invincible = 0; // frames left of invincibility
+ // For touch drag
+ self.down = function (x, y, obj) {};
+ self.up = function (x, y, obj) {};
+ // For powerup
+ self.powered = false;
+ self.powerTimer = 0;
+ self.update = function () {
+ if (!self.isAlive) return;
+ if (self.invincible > 0) self.invincible--;
+ if (self.powered) {
+ self.powerTimer--;
+ if (self.powerTimer <= 0) {
+ self.powered = false;
+ sprite.tint = 0x2a6cff;
+ }
+ }
+ if (self.shootCooldown > 0) self.shootCooldown--;
+ };
+ // Shoot a bullet in direction (dx, dy)
+ self.shoot = function (dx, dy) {
+ if (!self.isAlive) return;
+ if (self.shootCooldown > 0) return;
+ var mag = Math.sqrt(dx * dx + dy * dy);
+ if (mag < 0.1) return;
+ var bullet = new Bullet();
+ bullet.x = self.x + dx / mag * (self.radius + 30);
+ bullet.y = self.y + dy / mag * (self.radius + 30);
+ bullet.dx = dx / mag;
+ bullet.dy = dy / mag;
+ bullet.isPlayer = true;
+ bullets.push(bullet);
+ game.addChild(bullet);
+ self.shootCooldown = self.powered ? 5 : self.shootDelay;
+ };
+ // Take damage
+ self.hit = function () {
+ if (self.invincible > 0 || !self.isAlive) return;
+ self.isAlive = false;
+ tween(sprite, {
+ alpha: 0
+ }, {
+ duration: 400,
+ onFinish: function onFinish() {
+ LK.effects.flashScreen(0xff0000, 800);
+ LK.showGameOver();
+ }
+ });
+ };
+ // Powerup
+ self.powerup = function () {
+ self.powered = true;
+ self.powerTimer = 360; // 6 seconds
+ sprite.tint = 0x3be14a;
+ };
+ return self;
+});
+// Powerup class
+var Powerup = Container.expand(function () {
+ var self = Container.call(this);
+ var sprite = self.attachAsset('powerup', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.radius = 30;
+ self.type = 'rapid';
+ self.lifetime = 600; // 10 seconds
+ self.update = function () {
+ self.lifetime--;
+ };
+ return self;
+});
+
+/****
* Initialize Game
-****/
+****/
var game = new LK.Game({
- backgroundColor: 0x000000
-});
\ No newline at end of file
+ backgroundColor: 0x181818
+});
+
+/****
+* Game Code
+****/
+// Powerup: green ellipse
+// Obstacle: gray box
+// Bullet: yellow ellipse
+// Enemy: red box
+// Player character: blue box
+// Arena bounds
+var arena = {
+ x: 180,
+ y: 300,
+ w: 2048 - 360,
+ h: 2732 - 600
+};
+// Game state
+var player;
+var enemies = [];
+var bullets = [];
+var obstacles = [];
+var powerups = [];
+var round = 1;
+var roundActive = false;
+var roundTimer = 0;
+var spawnTimer = 0;
+var dragNode = null;
+var lastTouch = {
+ x: 0,
+ y: 0
+};
+var scoreTxt;
+var roundTxt;
+var powerupTxt;
+var gameOver = false;
+// GUI
+scoreTxt = new Text2('Score: 0', {
+ size: 90,
+ fill: 0xFFFFFF
+});
+scoreTxt.anchor.set(0.5, 0);
+LK.gui.top.addChild(scoreTxt);
+roundTxt = new Text2('Round 1', {
+ size: 70,
+ fill: 0xFFCC00
+});
+roundTxt.anchor.set(0.5, 0);
+LK.gui.top.addChild(roundTxt);
+powerupTxt = new Text2('', {
+ size: 60,
+ fill: 0x3BE14A
+});
+powerupTxt.anchor.set(0.5, 0);
+LK.gui.bottom.addChild(powerupTxt);
+// Place obstacles
+function setupObstacles() {
+ for (var i = 0; i < obstacles.length; ++i) obstacles[i].destroy();
+ obstacles = [];
+ // Place 4-6 obstacles randomly
+ var obsCount = 4 + Math.floor(Math.random() * 3);
+ for (var i = 0; i < obsCount; ++i) {
+ var obs = new Obstacle();
+ obs.x = arena.x + 200 + Math.random() * (arena.w - 400);
+ obs.y = arena.y + 200 + Math.random() * (arena.h - 400);
+ if (i % 2 == 0) obs.rotation = Math.PI / 2;
+ obstacles.push(obs);
+ game.addChild(obs);
+ }
+}
+// Place player
+function setupPlayer() {
+ if (player) player.destroy();
+ player = new Player();
+ player.x = arena.x + arena.w / 2;
+ player.y = arena.y + arena.h - 200;
+ game.addChild(player);
+}
+// Place enemies
+function setupEnemies(n) {
+ for (var i = 0; i < enemies.length; ++i) enemies[i].destroy();
+ enemies = [];
+ for (var i = 0; i < n; ++i) {
+ var e = new Enemy();
+ // Spawn at top or sides
+ var edge = Math.floor(Math.random() * 4);
+ if (edge == 0) {
+ // top
+ e.x = arena.x + 200 + Math.random() * (arena.w - 400);
+ e.y = arena.y + 120;
+ } else if (edge == 1) {
+ // left
+ e.x = arena.x + 120;
+ e.y = arena.y + 200 + Math.random() * (arena.h - 400);
+ } else if (edge == 2) {
+ // right
+ e.x = arena.x + arena.w - 120;
+ e.y = arena.y + 200 + Math.random() * (arena.h - 400);
+ } else {
+ // bottom
+ e.x = arena.x + 200 + Math.random() * (arena.w - 400);
+ e.y = arena.y + arena.h - 120;
+ }
+ enemies.push(e);
+ game.addChild(e);
+ }
+}
+// Place powerup
+function spawnPowerup() {
+ var p = new Powerup();
+ p.x = arena.x + 150 + Math.random() * (arena.w - 300);
+ p.y = arena.y + 150 + Math.random() * (arena.h - 300);
+ powerups.push(p);
+ game.addChild(p);
+}
+// Start round
+function startRound() {
+ roundActive = true;
+ roundTimer = 0;
+ spawnTimer = 0;
+ setupObstacles();
+ setupPlayer();
+ setupEnemies(2 + round);
+ for (var i = 0; i < bullets.length; ++i) bullets[i].destroy();
+ bullets = [];
+ for (var i = 0; i < powerups.length; ++i) powerups[i].destroy();
+ powerups = [];
+ roundTxt.setText('Round ' + round);
+ powerupTxt.setText('');
+ LK.setScore(LK.getScore());
+ scoreTxt.setText('Score: ' + LK.getScore());
+}
+// Utility: circle-rectangle collision
+function rectCircleCollide(obs, cx, cy, cr) {
+ var rx = obs.x - obs.width / 2;
+ var ry = obs.y - obs.height / 2;
+ var rw = obs.width;
+ var rh = obs.height;
+ var closestX = Math.max(rx, Math.min(cx, rx + rw));
+ var closestY = Math.max(ry, Math.min(cy, ry + rh));
+ var dx = cx - closestX;
+ var dy = cy - closestY;
+ return dx * dx + dy * dy < cr * cr;
+}
+// Utility: circle-circle collision
+function circleCollide(a, b) {
+ var dx = a.x - b.x;
+ var dy = a.y - b.y;
+ var r = (a.radius || 0) + (b.radius || 0);
+ return dx * dx + dy * dy < r * r;
+}
+// Utility: line of sight (returns true if no obstacle blocks)
+function canSee(x1, y1, x2, y2) {
+ for (var i = 0; i < obstacles.length; ++i) {
+ if (lineRectIntersect(x1, y1, x2, y2, obstacles[i])) return false;
+ }
+ return true;
+}
+// Utility: line-rectangle intersection
+function lineRectIntersect(x1, y1, x2, y2, obs) {
+ var rx = obs.x - obs.width / 2;
+ var ry = obs.y - obs.height / 2;
+ var rw = obs.width;
+ var rh = obs.height;
+ // Check each side
+ if (lineLineIntersect(x1, y1, x2, y2, rx, ry, rx + rw, ry)) return true;
+ if (lineLineIntersect(x1, y1, x2, y2, rx + rw, ry, rx + rw, ry + rh)) return true;
+ if (lineLineIntersect(x1, y1, x2, y2, rx + rw, ry + rh, rx, ry + rh)) return true;
+ if (lineLineIntersect(x1, y1, x2, y2, rx, ry + rh, rx, ry)) return true;
+ return false;
+}
+// Utility: line-line intersection
+function lineLineIntersect(x1, y1, x2, y2, x3, y3, x4, y4) {
+ var denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
+ if (denom == 0) return false;
+ var ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denom;
+ var ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denom;
+ return ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1;
+}
+// Clamp player inside arena and avoid obstacles
+function clampPlayer(px, py) {
+ // Clamp to arena
+ px = Math.max(arena.x + player.radius, Math.min(arena.x + arena.w - player.radius, px));
+ py = Math.max(arena.y + player.radius, Math.min(arena.y + arena.h - player.radius, py));
+ // Avoid obstacles
+ for (var i = 0; i < obstacles.length; ++i) {
+ if (rectCircleCollide(obstacles[i], px, py, player.radius)) {
+ // Push out
+ var ox = obstacles[i].x;
+ var oy = obstacles[i].y;
+ var dx = px - ox;
+ var dy = py - oy;
+ var mag = Math.sqrt(dx * dx + dy * dy);
+ if (mag > 0) {
+ px = ox + dx / mag * (obstacles[i].width / 2 + player.radius + 2);
+ py = oy + dy / mag * (obstacles[i].height / 2 + player.radius + 2);
+ }
+ }
+ }
+ return {
+ x: px,
+ y: py
+ };
+}
+// Touch controls: drag to move, tap to shoot
+game.down = function (x, y, obj) {
+ if (!player.isAlive) return;
+ // If touch is near player, start drag
+ var dx = x - player.x;
+ var dy = y - player.y;
+ if (dx * dx + dy * dy < player.radius * player.radius * 2) {
+ dragNode = player;
+ } else {
+ // Else, shoot toward touch
+ var dirx = x - player.x;
+ var diry = y - player.y;
+ player.shoot(dirx, diry);
+ }
+ lastTouch.x = x;
+ lastTouch.y = y;
+};
+game.move = function (x, y, obj) {
+ if (!player.isAlive) return;
+ if (dragNode === player) {
+ // Move player toward touch, but clamp
+ var dx = x - player.x;
+ var dy = y - player.y;
+ var mag = Math.sqrt(dx * dx + dy * dy);
+ if (mag > 0) {
+ var step = Math.min(player.speed, mag);
+ var nx = player.x + dx / mag * step;
+ var ny = player.y + dy / mag * step;
+ var clamped = clampPlayer(nx, ny);
+ player.x = clamped.x;
+ player.y = clamped.y;
+ }
+ lastTouch.x = x;
+ lastTouch.y = y;
+ }
+};
+game.up = function (x, y, obj) {
+ dragNode = null;
+};
+// Main update loop
+game.update = function () {
+ if (!roundActive) {
+ startRound();
+ return;
+ }
+ if (!player.isAlive) return;
+ // Update player
+ player.update();
+ // Update enemies
+ for (var i = enemies.length - 1; i >= 0; --i) {
+ var e = enemies[i];
+ e.update();
+ // If dead, remove
+ if (!e.isAlive && e.alpha <= 0) {
+ e.destroy();
+ enemies.splice(i, 1);
+ LK.setScore(LK.getScore() + 1);
+ scoreTxt.setText('Score: ' + LK.getScore());
+ }
+ }
+ // Update bullets
+ for (var i = bullets.length - 1; i >= 0; --i) {
+ var b = bullets[i];
+ b.update();
+ // Remove if out of bounds or expired
+ if (b.x < arena.x - 100 || b.x > arena.x + arena.w + 100 || b.y < arena.y - 100 || b.y > arena.y + arena.h + 100 || b.lifetime <= 0) {
+ b.destroy();
+ bullets.splice(i, 1);
+ continue;
+ }
+ // Collide with obstacles
+ var hitObs = false;
+ for (var j = 0; j < obstacles.length; ++j) {
+ if (rectCircleCollide(obstacles[j], b.x, b.y, b.radius)) {
+ hitObs = true;
+ break;
+ }
+ }
+ if (hitObs) {
+ b.destroy();
+ bullets.splice(i, 1);
+ continue;
+ }
+ // Collide with enemies (if player bullet)
+ if (b.isPlayer) {
+ for (var j = 0; j < enemies.length; ++j) {
+ if (enemies[j].isAlive && circleCollide(b, enemies[j])) {
+ enemies[j].hit();
+ b.destroy();
+ bullets.splice(i, 1);
+ break;
+ }
+ }
+ } else {
+ // Collide with player
+ if (player.isAlive && circleCollide(b, player)) {
+ if (player.invincible <= 0) {
+ player.hit();
+ }
+ b.destroy();
+ bullets.splice(i, 1);
+ }
+ }
+ }
+ // Update powerups
+ for (var i = powerups.length - 1; i >= 0; --i) {
+ var p = powerups[i];
+ p.update();
+ if (p.lifetime <= 0) {
+ p.destroy();
+ powerups.splice(i, 1);
+ continue;
+ }
+ // Collide with player
+ if (player.isAlive && circleCollide(p, player)) {
+ player.powerup();
+ powerupTxt.setText('Rapid Fire!');
+ p.destroy();
+ powerups.splice(i, 1);
+ }
+ }
+ // Spawn powerup occasionally
+ if (LK.ticks % 600 == 0 && powerups.length == 0) {
+ spawnPowerup();
+ }
+ // End round if all enemies dead
+ if (enemies.length == 0) {
+ roundActive = false;
+ round++;
+ LK.setTimeout(function () {
+ startRound();
+ }, 1200);
+ }
+};
+// Initial round
+startRound();
\ No newline at end of file