User prompt
Please fix the bug: 'RangeError: Maximum call stack size exceeded' in or related to this line: 'return {' Line Number: 199
User prompt
Please fix the bug: 'RangeError: Maximum call stack size exceeded' in or related to this line: 'return {' Line Number: 199
User prompt
Please fix the bug: 'RangeError: Maximum call stack size exceeded' in or related to this line: 'return {' Line Number: 199
User prompt
Please fix the bug: 'RangeError: Maximum call stack size exceeded' in or related to this line: 'return {' Line Number: 199
User prompt
Please fix the bug: 'RangeError: Maximum call stack size exceeded' in or related to this line: 'return {' Line Number: 199
User prompt
Please fix the bug: 'RangeError: Maximum call stack size exceeded' in or related to this line: 'return {' Line Number: 199
User prompt
Please fix the bug: 'RangeError: Maximum call stack size exceeded' in or related to this line: 'return {' Line Number: 199
User prompt
Please fix the bug: 'RangeError: Maximum call stack size exceeded' in or related to this line: 'return {' Line Number: 199
User prompt
Please fix the bug: 'RangeError: Maximum call stack size exceeded' in or related to this line: 'return {' Line Number: 199
User prompt
Please fix the bug: 'RangeError: Maximum call stack size exceeded' in or related to this line: 'return {' Line Number: 199
User prompt
Please fix the bug: 'RangeError: Maximum call stack size exceeded' in or related to this line: 'return {' Line Number: 199
User prompt
Please fix the bug: 'RangeError: Maximum call stack size exceeded' in or related to this line: 'return {' Line Number: 199
User prompt
Please fix the bug: 'RangeError: Maximum call stack size exceeded' in or related to this line: 'return {' Line Number: 199
User prompt
Please fix the bug: 'RangeError: Maximum call stack size exceeded' in or related to this line: 'return {' Line Number: 199
User prompt
Please fix the bug: 'RangeError: Maximum call stack size exceeded' in or related to this line: 'return {' Line Number: 199
User prompt
Please fix the bug: 'RangeError: Maximum call stack size exceeded' in or related to this line: 'return {' Line Number: 198
Code edit (1 edits merged)
Please save this source code
User prompt
Chain Bot Bouncer
Initial prompt
The player controls a robot character. The robot can only move horizontally (left and right). The robot can fire a chain (grappling hook-like weapon) in any direction, based on mouse position (360° aiming). The firing is triggered with the left mouse button. A line indicator will show the direction in which the robot is aiming.There are always 2 bouncing balls: one red and one green. Balls will: Continuously bounce off all surfaces, including: Screen edges Obstacles The player character Each other Balls shrink each time they collide with a chain. When a chain hits a ball, the ball reduces in size but doesn’t disappear immediately.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Ball class: bounces, shrinks on hit, rebounds off everything var Ball = Container.expand(function () { var self = Container.call(this); // Ball type: 'red' or 'green' self.type = 'red'; self.radius = 90; // initial radius self.minRadius = 40; // minimum radius after shrinking self.shrinkAmount = 18; // how much to shrink per hit // Attach asset self.asset = null; self.setType = function (type) { self.type = type; if (self.asset) self.removeChild(self.asset); var assetId = type === 'red' ? 'ballRed' : 'ballGreen'; self.asset = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5, width: self.radius * 2, height: self.radius * 2 }); }; // Set initial type self.setType(self.type); // Physics self.vx = 0; self.vy = 0; self.bounce = 0.98; // energy loss on bounce self.gravity = 1.2; // For collision detection self.getRadius = function () { return self.radius; }; // Shrink ball self.shrink = function () { if (self.radius > self.minRadius) { self.radius -= self.shrinkAmount; if (self.radius < self.minRadius) self.radius = self.minRadius; // Animate shrink tween(self.asset, { width: self.radius * 2, height: self.radius * 2 }, { duration: 180, easing: tween.easeOut }); } }; // Update per frame self.update = function () { // Move self.x += self.vx; self.y += self.vy; // Gravity self.vy += self.gravity; // Bounce off walls if (self.x - self.radius < 0) { self.x = self.radius; self.vx = -self.vx * self.bounce; } if (self.x + self.radius > 2048) { self.x = 2048 - self.radius; self.vx = -self.vx * self.bounce; } // Bounce off floor/ceiling if (self.y - self.radius < 0) { self.y = self.radius; self.vy = -self.vy * self.bounce; } if (self.y + self.radius > 2732) { self.y = 2732 - self.radius; self.vy = -self.vy * self.bounce; } }; return self; }); // Chain class: fires in direction, stops at collision or edge var Chain = Container.expand(function () { var self = Container.call(this); // Chain is a series of segments self.segments = []; self.segmentLength = 64; self.maxSegments = 32; self.dirX = 0; self.dirY = 0; self.originX = 0; self.originY = 0; self.active = true; // For collision self.getTip = function () { if (self.segments.length === 0) return { x: self.originX, y: self.originY }; var last = self.segments[self.segments.length - 1]; return { x: last.x, y: last.y }; }; // Initialize chain self.fire = function (originX, originY, dirX, dirY) { self.originX = originX; self.originY = originY; self.dirX = dirX; self.dirY = dirY; self.segments = []; self.active = true; // Remove old children while (self.children.length) self.removeChild(self.children[0]); // Add first segment var seg = LK.getAsset('chain', { anchorX: 0.5, anchorY: 0.5 }); seg.x = originX; seg.y = originY; self.addChild(seg); self.segments.push(seg); }; // Update per frame self.update = function () { if (!self.active) return; // Add new segment in direction var last = self.segments[self.segments.length - 1]; var nx = last.x + self.dirX * self.segmentLength; var ny = last.y + self.dirY * self.segmentLength; // Stop if out of bounds if (nx < 0 || nx > 2048 || ny < 0 || ny > 2732) { self.active = false; return; } // Add new segment var seg = LK.getAsset('chain', { anchorX: 0.5, anchorY: 0.5 }); seg.x = nx; seg.y = ny; self.addChild(seg); self.segments.push(seg); // Limit length if (self.segments.length > self.maxSegments) { self.active = false; } }; // Remove all segments self.clear = function () { while (self.children.length) self.removeChild(self.children[0]); self.segments = []; self.active = false; }; return self; }); // Robot class: moves left/right, fires chain var Robot = Container.expand(function () { var self = Container.call(this); self.asset = self.attachAsset('robot', { anchorX: 0.5, anchorY: 0.5 }); self.width = self.asset.width; self.height = self.asset.height; // For movement self.speed = 0; self.maxSpeed = 32; self.targetX = 1024; // For collision self.getBounds = function () { return { left: self.x - self.width / 2, right: self.x + self.width / 2, top: self.y - self.height / 2, bottom: self.y + self.height / 2 }; }; // Update per frame self.update = function () { // Move towards targetX var dx = self.targetX - self.x; if (Math.abs(dx) > 8) { self.speed = Math.max(-self.maxSpeed, Math.min(self.maxSpeed, dx * 0.25)); self.x += self.speed; } else { self.x = self.targetX; self.speed = 0; } // Clamp to screen if (self.x < self.width / 2) self.x = self.width / 2; if (self.x > 2048 - self.width / 2) self.x = 2048 - self.width / 2; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181818 }); /**** * Game Code ****/ // Aiming line: white box (thin, will be scaled) // Chain: yellow box (thin) // Ball: green // Ball: red // Robot: blue box // --- Game variables --- var robot = new Robot(); var balls = []; var chain = new Chain(); var aimLine = null; var isAiming = false; var aimStart = { x: 0, y: 0 }; var aimEnd = { x: 0, y: 0 }; var canFire = true; var fireCooldown = 18; // frames between shots var fireTimer = 0; var score = 0; var scoreTxt = null; // --- Add robot --- robot.x = 1024; robot.y = 2732 - 120; game.addChild(robot); // --- Add balls --- var ball1 = new Ball(); ball1.setType('red'); ball1.radius = 90; ball1.asset.width = ball1.radius * 2; ball1.asset.height = ball1.radius * 2; ball1.x = 600; ball1.y = 800; ball1.vx = 18; ball1.vy = -22; balls.push(ball1); game.addChild(ball1); var ball2 = new Ball(); ball2.setType('green'); ball2.radius = 90; ball2.asset.width = ball2.radius * 2; ball2.asset.height = ball2.radius * 2; ball2.x = 1448; ball2.y = 900; ball2.vx = -16; ball2.vy = -18; balls.push(ball2); game.addChild(ball2); // --- Add chain (hidden at start) --- game.addChild(chain); // --- Score text --- scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // --- Aiming line --- function updateAimLine() { if (!aimLine) { aimLine = LK.getAsset('aimLine', { anchorX: 0, anchorY: 0.5 }); aimLine.alpha = 0.5; game.addChild(aimLine); } var dx = aimEnd.x - aimStart.x; var dy = aimEnd.y - aimStart.y; var len = Math.sqrt(dx * dx + dy * dy); if (len < 80) len = 80; // minimum length aimLine.x = aimStart.x; aimLine.y = aimStart.y; aimLine.width = len; aimLine.height = 16; aimLine.rotation = Math.atan2(dy, dx); aimLine.visible = true; } function hideAimLine() { if (aimLine) aimLine.visible = false; } // --- Input handling --- game.down = function (x, y, obj) { // Only allow aiming if touch is in lower 1/3 of screen if (y > 2732 - 600) { isAiming = true; aimStart.x = robot.x; aimStart.y = robot.y - robot.height / 2; aimEnd.x = x; aimEnd.y = y; updateAimLine(); } }; game.move = function (x, y, obj) { if (isAiming) { // Update aim direction aimEnd.x = x; aimEnd.y = y; updateAimLine(); } else { // Move robot left/right only robot.targetX = x; } }; game.up = function (x, y, obj) { if (isAiming && canFire) { // Fire chain in direction var dx = aimEnd.x - aimStart.x; var dy = aimEnd.y - aimStart.y; var len = Math.sqrt(dx * dx + dy * dy); if (len > 80) { var dirX = dx / len; var dirY = dy / len; chain.clear(); chain.fire(aimStart.x, aimStart.y, dirX, dirY); canFire = false; fireTimer = fireCooldown; } } isAiming = false; hideAimLine(); }; // --- Ball-ball collision --- function ballsCollide(b1, b2) { var dx = b1.x - b2.x; var dy = b1.y - b2.y; var dist = Math.sqrt(dx * dx + dy * dy); return dist < b1.radius + b2.radius; } function resolveBallCollision(b1, b2) { var dx = b1.x - b2.x; var dy = b1.y - b2.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist === 0) return; var overlap = b1.radius + b2.radius - dist; if (overlap > 0) { // Push balls apart var nx = dx / dist; var ny = dy / dist; b1.x += nx * (overlap / 2); b1.y += ny * (overlap / 2); b2.x -= nx * (overlap / 2); b2.y -= ny * (overlap / 2); // Exchange velocities (elastic) var tx = nx; var ty = ny; var v1 = b1.vx * tx + b1.vy * ty; var v2 = b2.vx * tx + b2.vy * ty; var m1 = b1.radius; var m2 = b2.radius; var newV1 = (v1 * (m1 - m2) + 2 * m2 * v2) / (m1 + m2); var newV2 = (v2 * (m2 - m1) + 2 * m1 * v1) / (m1 + m2); b1.vx += (newV1 - v1) * tx; b1.vy += (newV1 - v1) * ty; b2.vx += (newV2 - v2) * tx; b2.vy += (newV2 - v2) * ty; } } // --- Ball-robot collision --- function ballHitsRobot(ball, robot) { var bounds = robot.getBounds(); // Closest point on robot to ball center var cx = Math.max(bounds.left, Math.min(ball.x, bounds.right)); var cy = Math.max(bounds.top, Math.min(ball.y, bounds.bottom)); var dx = ball.x - cx; var dy = ball.y - cy; var dist = Math.sqrt(dx * dx + dy * dy); return dist < ball.radius; } function resolveBallRobotCollision(ball, robot) { var bounds = robot.getBounds(); var cx = Math.max(bounds.left, Math.min(ball.x, bounds.right)); var cy = Math.max(bounds.top, Math.min(ball.y, bounds.bottom)); var dx = ball.x - cx; var dy = ball.y - cy; var dist = Math.sqrt(dx * dx + dy * dy); if (dist === 0) return; var overlap = ball.radius - dist; if (overlap > 0) { // Push ball out var nx = dx / dist; var ny = dy / dist; ball.x += nx * overlap; ball.y += ny * overlap; // Reflect velocity var dot = ball.vx * nx + ball.vy * ny; ball.vx -= 2 * dot * nx; ball.vy -= 2 * dot * ny; ball.vx *= 0.9; ball.vy *= 0.9; } } // --- Chain-ball collision --- function chainHitsBall(chain, ball) { // Check each segment for (var i = 0; i < chain.segments.length; ++i) { var seg = chain.segments[i]; var dx = seg.x - ball.x; var dy = seg.y - ball.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < ball.radius) { return true; } } return false; } // --- Chain stops at first collision with ball or wall --- function updateChainCollisions() { if (!chain.active) return; for (var i = 0; i < balls.length; ++i) { if (chainHitsBall(chain, balls[i])) { balls[i].shrink(); score += 1; scoreTxt.setText(score); // Flash ball LK.effects.flashObject(balls[i], 0xffff00, 200); chain.active = false; break; } } } // --- Game update loop --- game.update = function () { // Update robot robot.update(); // Update balls for (var i = 0; i < balls.length; ++i) { balls[i].update(); } // Ball-ball collision if (balls.length >= 2) { if (ballsCollide(balls[0], balls[1])) { resolveBallCollision(balls[0], balls[1]); } } // Ball-robot collision for (var i = 0; i < balls.length; ++i) { if (ballHitsRobot(balls[i], robot)) { resolveBallRobotCollision(balls[i], robot); // Game over LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); return; } } // Update chain if (chain.active) { chain.update(); updateChainCollisions(); } // Fire cooldown if (!canFire) { fireTimer--; if (fireTimer <= 0) { canFire = true; } } };
===================================================================
--- original.js
+++ change.js
@@ -1,6 +1,472 @@
-/****
+/****
+* Plugins
+****/
+var tween = LK.import("@upit/tween.v1");
+
+/****
+* Classes
+****/
+// Ball class: bounces, shrinks on hit, rebounds off everything
+var Ball = Container.expand(function () {
+ var self = Container.call(this);
+ // Ball type: 'red' or 'green'
+ self.type = 'red';
+ self.radius = 90; // initial radius
+ self.minRadius = 40; // minimum radius after shrinking
+ self.shrinkAmount = 18; // how much to shrink per hit
+ // Attach asset
+ self.asset = null;
+ self.setType = function (type) {
+ self.type = type;
+ if (self.asset) self.removeChild(self.asset);
+ var assetId = type === 'red' ? 'ballRed' : 'ballGreen';
+ self.asset = self.attachAsset(assetId, {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ width: self.radius * 2,
+ height: self.radius * 2
+ });
+ };
+ // Set initial type
+ self.setType(self.type);
+ // Physics
+ self.vx = 0;
+ self.vy = 0;
+ self.bounce = 0.98; // energy loss on bounce
+ self.gravity = 1.2;
+ // For collision detection
+ self.getRadius = function () {
+ return self.radius;
+ };
+ // Shrink ball
+ self.shrink = function () {
+ if (self.radius > self.minRadius) {
+ self.radius -= self.shrinkAmount;
+ if (self.radius < self.minRadius) self.radius = self.minRadius;
+ // Animate shrink
+ tween(self.asset, {
+ width: self.radius * 2,
+ height: self.radius * 2
+ }, {
+ duration: 180,
+ easing: tween.easeOut
+ });
+ }
+ };
+ // Update per frame
+ self.update = function () {
+ // Move
+ self.x += self.vx;
+ self.y += self.vy;
+ // Gravity
+ self.vy += self.gravity;
+ // Bounce off walls
+ if (self.x - self.radius < 0) {
+ self.x = self.radius;
+ self.vx = -self.vx * self.bounce;
+ }
+ if (self.x + self.radius > 2048) {
+ self.x = 2048 - self.radius;
+ self.vx = -self.vx * self.bounce;
+ }
+ // Bounce off floor/ceiling
+ if (self.y - self.radius < 0) {
+ self.y = self.radius;
+ self.vy = -self.vy * self.bounce;
+ }
+ if (self.y + self.radius > 2732) {
+ self.y = 2732 - self.radius;
+ self.vy = -self.vy * self.bounce;
+ }
+ };
+ return self;
+});
+// Chain class: fires in direction, stops at collision or edge
+var Chain = Container.expand(function () {
+ var self = Container.call(this);
+ // Chain is a series of segments
+ self.segments = [];
+ self.segmentLength = 64;
+ self.maxSegments = 32;
+ self.dirX = 0;
+ self.dirY = 0;
+ self.originX = 0;
+ self.originY = 0;
+ self.active = true;
+ // For collision
+ self.getTip = function () {
+ if (self.segments.length === 0) return {
+ x: self.originX,
+ y: self.originY
+ };
+ var last = self.segments[self.segments.length - 1];
+ return {
+ x: last.x,
+ y: last.y
+ };
+ };
+ // Initialize chain
+ self.fire = function (originX, originY, dirX, dirY) {
+ self.originX = originX;
+ self.originY = originY;
+ self.dirX = dirX;
+ self.dirY = dirY;
+ self.segments = [];
+ self.active = true;
+ // Remove old children
+ while (self.children.length) self.removeChild(self.children[0]);
+ // Add first segment
+ var seg = LK.getAsset('chain', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ seg.x = originX;
+ seg.y = originY;
+ self.addChild(seg);
+ self.segments.push(seg);
+ };
+ // Update per frame
+ self.update = function () {
+ if (!self.active) return;
+ // Add new segment in direction
+ var last = self.segments[self.segments.length - 1];
+ var nx = last.x + self.dirX * self.segmentLength;
+ var ny = last.y + self.dirY * self.segmentLength;
+ // Stop if out of bounds
+ if (nx < 0 || nx > 2048 || ny < 0 || ny > 2732) {
+ self.active = false;
+ return;
+ }
+ // Add new segment
+ var seg = LK.getAsset('chain', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ seg.x = nx;
+ seg.y = ny;
+ self.addChild(seg);
+ self.segments.push(seg);
+ // Limit length
+ if (self.segments.length > self.maxSegments) {
+ self.active = false;
+ }
+ };
+ // Remove all segments
+ self.clear = function () {
+ while (self.children.length) self.removeChild(self.children[0]);
+ self.segments = [];
+ self.active = false;
+ };
+ return self;
+});
+// Robot class: moves left/right, fires chain
+var Robot = Container.expand(function () {
+ var self = Container.call(this);
+ self.asset = self.attachAsset('robot', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.width = self.asset.width;
+ self.height = self.asset.height;
+ // For movement
+ self.speed = 0;
+ self.maxSpeed = 32;
+ self.targetX = 1024;
+ // For collision
+ self.getBounds = function () {
+ return {
+ left: self.x - self.width / 2,
+ right: self.x + self.width / 2,
+ top: self.y - self.height / 2,
+ bottom: self.y + self.height / 2
+ };
+ };
+ // Update per frame
+ self.update = function () {
+ // Move towards targetX
+ var dx = self.targetX - self.x;
+ if (Math.abs(dx) > 8) {
+ self.speed = Math.max(-self.maxSpeed, Math.min(self.maxSpeed, dx * 0.25));
+ self.x += self.speed;
+ } else {
+ self.x = self.targetX;
+ self.speed = 0;
+ }
+ // Clamp to screen
+ if (self.x < self.width / 2) self.x = self.width / 2;
+ if (self.x > 2048 - self.width / 2) self.x = 2048 - self.width / 2;
+ };
+ return self;
+});
+
+/****
* Initialize Game
-****/
+****/
var game = new LK.Game({
- backgroundColor: 0x000000
-});
\ No newline at end of file
+ backgroundColor: 0x181818
+});
+
+/****
+* Game Code
+****/
+// Aiming line: white box (thin, will be scaled)
+// Chain: yellow box (thin)
+// Ball: green
+// Ball: red
+// Robot: blue box
+// --- Game variables ---
+var robot = new Robot();
+var balls = [];
+var chain = new Chain();
+var aimLine = null;
+var isAiming = false;
+var aimStart = {
+ x: 0,
+ y: 0
+};
+var aimEnd = {
+ x: 0,
+ y: 0
+};
+var canFire = true;
+var fireCooldown = 18; // frames between shots
+var fireTimer = 0;
+var score = 0;
+var scoreTxt = null;
+// --- Add robot ---
+robot.x = 1024;
+robot.y = 2732 - 120;
+game.addChild(robot);
+// --- Add balls ---
+var ball1 = new Ball();
+ball1.setType('red');
+ball1.radius = 90;
+ball1.asset.width = ball1.radius * 2;
+ball1.asset.height = ball1.radius * 2;
+ball1.x = 600;
+ball1.y = 800;
+ball1.vx = 18;
+ball1.vy = -22;
+balls.push(ball1);
+game.addChild(ball1);
+var ball2 = new Ball();
+ball2.setType('green');
+ball2.radius = 90;
+ball2.asset.width = ball2.radius * 2;
+ball2.asset.height = ball2.radius * 2;
+ball2.x = 1448;
+ball2.y = 900;
+ball2.vx = -16;
+ball2.vy = -18;
+balls.push(ball2);
+game.addChild(ball2);
+// --- Add chain (hidden at start) ---
+game.addChild(chain);
+// --- Score text ---
+scoreTxt = new Text2('0', {
+ size: 120,
+ fill: 0xFFFFFF
+});
+scoreTxt.anchor.set(0.5, 0);
+LK.gui.top.addChild(scoreTxt);
+// --- Aiming line ---
+function updateAimLine() {
+ if (!aimLine) {
+ aimLine = LK.getAsset('aimLine', {
+ anchorX: 0,
+ anchorY: 0.5
+ });
+ aimLine.alpha = 0.5;
+ game.addChild(aimLine);
+ }
+ var dx = aimEnd.x - aimStart.x;
+ var dy = aimEnd.y - aimStart.y;
+ var len = Math.sqrt(dx * dx + dy * dy);
+ if (len < 80) len = 80; // minimum length
+ aimLine.x = aimStart.x;
+ aimLine.y = aimStart.y;
+ aimLine.width = len;
+ aimLine.height = 16;
+ aimLine.rotation = Math.atan2(dy, dx);
+ aimLine.visible = true;
+}
+function hideAimLine() {
+ if (aimLine) aimLine.visible = false;
+}
+// --- Input handling ---
+game.down = function (x, y, obj) {
+ // Only allow aiming if touch is in lower 1/3 of screen
+ if (y > 2732 - 600) {
+ isAiming = true;
+ aimStart.x = robot.x;
+ aimStart.y = robot.y - robot.height / 2;
+ aimEnd.x = x;
+ aimEnd.y = y;
+ updateAimLine();
+ }
+};
+game.move = function (x, y, obj) {
+ if (isAiming) {
+ // Update aim direction
+ aimEnd.x = x;
+ aimEnd.y = y;
+ updateAimLine();
+ } else {
+ // Move robot left/right only
+ robot.targetX = x;
+ }
+};
+game.up = function (x, y, obj) {
+ if (isAiming && canFire) {
+ // Fire chain in direction
+ var dx = aimEnd.x - aimStart.x;
+ var dy = aimEnd.y - aimStart.y;
+ var len = Math.sqrt(dx * dx + dy * dy);
+ if (len > 80) {
+ var dirX = dx / len;
+ var dirY = dy / len;
+ chain.clear();
+ chain.fire(aimStart.x, aimStart.y, dirX, dirY);
+ canFire = false;
+ fireTimer = fireCooldown;
+ }
+ }
+ isAiming = false;
+ hideAimLine();
+};
+// --- Ball-ball collision ---
+function ballsCollide(b1, b2) {
+ var dx = b1.x - b2.x;
+ var dy = b1.y - b2.y;
+ var dist = Math.sqrt(dx * dx + dy * dy);
+ return dist < b1.radius + b2.radius;
+}
+function resolveBallCollision(b1, b2) {
+ var dx = b1.x - b2.x;
+ var dy = b1.y - b2.y;
+ var dist = Math.sqrt(dx * dx + dy * dy);
+ if (dist === 0) return;
+ var overlap = b1.radius + b2.radius - dist;
+ if (overlap > 0) {
+ // Push balls apart
+ var nx = dx / dist;
+ var ny = dy / dist;
+ b1.x += nx * (overlap / 2);
+ b1.y += ny * (overlap / 2);
+ b2.x -= nx * (overlap / 2);
+ b2.y -= ny * (overlap / 2);
+ // Exchange velocities (elastic)
+ var tx = nx;
+ var ty = ny;
+ var v1 = b1.vx * tx + b1.vy * ty;
+ var v2 = b2.vx * tx + b2.vy * ty;
+ var m1 = b1.radius;
+ var m2 = b2.radius;
+ var newV1 = (v1 * (m1 - m2) + 2 * m2 * v2) / (m1 + m2);
+ var newV2 = (v2 * (m2 - m1) + 2 * m1 * v1) / (m1 + m2);
+ b1.vx += (newV1 - v1) * tx;
+ b1.vy += (newV1 - v1) * ty;
+ b2.vx += (newV2 - v2) * tx;
+ b2.vy += (newV2 - v2) * ty;
+ }
+}
+// --- Ball-robot collision ---
+function ballHitsRobot(ball, robot) {
+ var bounds = robot.getBounds();
+ // Closest point on robot to ball center
+ var cx = Math.max(bounds.left, Math.min(ball.x, bounds.right));
+ var cy = Math.max(bounds.top, Math.min(ball.y, bounds.bottom));
+ var dx = ball.x - cx;
+ var dy = ball.y - cy;
+ var dist = Math.sqrt(dx * dx + dy * dy);
+ return dist < ball.radius;
+}
+function resolveBallRobotCollision(ball, robot) {
+ var bounds = robot.getBounds();
+ var cx = Math.max(bounds.left, Math.min(ball.x, bounds.right));
+ var cy = Math.max(bounds.top, Math.min(ball.y, bounds.bottom));
+ var dx = ball.x - cx;
+ var dy = ball.y - cy;
+ var dist = Math.sqrt(dx * dx + dy * dy);
+ if (dist === 0) return;
+ var overlap = ball.radius - dist;
+ if (overlap > 0) {
+ // Push ball out
+ var nx = dx / dist;
+ var ny = dy / dist;
+ ball.x += nx * overlap;
+ ball.y += ny * overlap;
+ // Reflect velocity
+ var dot = ball.vx * nx + ball.vy * ny;
+ ball.vx -= 2 * dot * nx;
+ ball.vy -= 2 * dot * ny;
+ ball.vx *= 0.9;
+ ball.vy *= 0.9;
+ }
+}
+// --- Chain-ball collision ---
+function chainHitsBall(chain, ball) {
+ // Check each segment
+ for (var i = 0; i < chain.segments.length; ++i) {
+ var seg = chain.segments[i];
+ var dx = seg.x - ball.x;
+ var dy = seg.y - ball.y;
+ var dist = Math.sqrt(dx * dx + dy * dy);
+ if (dist < ball.radius) {
+ return true;
+ }
+ }
+ return false;
+}
+// --- Chain stops at first collision with ball or wall ---
+function updateChainCollisions() {
+ if (!chain.active) return;
+ for (var i = 0; i < balls.length; ++i) {
+ if (chainHitsBall(chain, balls[i])) {
+ balls[i].shrink();
+ score += 1;
+ scoreTxt.setText(score);
+ // Flash ball
+ LK.effects.flashObject(balls[i], 0xffff00, 200);
+ chain.active = false;
+ break;
+ }
+ }
+}
+// --- Game update loop ---
+game.update = function () {
+ // Update robot
+ robot.update();
+ // Update balls
+ for (var i = 0; i < balls.length; ++i) {
+ balls[i].update();
+ }
+ // Ball-ball collision
+ if (balls.length >= 2) {
+ if (ballsCollide(balls[0], balls[1])) {
+ resolveBallCollision(balls[0], balls[1]);
+ }
+ }
+ // Ball-robot collision
+ for (var i = 0; i < balls.length; ++i) {
+ if (ballHitsRobot(balls[i], robot)) {
+ resolveBallRobotCollision(balls[i], robot);
+ // Game over
+ LK.effects.flashScreen(0xff0000, 1000);
+ LK.showGameOver();
+ return;
+ }
+ }
+ // Update chain
+ if (chain.active) {
+ chain.update();
+ updateChainCollisions();
+ }
+ // Fire cooldown
+ if (!canFire) {
+ fireTimer--;
+ if (fireTimer <= 0) {
+ canFire = true;
+ }
+ }
+};
\ No newline at end of file