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