User prompt
took me to level 1
User prompt
Do this with this logic until level 100 and as it gets harder the obstacles will move horizontally or vertically.
User prompt
make restart button purple with different font
User prompt
obstacle difficulty increases proportionally to level
User prompt
add more levels
User prompt
go back to previous version
User prompt
Make 100 levels and change the ball and target positions in each level
User prompt
use background music in assets and make it infinite
User prompt
use bounce sound in assets for every bounce
User prompt
add restart button
User prompt
make ball more smooth and fast
Code edit (1 edits merged)
Please save this source code
User prompt
Bounce Quest: Precision Puzzle
Initial prompt
Overview This is a 2D physics-based puzzle game designed for mobile platforms. The core mechanic revolves around a limited-bounce ball that the player must launch toward a reward zone, navigating through and around obstacles while minimizing the number of bounces. Core Gameplay Mechanics Player Input: The player drags and releases the ball to determine its launch direction and power. This simulates a slingshot-style interaction common in mobile games. Ball Behavior: The ball bounces off walls and obstacles, obeying basic 2D reflection physics. Each bounce reduces the allowed bounce count. Once the bounce limit is reached, the ball stops and the level is failed unless the goal has already been reached. Bounce Limit: The ball has a maximum number of allowed bounces, which decreases as levels progress to increase difficulty. Bounce limits are enforced per level and reset with each new attempt. Goal Zones: Each level contains a square-shaped “reward zone”. The player must get the ball to enter this zone to successfully complete the level. These reward zones are surrounded by obstacles, making the path to them more complex. Obstacles: Static, rectangular blocks that deflect the ball upon collision. Strategically placed to require careful planning and skillful launching. Level Design: Increasing complexity across levels: more obstacles, tighter angles, fewer allowed bounces. Emphasis on puzzle-solving and precision. Objectives The main objective is to reach the reward zone using the minimum number of bounces. Players are rewarded for efficiency: fewer bounces, higher scores. Platform and Controls Target Platform: Mobile (iOS, Android) Controls: Touch and drag input to control the ball's launch direction and power. UI Elements: Bounce counter Reset button Level progression Optional hint system Visual & Audio Design (Optional for AI Planning) Minimalistic and clean graphics for clarity. Use of color-coded zones: e.g., red for obstacles, green for reward zone. Sound effects for bounces, launches, and successful goals. Summary for AI Implementation Implement a 2D physics simulation using a game engine such as Unity or Godot. Model the ball with collision logic and bounce counting. Design levels with obstacle layouts and target zones. Create UI and input handling for mobile touch gestures. Add progression logic: increasing difficulty, scoring system, bounce constraints.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { level: 1 }); /**** * Classes ****/ // Ball class var Ball = Container.expand(function () { var self = Container.call(this); var ballGfx = self.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5 }); self.radius = ballGfx.width / 2; self.vx = 0; self.vy = 0; self.isMoving = false; self.bouncesLeft = 0; self.update = function () { if (!self.isMoving) return; // Move self.x += self.vx; self.y += self.vy; // Reduced friction for smoother, faster movement self.vx *= 0.998; self.vy *= 0.998; // Gravity (none for now, can add later) // Wall collision var bounced = false; // Left wall if (self.x - self.radius < 0) { self.x = self.radius; self.vx = -self.vx; bounced = true; } // Right wall if (self.x + self.radius > 2048) { self.x = 2048 - self.radius; self.vx = -self.vx; bounced = true; } // Top wall if (self.y - self.radius < 0) { self.y = self.radius; self.vy = -self.vy; bounced = true; } // Bottom wall if (self.y + self.radius > 2732) { self.y = 2732 - self.radius; self.vy = -self.vy; bounced = true; } // Obstacle collision for (var i = 0; i < obstacles.length; i++) { var obs = obstacles[i]; if (circleRectIntersect(self, obs)) { // Find the closest point on the rectangle to the ball center var closestX = clamp(self.x, obs.x, obs.x + obs.width); var closestY = clamp(self.y, obs.y, obs.y + obs.height); // Calculate the distance between the ball's center and this closest point var dx = self.x - closestX; var dy = self.y - closestY; // If the distance is less than the radius, we have a collision var dist = Math.sqrt(dx * dx + dy * dy); if (dist < self.radius) { // Reflect velocity // Determine which side was hit var overlapX = Math.min(Math.abs(self.x - obs.x), Math.abs(self.x - (obs.x + obs.width))); var overlapY = Math.min(Math.abs(self.y - obs.y), Math.abs(self.y - (obs.y + obs.height))); if (overlapX < overlapY) { self.vx = -self.vx; // Nudge out if (self.x < obs.x) self.x = obs.x - self.radius;else self.x = obs.x + obs.width + self.radius; } else { self.vy = -self.vy; if (self.y < obs.y) self.y = obs.y - self.radius;else self.y = obs.y + obs.height + self.radius; } bounced = true; } } } if (bounced) { self.bouncesLeft--; updateBounceCounter(); LK.effects.flashObject(self, 0x00b4d8, 200); // Play bounce sound LK.getSound('bounce').play(); if (self.bouncesLeft < 0) { // Out of bounces endLevel(false); } } // Reward zone check if (!self.hasWon && self.intersects(rewardZone)) { self.hasWon = true; endLevel(true); } }; return self; }); // Obstacle class var Obstacle = Container.expand(function () { var self = Container.call(this); var obsGfx = self.attachAsset('obstacle', { anchorX: 0, anchorY: 0 }); self.width = obsGfx.width; self.height = obsGfx.height; // Movement properties self.moveType = null; // 'horizontal' or 'vertical' self.moveRange = 0; self.moveSpeed = 0; self.baseX = 0; self.baseY = 0; self.movePhase = 0; self.update = function () { if (!self.moveType) return; self.movePhase += self.moveSpeed; if (self.moveType === 'horizontal') { self.x = self.baseX + Math.sin(self.movePhase) * self.moveRange; } else if (self.moveType === 'vertical') { self.y = self.baseY + Math.sin(self.movePhase) * self.moveRange; } }; return self; }); // Trajectory preview dot var PreviewDot = Container.expand(function () { var self = Container.call(this); self.attachAsset('previewDot', { anchorX: 0.5, anchorY: 0.5, alpha: 0.5 }); return self; }); // Reward zone class var RewardZone = Container.expand(function () { var self = Container.call(this); var rewardGfx = self.attachAsset('reward', { anchorX: 0.5, anchorY: 0.5 }); self.width = rewardGfx.width; self.height = rewardGfx.height; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // --- Utility functions --- // Ball (player) // Reward zone // Obstacle // Trajectory preview function clamp(val, min, max) { if (val < min) return min; if (val > max) return max; return val; } function circleRectIntersect(circle, rect) { // rect: x, y, width, height (top-left) var cx = circle.x, cy = circle.y, r = circle.radius; var rx = rect.x, ry = rect.y, rw = rect.width, rh = rect.height; var closestX = clamp(cx, rx, rx + rw); var closestY = clamp(cy, ry, ry + rh); var dx = cx - closestX; var dy = cy - closestY; return dx * dx + dy * dy < r * r; } // --- Level Data --- var levels = [{ ball: { x: 300, y: 2200, bounces: 5 }, reward: { x: 1700, y: 500 }, obstacles: [{ x: 800, y: 1200, width: 400, height: 60 }, { x: 1200, y: 1800, width: 60, height: 400 }] }, { ball: { x: 400, y: 2100, bounces: 4 }, reward: { x: 1800, y: 400 }, obstacles: [{ x: 900, y: 1000, width: 400, height: 60 }, { x: 1300, y: 1700, width: 60, height: 400 }] }, { ball: { x: 350, y: 2300, bounces: 6 }, reward: { x: 1600, y: 600 }, obstacles: [{ x: 1000, y: 1300, width: 400, height: 60 }, { x: 1100, y: 1600, width: 60, height: 400 }] }, { ball: { x: 500, y: 2000, bounces: 5 }, reward: { x: 1700, y: 700 }, obstacles: [{ x: 700, y: 1100, width: 400, height: 60 }, { x: 1400, y: 1500, width: 60, height: 400 }] }, { ball: { x: 600, y: 2200, bounces: 7 }, reward: { x: 1500, y: 800 }, obstacles: [{ x: 1200, y: 1200, width: 400, height: 60 }, { x: 900, y: 1700, width: 60, height: 400 }] }, { ball: { x: 300, y: 2100, bounces: 4 }, reward: { x: 1800, y: 900 }, obstacles: [{ x: 1000, y: 1400, width: 400, height: 60 }, { x: 1200, y: 1600, width: 60, height: 400 }] }, { ball: { x: 400, y: 2200, bounces: 6 }, reward: { x: 1700, y: 1000 }, obstacles: [{ x: 1100, y: 1200, width: 400, height: 60 }, { x: 1300, y: 1800, width: 60, height: 400 }] }, { ball: { x: 350, y: 2000, bounces: 5 }, reward: { x: 1600, y: 1100 }, obstacles: [{ x: 900, y: 1300, width: 400, height: 60 }, { x: 1400, y: 1700, width: 60, height: 400 }] }, { ball: { x: 500, y: 2300, bounces: 7 }, reward: { x: 1500, y: 1200 }, obstacles: [{ x: 1200, y: 1100, width: 400, height: 60 }, { x: 1000, y: 1600, width: 60, height: 400 }] }, { ball: { x: 600, y: 2100, bounces: 6 }, reward: { x: 1400, y: 1300 }, obstacles: [{ x: 800, y: 1200, width: 400, height: 60 }, { x: 1200, y: 1800, width: 60, height: 400 }] }]; // --- Game State --- var currentLevel = 1; storage.level = 1; if (currentLevel < 1) currentLevel = 1; if (currentLevel > levels.length) currentLevel = levels.length; var ball = null; var rewardZone = null; var obstacles = []; var previewDots = []; var isDragging = false; var dragStart = { x: 0, y: 0 }; var dragEnd = { x: 0, y: 0 }; var bounceCounterTxt = null; var levelTxt = null; var hintTxt = null; var canLaunch = true; // --- UI Setup --- bounceCounterTxt = new Text2('', { size: 90, fill: 0x22223B }); bounceCounterTxt.anchor.set(0.5, 0); LK.gui.top.addChild(bounceCounterTxt); levelTxt = new Text2('', { size: 70, fill: 0x3A86FF }); levelTxt.anchor.set(0.5, 0); LK.gui.top.addChild(levelTxt); hintTxt = new Text2('', { size: 60, fill: 0xADB5BD }); hintTxt.anchor.set(0.5, 0); LK.gui.bottom.addChild(hintTxt); // --- Restart Button --- var restartBtn = new Text2('Restart', { size: 70, fill: 0x8000ff, // purple font: "'Comic Sans MS', 'Comic Sans', cursive" }); restartBtn.anchor.set(0.5, 0.5); // Place at top right, but not in the top left 100x100 reserved area restartBtn.x = -160; // offset from right edge restartBtn.y = 100; // below top edge, outside reserved area restartBtn.interactive = true; restartBtn.buttonMode = true; restartBtn.down = function (x, y, obj) { loadLevel(currentLevel); }; LK.gui.topRight.addChild(restartBtn); // --- Level Loader --- function loadLevel(n) { // Clean up previous if (ball) { ball.destroy(); ball = null; } if (rewardZone) { rewardZone.destroy(); rewardZone = null; } for (var i = 0; i < obstacles.length; i++) obstacles[i].destroy(); obstacles = []; for (var i = 0; i < previewDots.length; i++) previewDots[i].destroy(); previewDots = []; isDragging = false; canLaunch = true; hintTxt.setText(''); // Clamp level if (n < 1) n = 1; if (n > levels.length) n = levels.length; currentLevel = n; storage.level = n; var lvl = levels[n - 1]; // Ball ball = new Ball(); ball.x = lvl.ball.x; ball.y = lvl.ball.y; ball.vx = 0; ball.vy = 0; ball.isMoving = false; ball.bouncesLeft = lvl.ball.bounces; ball.hasWon = false; game.addChild(ball); // Reward rewardZone = new RewardZone(); rewardZone.x = lvl.reward.x; rewardZone.y = lvl.reward.y; game.addChild(rewardZone); // Obstacles // Difficulty scaling: increase size and number of obstacles with level var difficultyScale = 1 + (currentLevel - 1) * 0.08; // 8% harder per level var extraObstacles = Math.floor((currentLevel - 1) / 3); // Add 1 extra every 3 levels // Place original obstacles, scaled for (var i = 0; i < lvl.obstacles.length; i++) { var o = lvl.obstacles[i]; var obs = new Obstacle(); obs.x = o.x; obs.y = o.y; obs.width = o.width * difficultyScale; obs.height = o.height * difficultyScale; obs.children[0].width = obs.width; obs.children[0].height = obs.height; // Add movement for higher levels if (currentLevel > 5) { obs.baseX = obs.x; obs.baseY = obs.y; // Alternate movement type for variety if (i % 2 === 0) { obs.moveType = 'horizontal'; obs.moveRange = 60 + currentLevel * 3; // Range increases with level obs.moveSpeed = 0.012 + currentLevel * 0.0007; // Speed increases with level } else { obs.moveType = 'vertical'; obs.moveRange = 60 + currentLevel * 3; obs.moveSpeed = 0.012 + currentLevel * 0.0007; } obs.movePhase = Math.random() * Math.PI * 2; } obstacles.push(obs); game.addChild(obs); } // Add extra obstacles for higher levels for (var j = 0; j < extraObstacles; j++) { // Place extra obstacles in a pattern, e.g. diagonal, and randomize a bit var obs = new Obstacle(); var baseX = 400 + j * 200 + currentLevel * 13 % 300; var baseY = 900 + j * 300 + currentLevel * 17 % 400; obs.x = Math.min(1800, baseX + currentLevel * 23 % 100); obs.y = Math.min(2200, baseY + currentLevel * 31 % 100); obs.width = 200 * difficultyScale; obs.height = 60 * difficultyScale; obs.children[0].width = obs.width; obs.children[0].height = obs.height; // Add movement for extra obstacles as well if (currentLevel > 5) { obs.baseX = obs.x; obs.baseY = obs.y; if (j % 2 === 0) { obs.moveType = 'horizontal'; obs.moveRange = 80 + currentLevel * 4; obs.moveSpeed = 0.014 + currentLevel * 0.0008; } else { obs.moveType = 'vertical'; obs.moveRange = 80 + currentLevel * 4; obs.moveSpeed = 0.014 + currentLevel * 0.0008; } obs.movePhase = Math.random() * Math.PI * 2; } obstacles.push(obs); game.addChild(obs); } updateBounceCounter(); levelTxt.setText('Level ' + n); // Hint if (n === 1) { hintTxt.setText('Drag and release to launch the ball!'); } else if (n === 2) { hintTxt.setText('Bounce off walls and avoid obstacles.'); } else if (n === 3) { hintTxt.setText('Plan your shot to use fewer bounces.'); } else { hintTxt.setText(''); } } // --- UI Update --- function updateBounceCounter() { if (!ball) return; bounceCounterTxt.setText('Bounces: ' + Math.max(0, ball.bouncesLeft)); } // --- End Level --- function endLevel(won) { canLaunch = false; if (won) { LK.effects.flashScreen(0x83de44, 600); LK.setScore(currentLevel); if (currentLevel >= levels.length) { LK.showYouWin(); } else { // Next level after short delay LK.setTimeout(function () { loadLevel(currentLevel + 1); }, 1200); } } else { LK.effects.flashScreen(0xff595e, 800); LK.setTimeout(function () { loadLevel(currentLevel); }, 1200); } } // --- Trajectory Preview --- function showTrajectoryPreview(fromX, fromY, toX, toY) { // Remove old dots for (var i = 0; i < previewDots.length; i++) previewDots[i].destroy(); previewDots = []; // Calculate initial velocity var dx = fromX - toX; var dy = fromY - toY; var power = Math.sqrt(dx * dx + dy * dy); if (power < 30) return; var maxPower = 900; if (power > maxPower) power = maxPower; var angle = Math.atan2(dy, dx); var speed = 0.055 * power; // Match ball launch speed for accurate preview var vx = Math.cos(angle) * speed; var vy = Math.sin(angle) * speed; // Simulate var px = fromX, py = fromY; var pvx = vx, pvy = vy; var bounces = ball ? ball.bouncesLeft : 3; var simRadius = ball ? ball.radius : 50; var simSteps = 0; var maxDots = 18; while (bounces >= 0 && simSteps < maxDots) { // Move px += pvx * 8; py += pvy * 8; // Friction pvx *= 0.995; pvy *= 0.995; // Wall collision var bounced = false; if (px - simRadius < 0) { px = simRadius; pvx = -pvx; bounced = true; } if (px + simRadius > 2048) { px = 2048 - simRadius; pvx = -pvx; bounced = true; } if (py - simRadius < 0) { py = simRadius; pvy = -pvy; bounced = true; } if (py + simRadius > 2732) { py = 2732 - simRadius; pvy = -pvy; bounced = true; } // Obstacle collision for (var i = 0; i < obstacles.length; i++) { var obs = obstacles[i]; if (circleRectIntersect({ x: px, y: py, radius: simRadius }, obs)) { // Find the closest point on the rectangle to the ball center var closestX = clamp(px, obs.x, obs.x + obs.width); var closestY = clamp(py, obs.y, obs.y + obs.height); var dx2 = px - closestX; var dy2 = py - closestY; var dist = Math.sqrt(dx2 * dx2 + dy2 * dy2); if (dist < simRadius) { var overlapX = Math.min(Math.abs(px - obs.x), Math.abs(px - (obs.x + obs.width))); var overlapY = Math.min(Math.abs(py - obs.y), Math.abs(py - (obs.y + obs.height))); if (overlapX < overlapY) { pvx = -pvx; if (px < obs.x) px = obs.x - simRadius;else px = obs.x + obs.width + simRadius; } else { pvy = -pvy; if (py < obs.y) py = obs.y - simRadius;else py = obs.y + obs.height + simRadius; } bounced = true; } } } if (bounced) bounces--; // Place dot var dot = new PreviewDot(); dot.x = px; dot.y = py; game.addChild(dot); previewDots.push(dot); simSteps++; } } // --- Input Handling --- game.down = function (x, y, obj) { if (!canLaunch || !ball || ball.isMoving) return; // Only allow drag if touch is on ball var dx = x - ball.x; var dy = y - ball.y; if (dx * dx + dy * dy > ball.radius * ball.radius * 1.2) return; isDragging = true; dragStart.x = ball.x; dragStart.y = ball.y; dragEnd.x = x; dragEnd.y = y; showTrajectoryPreview(dragStart.x, dragStart.y, dragEnd.x, dragEnd.y); }; game.move = function (x, y, obj) { if (!isDragging) return; dragEnd.x = x; dragEnd.y = y; showTrajectoryPreview(dragStart.x, dragStart.y, dragEnd.x, dragEnd.y); }; game.up = function (x, y, obj) { if (!isDragging) return; isDragging = false; // Remove preview for (var i = 0; i < previewDots.length; i++) previewDots[i].destroy(); previewDots = []; // Launch var dx = dragStart.x - dragEnd.x; var dy = dragStart.y - dragEnd.y; var power = Math.sqrt(dx * dx + dy * dy); if (power < 30) return; // Too short var maxPower = 900; if (power > maxPower) power = maxPower; var angle = Math.atan2(dy, dx); var speed = 0.055 * power; // Increased speed for more dynamic launch ball.vx = Math.cos(angle) * speed; ball.vy = Math.sin(angle) * speed; ball.isMoving = true; canLaunch = false; }; // --- Main Update Loop --- game.update = function () { if (ball) ball.update(); for (var i = 0; i < obstacles.length; i++) { if (typeof obstacles[i].update === 'function') obstacles[i].update(); } }; // --- Start Game --- loadLevel(currentLevel); // Play background music infinitely LK.playMusic('Bg');
===================================================================
--- original.js
+++ change.js
@@ -162,13 +162,13 @@
/****
* Game Code
****/
-// Trajectory preview
-// Obstacle
-// Reward zone
-// Ball (player)
// --- Utility functions ---
+// Ball (player)
+// Reward zone
+// Obstacle
+// Trajectory preview
function clamp(val, min, max) {
if (val < min) return min;
if (val > max) return max;
return val;
@@ -400,9 +400,10 @@
height: 400
}]
}];
// --- Game State ---
-var currentLevel = storage.level || 1;
+var currentLevel = 1;
+storage.level = 1;
if (currentLevel < 1) currentLevel = 1;
if (currentLevel > levels.length) currentLevel = levels.length;
var ball = null;
var rewardZone = null;