User prompt
When the scoreboard reaches 100, the game should stop and a GAME OVER screen should appear. The score display should remain at 100.
User prompt
Oyuna bir baslangıc muzigi ve normal akıs muzigi ve bir kapanıs muzigi ekleyecegim ayrı ayrı ac
User prompt
Remove and delete one (2H) and one (3H) ball from the game.
User prompt
Make sure that when the (2H) and (3H) balls collide on the screen, the collision is counted on the scoreboard.
User prompt
If the maximum limit of 100 is reached, the game is over and the system stops counting
User prompt
Duplicate each ball once
User prompt
Set initial ball size to 3000x3000 and update initial velocities ✅ Allow balls to keep splitting until their size resets to zero ✅ Allow balls to keep splitting until their size resets to zero (chain splitter logic in game.update)
User prompt
Set the initial size of the balls to 4000x4000. They keep splitting until their size resets to zero.
User prompt
H2 name change with 2H
User prompt
The screen layer will stay at the very back of the display.
User prompt
The screen assets will remain visible from the start of the game until game over. After 100 collisions, the scoreboard will stop counting.
User prompt
When there are no balls left in the game, "YOU WIN!" should appear on the screen. If the balls collide 100 times, "GAME OVER" should be displayed. The screen should also show the message: "Max limit 100". A new image asset named screen will be created. This image will cover the entire screen.
User prompt
Balls should be able to shrink down to 160x160 pixels. If they are hit again after reaching this size, they should be destroyed.
User prompt
The ball still keeps colliding with the first line I drew, even though that line is no longer visible or supposed to exist.
User prompt
Previously drawn lines are still affecting the balls, but they shouldn't. A line's effect should be valid only during the moment it is being drawn, and it should be removed right after.
User prompt
The aim line is only valid while it is being drawn. When a new line is drawn, the previous line has no effect.
User prompt
Initialize special explosion sound asset
User prompt
Give the robot a bow in its hand. The robot can use the bow to destroy the smallest balls.
User prompt
Right mouse button is no longer active.Why?
User prompt
Update and clean up atom splitters in game update loop
User prompt
Display the text 'Atom collisions scoreboard' on the screen scoreboard. Each time the red or green ball collides, add 1 point to the scoreboard
User prompt
When the right mouse button is pressed, the game continues running and a sound plays
User prompt
When the right mouse button is pressed, the robot will destroy the balls directly beneath it within a 250x250 area. Only the nearest balls that are touching the robot will be destroyed, and a sound will play for each ball that gets destroyed.
User prompt
Balls should be more dynamic and bounce off everything they hit.
User prompt
The robot can destroy any ball with the right mouse button, but only if the ball has already been split and shrunk 3 times.
/**** * 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 self.shrinkCount = 0; // Tracks how many times shrink() has been called // 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 () { self.shrinkCount++; // Increment count for every shrink attempt/hit 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 screen boundaries (walls and corners) // Horizontal bounces if (self.x - self.radius < 0) { self.x = self.radius; self.vx = -self.vx * self.bounce; } else if (self.x + self.radius > 2048) { self.x = 2048 - self.radius; self.vx = -self.vx * self.bounce; } // Vertical bounces if (self.y - self.radius < 0) { self.y = self.radius; self.vy = -self.vy * self.bounce; } else if (self.y + self.radius > 2732) { self.y = 2732 - self.radius; self.vy = -self.vy * self.bounce; } // --- Prevent balls from ever crossing any line drawn by the left mouse button --- // We'll store all drawn lines in a global array: drawnLines if (typeof drawnLines !== "undefined" && drawnLines && drawnLines.length > 0) { for (var i = 0; i < drawnLines.length; ++i) { var line = drawnLines[i]; // Each line has: {x1, y1, x2, y2} var dx = line.x2 - line.x1; var dy = line.y2 - line.y1; var len = Math.sqrt(dx * dx + dy * dy); if (len === 0) continue; var nx = dx / len; var ny = dy / len; // Project ball center onto the line var px = self.x - line.x1; var py = self.y - line.y1; var proj = px * nx + py * ny; // Clamp projection to the line segment if (proj < 0) proj = 0; if (proj > len) proj = len; // Closest point on the line segment var closestX = line.x1 + nx * proj; var closestY = line.y1 + ny * proj; var distX = self.x - closestX; var distY = self.y - closestY; var dist = Math.sqrt(distX * distX + distY * distY); if (dist < self.radius) { // Ball is overlapping the line, push it out and reflect velocity var overlap = self.radius - dist; // Defensive: avoid division by zero if (dist === 0) { distX = 1; distY = 0; dist = 1; } var nnx = distX / dist; var nny = distY / dist; self.x += nnx * overlap; self.y += nny * overlap; // Project velocity onto normal var dot = self.vx * nnx + self.vy * nny; // Reflect velocity (elastic bounce) self.vx -= 2 * dot * nnx; self.vy -= 2 * dot * nny; // Dampen to simulate energy loss (same as ball-ball) self.vx *= self.bounce; self.vy *= self.bounce; } } } // --- Prevent balls from ever crossing the aimLine and bounce off it like a wall --- // Only enforce if aimLine exists and is visible if (typeof aimLine !== "undefined" && aimLine && aimLine.visible) { // Calculate the normal vector of the aimLine var dx = Math.cos(aimLine.rotation); var dy = Math.sin(aimLine.rotation); // Vector from aimLine start to ball center var px = self.x - aimLine.x; var py = self.y - aimLine.y; // Project vector onto aimLine direction to get closest point on the line var proj = px * dx + py * dy; // Clamp projection to the aimLine segment var len = aimLine.width; if (proj < 0) proj = 0; if (proj > len) proj = len; // Closest point on the aimLine var closestX = aimLine.x + dx * proj; var closestY = aimLine.y + dy * proj; // Distance from ball center to closest point var distX = self.x - closestX; var distY = self.y - closestY; var dist = Math.sqrt(distX * distX + distY * distY); if (dist < self.radius) { // Ball is overlapping the aimLine, push it out and reflect velocity var overlap = self.radius - dist; // Defensive: avoid division by zero if (dist === 0) { distX = 1; distY = 0; dist = 1; } var nx = distX / dist; var ny = distY / dist; self.x += nx * overlap; self.y += ny * overlap; // Project velocity onto normal var dot = self.vx * nx + self.vy * ny; // Reflect velocity (elastic bounce) self.vx -= 2 * dot * nx; self.vy -= 2 * dot * ny; // Dampen to simulate energy loss (same as ball-ball) self.vx *= self.bounce; 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; // Defensive: Only proceed if there is at least one segment if (self.segments.length === 0) 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 // 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 ****/ // --- Game variables --- // Robot: blue box // Play intro music at game start LK.playMusic('introMusic', { loop: false }); // Ball: red // Ball: green // Chain: yellow box (thin) // Aiming line: white box (thin, will be scaled) 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); // After intro music ends, start main music (simulate with timeout, since no event system) LK.setTimeout(function () { LK.playMusic('mainMusic', { loop: true }); }, 4000); // Adjust 4000ms to match your intro music length // --- Add balls --- var ball1 = new Ball(); ball1.setType('red'); ball1.radius = 160; // enlarged radius ball1.asset.width = ball1.radius * 2; ball1.asset.height = ball1.radius * 2; ball1.x = 600; ball1.y = 800; // Set initial velocity to make the ball start moving ball1.vx = 18; ball1.vy = -22; // Only add one red ball (remove this one) // balls.push(ball1); // game.addChild(ball1); // Duplicate ball1 (keep only one red ball) var ball1b = new Ball(); ball1b.setType('red'); ball1b.radius = 160; ball1b.asset.width = ball1b.radius * 2; ball1b.asset.height = ball1b.radius * 2; ball1b.x = 600 + 120; // offset to avoid overlap ball1b.y = 800 + 120; ball1b.vx = 18; ball1b.vy = -22; balls.push(ball1b); game.addChild(ball1b); var ball2 = new Ball(); ball2.setType('green'); ball2.radius = 160; // enlarged radius ball2.asset.width = ball2.radius * 2; ball2.asset.height = ball2.radius * 2; ball2.x = 1448; ball2.y = 900; // Set initial velocity to make the ball start moving ball2.vx = -16; ball2.vy = -18; // Only add one green ball (remove this one) // balls.push(ball2); // game.addChild(ball2); // Duplicate ball2 (keep only one green ball) var ball2b = new Ball(); ball2b.setType('green'); ball2b.radius = 160; ball2b.asset.width = ball2b.radius * 2; ball2b.asset.height = ball2b.radius * 2; ball2b.x = 1448 - 120; // offset to avoid overlap ball2b.y = 900 + 120; ball2b.vx = -16; ball2b.vy = -18; balls.push(ball2b); game.addChild(ball2b); // --- Add chain (hidden at start) --- game.addChild(chain); // --- Score text --- // Scoreboard label var scoreboardLabel = new Text2('²H collisions scoreboard', { size: 64, fill: 0xFFFFFF }); scoreboardLabel.anchor.set(0.5, 0); LK.gui.top.addChild(scoreboardLabel); scoreboardLabel.y = 0; // Score text scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); scoreTxt.y = scoreboardLabel.height + 10; LK.gui.top.addChild(scoreTxt); // --- Store all drawn lines globally --- var drawnLines = []; // --- 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) { // Handle aiming or ball destruction based on tap location if (y > 2732 - 600) { // Aiming zone (lower part of screen) isAiming = true; aimStart.x = robot.x; aimStart.y = robot.y - robot.height / 2; aimEnd.x = x; aimEnd.y = y; updateAimLine(); } else { // Destruction zone (upper part of screen - our "right-click" equivalent) // When the "right mouse button" is pressed, the game continues running and a sound plays. LK.getSound('shoot').play(); } }; 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; } // Add the drawn line to drawnLines array if it is long enough if (len > 80) { drawnLines.push({ x1: aimStart.x, y1: aimStart.y, x2: aimEnd.x, y2: aimEnd.y }); } // Remove all but the most recent line (keep only the last one) if (drawnLines.length > 1) { drawnLines.splice(0, drawnLines.length - 1); } } isAiming = false; hideAimLine(); }; // Helper function to get distance to robot if a ball is touching it function getDistanceToRobotIfTouching(ball, robotInstance) { var bounds = robotInstance.getBounds(); // Closest point on robot's bounding box 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 dxBallToRobotRect = ball.x - cx; var dyBallToRobotRect = ball.y - cy; var distSquared = dxBallToRobotRect * dxBallToRobotRect + dyBallToRobotRect * dyBallToRobotRect; var dist = Math.sqrt(distSquared); // A ball is touching if the distance from its center to the robot's rectangle // is less than its radius. if (dist < ball.getRadius()) { return dist; // Return the actual distance } return Infinity; // Not touching } // --- 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])) { var hitBall = balls[i]; // --- Chain acts as a barrier: bounce ball off chain, do not allow pass-through --- // Find the closest segment that collides var closestSeg = null; var minDist = Infinity; for (var j = 0; j < chain.segments.length; ++j) { var seg = chain.segments[j]; var dx = seg.x - hitBall.x; var dy = seg.y - hitBall.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < hitBall.radius && dist < minDist) { minDist = dist; closestSeg = seg; } } if (closestSeg) { // Push ball out of the chain segment and reflect velocity (barrier bounce) var dx = hitBall.x - closestSeg.x; var dy = hitBall.y - closestSeg.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist === 0) { // Avoid division by zero, nudge dx = 1; dy = 0; dist = 1; } var overlap = hitBall.radius - dist; if (overlap > 0) { var nx = dx / dist; var ny = dy / dist; // Move ball out of the chain hitBall.x += nx * overlap; hitBall.y += ny * overlap; // --- Ball bounce off chain, just like ball-ball collision --- // Project velocity onto normal var dot = hitBall.vx * nx + hitBall.vy * ny; // Reflect velocity (elastic bounce) hitBall.vx -= 2 * dot * nx; hitBall.vy -= 2 * dot * ny; // Dampen to simulate energy loss (same as ball-ball) hitBall.vx *= hitBall.bounce; hitBall.vy *= hitBall.bounce; } // Do NOT deactivate the chain here; allow it to persist as a barrier (chain acts as a wall) } // --- Shrink and split logic --- if (hitBall.radius > hitBall.minRadius) { // Shrink the ball down to minRadius if possible hitBall.shrink(); // If after shrinking, it's still above minRadius + epsilon, allow further splitting if (hitBall.radius > hitBall.minRadius) { // Split into two balls var newRadius = Math.max(hitBall.radius / 1.5, hitBall.minRadius); for (var s = 0; s < 2; ++s) { var newBall = new Ball(); newBall.setType(hitBall.type); newBall.radius = newRadius; newBall.asset.width = newRadius * 2; newBall.asset.height = newRadius * 2; // Offset new balls slightly to avoid overlap var angle = Math.PI / 4 + s * (Math.PI / 2); newBall.x = hitBall.x + Math.cos(angle) * newRadius; newBall.y = hitBall.y + Math.sin(angle) * newRadius; // Give them some velocity away from each other newBall.vx = hitBall.vx + (s === 0 ? -12 : 12); newBall.vy = hitBall.vy - 10; balls.push(newBall); game.addChild(newBall); } // Remove the original ball game.removeChild(hitBall); balls.splice(i, 1); i--; // adjust index after removal } } else { // Ball is at minimum size, destroy it game.removeChild(hitBall); balls.splice(i, 1); i--; // adjust index after removal } score += 1; scoreTxt.setText(score); // Flash ball LK.effects.flashObject(hitBall, 0xffff00, 200); break; } } } // --- Game update loop --- var ballCollisionCount = 0; var maxBallCollisions = 100; var maxLimitTxt = null; var winMessage = null; var gameOverMessage = null; var screenOverlay = LK.getAsset('screen', { anchorX: 0, anchorY: 0, width: 2048, height: 2732, x: 0, y: 0 }); game.addChildAt(screenOverlay, 0); game.update = function () { // Show max limit message if not already shown if (!maxLimitTxt) { maxLimitTxt = new Text2("Max limit 100", { size: 64, fill: 0xff0000 }); maxLimitTxt.anchor.set(0.5, 0); maxLimitTxt.y = scoreTxt.y + scoreTxt.height + 10; LK.gui.top.addChild(maxLimitTxt); } // Update robot robot.update(); // Update balls for (var i = 0; i < balls.length; ++i) { balls[i].update(); // --- Enforce chain as a barrier every frame, so balls never pass through --- if (chain.active) { // For each segment, check collision and bounce if needed for (var j = 0; j < chain.segments.length; ++j) { var seg = chain.segments[j]; var dx = balls[i].x - seg.x; var dy = balls[i].y - seg.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < balls[i].radius) { // Push ball out of the chain segment and reflect velocity (barrier bounce) var overlap = balls[i].radius - dist; if (dist === 0) { dx = 1; dy = 0; dist = 1; } var nx = dx / dist; var ny = dy / dist; balls[i].x += nx * overlap; balls[i].y += ny * overlap; // Project velocity onto normal var dot = balls[i].vx * nx + balls[i].vy * ny; // Reflect velocity (elastic bounce) balls[i].vx -= 2 * dot * nx; balls[i].vy -= 2 * dot * ny; // Dampen to simulate energy loss (same as ball-ball) balls[i].vx *= balls[i].bounce; balls[i].vy *= balls[i].bounce; } } } } // Ball-ball collision // Check all unique pairs of balls for collision for (var i = 0; i < balls.length; ++i) { for (var j = i + 1; j < balls.length; ++j) { var b1 = balls[i]; var b2 = balls[j]; // Track last and current collision state for this pair if (b1.lastCollided === undefined) b1.lastCollided = {}; if (b2.lastCollided === undefined) b2.lastCollided = {}; var pairKey = j; // unique for (i,j) var lastColliding = b1.lastCollided[pairKey] || false; var nowColliding = ballsCollide(b1, b2); // Only increment score on the exact frame the collision starts if (!lastColliding && nowColliding) { // Only increment score if one is red and one is green (²H and ³H) var t1 = b1.type; var t2 = b2.type; var isRedGreen = t1 === 'red' && t2 === 'green' || t1 === 'green' && t2 === 'red'; if (isRedGreen) { if (ballCollisionCount < maxBallCollisions) { score += 1; if (score > maxBallCollisions) score = maxBallCollisions; scoreTxt.setText(score); } } // Always resolve collision on first contact resolveBallCollision(b1, b2); // Increment collision counter and check for game over if (isRedGreen && ballCollisionCount < maxBallCollisions) { ballCollisionCount++; } if (ballCollisionCount >= maxBallCollisions) { // Clamp score to 100 and update display score = maxBallCollisions; scoreTxt.setText(score); // Show overlay and GAME OVER message if (!screenOverlay) { screenOverlay = LK.getAsset('screen', { anchorX: 0, anchorY: 0, width: 2048, height: 2732, x: 0, y: 0 }); game.addChildAt(screenOverlay, 0); } if (!gameOverMessage) { gameOverMessage = new Text2("GAME OVER", { size: 220, fill: 0xff0000 }); gameOverMessage.anchor.set(0.5, 0.5); gameOverMessage.x = 2048 / 2; gameOverMessage.y = 2732 / 2; game.addChild(gameOverMessage); } // Play outro music LK.playMusic('outroMusic', { loop: false }); // Show GAME OVER popup and stop the game LK.showGameOver(); return; } } else if (nowColliding) { // If still colliding, continue to resolve collision resolveBallCollision(b1, b2); } // Update last collision state for this pair b1.lastCollided[pairKey] = nowColliding; } } // 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(); // --- Atom splitter logic: check for chain-ball collisions and handle split/shrink --- for (var i = 0; i < balls.length; ++i) { var ball = balls[i]; // Track last and current collision state for this ball if (ball.lastChainColliding === undefined) ball.lastChainColliding = false; var currentlyColliding = chainHitsBall(chain, ball); // Only trigger split/shrink on the exact frame the collision starts if (!ball.lastChainColliding && currentlyColliding) { // Find the closest segment that collides var closestSeg = null; var minDist = Infinity; for (var j = 0; j < chain.segments.length; ++j) { var seg = chain.segments[j]; var dx = seg.x - ball.x; var dy = seg.y - ball.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < ball.radius && dist < minDist) { minDist = dist; closestSeg = seg; } } if (closestSeg) { // Push ball out of the chain segment and reflect velocity (barrier bounce) var dx = ball.x - closestSeg.x; var dy = ball.y - closestSeg.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist === 0) { dx = 1; dy = 0; dist = 1; } var overlap = ball.radius - dist; if (overlap > 0) { var nx = dx / dist; var ny = dy / dist; ball.x += nx * overlap; ball.y += ny * overlap; // Project velocity onto normal var dot = ball.vx * nx + ball.vy * ny; // Reflect velocity (elastic bounce) ball.vx -= 2 * dot * nx; ball.vy -= 2 * dot * ny; // Dampen to simulate energy loss (same as ball-ball) ball.vx *= ball.bounce; ball.vy *= ball.bounce; } } // --- Shrink and split logic --- if (ball.radius > ball.minRadius) { // Shrink the ball down to minRadius if possible ball.shrink(); // If after shrinking, it's still above minRadius + epsilon, allow further splitting if (ball.radius > ball.minRadius) { // Split into two balls var newRadius = Math.max(ball.radius / 1.5, ball.minRadius); for (var s = 0; s < 2; ++s) { var newBall = new Ball(); newBall.setType(ball.type); newBall.radius = newRadius; newBall.asset.width = newRadius * 2; newBall.asset.height = newRadius * 2; // Offset new balls slightly to avoid overlap var angle = Math.PI / 4 + s * (Math.PI / 2); newBall.x = ball.x + Math.cos(angle) * newRadius; newBall.y = ball.y + Math.sin(angle) * newRadius; // Give them some velocity away from each other newBall.vx = ball.vx + (s === 0 ? -12 : 12); newBall.vy = ball.vy - 10; balls.push(newBall); game.addChild(newBall); } // Remove the original ball game.removeChild(ball); balls.splice(i, 1); i--; // adjust index after removal } } else { // Ball is at minimum size, destroy it game.removeChild(ball); balls.splice(i, 1); i--; // adjust index after removal } score += 1; scoreTxt.setText(score); // Flash ball LK.effects.flashObject(ball, 0xffff00, 200); } // Update last collision state ball.lastChainColliding = currentlyColliding; } } // Fire cooldown if (!canFire) { fireTimer--; if (fireTimer <= 0) { canFire = true; } } // WIN condition: if no balls left, show YOU WIN! if (balls.length === 0) { if (!screenOverlay) { screenOverlay = LK.getAsset('screen', { anchorX: 0, anchorY: 0, width: 2048, height: 2732, x: 0, y: 0 }); game.addChildAt(screenOverlay, 0); } if (!winMessage) { winMessage = new Text2("YOU WIN!", { size: 220, fill: 0x00ff00 }); winMessage.anchor.set(0.5, 0.5); winMessage.x = 2048 / 2; winMessage.y = 2732 / 2; game.addChild(winMessage); // Play outro music LK.playMusic('outroMusic', { loop: false }); } return; } };
===================================================================
--- original.js
+++ change.js
@@ -768,8 +768,9 @@
var isRedGreen = t1 === 'red' && t2 === 'green' || t1 === 'green' && t2 === 'red';
if (isRedGreen) {
if (ballCollisionCount < maxBallCollisions) {
score += 1;
+ if (score > maxBallCollisions) score = maxBallCollisions;
scoreTxt.setText(score);
}
}
// Always resolve collision on first contact
@@ -778,8 +779,11 @@
if (isRedGreen && ballCollisionCount < maxBallCollisions) {
ballCollisionCount++;
}
if (ballCollisionCount >= maxBallCollisions) {
+ // Clamp score to 100 and update display
+ score = maxBallCollisions;
+ scoreTxt.setText(score);
// Show overlay and GAME OVER message
if (!screenOverlay) {
screenOverlay = LK.getAsset('screen', {
anchorX: 0,
@@ -804,9 +808,10 @@
// Play outro music
LK.playMusic('outroMusic', {
loop: false
});
- // Stop scoreboard from counting after max limit reached
+ // Show GAME OVER popup and stop the game
+ LK.showGameOver();
return;
}
} else if (nowColliding) {
// If still colliding, continue to resolve collision