User prompt
Adjust the basketball hoop as follows: Make the rim (the hoop ring) noticeably wider than standard, to allow the ball to pass through more easily. Ensure the net and rim opening are forgiving — the ball should not frequently get stuck or bounce off unfairly. Soften the collision edges around the rim: Shots that are slightly off-center should still have a good chance to go in. Reduce collision sensitivity on the rim edge (less "bounciness" or rejection). Keep the visual appearance natural, but prioritize smooth, satisfying scoring over realism. Optional (for better feel): Add swish animations or slowed-down entry effects to reward clean shots. Slightly widen the hitbox inside the rim, invisible to the player, to guide borderline shots in. This makes the hoop feel fair, responsive, and enjoyable, especially in early or casual levels. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Adjust the basketball hoop as follows: Make the rim (the hoop ring) noticeably wider than standard, to allow the ball to pass through more easily. Ensure the net and rim opening are forgiving — the ball should not frequently get stuck or bounce off unfairly. Soften the collision edges around the rim: Shots that are slightly off-center should still have a good chance to go in. Reduce collision sensitivity on the rim edge (less "bounciness" or rejection). Keep the visual appearance natural, but prioritize smooth, satisfying scoring over realism. Optional (for better feel): Add swish animations or slowed-down entry effects to reward clean shots. Slightly widen the hitbox inside the rim, invisible to the player, to guide borderline shots in. This makes the hoop feel fair, responsive, and enjoyable, especially in early or casual levels. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
In every level (or selected levels), implement the following rule: Once the player releases the ball (after dragging and shooting): Start a strict 3-second countdown. During this time, allow the ball to move, bounce, or score. Do not wait for the ball to stop moving. After exactly 3 seconds, the level must end immediately, no matter what: ✅ If the objective was completed during those 3 seconds → mark as success. ❌ If the objective was not completed → mark as fail. Even if the ball keeps rolling or is mid-air — ignore it — just end the level after 3 seconds. Optional (visual polish): Show a visible timer countdown (e.g., top of screen). Fade the screen out after 3 seconds while showing result ("Success!" or "Failed"). This ensures fast, responsive level transitions, and avoids long waiting due to slow or endless ball movement. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
In every level (or specific levels), implement the following behavior: Once the player releases the ball (drag and release): Start a 3-second timer immediately. During these 3 seconds: If the ball scores within the first second, mark the level as success, but do not end immediately. If the ball scores after 1 second, or doesn’t score, it is considered fail. Regardless of success or failure, end the level exactly 3 seconds after the shot was released. Summary: ✅ Score within 1s → Success (but level ends after 3s total). ❌ Score after 1s or miss → Fail (level ends after 3s). No waiting for the ball to fully stop; always end after 3 seconds from release. Show result ("Success" or "Fail") just before transitioning. This ensures fast-paced gameplay with instant feedback, while still giving time for dramatic shots to land. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading '__colorFlash_current_tick')' in or related to this line: 'LK.effects.flashObject(hoop.rimFront, 0xFF6600, 300);' Line Number: 1084
User prompt
For a specific level, implement the following behavior: Once the player releases the ball (after dragging and shooting): Start a 3-second countdown immediately. If the player successfully scores within the first second (1 second after release): Display a "Success!" message or animation. Allow a brief pause, then automatically proceed to the next level. If the player scores after 1 second, or misses entirely, then: After the full 3 seconds pass, display "Fail" or "Game Over". Restart the level or reset the game, depending on your design. Additional Behavior: During the 3-second window, no input should be accepted (freeze drag/shoot). Optionally show a countdown timer or effect. Clearly indicate success or failure with color, text, or animation. This system adds time pressure and encourages quick precision shots, creating a dynamic challenge for that level. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Design the basketball hoop with a clean, realistic side profile: The backboard should be rotated exactly 90 degrees, so only its side (thickness) is visible. Do not show the front face of the backboard. The visible side should appear as a slim vertical bar, attached to the right wall. The rim (hoop) must be: Properly aligned to extend horizontally toward the left, as it would in a real side-facing setup. Center-mounted on the visible thickness of the board. Drawn in perspective so it appears like you’re looking at the basket directly from the side. The net should: Hang down naturally from the rim. React with physics when the ball passes through. Be stylized or semi-realistic, but always match the side view. Extra Details: Add a small support bracket connecting the rim to the board for realism. The entire hoop assembly must appear attached to the right wall, not floating. No backboard face should be visible — only the thin edge profile. Keep proportions true to a real basketball hoop but simplify for clean 2D or isometric art. This setup provides a minimalist yet realistic side-view hoop perfect for skill-based physics gameplay. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Set up the game so that: All four edges of the screen (top, bottom, left, right) act as solid walls. The ball should collide and bounce off these walls according to real-world physics: Use accurate angle-based reflection: Angle of incidence = angle of reflection The ball should lose a small amount of speed with each bounce to simulate friction or energy loss. Ball Physics Details: Apply realistic gravity, velocity, and friction values to the ball. Ensure the ball reacts differently based on how hard and from what angle it hits a surface. Shots can include bank shots off the walls — this should be part of gameplay strategy. Additional Suggestions: Add bouncing sound effects when the ball hits any wall. Use a small bounce animation or particle effect for extra visual feedback. Make sure wall collisions do not glitch or trap the ball. This environment makes the court feel like a fully enclosed arena, allowing for advanced trick shots and more skill-based gameplay using realistic physics. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Implement a system where: Each level has a specific objective (e.g., "Score with 1 bounce", "Make it in one shot", "Don’t touch the wall"). If the player fails to complete the objective, the game should: Immediately show a "Game Over" screen. After a short delay (e.g., 2 seconds), restart the game from Level 1. Display a message indicating the reason for failure, such as: "Objective Failed: You touched the wall" "Missed the hoop" "You didn’t bounce" Optional enhancements: Add a fade-out animation or sound effect for failure. Reset all progress or allow the player to retry the level (depending on design choice). This ensures that each level’s challenge must be met, increasing difficulty and focus, while creating a satisfying retry loop. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
oyun kaybedilince 1.nölüme geri dönülsün
User prompt
Design the basketball hoop to be: Always located on the right side of the screen, attached to the right wall. Rendered with a realistic, side-facing angle, so the player sees the hoop from a slight 3/4 perspective (as if shooting from the side). Include visible rim depth, a mounted backboard, and a slightly angled net to enhance realism. Make the hoop a physical object, so the ball can: Bounce off the rim, backboard, and even drop through the net with real physics. Respond naturally to different shot angles and power levels. Per-Level Adjustments: In each level, the hoop’s: Height, angle, or distance from the ball can vary based on the challenge. May require bank shots, trick angles, or timed releases depending on the level's goal. Examples: Level 1: Hoop is fixed, centered vertically on the right wall. Level 3: Hoop slightly tilted or placed higher for bank shot. Level 5+: Hoop interacts with moving walls or obstacles. Level 10+: Hoop may shake, tilt, or have smaller rims for difficulty. Visual and Gameplay Enhancements: Use shadows and light to reinforce the 3D angle and depth of the hoop. Add a realistic net animation when scoring. Include audio feedback (rim hit, swish, backboard bounce). Display a level-specific challenge text like "Bounce off one wall" or "Score without touching the rim". This will give the game a more immersive and satisfying basketball experience with visually believable hoops and progressively challenging shot setups. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
In every level of the game, position the basketball hoop as follows: The hoop should be attached to the right wall, slightly above ground level. The hoop must always be rotated at a side angle, so that it appears to be facing toward the player (toward the left side of the screen). The net should have a semi-3D effect, showing depth, making it look like you're shooting into the side of the hoop, not frontally. The hoop and backboard are visibly mounted on the right wall. Ensure the hoop's position remains consistent across all levels (right side), but its height and slight tilt can vary per level to increase difficulty. Use shading or perspective lines to emphasize the side angle and wall attachment. The backboard can be partially visible, giving a realistic impression that it’s sticking out from the wall. Optional: Add subtle shadowing on the wall for depth. Include net movement or swish animation when the ball scores. Make sure the ball’s collision aligns correctly with the hoop’s angled shape. This setup gives the player a side-shot challenge in every level, making gameplay more engaging and visually dynamic. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
In every level of the game: The basketball should start at the center of the screen. Its position should be horizontally and vertically centered: x = screenWidth / 2, y = screenHeight / 2. The ball must be static at the beginning, waiting for the player to drag and release to shoot. Ensure that no obstacles or UI elements block the ball's initial position. The player should be able to drag from this central starting point in any direction, allowing flexible shot angles. For better user experience, show a small shadow or ring under the ball to emphasize it's draggable and ready to shoot. This setup provides a neutral, balanced starting point and makes the shot feel fair and consistent across all levels. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
top daha ortada olsun
User prompt
The player can pull the ball much further using the mouse. The dragging distance should be extended to allow for more control over shot strength. The further the player pulls the ball back, the stronger and faster the shot should be. Implement a maximum drag limit, and when the player pulls to that limit, the shot becomes full power. Include a visual cue (e.g., stretching line or power bar) to show how much strength the shot will have. Make the mechanic feel responsive and smooth, suitable for desktop play. Ensure that: Even small mouse movements cause noticeable power differences. Maximum power can only be achieved by pulling the ball to the far edge of the allowed drag radius. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
topu daha güçlü çekebileyim
User prompt
topu çekme limitimi arttır
User prompt
arka plan daha koyu renk olsun sağ tarafta duara dayalı gerçek bir pta olsun , basketbl topu çok daha büyük olsun
Code edit (1 edits merged)
Please save this source code
User prompt
Basket Bounce Challenge
Initial prompt
Create a colorful, level-based basketball game where the player pulls and releases the ball to shoot it into the basket. The game includes 20 unique levels, and each level becomes progressively more difficult. Core Mechanics: The player interacts by dragging and releasing the ball (slingshot mechanic). Each level has a fixed basketball hoop placed on the right side of the screen. The game is vibrant and colorful, with smooth physics and satisfying animations. Level Progression Examples: Level 1: Simple straight shot – hoop is stationary and centered on the right side. Level 2: Hoop is at a higher or steeper angle – still static. Level 3: Player must bounce the ball once off a wall to score. Level 4: Two walls – a bank shot puzzle begins. Level 5-10: Obstacles are introduced (spikes, rotating blocks). Level 11+: The hoop begins to move horizontally or vertically. Level 15+: Limited attempts or time-based mechanics. Level 20: Final challenge – multiple bounces, moving hoop, and precision required. Additional Features: Each level displays a goal (e.g., "Bounce off one wall", "Make it in one shot"). Add particle effects when the ball scores. Show level complete animations and smooth transitions. Include a level select screen and unlock progression system. Design with a friendly, playful aesthetic, suitable for casual players but challenging for completionists.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var Backboard = Container.expand(function () { var self = Container.call(this); var pole = self.attachAsset('backboardPole', { anchorX: 0.5, anchorY: 1 }); pole.x = 0; pole.y = 0; var board = self.attachAsset('backboard', { anchorX: 0.5, anchorY: 1 }); board.x = 0; board.y = -50; return self; }); var Basketball = Container.expand(function () { var self = Container.call(this); var ball = self.attachAsset('basketball', { anchorX: 0.5, anchorY: 0.5 }); self.velocityX = 0; self.velocityY = 0; self.isLaunched = false; self.gravity = 0.3; self.bounceDecay = 0.8; self.startX = 0; self.startY = 0; self.reset = function () { self.x = self.startX; self.y = self.startY; self.velocityX = 0; self.velocityY = 0; self.isLaunched = false; // Update shadow position if it exists if (self.shadow) { self.shadow.x = self.x; self.shadow.y = self.y + 90; } }; self.launch = function (forceX, forceY) { self.velocityX = forceX; self.velocityY = forceY; self.isLaunched = true; LK.getSound('launch').play(); }; self.update = function () { // Handle vertical movement when not launched (level 5) if (!self.isLaunched && self.isMovingVertically) { self.y += self.moveSpeed * self.moveDirection; if (self.y <= self.minY || self.y >= self.maxY) { self.moveDirection *= -1; } // Update shadow position during movement if (self.shadow) { self.shadow.y = self.y + 90; } // Update start position for reset self.startY = self.y; } // Handle horizontal movement when not launched (level 11) if (!self.isLaunched && self.isMovingHorizontally) { self.x += self.moveSpeed * self.moveDirection; if (self.x <= self.minX || self.x >= self.maxX) { self.moveDirection *= -1; } // Update shadow position during movement if (self.shadow) { self.shadow.x = self.x; } // Update start position for reset self.startX = self.x; } if (self.isLaunched) { self.x += self.velocityX; self.y += self.velocityY; self.velocityY += self.gravity; // Realistic physics for all four screen edges var ballRadius = 80; // Half of basketball width/height var energyLoss = 0.85; // Energy retained after bounce (realistic friction) var minVelocity = 0.5; // Minimum velocity to prevent infinite micro-bounces // Left wall collision if (self.x <= ballRadius) { self.x = ballRadius; if (self.velocityX < 0) { // Only bounce if moving toward wall self.velocityX = -self.velocityX * energyLoss; self.velocityY = self.velocityY * energyLoss; // Slight energy loss on Y too if (Math.abs(self.velocityX) < minVelocity) { self.velocityX = 0; } LK.getSound('bounce').play(); // Track left wall bounce for level 10 objective if (leftWallBounceRequired && !leftWallBounced) { leftWallBounced = true; } } } // Right wall collision if (self.x >= 2048 - ballRadius) { self.x = 2048 - ballRadius; if (self.velocityX > 0) { // Only bounce if moving toward wall self.velocityX = -self.velocityX * energyLoss; self.velocityY = self.velocityY * energyLoss; // Slight energy loss on Y too if (Math.abs(self.velocityX) < minVelocity) { self.velocityX = 0; } LK.getSound('bounce').play(); } } // Top wall collision if (self.y <= ballRadius) { self.y = ballRadius; if (self.velocityY < 0) { // Only bounce if moving toward wall self.velocityY = -self.velocityY * energyLoss; self.velocityX = self.velocityX * energyLoss; // Slight energy loss on X too if (Math.abs(self.velocityY) < minVelocity) { self.velocityY = 0; } LK.getSound('bounce').play(); } } // Bottom wall collision if (self.y >= 2732 - ballRadius) { self.y = 2732 - ballRadius; if (self.velocityY > 0) { // Only bounce if moving toward wall self.velocityY = -self.velocityY * energyLoss; self.velocityX = self.velocityX * energyLoss; // Slight energy loss on X too if (Math.abs(self.velocityY) < minVelocity) { self.velocityY = 0; } LK.getSound('bounce').play(); } } } // Update shadow position if it exists if (self.shadow) { self.shadow.x = self.x; self.shadow.y = self.y + 90; } }; return self; }); var Fireball = Container.expand(function () { var self = Container.call(this); var fireballGraphics = self.attachAsset('spike', { anchorX: 0.5, anchorY: 0.5, tint: 0xFF4500 }); self.velocityX = 0; self.velocityY = 0; self.speed = 2; self.update = function () { self.x += self.velocityX; self.y += self.velocityY; // Bounce off screen edges if (self.x <= 20) { self.x = 20; self.velocityX = -self.velocityX; } if (self.x >= 2028) { self.x = 2028; self.velocityX = -self.velocityX; } if (self.y <= 20) { self.y = 20; self.velocityY = -self.velocityY; } if (self.y >= 2712) { self.y = 2712; self.velocityY = -self.velocityY; } // Bounce off walls walls.forEach(function (wall) { if (self.intersects(wall)) { var ballCenterX = self.x; var ballCenterY = self.y; var wallCenterX = wall.x; var wallCenterY = wall.y; var wallWidth = wall.width * wall.scaleX; var wallHeight = wall.height * wall.scaleY; var overlapX = Math.abs(ballCenterX - wallCenterX) - (20 + wallWidth / 2); var overlapY = Math.abs(ballCenterY - wallCenterY) - (20 + wallHeight / 2); if (overlapX < overlapY) { self.velocityX = -self.velocityX; self.x += ballCenterX < wallCenterX ? -5 : 5; } else { self.velocityY = -self.velocityY; self.y += ballCenterY < wallCenterY ? -5 : 5; } } }); }; return self; }); var Hoop = Container.expand(function () { var self = Container.call(this); // Create backboard as slim vertical bar (only thickness visible) - flush mounted to wall var backboard = self.attachAsset('wall', { anchorX: 1.0, // Right edge anchor for wall attachment anchorY: 0.5, scaleX: 0.2, // Slightly thicker for better visibility scaleY: 2.8, // Much taller backboard extending well above rim tint: 0xFFFFFF }); backboard.x = 0; // Completely flush with wall position (no gap) backboard.y = -80; // Positioned higher so it extends above rim // Create support bracket connecting backboard to wall var wallBracket = self.attachAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.3, scaleY: 0.2, tint: 0x808080 }); wallBracket.x = 12; // Closer to wall for flush mounting wallBracket.y = -50; // Create horizontal rim extending toward left (side view) - made extra wide var rim = self.attachAsset('hoop', { anchorX: 1.0, // Right edge anchored to backboard anchorY: 0.5, scaleX: 2.4, // Extended horizontally toward player - extra wide for very forgiving scoring scaleY: 0.2, // Thin rim profile from side tint: 0xFF4500 }); rim.x = -2; // Closer to backboard for flush mounting rim.y = -80; // Create rim support bracket var rimBracket = self.attachAsset('wall', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.6, scaleY: 0.15, tint: 0x808080 }); rimBracket.x = -35; // Adjusted for better alignment rimBracket.y = -70; // Create vertical backboard support line behind the rim - taller and thicker var supportLine = self.attachAsset('backboardSupport', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, // Thicker for more visual solidity scaleY: 2.0, // Taller extending well above rim tint: 0xC0C0C0, alpha: 0.9 }); supportLine.x = -8; // Closer to backboard for flush mounting supportLine.y = -130; // Positioned much higher above the rim, never dipping below // Create realistic side-view net hanging from rim - positioned to hang naturally below var netSegments = []; for (var i = 0; i < 8; i++) { var netSegment = self.attachAsset('netSegment', { anchorX: 0.5, anchorY: 0, scaleX: 0.8 - i * 0.06, // Tapers as it hangs down scaleY: 1.0, tint: 0xFFFFFF, alpha: 0.7 }); netSegment.x = -140 + i * 10; // Spread across rim width netSegment.y = -60 + i * 12; // Hangs lower below rim with more natural drape netSegments.push(netSegment); } // Store references for animations self.netSegments = netSegments; self.rim = rim; self.backboard = backboard; self.supportLine = supportLine; // Physics collision zones for realistic ball interaction - ultra forgiving self.rimCollisionZone = { x: -110, // Extends even further left from backboard y: -80, width: 320, // Ultra wide rim width for maximum forgiving scoring height: 50 // Extra thick rim height for easiest scoring }; self.backboardCollisionZone = { x: 0, // At wall position - completely flush y: -80, // Positioned higher to match visual backboard width: 40, // Thicker to match visual appearance height: 420 // Much taller backboard height matching visual }; // Vertical support line collision zone for soft redirection self.supportLineCollisionZone = { x: -8, // Closer to backboard for flush mounting y: -130, // Positioned higher above rim width: 12, // Thicker support line width height: 240 // Taller support line height extending well above rim }; self.isMoving = false; self.moveSpeed = 2; self.moveDirection = 1; self.minX = 1700; self.maxX = 1900; self.minY = 200; self.maxY = 2000; self.moveHorizontal = true; self.update = function () { if (self.isMoving) { if (self.moveHorizontal) { self.x += self.moveSpeed * self.moveDirection; if (self.x <= self.minX || self.x >= self.maxX) { self.moveDirection *= -1; } } else { self.y += self.moveSpeed * self.moveDirection; if (self.y <= self.minY || self.y >= self.maxY) { self.moveDirection *= -1; } } } }; return self; }); var RotatingBlock = Container.expand(function () { var self = Container.call(this); var blockGraphics = self.attachAsset('rotatingBlock', { anchorX: 0.5, anchorY: 0.5 }); self.rotationSpeed = 0.05; self.update = function () { blockGraphics.rotation += self.rotationSpeed; // Add horizontal movement for level 16 if (self.isMovingHorizontally) { self.x += self.moveSpeed * self.moveDirection; if (self.x <= self.minX || self.x >= self.maxX) { self.moveDirection *= -1; } } }; return self; }); var Spike = Container.expand(function () { var self = Container.call(this); var spikeGraphics = self.attachAsset('spike', { anchorX: 0.5, anchorY: 0.5 }); self.isMoving = false; self.moveSpeed = 8; // Fast movement speed self.moveDirection = 1; self.minY = 200; self.maxY = 2000; self.minX = 200; self.maxX = 2000; self.update = function () { if (self.isMoving) { // Check if horizontal movement is enabled (has minX/maxX set differently from defaults) if (self.minX !== 200 || self.maxX !== 2000) { // Horizontal movement self.x += self.moveSpeed * self.moveDirection; if (self.x <= self.minX || self.x >= self.maxX) { self.moveDirection *= -1; } } else { // Vertical movement (default) self.y += self.moveSpeed * self.moveDirection; if (self.y <= self.minY || self.y >= self.maxY) { self.moveDirection *= -1; } } } }; return self; }); var TrajectoryDot = Container.expand(function () { var self = Container.call(this); var dot = self.attachAsset('trajectory', { anchorX: 0.5, anchorY: 0.5 }); dot.alpha = 0.6; return self; }); var Wall = Container.expand(function () { var self = Container.call(this); var wallGraphics = self.attachAsset('wall', { anchorX: 0.5, anchorY: 0.5 }); self.isMoving = false; self.moveSpeed = 2; self.moveDirection = 1; self.minY = 200; self.maxY = 2000; self.update = function () { if (self.isMoving) { self.y += self.moveSpeed * self.moveDirection; if (self.y <= self.minY || self.y >= self.maxY) { self.moveDirection *= -1; } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2F4F4F }); /**** * Game Code ****/ // Game state var fireballTimer = null; var currentLevel = storage.currentLevel || 1; var maxLevel = storage.maxLevel || 1; var gameState = 'playing'; // playing, levelComplete, gameOver var attempts = 0; var maxAttempts = 0; var scored = false; var levelGoal = ''; var backgroundImage = null; // Objective tracking var wallBounces = 0; var requiredBounces = 0; var touchedWall = false; var leftWallBounced = false; var leftWallBounceRequired = false; var touchedNonLeftWall = false; var objectiveFailed = false; var failureReason = ''; // Timer challenge system var ballReleaseTime = 0; var timerChallenge = false; var timerCountdown = 0; var inputDisabled = false; var timerCountdownText = null; // Global 3-second timer for all levels var globalTimerActive = false; var globalTimerStartTime = 0; // Game objects var basketball = null; var hoop = null; var walls = []; var spikes = []; var rotatingBlocks = []; var trajectoryDots = []; var fireballs = []; // Input tracking var isDragging = false; var dragStartX = 0; var dragStartY = 0; // Helper function to generate random positions for obstacles function getRandomPosition() { return { x: Math.random() * 1200 + 300, // Random x between 300 and 1500 y: Math.random() * 1000 + 800 // Random y between 800 and 1800 }; } // Level definitions var levels = [ // Level 1-4: Basic shots { ballX: 1024, ballY: 1366, hoopX: 1024, hoopY: 1366, goal: 'Make your first shot!', objective: 'score', // Just score walls: [], spikes: [], rotatingBlocks: [], movingHoop: false, maxAttempts: 0 }, { ballX: 1024, ballY: 1366, hoopX: 1024, hoopY: 800, goal: 'Higher target challenge!', objective: 'score', // Just score walls: [], spikes: [], rotatingBlocks: [], movingHoop: false, maxAttempts: 0 }, { ballX: 1024, ballY: 1366, hoopX: 1024, hoopY: 800, goal: 'Straight shot challenge!', objective: 'no_wall', // Don't touch any wall walls: [], spikes: [], rotatingBlocks: [], movingHoop: false, maxAttempts: 0 }, { ballX: 1024, ballY: 1366, hoopX: 1024, hoopY: 1366, goal: 'Bounce off one wall to score!', objective: 'bounce_once', // Must bounce off exactly one wall walls: [], spikes: [], rotatingBlocks: [], movingHoop: false, maxAttempts: 0 }, // Level 5-10: Bank shots and obstacles { ballX: 1024, ballY: 1366, hoopX: 1024, hoopY: 1366, goal: 'Bounce off one wall!', objective: 'bounce_once', // Must bounce off exactly one wall walls: [], spikes: [], rotatingBlocks: [], movingHoop: false, maxAttempts: 0 }, { ballX: 1024, ballY: 1366, hoopX: 1024, hoopY: 1366, goal: 'Avoid the spikes!', objective: 'no_wall', // Don't touch any wall (spikes auto-fail) walls: [], spikes: [{ x: 1550, y: 1100 }], rotatingBlocks: [], movingHoop: false, maxAttempts: 0 }, { ballX: 1024, ballY: 1366, hoopX: 1024, hoopY: 1366, goal: 'Navigate the rotating block!', objective: 'score', // Just score while avoiding blocks walls: [], spikes: [], rotatingBlocks: [{ x: 1700, y: 1050 }], movingHoop: false, maxAttempts: 0 }, { ballX: 1024, ballY: 1366, hoopX: 1024, hoopY: 1366, goal: 'Avoid walls challenge!', objective: 'no_wall', // Don't touch any wall walls: [{}, { x: 1700, y: 1100, width: 30, height: 200 }], spikes: [], rotatingBlocks: [], movingHoop: false, maxAttempts: 0 }, { ballX: 1024, ballY: 1366, hoopX: 1024, hoopY: 1366, goal: 'Precision bank shot!', walls: [{ x: 1600, y: 800, width: 400, height: 30 }], spikes: [{ x: 1650, y: 1200 }], rotatingBlocks: [], movingHoop: false, maxAttempts: 0 }, { ballX: 1024, ballY: 1366, hoopX: 1024, hoopY: 1366, goal: 'Precision bank shot!', walls: [{ x: 1600, y: 800, width: 400, height: 30 }], spikes: [{ x: 1650, y: 1200 }], rotatingBlocks: [], movingHoop: false, maxAttempts: 0 }, // Level 11-19: Moving hoops and restrictions { ballX: 1024, ballY: 1366, hoopX: 1024, hoopY: 1366, goal: 'Hit the moving hoop!', walls: [], spikes: [], rotatingBlocks: [], movingHoop: true, maxAttempts: 0 }, { ballX: 1024, ballY: 1366, hoopX: 1024, hoopY: 1366, goal: 'Moving target with walls!', walls: [{ x: 1700, y: 1000, width: 30, height: 300 }], spikes: [], rotatingBlocks: [], movingHoop: true, maxAttempts: 0 }, { ballX: 1024, ballY: 1366, hoopX: 1024, hoopY: 1366, goal: 'One shot only!', walls: [], spikes: [{ x: 1800, y: 1200 }], rotatingBlocks: [], movingHoop: false, maxAttempts: 1 }, { ballX: 1024, ballY: 1366, hoopX: 1024, hoopY: 1366, goal: 'Moving hoop precision!', walls: [{ x: 1600, y: 1000, width: 200, height: 30 }], spikes: [{ x: 1750, y: 1150 }], rotatingBlocks: [], movingHoop: true, maxAttempts: 3 }, { ballX: 1024, ballY: 1366, hoopX: 1024, hoopY: 1366, goal: 'Vertical moving hoop!', walls: [{ x: 1600, y: 600, width: 30, height: 400 }, { x: 1800, y: 1100, width: 30, height: 400 }], spikes: [], rotatingBlocks: [], movingHoop: true, maxAttempts: 0 }, { ballX: 1024, ballY: 1366, hoopX: 1024, hoopY: 1366, goal: 'Ultimate obstacle course!', walls: [{ x: 1700, y: 1000, width: 30, height: 200 }, { x: 1800, y: 800, width: 200, height: 30 }], spikes: [{ x: 1650, y: 1200 }, { x: 1850, y: 950 }], rotatingBlocks: [{ x: 1750, y: 1100 }], movingHoop: false, maxAttempts: 2 }, { ballX: 1024, ballY: 1366, hoopX: 1024, hoopY: 1366, goal: 'Moving maze challenge!', walls: [{ x: 1650, y: 350, width: 30, height: 300 }, { x: 1850, y: 1000, width: 30, height: 300 }], spikes: [{ x: 1700, y: 1150 }, { x: 1800, y: 1050 }], rotatingBlocks: [{ x: 1750, y: 1200 }], movingHoop: true, maxAttempts: 3 }, { ballX: 1024, ballY: 1366, hoopX: 1024, hoopY: 1366, goal: 'Dont Touch Anywhere', objective: 'bounce_twice', walls: [{ x: 1700, y: 800, width: 30, height: 600 }, { x: 1800, y: 900, width: 30, height: 600 }], spikes: [{ x: 1650, y: 170 }, { x: 1850, y: 1200 }], rotatingBlocks: [], movingHoop: false, maxAttempts: 3 }, { ballX: 1024, ballY: 1366, hoopX: 1024, hoopY: 1366, goal: 'Fireball timee!', walls: [{ x: 1600, y: 900, width: 200, height: 30 }, { x: 1700, y: 1100, width: 200, height: 30 }], spikes: [{ x: 1750, y: 1200 }, { x: 1800, y: 1000 }], rotatingBlocks: [{ x: 1850, y: 1050 }], movingHoop: true, maxAttempts: 2 }, // Level 20: Ultimate challenge { ballX: 1024, ballY: 1366, hoopX: 1024, hoopY: 1366, goal: 'Whaaatt! U need LUCK', walls: [{ x: 1600, y: 800, width: 30, height: 400 }, { x: 1700, y: 1000, width: 200, height: 30 }, { x: 1750, y: 900, width: 200, height: 30 }, { x: 1850, y: 1100, width: 30, height: 400 }], spikes: [{ x: 1650, y: 1200 }, { x: 1750, y: 1050 }, { x: 1800, y: 1250 }], rotatingBlocks: [{ x: 1700, y: 1100 }, { x: 1850, y: 950 }], movingHoop: true, maxAttempts: 1 }, { ballX: 1024, ballY: 1366, hoopX: 1024, hoopY: 1366, goal: 'Impossible!', walls: [{ x: 1800, y: 800, width: 30, height: 200 }], spikes: [], rotatingBlocks: [{ x: 1600, y: 1000 }, { x: 1700, y: 1200 }], movingHoop: false, maxAttempts: 0 }]; // UI elements var levelText = new Text2('Level 1', { size: 60, fill: 0xFFFFFF }); levelText.anchor.set(0.5, 0); LK.gui.top.addChild(levelText); var goalText = new Text2('', { size: 40, fill: 0xFFFF00 }); goalText.anchor.set(0.5, 0); goalText.y = 80; LK.gui.top.addChild(goalText); var attemptsText = new Text2('', { size: 35, fill: 0xFFFFFF }); attemptsText.anchor.set(1, 0); LK.gui.topRight.addChild(attemptsText); // Timer countdown text timerCountdownText = new Text2('', { size: 80, fill: 0xFF0000 }); timerCountdownText.anchor.set(0.5, 0.5); LK.gui.center.addChild(timerCountdownText); timerCountdownText.visible = false; // Next Level button var nextLevelButton = new Text2('Next Level →', { size: 60, fill: 0x00FF00 }); nextLevelButton.anchor.set(0.5, 0.5); nextLevelButton.x = 1024; nextLevelButton.y = 2200; nextLevelButton.visible = false; game.addChild(nextLevelButton); // Add button click functionality nextLevelButton.down = function (x, y, obj) { if (nextLevelButton.visible && gameState === 'levelComplete') { // Hide button nextLevelButton.visible = false; // Proceed to next level if (currentLevel < levels.length) { currentLevel++; if (currentLevel > maxLevel) { maxLevel = currentLevel; storage.maxLevel = maxLevel; } storage.currentLevel = currentLevel; initializeLevel(currentLevel); } else { LK.showYouWin(); } } }; function initializeLevel(levelNum) { if (fireballTimer !== null) { LK.clearInterval(fireballTimer); fireballTimer = null; } if (levelNum > levels.length || levelNum < 1) { return; } // Set background image if (backgroundImage && backgroundImage.parent) { backgroundImage.destroy(); } backgroundImage = LK.getAsset('basket', { anchorX: 0.5, anchorY: 0.5, scaleX: 2048 / 100, scaleY: 2732 / 100 }); backgroundImage.x = 1024; backgroundImage.y = 1366; game.addChildAt(backgroundImage, 0); // Set background color based on level game.setBackgroundColor(0x2F4F4F); // Clear existing objects if (basketball) { if (basketball.shadow) { basketball.shadow.destroy(); } basketball.destroy(); } if (hoop) { hoop.destroy(); } walls.forEach(function (wall) { wall.destroy(); }); spikes.forEach(function (spike) { spike.destroy(); }); rotatingBlocks.forEach(function (block) { block.destroy(); }); trajectoryDots.forEach(function (dot) { dot.destroy(); }); fireballs.forEach(function (fireball) { fireball.destroy(); }); walls = []; spikes = []; rotatingBlocks = []; trajectoryDots = []; fireballs = []; var level = levels[levelNum - 1]; // Create basketball basketball = game.addChild(new Basketball()); // Set custom position for level 21 if (levelNum === 21) { basketball.x = 1800; basketball.y = 2300; basketball.startX = 1800; basketball.startY = 2300; } else { basketball.x = level.ballX; basketball.y = level.ballY; basketball.startX = level.ballX; basketball.startY = level.ballY; } // Add vertical movement for level 5 and level 10 if (levelNum === 5 || levelNum === 10) { basketball.isMovingVertically = true; basketball.moveDirection = 1; basketball.moveSpeed = 8; basketball.minY = 1000; basketball.maxY = 1700; } // Add horizontal movement for level 6 if (levelNum === 6) { basketball.isMovingHorizontally = true; basketball.moveDirection = 1; basketball.moveSpeed = 12; basketball.minX = 800; basketball.maxX = 1300; } // Add horizontal movement for level 11 if (levelNum === 11) { basketball.isMovingHorizontally = true; basketball.moveDirection = 1; basketball.moveSpeed = 12; // Increased horizontal movement speed basketball.minX = 800; basketball.maxX = 1300; // Add smooth acceleration using tween for extra visual impact tween(basketball, { scaleX: 1.1, scaleY: 0.9 }, { duration: 300, easing: tween.easeInOut }); } // Add shadow ring under basketball to indicate it's draggable var shadow = LK.getAsset('basketball', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 0.3, tint: 0x000000, alpha: 0.3 }); shadow.x = basketball.x; shadow.y = basketball.y + 90; // Position below ball game.addChild(shadow); basketball.shadow = shadow; // Make basketball and shadow invisible for level 21 if (levelNum === 21) { basketball.alpha = 0; shadow.alpha = 0; } // Create hoop hoop = game.addChild(new Hoop()); hoop.x = 2048; // Always position flush against rightmost wall hoop.y = level.hoopY; hoop.isMoving = level.movingHoop; // Make hoop components invisible for level 20 if (levelNum === 20) { hoop.rim.alpha = 0; hoop.backboard.alpha = 0; hoop.supportLine.alpha = 0; hoop.netSegments.forEach(function (segment) { segment.alpha = 0; }); // Make brackets invisible too var wallBracket = hoop.children.find(function (child) { return child.tint === 0x808080 && child.scaleX === 0.3; }); var rimBracket = hoop.children.find(function (child) { return child.tint === 0x808080 && child.scaleX === 0.6; }); if (wallBracket) { wallBracket.alpha = 0; } if (rimBracket) { rimBracket.alpha = 0; } } // Configure movement for level 10 and levels after 10 if (levelNum >= 10) { hoop.isMoving = true; hoop.moveHorizontal = false; // Always vertical movement after level 10 hoop.minY = 300; hoop.maxY = 1500; // Gradually increase speed based on level var speedMultiplier = Math.max(1, (levelNum - 10) * 0.5); // Increase by 0.5 each level after 10 hoop.moveSpeed = 2 + speedMultiplier; } // Configure movement for level 15 if (levelNum >= 15) { hoop.isMoving = true; hoop.moveHorizontal = false; hoop.minY = 600; hoop.maxY = 1500; // Gradually increase speed based on level var speedMultiplier = Math.max(1, (levelNum - 10) * 0.5); // Increase by 0.5 each level after 10 hoop.moveSpeed = 6 + speedMultiplier; } // Configure movement for level 21 if (levelNum === 21) { hoop.isMoving = true; hoop.moveHorizontal = false; hoop.minY = 300; hoop.maxY = 1500; hoop.moveSpeed = 20; } // Configure vertical movement for level 17 (keeping original logic for specific level) if (levelNum === 17) { hoop.moveHorizontal = false; hoop.minY = 300; hoop.maxY = 1500; hoop.moveSpeed = 3; } // Create walls level.walls.forEach(function (wallData) { var wall = game.addChild(new Wall()); wall.x = wallData.x; wall.y = wallData.y; if (wallData.width) { wall.scaleX = wallData.width / 200; } if (wallData.height) { wall.scaleY = wallData.height / 30; } // Configure wall movement for level 9 if (levelNum === 9) { wall.isMoving = true; wall.moveSpeed = 12; // Fast vertical movement wall.moveDirection = 1; wall.minY = 500; wall.maxY = 1600; // Start walls at different phases for varied movement var wallIndex = walls.length; if (wallIndex % 2 === 1) { wall.moveDirection = -1; // Alternate initial direction } // Use tween for smooth movement animation tween(wall, { y: wall.y + wall.moveDirection * 100 }, { duration: 1000, easing: tween.easeInOut }); } // Configure wall movement for level 10 if (levelNum === 10) { wall.isMoving = true; wall.moveSpeed = 9; // Fast vertical movement wall.moveDirection = 1; wall.minY = 500; wall.maxY = 1600; // Start walls at different phases for varied movement var wallIndex = walls.length; if (wallIndex % 2 === 1) { wall.moveDirection = -1; // Alternate initial direction } // Use tween for smooth movement animation tween(wall, { y: wall.y + wall.moveDirection * 100 }, { duration: 1000, easing: tween.easeInOut }); } // Configure wall movement for level 14 if (levelNum === 14) { wall.isMoving = true; wall.moveSpeed = 4; // Same speed as hoop wall.moveDirection = 1; wall.minY = 300; wall.maxY = 1500; // Start walls at different phases for varied movement var wallIndex = walls.length; if (wallIndex % 2 === 1) { wall.moveDirection = -1; // Alternate initial direction } // Use tween for smooth movement animation tween(wall, { y: wall.y + wall.moveDirection * 100 }, { duration: 1000, easing: tween.easeInOut }); } // Configure wall rotation for level 15 if (levelNum === 15 && wall.x === 1600 && wall.y === 600) { // Create continuous rotation around center point var _rotateWall = function rotateWall() { tween(wall, { rotation: wall.rotation + Math.PI * 2 // Full 360 degree rotation }, { duration: 3000, // 3 seconds per rotation easing: tween.linear, onFinish: function onFinish() { _rotateWall(); // Loop the rotation continuously } }); }; _rotateWall(); // Start the rotation } // Configure wall movement for level 16 if (levelNum === 16) { wall.isMoving = true; wall.moveSpeed = 9; wall.moveDirection = 1; wall.minY = 400; wall.maxY = 1200; // Use tween for smooth movement animation tween(wall, { y: wall.y + wall.moveDirection * 100 }, { duration: 500, easing: tween.easeInOut }); } // Configure wall rotation for level 17 if (levelNum === 17) { // Create continuous rotation around center point var _rotateWall2 = function rotateWall() { tween(wall, { rotation: wall.rotation + Math.PI * 2 // Full 360 degree rotation }, { duration: 3000, // 3 seconds per rotation easing: tween.linear, onFinish: function onFinish() { _rotateWall2(); // Loop the rotation continuously } }); }; _rotateWall2(); // Start the rotation } // Configure wall movement for level 18 if (levelNum === 18) { wall.isMoving = true; wall.moveSpeed = 10; // Fast vertical movement wall.moveDirection = 1; wall.minY = 810; wall.maxY = 900; // Start walls at different phases for varied movement var wallIndex = walls.length; if (wallIndex % 2 === 1) { wall.moveDirection = -1; // Alternate initial direction } // Use tween for smooth movement animation tween(wall, { y: wall.y + wall.moveDirection * 100 }, { duration: 800, easing: tween.easeInOut }); } // Make walls invisible for level 20 if (levelNum === 20) { wall.alpha = 0; } walls.push(wall); }); // Create spikes level.spikes.forEach(function (spikeData) { // Skip static spike creation for level 14 if (levelNum === 14) { return; // Don't create static spikes for level 14 } var spike = game.addChild(new Spike()); spike.x = spikeData.x; spike.y = spikeData.y; // Configure spike movement for level 13 if (levelNum === 13) { spike.isMoving = true; spike.moveSpeed = 8; // Fast vertical movement spike.moveDirection = 1; spike.minY = 200; spike.maxY = 1600; // Start spikes at different phases for varied movement var spikeIndex = spikes.length; if (spikeIndex % 2 === 1) { spike.moveDirection = -1; // Alternate initial direction } // Use tween for smooth movement animation tween(spike, { y: spike.y + spike.moveDirection * 100 }, { duration: 500, easing: tween.easeInOut }); } spikes.push(spike); }); level.spikes.forEach(function (spikeData) { var spike = game.addChild(new Spike()); spike.x = spikeData.x; spike.y = spikeData.y; // Configure spike movement for level 14 if (levelNum === 14) { spike.isMoving = true; spike.moveSpeed = 6; // Fast horizontal movement spike.moveDirection = 1; spike.minX = 200; spike.maxX = 2000; // Start spikes at different phases for varied movement var spikeIndex = spikes.length; if (spikeIndex % 2 === 1) { spike.moveDirection = -1; // Alternate initial direction } // Use tween for smooth movement animation tween(spike, { x: spike.x + spike.moveDirection * 50 }, { duration: 500, easing: tween.easeInOut }); } spikes.push(spike); }); // Add 3 additional moving spikes for level 13 if (levelNum === 13) { // Additional spike 1 var extraSpike1 = game.addChild(new Spike()); extraSpike1.x = 1400; extraSpike1.y = 900; extraSpike1.isMoving = true; extraSpike1.moveSpeed = 8; extraSpike1.moveDirection = -1; extraSpike1.minY = 200; extraSpike1.maxY = 2000; tween(extraSpike1, { y: extraSpike1.y + extraSpike1.moveDirection * 100 }, { duration: 500, easing: tween.easeInOut }); spikes.push(extraSpike1); // Additional spike 2 var extraSpike2 = game.addChild(new Spike()); extraSpike2.x = 1600; extraSpike2.y = 1300; extraSpike2.isMoving = true; extraSpike2.moveSpeed = 8; extraSpike2.moveDirection = 1; extraSpike2.minY = 200; extraSpike2.maxY = 2000; tween(extraSpike2, { y: extraSpike2.y + extraSpike2.moveDirection * 100 }, { duration: 500, easing: tween.easeInOut }); spikes.push(extraSpike2); // Additional spike 3 var extraSpike3 = game.addChild(new Spike()); extraSpike3.x = 1900; extraSpike3.y = 700; extraSpike3.isMoving = true; extraSpike3.moveSpeed = 8; extraSpike3.moveDirection = -1; extraSpike3.minY = 200; extraSpike3.maxY = 2000; tween(extraSpike3, { y: extraSpike3.y + extraSpike3.moveDirection * 100 }, { duration: 500, easing: tween.easeInOut }); spikes.push(extraSpike3); } // Create rotating blocks level.rotatingBlocks.forEach(function (blockData) { var block = game.addChild(new RotatingBlock()); block.x = blockData.x; block.y = blockData.y; // Configure horizontal movement for level 16 if (levelNum === 16) { block.isMovingHorizontally = true; block.moveSpeed = 6; block.moveDirection = 1; block.minX = 600; block.maxX = 1900; // Use tween for smooth movement animation tween(block, { x: block.x + block.moveDirection * 50 }, { duration: 1000, easing: tween.easeInOut }); } // Configure horizontal movement for level 17 if (levelNum === 17) { block.isMovingHorizontally = true; block.moveSpeed = 8; block.moveDirection = 1; block.minX = 600; block.maxX = 1900; // Use tween for smooth movement animation tween(block, { x: block.x + block.moveDirection * 50 }, { duration: 1000, easing: tween.easeInOut }); } rotatingBlocks.push(block); }); // Reset game state gameState = 'playing'; attempts = 0; maxAttempts = level.maxAttempts; scored = false; levelGoal = level.goal; // Reset objective tracking wallBounces = 0; requiredBounces = level.objective === 'bounce_once' ? 1 : level.objective === 'bounce_twice' ? 2 : 0; touchedWall = false; leftWallBounced = false; leftWallBounceRequired = levelNum === 10; touchedNonLeftWall = false; objectiveFailed = false; failureReason = ''; // Reset timer challenge variables timerChallenge = level.objective === 'timer_challenge'; ballReleaseTime = 0; timerCountdown = 0; inputDisabled = false; globalTimerActive = false; globalTimerStartTime = 0; if (timerCountdownText) { timerCountdownText.visible = false; } // Hide Next Level button if (nextLevelButton) { nextLevelButton.visible = false; } // Create fire launcher for level 19 only if (levelNum === 19) { fireballTimer = LK.setInterval(function () { // Create fireball at launcher position var fireball = game.addChild(new Fireball()); fireball.x = 1024; fireball.y = 2200; // Set diagonal velocity (top-left, top-right, bottom-left, bottom-right) var directions = [{ x: -2, y: -2 }, // top-left { x: 2, y: -2 }, // top-right { x: -2, y: 2 }, // bottom-left { x: 2, y: 2 } // bottom-right ]; var direction = directions[Math.floor(Math.random() * directions.length)]; fireball.velocityX = direction.x; fireball.velocityY = direction.y; fireballs.push(fireball); }, 1000); // Every 1 second } // Update UI levelText.setText('Level ' + levelNum); goalText.setText(levelGoal); updateAttemptsText(); } function updateAttemptsText() { if (maxAttempts > 0) { attemptsText.setText('Attempts: ' + attempts + '/' + maxAttempts); } else { attemptsText.setText('Attempts: ' + attempts); } } function checkCollisions() { if (!basketball.isLaunched) { return; } // Check wall collisions (level walls) walls.forEach(function (wall) { if (basketball.intersects(wall)) { var ballCenterX = basketball.x; var ballCenterY = basketball.y; var wallCenterX = wall.x; var wallCenterY = wall.y; var wallWidth = wall.width * wall.scaleX; var wallHeight = wall.height * wall.scaleY; var overlapX = Math.abs(ballCenterX - wallCenterX) - (80 + wallWidth / 2); var overlapY = Math.abs(ballCenterY - wallCenterY) - (80 + wallHeight / 2); // Realistic physics collision with angle-based reflection // For level 9, use better bouncing physics with proper angle calculation var energyLoss = currentLevel === 9 ? 0.85 : 0.85; var minVelocity = 0.5; // Calculate collision normal for proper physics reflection var dx = ballCenterX - wallCenterX; var dy = ballCenterY - wallCenterY; var distance = Math.sqrt(dx * dx + dy * dy); if (currentLevel === 9 && distance > 0) { // Level 9: Use proper physics reflection based on collision angle var normalX = dx / distance; var normalY = dy / distance; // Calculate relative velocity var relativeVelocityX = basketball.velocityX; var relativeVelocityY = basketball.velocityY; // Calculate velocity component along normal var velocityAlongNormal = relativeVelocityX * normalX + relativeVelocityY * normalY; // Only reflect if moving toward the wall if (velocityAlongNormal < 0) { // Calculate reflection with proper physics basketball.velocityX = basketball.velocityX - 2 * velocityAlongNormal * normalX; basketball.velocityY = basketball.velocityY - 2 * velocityAlongNormal * normalY; // Apply energy loss basketball.velocityX *= energyLoss; basketball.velocityY *= energyLoss; // Separate ball from wall to prevent sticking var separation = 85; // Ball radius + small margin basketball.x = wallCenterX + normalX * separation; basketball.y = wallCenterY + normalY * separation; } } else { // Other levels: Use original collision logic if (overlapX < overlapY) { if (ballCenterX < wallCenterX && basketball.velocityX > 0 || ballCenterX > wallCenterX && basketball.velocityX < 0) { basketball.velocityX = -basketball.velocityX * energyLoss; basketball.velocityY = basketball.velocityY * energyLoss; // Apply energy loss to perpendicular component too if (Math.abs(basketball.velocityX) < minVelocity) { basketball.velocityX = 0; } } basketball.x += ballCenterX < wallCenterX ? -5 : 5; } else { if (ballCenterY < wallCenterY && basketball.velocityY > 0 || ballCenterY > wallCenterY && basketball.velocityY < 0) { basketball.velocityY = -basketball.velocityY * energyLoss; basketball.velocityX = basketball.velocityX * energyLoss; // Apply energy loss to perpendicular component too if (Math.abs(basketball.velocityY) < minVelocity) { basketball.velocityY = 0; } } basketball.y += ballCenterY < wallCenterY ? -5 : 5; } } LK.getSound('bounce').play(); // Track wall bounces and check objectives wallBounces++; touchedWall = true; // For level 10, check if touching non-left wall before left wall if (leftWallBounceRequired && !leftWallBounced) { touchedNonLeftWall = true; objectiveFailed = true; failureReason = 'You must hit the left wall first!'; } var level = levels[currentLevel - 1]; if (level.objective === 'no_wall') { objectiveFailed = true; failureReason = 'You touched the wall!'; } // For level 17, hitting any wall causes failure if (currentLevel === 17) { objectiveFailed = true; failureReason = 'You hit the wall!'; } } }); // Check spike collisions spikes.forEach(function (spike) { if (basketball.intersects(spike)) { objectiveFailed = true; failureReason = 'You hit the spikes!'; } }); // Check fireball collisions fireballs.forEach(function (fireball) { if (basketball.intersects(fireball)) { objectiveFailed = true; failureReason = 'You hit the fireball!'; } }); // Check rotating block collisions rotatingBlocks.forEach(function (block) { if (basketball.intersects(block)) { var ballCenterX = basketball.x; var ballCenterY = basketball.y; var blockCenterX = block.x; var blockCenterY = block.y; var overlapX = Math.abs(ballCenterX - blockCenterX) - 120; var overlapY = Math.abs(ballCenterY - blockCenterY) - 120; if (overlapX < overlapY) { basketball.velocityX = -basketball.velocityX * basketball.bounceDecay; basketball.x += ballCenterX < blockCenterX ? -5 : 5; } else { basketball.velocityY = -basketball.velocityY * basketball.bounceDecay; basketball.y += ballCenterY < blockCenterY ? -5 : 5; } LK.getSound('bounce').play(); } }); // Check backboard collision for realistic physics var ballCenterX = basketball.x; var ballCenterY = basketball.y; var hoopCenterX = hoop.x + hoop.backboardCollisionZone.x; var hoopCenterY = hoop.y + hoop.backboardCollisionZone.y; if (Math.abs(ballCenterX - hoopCenterX) < 80 + hoop.backboardCollisionZone.width / 2 && Math.abs(ballCenterY - hoopCenterY) < 80 + hoop.backboardCollisionZone.height / 2) { // Ball hit backboard basketball.velocityX = -basketball.velocityX * basketball.bounceDecay * 0.9; basketball.velocityY = basketball.velocityY * 0.95; basketball.x += basketball.velocityX > 0 ? -8 : 8; LK.getSound('bounce').play(); // Flash backboard LK.effects.flashObject(hoop, 0xFFFFFF, 200); } // Check vertical support line collision for soft redirection helping var supportLineCenterX = hoop.x + hoop.supportLineCollisionZone.x; var supportLineCenterY = hoop.y + hoop.supportLineCollisionZone.y; if (Math.abs(ballCenterX - supportLineCenterX) < 80 + hoop.supportLineCollisionZone.width / 2 && Math.abs(ballCenterY - supportLineCenterY) < 80 + hoop.supportLineCollisionZone.height / 2) { // Ball hit vertical support line - apply soft downward redirection instead of hard bounce var distanceFromRim = Math.abs(ballCenterY - (hoop.y + hoop.rimCollisionZone.y)); var redirectionStrength = Math.max(0.1, 1.0 - distanceFromRim / 80); // Stronger redirection when closer to rim // Gently redirect ball downward into the hoop if shot is close if (basketball.velocityX < 0) { // Ball moving toward hoop from right side var rimCenterX = hoop.x + hoop.rimCollisionZone.x; var rimCenterY = hoop.y + hoop.rimCollisionZone.y; var directionToRimX = rimCenterX - ballCenterX; var directionToRimY = rimCenterY - ballCenterY; // Apply gentle redirection toward rim center basketball.velocityX += directionToRimX * 0.003 * redirectionStrength; basketball.velocityY += Math.abs(directionToRimY) * 0.002 * redirectionStrength; // Gentle downward push // Reduce horizontal velocity to prevent harsh deflection basketball.velocityX *= 0.85; // Small position adjustment to prevent getting stuck basketball.x -= 3; // Subtle flash effect for support line hit LK.effects.flashObject(hoop.supportLine, 0xFFFFAA, 150); } } // Check rim collision for realistic bouncing - extra forgiving scoring zone var rimCenterX = hoop.x + hoop.rimCollisionZone.x; var rimCenterY = hoop.y + hoop.rimCollisionZone.y; if (Math.abs(ballCenterX - rimCenterX) < 80 + hoop.rimCollisionZone.width / 2 && Math.abs(ballCenterY - rimCenterY) < 80 + hoop.rimCollisionZone.height / 2) { // Check if ball is going through the hoop (scoring) - ultra wide scoring zone with auto-absorption var scoringZoneWidth = 140; // Even wider invisible scoring zone for maximum forgiveness var scoringZoneHeight = 40; // Taller scoring zone for easier front rim entry var autoAbsorptionZone = 80; // Larger zone where ball gets gently pulled toward center // Auto-absorption: if ball is close to center, gently guide it inward var distanceFromCenterX = Math.abs(ballCenterX - rimCenterX); var distanceFromCenterY = Math.abs(ballCenterY - rimCenterY); if (distanceFromCenterX < autoAbsorptionZone && distanceFromCenterY < autoAbsorptionZone && basketball.velocityY > 0) { // Gently pull ball toward center for easier scoring var pullStrength = 0.15; // Subtle pull force if (ballCenterX < rimCenterX) { basketball.velocityX += pullStrength; } else if (ballCenterX > rimCenterX) { basketball.velocityX -= pullStrength; } } if (basketball.velocityY > 0 && ballCenterX > rimCenterX - scoringZoneWidth && ballCenterX < rimCenterX + scoringZoneWidth && ballCenterY > rimCenterY - scoringZoneHeight && ballCenterY < rimCenterY + scoringZoneHeight && ballCenterY < rimCenterY) { // Check if objective was met before scoring var level = levels[currentLevel - 1]; var objectiveMet = true; var objectiveFailureMessage = ''; if (leftWallBounceRequired && !leftWallBounced) { objectiveMet = false; objectiveFailureMessage = 'You must hit the left wall first!'; } else if (level.objective === 'bounce_once' && wallBounces !== 1) { objectiveMet = false; objectiveFailureMessage = wallBounces === 0 ? 'You must bounce off one wall!' : 'You bounced too many times!'; } else if (level.objective === 'bounce_twice' && wallBounces !== 2) { objectiveMet = false; objectiveFailureMessage = wallBounces === 0 ? 'You must bounce off two walls!' : wallBounces === 1 ? 'You must bounce off one more wall!' : 'You bounced too many times!'; } else if (level.objective === 'no_wall' && touchedWall) { objectiveMet = false; objectiveFailureMessage = 'You were not supposed to touch any walls!'; } if (!objectiveMet) { // Objective failed LK.effects.flashScreen(0xff0000, 1000); LK.setTimeout(function () { currentLevel = 1; storage.currentLevel = 1; LK.showGameOver(); }, 2000); return; // Don't continue with scoring } // Score! scored = true; LK.getSound('score').play(); // Check if it's a clean swish (ball passes through center of rim) var distanceFromCenter = Math.abs(ballCenterX - rimCenterX); var isCleanShot = distanceFromCenter < 30; // Within 30 pixels of center if (isCleanShot) { // Clean swish - special effects LK.effects.flashObject(hoop, 0x00FFFF, 800); // Cyan flash for perfect shot // Slow down ball slightly for dramatic effect basketball.velocityX *= 0.8; basketball.velocityY *= 0.9; } else { // Regular score LK.effects.flashObject(hoop, 0x00FF00, 500); } // Let global timer handle level progression for all levels // Animate net swish effect with enhanced 3D movement if (hoop.netSegments) { hoop.netSegments.forEach(function (segment, index) { var swishIntensity = isCleanShot ? 1.5 : 1.0; // More dramatic swish for clean shots // First phase: compress and sway tween(segment, { scaleY: 0.05 * swishIntensity, alpha: 0.2, rotation: segment.rotation + 0.3 * swishIntensity, y: segment.y + 15 // Pull net segments down more }, { duration: isCleanShot ? 200 : 150, easing: tween.easeOut }); LK.setTimeout(function () { // Second phase: bounce back with realistic sway tween(segment, { scaleY: 0.15, alpha: 0.8, rotation: segment.rotation - 0.1 * swishIntensity, y: segment.y - 10 // Natural bounce back position }, { duration: isCleanShot ? 600 : 400, easing: tween.bounceOut }); }, index * (isCleanShot ? 40 : 30)); }); } // Level complete - show success display and Next Level button gameState = 'levelComplete'; // Show immediate success feedback if (timerCountdownText) { timerCountdownText.setText('Level Complete!'); timerCountdownText.tint = 0x00FF00; timerCountdownText.visible = true; timerCountdownText.scaleX = 1.5; timerCountdownText.scaleY = 1.5; // Animate success text tween(timerCountdownText, { scaleX: 1.2, scaleY: 1.2 }, { duration: 300, easing: tween.bounceOut }); } // Show Next Level button nextLevelButton.visible = true; tween(nextLevelButton, { scaleX: 1.2, scaleY: 1.2, alpha: 1 }, { duration: 500, easing: tween.bounceOut }); // Lock input immediately upon scoring inputDisabled = true; } else { // Ball hit rim but didn't score - ultra soft rim bounce for maximum forgiveness var overlapX = Math.abs(ballCenterX - rimCenterX) - (80 + hoop.rimCollisionZone.width / 2); var overlapY = Math.abs(ballCenterY - rimCenterY) - (80 + hoop.rimCollisionZone.height / 2); // Ultra soft bounces with minimal energy loss for maximum forgiveness var softBounceDecay = 0.2; // Even softer bounce - less harsh rejection var energyLoss = 0.9; // Higher energy retention - keep more momentum var distanceFromCenter = Math.abs(ballCenterX - rimCenterX); var distanceFromFrontEdge = Math.abs(ballCenterX - (rimCenterX - hoop.rimCollisionZone.width / 2)); // Make front edge (facing player) extremely forgiving if (distanceFromFrontEdge < 30 && basketball.velocityX < 0) { // Front edge hit - minimal bounce with downward guidance softBounceDecay = 0.1; energyLoss = 0.95; // Add slight downward velocity to help ball drop into hoop basketball.velocityY += 0.5; } else if (distanceFromCenter < 50) { // Very close to center - barely bounce at all softBounceDecay = 0.15; energyLoss = 0.92; } else if (distanceFromCenter < 100) { // Moderately close - gentle bounce softBounceDecay = 0.25; energyLoss = 0.88; } if (overlapX < overlapY) { basketball.velocityX = -basketball.velocityX * softBounceDecay; basketball.velocityY = basketball.velocityY * energyLoss; // Keep more perpendicular velocity basketball.x += ballCenterX < rimCenterX ? -1 : 1; // Minimal position adjustment } else { basketball.velocityY = -basketball.velocityY * softBounceDecay; basketball.velocityX = basketball.velocityX * energyLoss; // Keep more perpendicular velocity basketball.y += ballCenterY < rimCenterY ? -1 : 1; // Minimal position adjustment } LK.getSound('bounce').play(); // Flash rim on hit with gentler color LK.effects.flashObject(hoop.rim, 0xFFAA00, 300); } } } function resetBall() { basketball.reset(); clearTrajectory(); clearPowerIndicators(); gameState = 'playing'; // Reset objective tracking for new attempt wallBounces = 0; touchedWall = false; leftWallBounced = false; touchedNonLeftWall = false; objectiveFailed = false; failureReason = ''; // Reset timer challenge variables ballReleaseTime = 0; timerCountdown = 0; inputDisabled = false; globalTimerActive = false; globalTimerStartTime = 0; if (timerCountdownText) { timerCountdownText.visible = false; } // Hide Next Level button if (nextLevelButton) { nextLevelButton.visible = false; } } function showTrajectory(forceX, forceY) { clearTrajectory(); var simX = basketball.x; var simY = basketball.y; var simVelX = forceX; var simVelY = forceY; var simGravity = 0.3; var forceMagnitude = Math.sqrt(forceX * forceX + forceY * forceY); var powerPercent = Math.min(forceMagnitude / 40, 1.0); for (var i = 0; i < 30; i++) { simX += simVelX; simY += simVelY; simVelY += simGravity; if (i % 3 === 0) { var dot = game.addChild(new TrajectoryDot()); dot.x = simX; dot.y = simY; // Scale dots based on power level dot.scaleX = 0.5 + powerPercent * 0.8; dot.scaleY = 0.5 + powerPercent * 0.8; // Color dots based on power dot.tint = powerPercent > 0.8 ? 0xff0000 : powerPercent > 0.5 ? 0xffff00 : 0x00ff00; trajectoryDots.push(dot); } if (simX <= 80 || simX >= 1968 || simY <= 80) { break; } } } function clearTrajectory() { trajectoryDots.forEach(function (dot) { dot.destroy(); }); trajectoryDots = []; } function updatePowerIndicators(currentX, currentY, dragDistance, forceMagnitude) { // Clear existing indicators clearPowerIndicators(); // Calculate power percentage (0 to 1) var powerPercent = Math.min(forceMagnitude / 40, 1.0); // Create aim line from ball to current drag position var aimLineAsset = LK.getAsset('trajectory', { anchorX: 0.5, anchorY: 0.5, scaleX: Math.max(1, dragDistance / 8), scaleY: Math.max(2, 2 + powerPercent * 3) }); aimLineAsset.x = (basketball.x + currentX) / 2; aimLineAsset.y = (basketball.y + currentY) / 2; aimLineAsset.rotation = Math.atan2(currentY - basketball.y, currentX - basketball.x); aimLineAsset.alpha = 0.8; aimLineAsset.tint = powerPercent > 0.8 ? 0xff0000 : powerPercent > 0.5 ? 0xffff00 : 0x00ff00; game.addChild(aimLineAsset); aimLine = aimLineAsset; // Create power bar indicator var powerBarWidth = 300; var powerBarHeight = 20; var powerBarX = basketball.x - powerBarWidth / 2; var powerBarY = basketball.y - 120; // Power bar background var powerBarBg = LK.getAsset('wall', { anchorX: 0, anchorY: 0, scaleX: powerBarWidth / 200, scaleY: powerBarHeight / 30, tint: 0x333333 }); powerBarBg.x = powerBarX; powerBarBg.y = powerBarY; powerBarBg.alpha = 0.7; game.addChild(powerBarBg); // Power bar fill var fillWidth = powerBarWidth * powerPercent; if (fillWidth > 0) { var powerBarFill = LK.getAsset('wall', { anchorX: 0, anchorY: 0, scaleX: fillWidth / 200, scaleY: powerBarHeight / 30, tint: powerPercent > 0.8 ? 0xff0000 : powerPercent > 0.5 ? 0xffff00 : 0x00ff00 }); powerBarFill.x = powerBarX; powerBarFill.y = powerBarY; powerBarFill.alpha = 0.9; game.addChild(powerBarFill); powerBar = [powerBarBg, powerBarFill]; } else { powerBar = [powerBarBg]; } } function clearPowerIndicators() { if (aimLine) { aimLine.destroy(); aimLine = null; } if (powerBar) { powerBar.forEach(function (bar) { bar.destroy(); }); powerBar = null; } } // Power indicator variables var powerBar = null; var aimLine = null; var maxDragDistance = 400; // Maximum pixel distance for full power // Input handling game.down = function (x, y, obj) { if (gameState !== 'playing' || basketball.isLaunched || inputDisabled) { return; } isDragging = true; dragStartX = x; dragStartY = y; }; game.move = function (x, y, obj) { if (!isDragging || gameState !== 'playing' || basketball.isLaunched || inputDisabled) { return; } // Calculate drag distance and force with increased sensitivity var dragX = dragStartX - x; var dragY = dragStartY - y; var dragDistance = Math.sqrt(dragX * dragX + dragY * dragY); var dragMultiplier = Math.min(dragDistance / maxDragDistance, 1.0); var forceX = dragX * 0.08 * dragMultiplier; var forceY = dragY * 0.08 * dragMultiplier; // Limit force to maximum power var magnitude = Math.sqrt(forceX * forceX + forceY * forceY); if (magnitude > 40) { forceX = forceX / magnitude * 40; forceY = forceY / magnitude * 40; } // Update visual feedback updatePowerIndicators(x, y, dragDistance, magnitude); showTrajectory(forceX, forceY); }; game.up = function (x, y, obj) { if (!isDragging || gameState !== 'playing' || basketball.isLaunched || inputDisabled) { return; } isDragging = false; // Calculate final force with increased sensitivity var dragX = dragStartX - x; var dragY = dragStartY - y; var dragDistance = Math.sqrt(dragX * dragX + dragY * dragY); var dragMultiplier = Math.min(dragDistance / maxDragDistance, 1.0); var forceX = dragX * 0.08 * dragMultiplier; var forceY = dragY * 0.08 * dragMultiplier; // Limit force to maximum power var magnitude = Math.sqrt(forceX * forceX + forceY * forceY); if (magnitude > 40) { forceX = forceX / magnitude * 40; forceY = forceY / magnitude * 40; } // Clear visual indicators clearPowerIndicators(); if (magnitude > 1) { attempts++; updateAttemptsText(); basketball.launch(forceX, forceY); clearTrajectory(); // Start global 3-second timer for all levels ballReleaseTime = LK.ticks; globalTimerActive = true; globalTimerStartTime = LK.ticks; inputDisabled = true; timerCountdownText.visible = true; // Check if out of attempts if (maxAttempts > 0 && attempts >= maxAttempts && !scored) { LK.setTimeout(function () { if (!scored) { currentLevel = 1; storage.currentLevel = 1; LK.showGameOver(); } }, 3000); } } }; // Main update loop game.update = function () { if (gameState === 'playing') { // Handle global 3-second timer countdown for all levels if (globalTimerActive && globalTimerStartTime > 0) { var timeSinceRelease = (LK.ticks - globalTimerStartTime) * (1000 / 60); // Convert to milliseconds var remainingTime = Math.max(0, 3000 - timeSinceRelease); var secondsLeft = Math.ceil(remainingTime / 1000); if (timerCountdownText) { timerCountdownText.setText(secondsLeft.toString()); timerCountdownText.visible = true; timerCountdownText.tint = 0xFFFFFF; // Reset tint to white // Animate countdown text var scale = 1.0 + Math.sin(LK.ticks * 0.3) * 0.2; timerCountdownText.scaleX = scale; timerCountdownText.scaleY = scale; } // Check if 3 seconds have passed - end level regardless of success/failure if (remainingTime <= 0) { globalTimerActive = false; var level = levels[currentLevel - 1]; var wasSuccessful = scored; // Check objective completion if (leftWallBounceRequired && !leftWallBounced) { wasSuccessful = false; } else if (level.objective === 'bounce_once' && wallBounces !== 1) { wasSuccessful = false; } else if (level.objective === 'bounce_twice' && wallBounces !== 2) { wasSuccessful = false; } else if (level.objective === 'no_wall' && touchedWall) { wasSuccessful = false; } else if (level.objective === 'timer_challenge') { // For timer challenge, must score within 1 second var timeSinceReleaseForTimer = (LK.ticks - ballReleaseTime) * (1000 / 60); if (!scored || timeSinceReleaseForTimer > 1000) { wasSuccessful = false; } } if (wasSuccessful) { // Flash screen with success color LK.effects.flashScreen(0x00FF00, 500); // Add sparkle effect to hoop if (hoop) { tween(hoop, { scaleX: 1.1, scaleY: 1.1 }, { duration: 200, easing: tween.easeOut }); LK.setTimeout(function () { tween(hoop, { scaleX: 1.0, scaleY: 1.0 }, { duration: 300, easing: tween.bounceOut }); }, 200); } // Hide timer countdown as level is complete if (timerCountdownText) { timerCountdownText.visible = false; } // Lock input but don't auto-progress - wait for button click inputDisabled = true; } else { // Level failed if (timerCountdownText) { timerCountdownText.setText('FAIL'); timerCountdownText.tint = 0xFF0000; } LK.effects.flashScreen(0xff0000, 1000); LK.setTimeout(function () { currentLevel = 1; storage.currentLevel = 1; LK.showGameOver(); }, 2000); } return; } } // Track screen edge bounces for objectives (before calling checkCollisions) if (basketball.isLaunched) { var ballRadius = 80; var lastWallBounces = wallBounces; // Check if ball just bounced off any screen edge this frame if (basketball.x <= ballRadius && basketball.velocityX > 0 || basketball.x >= 2048 - ballRadius && basketball.velocityX < 0 || basketball.y <= ballRadius && basketball.velocityY > 0 || basketball.y >= 2732 - ballRadius && basketball.velocityY < 0) { // Ball just bounced off a screen edge if (wallBounces === lastWallBounces) { // Prevent double counting wallBounces++; touchedWall = true; // Track non-left wall touches for level 10 if (leftWallBounceRequired && (basketball.x >= 2048 - ballRadius || basketball.y <= ballRadius || basketball.y >= 2732 - ballRadius)) { touchedNonLeftWall = true; objectiveFailed = true; failureReason = 'You must hit the left wall first!'; } var level = levels[currentLevel - 1]; if (level.objective === 'no_wall') { objectiveFailed = true; failureReason = 'You touched the wall!'; } } } } checkCollisions(); // Check for objective failures if (objectiveFailed && !scored) { LK.effects.flashScreen(0xff0000, 1000); LK.setTimeout(function () { currentLevel = 1; storage.currentLevel = 1; LK.showGameOver(); }, 2000); return; } // Check if ball is off screen (reset) if (basketball.isLaunched && basketball.y > 2800) { if (!scored) { // Ball missed - check if this constitutes objective failure var level = levels[currentLevel - 1]; var missedObjectiveMessage = 'Missed the hoop!'; if (leftWallBounceRequired && !leftWallBounced) { missedObjectiveMessage = 'You must hit the left wall first!'; } else if (level.objective === 'bounce_once' && wallBounces !== 1) { missedObjectiveMessage = wallBounces === 0 ? 'You must bounce off one wall!' : 'You bounced too many times!'; } else if (level.objective === 'bounce_twice' && wallBounces !== 2) { missedObjectiveMessage = wallBounces === 0 ? 'You must bounce off two walls!' : wallBounces === 1 ? 'You must bounce off one more wall!' : 'You bounced too many times!'; } else if (level.objective === 'no_wall' && touchedWall) { missedObjectiveMessage = 'You touched the wall!'; } LK.effects.flashScreen(0xff0000, 1000); LK.setTimeout(function () { currentLevel = 1; storage.currentLevel = 1; LK.showGameOver(); }, 2000); } else if (maxAttempts > 0 && attempts >= maxAttempts) { currentLevel = 1; storage.currentLevel = 1; LK.showGameOver(); } else { resetBall(); } } } }; // Initialize first level initializeLevel(currentLevel);
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Backboard = Container.expand(function () {
var self = Container.call(this);
var pole = self.attachAsset('backboardPole', {
anchorX: 0.5,
anchorY: 1
});
pole.x = 0;
pole.y = 0;
var board = self.attachAsset('backboard', {
anchorX: 0.5,
anchorY: 1
});
board.x = 0;
board.y = -50;
return self;
});
var Basketball = Container.expand(function () {
var self = Container.call(this);
var ball = self.attachAsset('basketball', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.isLaunched = false;
self.gravity = 0.3;
self.bounceDecay = 0.8;
self.startX = 0;
self.startY = 0;
self.reset = function () {
self.x = self.startX;
self.y = self.startY;
self.velocityX = 0;
self.velocityY = 0;
self.isLaunched = false;
// Update shadow position if it exists
if (self.shadow) {
self.shadow.x = self.x;
self.shadow.y = self.y + 90;
}
};
self.launch = function (forceX, forceY) {
self.velocityX = forceX;
self.velocityY = forceY;
self.isLaunched = true;
LK.getSound('launch').play();
};
self.update = function () {
// Handle vertical movement when not launched (level 5)
if (!self.isLaunched && self.isMovingVertically) {
self.y += self.moveSpeed * self.moveDirection;
if (self.y <= self.minY || self.y >= self.maxY) {
self.moveDirection *= -1;
}
// Update shadow position during movement
if (self.shadow) {
self.shadow.y = self.y + 90;
}
// Update start position for reset
self.startY = self.y;
}
// Handle horizontal movement when not launched (level 11)
if (!self.isLaunched && self.isMovingHorizontally) {
self.x += self.moveSpeed * self.moveDirection;
if (self.x <= self.minX || self.x >= self.maxX) {
self.moveDirection *= -1;
}
// Update shadow position during movement
if (self.shadow) {
self.shadow.x = self.x;
}
// Update start position for reset
self.startX = self.x;
}
if (self.isLaunched) {
self.x += self.velocityX;
self.y += self.velocityY;
self.velocityY += self.gravity;
// Realistic physics for all four screen edges
var ballRadius = 80; // Half of basketball width/height
var energyLoss = 0.85; // Energy retained after bounce (realistic friction)
var minVelocity = 0.5; // Minimum velocity to prevent infinite micro-bounces
// Left wall collision
if (self.x <= ballRadius) {
self.x = ballRadius;
if (self.velocityX < 0) {
// Only bounce if moving toward wall
self.velocityX = -self.velocityX * energyLoss;
self.velocityY = self.velocityY * energyLoss; // Slight energy loss on Y too
if (Math.abs(self.velocityX) < minVelocity) {
self.velocityX = 0;
}
LK.getSound('bounce').play();
// Track left wall bounce for level 10 objective
if (leftWallBounceRequired && !leftWallBounced) {
leftWallBounced = true;
}
}
}
// Right wall collision
if (self.x >= 2048 - ballRadius) {
self.x = 2048 - ballRadius;
if (self.velocityX > 0) {
// Only bounce if moving toward wall
self.velocityX = -self.velocityX * energyLoss;
self.velocityY = self.velocityY * energyLoss; // Slight energy loss on Y too
if (Math.abs(self.velocityX) < minVelocity) {
self.velocityX = 0;
}
LK.getSound('bounce').play();
}
}
// Top wall collision
if (self.y <= ballRadius) {
self.y = ballRadius;
if (self.velocityY < 0) {
// Only bounce if moving toward wall
self.velocityY = -self.velocityY * energyLoss;
self.velocityX = self.velocityX * energyLoss; // Slight energy loss on X too
if (Math.abs(self.velocityY) < minVelocity) {
self.velocityY = 0;
}
LK.getSound('bounce').play();
}
}
// Bottom wall collision
if (self.y >= 2732 - ballRadius) {
self.y = 2732 - ballRadius;
if (self.velocityY > 0) {
// Only bounce if moving toward wall
self.velocityY = -self.velocityY * energyLoss;
self.velocityX = self.velocityX * energyLoss; // Slight energy loss on X too
if (Math.abs(self.velocityY) < minVelocity) {
self.velocityY = 0;
}
LK.getSound('bounce').play();
}
}
}
// Update shadow position if it exists
if (self.shadow) {
self.shadow.x = self.x;
self.shadow.y = self.y + 90;
}
};
return self;
});
var Fireball = Container.expand(function () {
var self = Container.call(this);
var fireballGraphics = self.attachAsset('spike', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFF4500
});
self.velocityX = 0;
self.velocityY = 0;
self.speed = 2;
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
// Bounce off screen edges
if (self.x <= 20) {
self.x = 20;
self.velocityX = -self.velocityX;
}
if (self.x >= 2028) {
self.x = 2028;
self.velocityX = -self.velocityX;
}
if (self.y <= 20) {
self.y = 20;
self.velocityY = -self.velocityY;
}
if (self.y >= 2712) {
self.y = 2712;
self.velocityY = -self.velocityY;
}
// Bounce off walls
walls.forEach(function (wall) {
if (self.intersects(wall)) {
var ballCenterX = self.x;
var ballCenterY = self.y;
var wallCenterX = wall.x;
var wallCenterY = wall.y;
var wallWidth = wall.width * wall.scaleX;
var wallHeight = wall.height * wall.scaleY;
var overlapX = Math.abs(ballCenterX - wallCenterX) - (20 + wallWidth / 2);
var overlapY = Math.abs(ballCenterY - wallCenterY) - (20 + wallHeight / 2);
if (overlapX < overlapY) {
self.velocityX = -self.velocityX;
self.x += ballCenterX < wallCenterX ? -5 : 5;
} else {
self.velocityY = -self.velocityY;
self.y += ballCenterY < wallCenterY ? -5 : 5;
}
}
});
};
return self;
});
var Hoop = Container.expand(function () {
var self = Container.call(this);
// Create backboard as slim vertical bar (only thickness visible) - flush mounted to wall
var backboard = self.attachAsset('wall', {
anchorX: 1.0,
// Right edge anchor for wall attachment
anchorY: 0.5,
scaleX: 0.2,
// Slightly thicker for better visibility
scaleY: 2.8,
// Much taller backboard extending well above rim
tint: 0xFFFFFF
});
backboard.x = 0; // Completely flush with wall position (no gap)
backboard.y = -80; // Positioned higher so it extends above rim
// Create support bracket connecting backboard to wall
var wallBracket = self.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.2,
tint: 0x808080
});
wallBracket.x = 12; // Closer to wall for flush mounting
wallBracket.y = -50;
// Create horizontal rim extending toward left (side view) - made extra wide
var rim = self.attachAsset('hoop', {
anchorX: 1.0,
// Right edge anchored to backboard
anchorY: 0.5,
scaleX: 2.4,
// Extended horizontally toward player - extra wide for very forgiving scoring
scaleY: 0.2,
// Thin rim profile from side
tint: 0xFF4500
});
rim.x = -2; // Closer to backboard for flush mounting
rim.y = -80;
// Create rim support bracket
var rimBracket = self.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.15,
tint: 0x808080
});
rimBracket.x = -35; // Adjusted for better alignment
rimBracket.y = -70;
// Create vertical backboard support line behind the rim - taller and thicker
var supportLine = self.attachAsset('backboardSupport', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
// Thicker for more visual solidity
scaleY: 2.0,
// Taller extending well above rim
tint: 0xC0C0C0,
alpha: 0.9
});
supportLine.x = -8; // Closer to backboard for flush mounting
supportLine.y = -130; // Positioned much higher above the rim, never dipping below
// Create realistic side-view net hanging from rim - positioned to hang naturally below
var netSegments = [];
for (var i = 0; i < 8; i++) {
var netSegment = self.attachAsset('netSegment', {
anchorX: 0.5,
anchorY: 0,
scaleX: 0.8 - i * 0.06,
// Tapers as it hangs down
scaleY: 1.0,
tint: 0xFFFFFF,
alpha: 0.7
});
netSegment.x = -140 + i * 10; // Spread across rim width
netSegment.y = -60 + i * 12; // Hangs lower below rim with more natural drape
netSegments.push(netSegment);
}
// Store references for animations
self.netSegments = netSegments;
self.rim = rim;
self.backboard = backboard;
self.supportLine = supportLine;
// Physics collision zones for realistic ball interaction - ultra forgiving
self.rimCollisionZone = {
x: -110,
// Extends even further left from backboard
y: -80,
width: 320,
// Ultra wide rim width for maximum forgiving scoring
height: 50 // Extra thick rim height for easiest scoring
};
self.backboardCollisionZone = {
x: 0,
// At wall position - completely flush
y: -80,
// Positioned higher to match visual backboard
width: 40,
// Thicker to match visual appearance
height: 420 // Much taller backboard height matching visual
};
// Vertical support line collision zone for soft redirection
self.supportLineCollisionZone = {
x: -8,
// Closer to backboard for flush mounting
y: -130,
// Positioned higher above rim
width: 12,
// Thicker support line width
height: 240 // Taller support line height extending well above rim
};
self.isMoving = false;
self.moveSpeed = 2;
self.moveDirection = 1;
self.minX = 1700;
self.maxX = 1900;
self.minY = 200;
self.maxY = 2000;
self.moveHorizontal = true;
self.update = function () {
if (self.isMoving) {
if (self.moveHorizontal) {
self.x += self.moveSpeed * self.moveDirection;
if (self.x <= self.minX || self.x >= self.maxX) {
self.moveDirection *= -1;
}
} else {
self.y += self.moveSpeed * self.moveDirection;
if (self.y <= self.minY || self.y >= self.maxY) {
self.moveDirection *= -1;
}
}
}
};
return self;
});
var RotatingBlock = Container.expand(function () {
var self = Container.call(this);
var blockGraphics = self.attachAsset('rotatingBlock', {
anchorX: 0.5,
anchorY: 0.5
});
self.rotationSpeed = 0.05;
self.update = function () {
blockGraphics.rotation += self.rotationSpeed;
// Add horizontal movement for level 16
if (self.isMovingHorizontally) {
self.x += self.moveSpeed * self.moveDirection;
if (self.x <= self.minX || self.x >= self.maxX) {
self.moveDirection *= -1;
}
}
};
return self;
});
var Spike = Container.expand(function () {
var self = Container.call(this);
var spikeGraphics = self.attachAsset('spike', {
anchorX: 0.5,
anchorY: 0.5
});
self.isMoving = false;
self.moveSpeed = 8; // Fast movement speed
self.moveDirection = 1;
self.minY = 200;
self.maxY = 2000;
self.minX = 200;
self.maxX = 2000;
self.update = function () {
if (self.isMoving) {
// Check if horizontal movement is enabled (has minX/maxX set differently from defaults)
if (self.minX !== 200 || self.maxX !== 2000) {
// Horizontal movement
self.x += self.moveSpeed * self.moveDirection;
if (self.x <= self.minX || self.x >= self.maxX) {
self.moveDirection *= -1;
}
} else {
// Vertical movement (default)
self.y += self.moveSpeed * self.moveDirection;
if (self.y <= self.minY || self.y >= self.maxY) {
self.moveDirection *= -1;
}
}
}
};
return self;
});
var TrajectoryDot = Container.expand(function () {
var self = Container.call(this);
var dot = self.attachAsset('trajectory', {
anchorX: 0.5,
anchorY: 0.5
});
dot.alpha = 0.6;
return self;
});
var Wall = Container.expand(function () {
var self = Container.call(this);
var wallGraphics = self.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
});
self.isMoving = false;
self.moveSpeed = 2;
self.moveDirection = 1;
self.minY = 200;
self.maxY = 2000;
self.update = function () {
if (self.isMoving) {
self.y += self.moveSpeed * self.moveDirection;
if (self.y <= self.minY || self.y >= self.maxY) {
self.moveDirection *= -1;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2F4F4F
});
/****
* Game Code
****/
// Game state
var fireballTimer = null;
var currentLevel = storage.currentLevel || 1;
var maxLevel = storage.maxLevel || 1;
var gameState = 'playing'; // playing, levelComplete, gameOver
var attempts = 0;
var maxAttempts = 0;
var scored = false;
var levelGoal = '';
var backgroundImage = null;
// Objective tracking
var wallBounces = 0;
var requiredBounces = 0;
var touchedWall = false;
var leftWallBounced = false;
var leftWallBounceRequired = false;
var touchedNonLeftWall = false;
var objectiveFailed = false;
var failureReason = '';
// Timer challenge system
var ballReleaseTime = 0;
var timerChallenge = false;
var timerCountdown = 0;
var inputDisabled = false;
var timerCountdownText = null;
// Global 3-second timer for all levels
var globalTimerActive = false;
var globalTimerStartTime = 0;
// Game objects
var basketball = null;
var hoop = null;
var walls = [];
var spikes = [];
var rotatingBlocks = [];
var trajectoryDots = [];
var fireballs = [];
// Input tracking
var isDragging = false;
var dragStartX = 0;
var dragStartY = 0;
// Helper function to generate random positions for obstacles
function getRandomPosition() {
return {
x: Math.random() * 1200 + 300,
// Random x between 300 and 1500
y: Math.random() * 1000 + 800 // Random y between 800 and 1800
};
}
// Level definitions
var levels = [
// Level 1-4: Basic shots
{
ballX: 1024,
ballY: 1366,
hoopX: 1024,
hoopY: 1366,
goal: 'Make your first shot!',
objective: 'score',
// Just score
walls: [],
spikes: [],
rotatingBlocks: [],
movingHoop: false,
maxAttempts: 0
}, {
ballX: 1024,
ballY: 1366,
hoopX: 1024,
hoopY: 800,
goal: 'Higher target challenge!',
objective: 'score',
// Just score
walls: [],
spikes: [],
rotatingBlocks: [],
movingHoop: false,
maxAttempts: 0
}, {
ballX: 1024,
ballY: 1366,
hoopX: 1024,
hoopY: 800,
goal: 'Straight shot challenge!',
objective: 'no_wall',
// Don't touch any wall
walls: [],
spikes: [],
rotatingBlocks: [],
movingHoop: false,
maxAttempts: 0
}, {
ballX: 1024,
ballY: 1366,
hoopX: 1024,
hoopY: 1366,
goal: 'Bounce off one wall to score!',
objective: 'bounce_once',
// Must bounce off exactly one wall
walls: [],
spikes: [],
rotatingBlocks: [],
movingHoop: false,
maxAttempts: 0
},
// Level 5-10: Bank shots and obstacles
{
ballX: 1024,
ballY: 1366,
hoopX: 1024,
hoopY: 1366,
goal: 'Bounce off one wall!',
objective: 'bounce_once',
// Must bounce off exactly one wall
walls: [],
spikes: [],
rotatingBlocks: [],
movingHoop: false,
maxAttempts: 0
}, {
ballX: 1024,
ballY: 1366,
hoopX: 1024,
hoopY: 1366,
goal: 'Avoid the spikes!',
objective: 'no_wall',
// Don't touch any wall (spikes auto-fail)
walls: [],
spikes: [{
x: 1550,
y: 1100
}],
rotatingBlocks: [],
movingHoop: false,
maxAttempts: 0
}, {
ballX: 1024,
ballY: 1366,
hoopX: 1024,
hoopY: 1366,
goal: 'Navigate the rotating block!',
objective: 'score',
// Just score while avoiding blocks
walls: [],
spikes: [],
rotatingBlocks: [{
x: 1700,
y: 1050
}],
movingHoop: false,
maxAttempts: 0
}, {
ballX: 1024,
ballY: 1366,
hoopX: 1024,
hoopY: 1366,
goal: 'Avoid walls challenge!',
objective: 'no_wall',
// Don't touch any wall
walls: [{}, {
x: 1700,
y: 1100,
width: 30,
height: 200
}],
spikes: [],
rotatingBlocks: [],
movingHoop: false,
maxAttempts: 0
}, {
ballX: 1024,
ballY: 1366,
hoopX: 1024,
hoopY: 1366,
goal: 'Precision bank shot!',
walls: [{
x: 1600,
y: 800,
width: 400,
height: 30
}],
spikes: [{
x: 1650,
y: 1200
}],
rotatingBlocks: [],
movingHoop: false,
maxAttempts: 0
}, {
ballX: 1024,
ballY: 1366,
hoopX: 1024,
hoopY: 1366,
goal: 'Precision bank shot!',
walls: [{
x: 1600,
y: 800,
width: 400,
height: 30
}],
spikes: [{
x: 1650,
y: 1200
}],
rotatingBlocks: [],
movingHoop: false,
maxAttempts: 0
},
// Level 11-19: Moving hoops and restrictions
{
ballX: 1024,
ballY: 1366,
hoopX: 1024,
hoopY: 1366,
goal: 'Hit the moving hoop!',
walls: [],
spikes: [],
rotatingBlocks: [],
movingHoop: true,
maxAttempts: 0
}, {
ballX: 1024,
ballY: 1366,
hoopX: 1024,
hoopY: 1366,
goal: 'Moving target with walls!',
walls: [{
x: 1700,
y: 1000,
width: 30,
height: 300
}],
spikes: [],
rotatingBlocks: [],
movingHoop: true,
maxAttempts: 0
}, {
ballX: 1024,
ballY: 1366,
hoopX: 1024,
hoopY: 1366,
goal: 'One shot only!',
walls: [],
spikes: [{
x: 1800,
y: 1200
}],
rotatingBlocks: [],
movingHoop: false,
maxAttempts: 1
}, {
ballX: 1024,
ballY: 1366,
hoopX: 1024,
hoopY: 1366,
goal: 'Moving hoop precision!',
walls: [{
x: 1600,
y: 1000,
width: 200,
height: 30
}],
spikes: [{
x: 1750,
y: 1150
}],
rotatingBlocks: [],
movingHoop: true,
maxAttempts: 3
}, {
ballX: 1024,
ballY: 1366,
hoopX: 1024,
hoopY: 1366,
goal: 'Vertical moving hoop!',
walls: [{
x: 1600,
y: 600,
width: 30,
height: 400
}, {
x: 1800,
y: 1100,
width: 30,
height: 400
}],
spikes: [],
rotatingBlocks: [],
movingHoop: true,
maxAttempts: 0
}, {
ballX: 1024,
ballY: 1366,
hoopX: 1024,
hoopY: 1366,
goal: 'Ultimate obstacle course!',
walls: [{
x: 1700,
y: 1000,
width: 30,
height: 200
}, {
x: 1800,
y: 800,
width: 200,
height: 30
}],
spikes: [{
x: 1650,
y: 1200
}, {
x: 1850,
y: 950
}],
rotatingBlocks: [{
x: 1750,
y: 1100
}],
movingHoop: false,
maxAttempts: 2
}, {
ballX: 1024,
ballY: 1366,
hoopX: 1024,
hoopY: 1366,
goal: 'Moving maze challenge!',
walls: [{
x: 1650,
y: 350,
width: 30,
height: 300
}, {
x: 1850,
y: 1000,
width: 30,
height: 300
}],
spikes: [{
x: 1700,
y: 1150
}, {
x: 1800,
y: 1050
}],
rotatingBlocks: [{
x: 1750,
y: 1200
}],
movingHoop: true,
maxAttempts: 3
}, {
ballX: 1024,
ballY: 1366,
hoopX: 1024,
hoopY: 1366,
goal: 'Dont Touch Anywhere',
objective: 'bounce_twice',
walls: [{
x: 1700,
y: 800,
width: 30,
height: 600
}, {
x: 1800,
y: 900,
width: 30,
height: 600
}],
spikes: [{
x: 1650,
y: 170
}, {
x: 1850,
y: 1200
}],
rotatingBlocks: [],
movingHoop: false,
maxAttempts: 3
}, {
ballX: 1024,
ballY: 1366,
hoopX: 1024,
hoopY: 1366,
goal: 'Fireball timee!',
walls: [{
x: 1600,
y: 900,
width: 200,
height: 30
}, {
x: 1700,
y: 1100,
width: 200,
height: 30
}],
spikes: [{
x: 1750,
y: 1200
}, {
x: 1800,
y: 1000
}],
rotatingBlocks: [{
x: 1850,
y: 1050
}],
movingHoop: true,
maxAttempts: 2
},
// Level 20: Ultimate challenge
{
ballX: 1024,
ballY: 1366,
hoopX: 1024,
hoopY: 1366,
goal: 'Whaaatt! U need LUCK',
walls: [{
x: 1600,
y: 800,
width: 30,
height: 400
}, {
x: 1700,
y: 1000,
width: 200,
height: 30
}, {
x: 1750,
y: 900,
width: 200,
height: 30
}, {
x: 1850,
y: 1100,
width: 30,
height: 400
}],
spikes: [{
x: 1650,
y: 1200
}, {
x: 1750,
y: 1050
}, {
x: 1800,
y: 1250
}],
rotatingBlocks: [{
x: 1700,
y: 1100
}, {
x: 1850,
y: 950
}],
movingHoop: true,
maxAttempts: 1
}, {
ballX: 1024,
ballY: 1366,
hoopX: 1024,
hoopY: 1366,
goal: 'Impossible!',
walls: [{
x: 1800,
y: 800,
width: 30,
height: 200
}],
spikes: [],
rotatingBlocks: [{
x: 1600,
y: 1000
}, {
x: 1700,
y: 1200
}],
movingHoop: false,
maxAttempts: 0
}];
// UI elements
var levelText = new Text2('Level 1', {
size: 60,
fill: 0xFFFFFF
});
levelText.anchor.set(0.5, 0);
LK.gui.top.addChild(levelText);
var goalText = new Text2('', {
size: 40,
fill: 0xFFFF00
});
goalText.anchor.set(0.5, 0);
goalText.y = 80;
LK.gui.top.addChild(goalText);
var attemptsText = new Text2('', {
size: 35,
fill: 0xFFFFFF
});
attemptsText.anchor.set(1, 0);
LK.gui.topRight.addChild(attemptsText);
// Timer countdown text
timerCountdownText = new Text2('', {
size: 80,
fill: 0xFF0000
});
timerCountdownText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(timerCountdownText);
timerCountdownText.visible = false;
// Next Level button
var nextLevelButton = new Text2('Next Level →', {
size: 60,
fill: 0x00FF00
});
nextLevelButton.anchor.set(0.5, 0.5);
nextLevelButton.x = 1024;
nextLevelButton.y = 2200;
nextLevelButton.visible = false;
game.addChild(nextLevelButton);
// Add button click functionality
nextLevelButton.down = function (x, y, obj) {
if (nextLevelButton.visible && gameState === 'levelComplete') {
// Hide button
nextLevelButton.visible = false;
// Proceed to next level
if (currentLevel < levels.length) {
currentLevel++;
if (currentLevel > maxLevel) {
maxLevel = currentLevel;
storage.maxLevel = maxLevel;
}
storage.currentLevel = currentLevel;
initializeLevel(currentLevel);
} else {
LK.showYouWin();
}
}
};
function initializeLevel(levelNum) {
if (fireballTimer !== null) {
LK.clearInterval(fireballTimer);
fireballTimer = null;
}
if (levelNum > levels.length || levelNum < 1) {
return;
}
// Set background image
if (backgroundImage && backgroundImage.parent) {
backgroundImage.destroy();
}
backgroundImage = LK.getAsset('basket', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2048 / 100,
scaleY: 2732 / 100
});
backgroundImage.x = 1024;
backgroundImage.y = 1366;
game.addChildAt(backgroundImage, 0);
// Set background color based on level
game.setBackgroundColor(0x2F4F4F);
// Clear existing objects
if (basketball) {
if (basketball.shadow) {
basketball.shadow.destroy();
}
basketball.destroy();
}
if (hoop) {
hoop.destroy();
}
walls.forEach(function (wall) {
wall.destroy();
});
spikes.forEach(function (spike) {
spike.destroy();
});
rotatingBlocks.forEach(function (block) {
block.destroy();
});
trajectoryDots.forEach(function (dot) {
dot.destroy();
});
fireballs.forEach(function (fireball) {
fireball.destroy();
});
walls = [];
spikes = [];
rotatingBlocks = [];
trajectoryDots = [];
fireballs = [];
var level = levels[levelNum - 1];
// Create basketball
basketball = game.addChild(new Basketball());
// Set custom position for level 21
if (levelNum === 21) {
basketball.x = 1800;
basketball.y = 2300;
basketball.startX = 1800;
basketball.startY = 2300;
} else {
basketball.x = level.ballX;
basketball.y = level.ballY;
basketball.startX = level.ballX;
basketball.startY = level.ballY;
}
// Add vertical movement for level 5 and level 10
if (levelNum === 5 || levelNum === 10) {
basketball.isMovingVertically = true;
basketball.moveDirection = 1;
basketball.moveSpeed = 8;
basketball.minY = 1000;
basketball.maxY = 1700;
}
// Add horizontal movement for level 6
if (levelNum === 6) {
basketball.isMovingHorizontally = true;
basketball.moveDirection = 1;
basketball.moveSpeed = 12;
basketball.minX = 800;
basketball.maxX = 1300;
}
// Add horizontal movement for level 11
if (levelNum === 11) {
basketball.isMovingHorizontally = true;
basketball.moveDirection = 1;
basketball.moveSpeed = 12; // Increased horizontal movement speed
basketball.minX = 800;
basketball.maxX = 1300;
// Add smooth acceleration using tween for extra visual impact
tween(basketball, {
scaleX: 1.1,
scaleY: 0.9
}, {
duration: 300,
easing: tween.easeInOut
});
}
// Add shadow ring under basketball to indicate it's draggable
var shadow = LK.getAsset('basketball', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 0.3,
tint: 0x000000,
alpha: 0.3
});
shadow.x = basketball.x;
shadow.y = basketball.y + 90; // Position below ball
game.addChild(shadow);
basketball.shadow = shadow;
// Make basketball and shadow invisible for level 21
if (levelNum === 21) {
basketball.alpha = 0;
shadow.alpha = 0;
}
// Create hoop
hoop = game.addChild(new Hoop());
hoop.x = 2048; // Always position flush against rightmost wall
hoop.y = level.hoopY;
hoop.isMoving = level.movingHoop;
// Make hoop components invisible for level 20
if (levelNum === 20) {
hoop.rim.alpha = 0;
hoop.backboard.alpha = 0;
hoop.supportLine.alpha = 0;
hoop.netSegments.forEach(function (segment) {
segment.alpha = 0;
});
// Make brackets invisible too
var wallBracket = hoop.children.find(function (child) {
return child.tint === 0x808080 && child.scaleX === 0.3;
});
var rimBracket = hoop.children.find(function (child) {
return child.tint === 0x808080 && child.scaleX === 0.6;
});
if (wallBracket) {
wallBracket.alpha = 0;
}
if (rimBracket) {
rimBracket.alpha = 0;
}
}
// Configure movement for level 10 and levels after 10
if (levelNum >= 10) {
hoop.isMoving = true;
hoop.moveHorizontal = false; // Always vertical movement after level 10
hoop.minY = 300;
hoop.maxY = 1500;
// Gradually increase speed based on level
var speedMultiplier = Math.max(1, (levelNum - 10) * 0.5); // Increase by 0.5 each level after 10
hoop.moveSpeed = 2 + speedMultiplier;
}
// Configure movement for level 15
if (levelNum >= 15) {
hoop.isMoving = true;
hoop.moveHorizontal = false;
hoop.minY = 600;
hoop.maxY = 1500;
// Gradually increase speed based on level
var speedMultiplier = Math.max(1, (levelNum - 10) * 0.5); // Increase by 0.5 each level after 10
hoop.moveSpeed = 6 + speedMultiplier;
}
// Configure movement for level 21
if (levelNum === 21) {
hoop.isMoving = true;
hoop.moveHorizontal = false;
hoop.minY = 300;
hoop.maxY = 1500;
hoop.moveSpeed = 20;
}
// Configure vertical movement for level 17 (keeping original logic for specific level)
if (levelNum === 17) {
hoop.moveHorizontal = false;
hoop.minY = 300;
hoop.maxY = 1500;
hoop.moveSpeed = 3;
}
// Create walls
level.walls.forEach(function (wallData) {
var wall = game.addChild(new Wall());
wall.x = wallData.x;
wall.y = wallData.y;
if (wallData.width) {
wall.scaleX = wallData.width / 200;
}
if (wallData.height) {
wall.scaleY = wallData.height / 30;
}
// Configure wall movement for level 9
if (levelNum === 9) {
wall.isMoving = true;
wall.moveSpeed = 12; // Fast vertical movement
wall.moveDirection = 1;
wall.minY = 500;
wall.maxY = 1600;
// Start walls at different phases for varied movement
var wallIndex = walls.length;
if (wallIndex % 2 === 1) {
wall.moveDirection = -1; // Alternate initial direction
}
// Use tween for smooth movement animation
tween(wall, {
y: wall.y + wall.moveDirection * 100
}, {
duration: 1000,
easing: tween.easeInOut
});
}
// Configure wall movement for level 10
if (levelNum === 10) {
wall.isMoving = true;
wall.moveSpeed = 9; // Fast vertical movement
wall.moveDirection = 1;
wall.minY = 500;
wall.maxY = 1600;
// Start walls at different phases for varied movement
var wallIndex = walls.length;
if (wallIndex % 2 === 1) {
wall.moveDirection = -1; // Alternate initial direction
}
// Use tween for smooth movement animation
tween(wall, {
y: wall.y + wall.moveDirection * 100
}, {
duration: 1000,
easing: tween.easeInOut
});
}
// Configure wall movement for level 14
if (levelNum === 14) {
wall.isMoving = true;
wall.moveSpeed = 4; // Same speed as hoop
wall.moveDirection = 1;
wall.minY = 300;
wall.maxY = 1500;
// Start walls at different phases for varied movement
var wallIndex = walls.length;
if (wallIndex % 2 === 1) {
wall.moveDirection = -1; // Alternate initial direction
}
// Use tween for smooth movement animation
tween(wall, {
y: wall.y + wall.moveDirection * 100
}, {
duration: 1000,
easing: tween.easeInOut
});
}
// Configure wall rotation for level 15
if (levelNum === 15 && wall.x === 1600 && wall.y === 600) {
// Create continuous rotation around center point
var _rotateWall = function rotateWall() {
tween(wall, {
rotation: wall.rotation + Math.PI * 2 // Full 360 degree rotation
}, {
duration: 3000,
// 3 seconds per rotation
easing: tween.linear,
onFinish: function onFinish() {
_rotateWall(); // Loop the rotation continuously
}
});
};
_rotateWall(); // Start the rotation
}
// Configure wall movement for level 16
if (levelNum === 16) {
wall.isMoving = true;
wall.moveSpeed = 9;
wall.moveDirection = 1;
wall.minY = 400;
wall.maxY = 1200;
// Use tween for smooth movement animation
tween(wall, {
y: wall.y + wall.moveDirection * 100
}, {
duration: 500,
easing: tween.easeInOut
});
}
// Configure wall rotation for level 17
if (levelNum === 17) {
// Create continuous rotation around center point
var _rotateWall2 = function rotateWall() {
tween(wall, {
rotation: wall.rotation + Math.PI * 2 // Full 360 degree rotation
}, {
duration: 3000,
// 3 seconds per rotation
easing: tween.linear,
onFinish: function onFinish() {
_rotateWall2(); // Loop the rotation continuously
}
});
};
_rotateWall2(); // Start the rotation
}
// Configure wall movement for level 18
if (levelNum === 18) {
wall.isMoving = true;
wall.moveSpeed = 10; // Fast vertical movement
wall.moveDirection = 1;
wall.minY = 810;
wall.maxY = 900;
// Start walls at different phases for varied movement
var wallIndex = walls.length;
if (wallIndex % 2 === 1) {
wall.moveDirection = -1; // Alternate initial direction
}
// Use tween for smooth movement animation
tween(wall, {
y: wall.y + wall.moveDirection * 100
}, {
duration: 800,
easing: tween.easeInOut
});
}
// Make walls invisible for level 20
if (levelNum === 20) {
wall.alpha = 0;
}
walls.push(wall);
});
// Create spikes
level.spikes.forEach(function (spikeData) {
// Skip static spike creation for level 14
if (levelNum === 14) {
return; // Don't create static spikes for level 14
}
var spike = game.addChild(new Spike());
spike.x = spikeData.x;
spike.y = spikeData.y;
// Configure spike movement for level 13
if (levelNum === 13) {
spike.isMoving = true;
spike.moveSpeed = 8; // Fast vertical movement
spike.moveDirection = 1;
spike.minY = 200;
spike.maxY = 1600;
// Start spikes at different phases for varied movement
var spikeIndex = spikes.length;
if (spikeIndex % 2 === 1) {
spike.moveDirection = -1; // Alternate initial direction
}
// Use tween for smooth movement animation
tween(spike, {
y: spike.y + spike.moveDirection * 100
}, {
duration: 500,
easing: tween.easeInOut
});
}
spikes.push(spike);
});
level.spikes.forEach(function (spikeData) {
var spike = game.addChild(new Spike());
spike.x = spikeData.x;
spike.y = spikeData.y;
// Configure spike movement for level 14
if (levelNum === 14) {
spike.isMoving = true;
spike.moveSpeed = 6; // Fast horizontal movement
spike.moveDirection = 1;
spike.minX = 200;
spike.maxX = 2000;
// Start spikes at different phases for varied movement
var spikeIndex = spikes.length;
if (spikeIndex % 2 === 1) {
spike.moveDirection = -1; // Alternate initial direction
}
// Use tween for smooth movement animation
tween(spike, {
x: spike.x + spike.moveDirection * 50
}, {
duration: 500,
easing: tween.easeInOut
});
}
spikes.push(spike);
});
// Add 3 additional moving spikes for level 13
if (levelNum === 13) {
// Additional spike 1
var extraSpike1 = game.addChild(new Spike());
extraSpike1.x = 1400;
extraSpike1.y = 900;
extraSpike1.isMoving = true;
extraSpike1.moveSpeed = 8;
extraSpike1.moveDirection = -1;
extraSpike1.minY = 200;
extraSpike1.maxY = 2000;
tween(extraSpike1, {
y: extraSpike1.y + extraSpike1.moveDirection * 100
}, {
duration: 500,
easing: tween.easeInOut
});
spikes.push(extraSpike1);
// Additional spike 2
var extraSpike2 = game.addChild(new Spike());
extraSpike2.x = 1600;
extraSpike2.y = 1300;
extraSpike2.isMoving = true;
extraSpike2.moveSpeed = 8;
extraSpike2.moveDirection = 1;
extraSpike2.minY = 200;
extraSpike2.maxY = 2000;
tween(extraSpike2, {
y: extraSpike2.y + extraSpike2.moveDirection * 100
}, {
duration: 500,
easing: tween.easeInOut
});
spikes.push(extraSpike2);
// Additional spike 3
var extraSpike3 = game.addChild(new Spike());
extraSpike3.x = 1900;
extraSpike3.y = 700;
extraSpike3.isMoving = true;
extraSpike3.moveSpeed = 8;
extraSpike3.moveDirection = -1;
extraSpike3.minY = 200;
extraSpike3.maxY = 2000;
tween(extraSpike3, {
y: extraSpike3.y + extraSpike3.moveDirection * 100
}, {
duration: 500,
easing: tween.easeInOut
});
spikes.push(extraSpike3);
}
// Create rotating blocks
level.rotatingBlocks.forEach(function (blockData) {
var block = game.addChild(new RotatingBlock());
block.x = blockData.x;
block.y = blockData.y;
// Configure horizontal movement for level 16
if (levelNum === 16) {
block.isMovingHorizontally = true;
block.moveSpeed = 6;
block.moveDirection = 1;
block.minX = 600;
block.maxX = 1900;
// Use tween for smooth movement animation
tween(block, {
x: block.x + block.moveDirection * 50
}, {
duration: 1000,
easing: tween.easeInOut
});
}
// Configure horizontal movement for level 17
if (levelNum === 17) {
block.isMovingHorizontally = true;
block.moveSpeed = 8;
block.moveDirection = 1;
block.minX = 600;
block.maxX = 1900;
// Use tween for smooth movement animation
tween(block, {
x: block.x + block.moveDirection * 50
}, {
duration: 1000,
easing: tween.easeInOut
});
}
rotatingBlocks.push(block);
});
// Reset game state
gameState = 'playing';
attempts = 0;
maxAttempts = level.maxAttempts;
scored = false;
levelGoal = level.goal;
// Reset objective tracking
wallBounces = 0;
requiredBounces = level.objective === 'bounce_once' ? 1 : level.objective === 'bounce_twice' ? 2 : 0;
touchedWall = false;
leftWallBounced = false;
leftWallBounceRequired = levelNum === 10;
touchedNonLeftWall = false;
objectiveFailed = false;
failureReason = '';
// Reset timer challenge variables
timerChallenge = level.objective === 'timer_challenge';
ballReleaseTime = 0;
timerCountdown = 0;
inputDisabled = false;
globalTimerActive = false;
globalTimerStartTime = 0;
if (timerCountdownText) {
timerCountdownText.visible = false;
}
// Hide Next Level button
if (nextLevelButton) {
nextLevelButton.visible = false;
}
// Create fire launcher for level 19 only
if (levelNum === 19) {
fireballTimer = LK.setInterval(function () {
// Create fireball at launcher position
var fireball = game.addChild(new Fireball());
fireball.x = 1024;
fireball.y = 2200;
// Set diagonal velocity (top-left, top-right, bottom-left, bottom-right)
var directions = [{
x: -2,
y: -2
},
// top-left
{
x: 2,
y: -2
},
// top-right
{
x: -2,
y: 2
},
// bottom-left
{
x: 2,
y: 2
} // bottom-right
];
var direction = directions[Math.floor(Math.random() * directions.length)];
fireball.velocityX = direction.x;
fireball.velocityY = direction.y;
fireballs.push(fireball);
}, 1000); // Every 1 second
}
// Update UI
levelText.setText('Level ' + levelNum);
goalText.setText(levelGoal);
updateAttemptsText();
}
function updateAttemptsText() {
if (maxAttempts > 0) {
attemptsText.setText('Attempts: ' + attempts + '/' + maxAttempts);
} else {
attemptsText.setText('Attempts: ' + attempts);
}
}
function checkCollisions() {
if (!basketball.isLaunched) {
return;
}
// Check wall collisions (level walls)
walls.forEach(function (wall) {
if (basketball.intersects(wall)) {
var ballCenterX = basketball.x;
var ballCenterY = basketball.y;
var wallCenterX = wall.x;
var wallCenterY = wall.y;
var wallWidth = wall.width * wall.scaleX;
var wallHeight = wall.height * wall.scaleY;
var overlapX = Math.abs(ballCenterX - wallCenterX) - (80 + wallWidth / 2);
var overlapY = Math.abs(ballCenterY - wallCenterY) - (80 + wallHeight / 2);
// Realistic physics collision with angle-based reflection
// For level 9, use better bouncing physics with proper angle calculation
var energyLoss = currentLevel === 9 ? 0.85 : 0.85;
var minVelocity = 0.5;
// Calculate collision normal for proper physics reflection
var dx = ballCenterX - wallCenterX;
var dy = ballCenterY - wallCenterY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (currentLevel === 9 && distance > 0) {
// Level 9: Use proper physics reflection based on collision angle
var normalX = dx / distance;
var normalY = dy / distance;
// Calculate relative velocity
var relativeVelocityX = basketball.velocityX;
var relativeVelocityY = basketball.velocityY;
// Calculate velocity component along normal
var velocityAlongNormal = relativeVelocityX * normalX + relativeVelocityY * normalY;
// Only reflect if moving toward the wall
if (velocityAlongNormal < 0) {
// Calculate reflection with proper physics
basketball.velocityX = basketball.velocityX - 2 * velocityAlongNormal * normalX;
basketball.velocityY = basketball.velocityY - 2 * velocityAlongNormal * normalY;
// Apply energy loss
basketball.velocityX *= energyLoss;
basketball.velocityY *= energyLoss;
// Separate ball from wall to prevent sticking
var separation = 85; // Ball radius + small margin
basketball.x = wallCenterX + normalX * separation;
basketball.y = wallCenterY + normalY * separation;
}
} else {
// Other levels: Use original collision logic
if (overlapX < overlapY) {
if (ballCenterX < wallCenterX && basketball.velocityX > 0 || ballCenterX > wallCenterX && basketball.velocityX < 0) {
basketball.velocityX = -basketball.velocityX * energyLoss;
basketball.velocityY = basketball.velocityY * energyLoss; // Apply energy loss to perpendicular component too
if (Math.abs(basketball.velocityX) < minVelocity) {
basketball.velocityX = 0;
}
}
basketball.x += ballCenterX < wallCenterX ? -5 : 5;
} else {
if (ballCenterY < wallCenterY && basketball.velocityY > 0 || ballCenterY > wallCenterY && basketball.velocityY < 0) {
basketball.velocityY = -basketball.velocityY * energyLoss;
basketball.velocityX = basketball.velocityX * energyLoss; // Apply energy loss to perpendicular component too
if (Math.abs(basketball.velocityY) < minVelocity) {
basketball.velocityY = 0;
}
}
basketball.y += ballCenterY < wallCenterY ? -5 : 5;
}
}
LK.getSound('bounce').play();
// Track wall bounces and check objectives
wallBounces++;
touchedWall = true;
// For level 10, check if touching non-left wall before left wall
if (leftWallBounceRequired && !leftWallBounced) {
touchedNonLeftWall = true;
objectiveFailed = true;
failureReason = 'You must hit the left wall first!';
}
var level = levels[currentLevel - 1];
if (level.objective === 'no_wall') {
objectiveFailed = true;
failureReason = 'You touched the wall!';
}
// For level 17, hitting any wall causes failure
if (currentLevel === 17) {
objectiveFailed = true;
failureReason = 'You hit the wall!';
}
}
});
// Check spike collisions
spikes.forEach(function (spike) {
if (basketball.intersects(spike)) {
objectiveFailed = true;
failureReason = 'You hit the spikes!';
}
});
// Check fireball collisions
fireballs.forEach(function (fireball) {
if (basketball.intersects(fireball)) {
objectiveFailed = true;
failureReason = 'You hit the fireball!';
}
});
// Check rotating block collisions
rotatingBlocks.forEach(function (block) {
if (basketball.intersects(block)) {
var ballCenterX = basketball.x;
var ballCenterY = basketball.y;
var blockCenterX = block.x;
var blockCenterY = block.y;
var overlapX = Math.abs(ballCenterX - blockCenterX) - 120;
var overlapY = Math.abs(ballCenterY - blockCenterY) - 120;
if (overlapX < overlapY) {
basketball.velocityX = -basketball.velocityX * basketball.bounceDecay;
basketball.x += ballCenterX < blockCenterX ? -5 : 5;
} else {
basketball.velocityY = -basketball.velocityY * basketball.bounceDecay;
basketball.y += ballCenterY < blockCenterY ? -5 : 5;
}
LK.getSound('bounce').play();
}
});
// Check backboard collision for realistic physics
var ballCenterX = basketball.x;
var ballCenterY = basketball.y;
var hoopCenterX = hoop.x + hoop.backboardCollisionZone.x;
var hoopCenterY = hoop.y + hoop.backboardCollisionZone.y;
if (Math.abs(ballCenterX - hoopCenterX) < 80 + hoop.backboardCollisionZone.width / 2 && Math.abs(ballCenterY - hoopCenterY) < 80 + hoop.backboardCollisionZone.height / 2) {
// Ball hit backboard
basketball.velocityX = -basketball.velocityX * basketball.bounceDecay * 0.9;
basketball.velocityY = basketball.velocityY * 0.95;
basketball.x += basketball.velocityX > 0 ? -8 : 8;
LK.getSound('bounce').play();
// Flash backboard
LK.effects.flashObject(hoop, 0xFFFFFF, 200);
}
// Check vertical support line collision for soft redirection helping
var supportLineCenterX = hoop.x + hoop.supportLineCollisionZone.x;
var supportLineCenterY = hoop.y + hoop.supportLineCollisionZone.y;
if (Math.abs(ballCenterX - supportLineCenterX) < 80 + hoop.supportLineCollisionZone.width / 2 && Math.abs(ballCenterY - supportLineCenterY) < 80 + hoop.supportLineCollisionZone.height / 2) {
// Ball hit vertical support line - apply soft downward redirection instead of hard bounce
var distanceFromRim = Math.abs(ballCenterY - (hoop.y + hoop.rimCollisionZone.y));
var redirectionStrength = Math.max(0.1, 1.0 - distanceFromRim / 80); // Stronger redirection when closer to rim
// Gently redirect ball downward into the hoop if shot is close
if (basketball.velocityX < 0) {
// Ball moving toward hoop from right side
var rimCenterX = hoop.x + hoop.rimCollisionZone.x;
var rimCenterY = hoop.y + hoop.rimCollisionZone.y;
var directionToRimX = rimCenterX - ballCenterX;
var directionToRimY = rimCenterY - ballCenterY;
// Apply gentle redirection toward rim center
basketball.velocityX += directionToRimX * 0.003 * redirectionStrength;
basketball.velocityY += Math.abs(directionToRimY) * 0.002 * redirectionStrength; // Gentle downward push
// Reduce horizontal velocity to prevent harsh deflection
basketball.velocityX *= 0.85;
// Small position adjustment to prevent getting stuck
basketball.x -= 3;
// Subtle flash effect for support line hit
LK.effects.flashObject(hoop.supportLine, 0xFFFFAA, 150);
}
}
// Check rim collision for realistic bouncing - extra forgiving scoring zone
var rimCenterX = hoop.x + hoop.rimCollisionZone.x;
var rimCenterY = hoop.y + hoop.rimCollisionZone.y;
if (Math.abs(ballCenterX - rimCenterX) < 80 + hoop.rimCollisionZone.width / 2 && Math.abs(ballCenterY - rimCenterY) < 80 + hoop.rimCollisionZone.height / 2) {
// Check if ball is going through the hoop (scoring) - ultra wide scoring zone with auto-absorption
var scoringZoneWidth = 140; // Even wider invisible scoring zone for maximum forgiveness
var scoringZoneHeight = 40; // Taller scoring zone for easier front rim entry
var autoAbsorptionZone = 80; // Larger zone where ball gets gently pulled toward center
// Auto-absorption: if ball is close to center, gently guide it inward
var distanceFromCenterX = Math.abs(ballCenterX - rimCenterX);
var distanceFromCenterY = Math.abs(ballCenterY - rimCenterY);
if (distanceFromCenterX < autoAbsorptionZone && distanceFromCenterY < autoAbsorptionZone && basketball.velocityY > 0) {
// Gently pull ball toward center for easier scoring
var pullStrength = 0.15; // Subtle pull force
if (ballCenterX < rimCenterX) {
basketball.velocityX += pullStrength;
} else if (ballCenterX > rimCenterX) {
basketball.velocityX -= pullStrength;
}
}
if (basketball.velocityY > 0 && ballCenterX > rimCenterX - scoringZoneWidth && ballCenterX < rimCenterX + scoringZoneWidth && ballCenterY > rimCenterY - scoringZoneHeight && ballCenterY < rimCenterY + scoringZoneHeight && ballCenterY < rimCenterY) {
// Check if objective was met before scoring
var level = levels[currentLevel - 1];
var objectiveMet = true;
var objectiveFailureMessage = '';
if (leftWallBounceRequired && !leftWallBounced) {
objectiveMet = false;
objectiveFailureMessage = 'You must hit the left wall first!';
} else if (level.objective === 'bounce_once' && wallBounces !== 1) {
objectiveMet = false;
objectiveFailureMessage = wallBounces === 0 ? 'You must bounce off one wall!' : 'You bounced too many times!';
} else if (level.objective === 'bounce_twice' && wallBounces !== 2) {
objectiveMet = false;
objectiveFailureMessage = wallBounces === 0 ? 'You must bounce off two walls!' : wallBounces === 1 ? 'You must bounce off one more wall!' : 'You bounced too many times!';
} else if (level.objective === 'no_wall' && touchedWall) {
objectiveMet = false;
objectiveFailureMessage = 'You were not supposed to touch any walls!';
}
if (!objectiveMet) {
// Objective failed
LK.effects.flashScreen(0xff0000, 1000);
LK.setTimeout(function () {
currentLevel = 1;
storage.currentLevel = 1;
LK.showGameOver();
}, 2000);
return; // Don't continue with scoring
}
// Score!
scored = true;
LK.getSound('score').play();
// Check if it's a clean swish (ball passes through center of rim)
var distanceFromCenter = Math.abs(ballCenterX - rimCenterX);
var isCleanShot = distanceFromCenter < 30; // Within 30 pixels of center
if (isCleanShot) {
// Clean swish - special effects
LK.effects.flashObject(hoop, 0x00FFFF, 800); // Cyan flash for perfect shot
// Slow down ball slightly for dramatic effect
basketball.velocityX *= 0.8;
basketball.velocityY *= 0.9;
} else {
// Regular score
LK.effects.flashObject(hoop, 0x00FF00, 500);
}
// Let global timer handle level progression for all levels
// Animate net swish effect with enhanced 3D movement
if (hoop.netSegments) {
hoop.netSegments.forEach(function (segment, index) {
var swishIntensity = isCleanShot ? 1.5 : 1.0; // More dramatic swish for clean shots
// First phase: compress and sway
tween(segment, {
scaleY: 0.05 * swishIntensity,
alpha: 0.2,
rotation: segment.rotation + 0.3 * swishIntensity,
y: segment.y + 15 // Pull net segments down more
}, {
duration: isCleanShot ? 200 : 150,
easing: tween.easeOut
});
LK.setTimeout(function () {
// Second phase: bounce back with realistic sway
tween(segment, {
scaleY: 0.15,
alpha: 0.8,
rotation: segment.rotation - 0.1 * swishIntensity,
y: segment.y - 10 // Natural bounce back position
}, {
duration: isCleanShot ? 600 : 400,
easing: tween.bounceOut
});
}, index * (isCleanShot ? 40 : 30));
});
}
// Level complete - show success display and Next Level button
gameState = 'levelComplete';
// Show immediate success feedback
if (timerCountdownText) {
timerCountdownText.setText('Level Complete!');
timerCountdownText.tint = 0x00FF00;
timerCountdownText.visible = true;
timerCountdownText.scaleX = 1.5;
timerCountdownText.scaleY = 1.5;
// Animate success text
tween(timerCountdownText, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
easing: tween.bounceOut
});
}
// Show Next Level button
nextLevelButton.visible = true;
tween(nextLevelButton, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 1
}, {
duration: 500,
easing: tween.bounceOut
});
// Lock input immediately upon scoring
inputDisabled = true;
} else {
// Ball hit rim but didn't score - ultra soft rim bounce for maximum forgiveness
var overlapX = Math.abs(ballCenterX - rimCenterX) - (80 + hoop.rimCollisionZone.width / 2);
var overlapY = Math.abs(ballCenterY - rimCenterY) - (80 + hoop.rimCollisionZone.height / 2);
// Ultra soft bounces with minimal energy loss for maximum forgiveness
var softBounceDecay = 0.2; // Even softer bounce - less harsh rejection
var energyLoss = 0.9; // Higher energy retention - keep more momentum
var distanceFromCenter = Math.abs(ballCenterX - rimCenterX);
var distanceFromFrontEdge = Math.abs(ballCenterX - (rimCenterX - hoop.rimCollisionZone.width / 2));
// Make front edge (facing player) extremely forgiving
if (distanceFromFrontEdge < 30 && basketball.velocityX < 0) {
// Front edge hit - minimal bounce with downward guidance
softBounceDecay = 0.1;
energyLoss = 0.95;
// Add slight downward velocity to help ball drop into hoop
basketball.velocityY += 0.5;
} else if (distanceFromCenter < 50) {
// Very close to center - barely bounce at all
softBounceDecay = 0.15;
energyLoss = 0.92;
} else if (distanceFromCenter < 100) {
// Moderately close - gentle bounce
softBounceDecay = 0.25;
energyLoss = 0.88;
}
if (overlapX < overlapY) {
basketball.velocityX = -basketball.velocityX * softBounceDecay;
basketball.velocityY = basketball.velocityY * energyLoss; // Keep more perpendicular velocity
basketball.x += ballCenterX < rimCenterX ? -1 : 1; // Minimal position adjustment
} else {
basketball.velocityY = -basketball.velocityY * softBounceDecay;
basketball.velocityX = basketball.velocityX * energyLoss; // Keep more perpendicular velocity
basketball.y += ballCenterY < rimCenterY ? -1 : 1; // Minimal position adjustment
}
LK.getSound('bounce').play();
// Flash rim on hit with gentler color
LK.effects.flashObject(hoop.rim, 0xFFAA00, 300);
}
}
}
function resetBall() {
basketball.reset();
clearTrajectory();
clearPowerIndicators();
gameState = 'playing';
// Reset objective tracking for new attempt
wallBounces = 0;
touchedWall = false;
leftWallBounced = false;
touchedNonLeftWall = false;
objectiveFailed = false;
failureReason = '';
// Reset timer challenge variables
ballReleaseTime = 0;
timerCountdown = 0;
inputDisabled = false;
globalTimerActive = false;
globalTimerStartTime = 0;
if (timerCountdownText) {
timerCountdownText.visible = false;
}
// Hide Next Level button
if (nextLevelButton) {
nextLevelButton.visible = false;
}
}
function showTrajectory(forceX, forceY) {
clearTrajectory();
var simX = basketball.x;
var simY = basketball.y;
var simVelX = forceX;
var simVelY = forceY;
var simGravity = 0.3;
var forceMagnitude = Math.sqrt(forceX * forceX + forceY * forceY);
var powerPercent = Math.min(forceMagnitude / 40, 1.0);
for (var i = 0; i < 30; i++) {
simX += simVelX;
simY += simVelY;
simVelY += simGravity;
if (i % 3 === 0) {
var dot = game.addChild(new TrajectoryDot());
dot.x = simX;
dot.y = simY;
// Scale dots based on power level
dot.scaleX = 0.5 + powerPercent * 0.8;
dot.scaleY = 0.5 + powerPercent * 0.8;
// Color dots based on power
dot.tint = powerPercent > 0.8 ? 0xff0000 : powerPercent > 0.5 ? 0xffff00 : 0x00ff00;
trajectoryDots.push(dot);
}
if (simX <= 80 || simX >= 1968 || simY <= 80) {
break;
}
}
}
function clearTrajectory() {
trajectoryDots.forEach(function (dot) {
dot.destroy();
});
trajectoryDots = [];
}
function updatePowerIndicators(currentX, currentY, dragDistance, forceMagnitude) {
// Clear existing indicators
clearPowerIndicators();
// Calculate power percentage (0 to 1)
var powerPercent = Math.min(forceMagnitude / 40, 1.0);
// Create aim line from ball to current drag position
var aimLineAsset = LK.getAsset('trajectory', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: Math.max(1, dragDistance / 8),
scaleY: Math.max(2, 2 + powerPercent * 3)
});
aimLineAsset.x = (basketball.x + currentX) / 2;
aimLineAsset.y = (basketball.y + currentY) / 2;
aimLineAsset.rotation = Math.atan2(currentY - basketball.y, currentX - basketball.x);
aimLineAsset.alpha = 0.8;
aimLineAsset.tint = powerPercent > 0.8 ? 0xff0000 : powerPercent > 0.5 ? 0xffff00 : 0x00ff00;
game.addChild(aimLineAsset);
aimLine = aimLineAsset;
// Create power bar indicator
var powerBarWidth = 300;
var powerBarHeight = 20;
var powerBarX = basketball.x - powerBarWidth / 2;
var powerBarY = basketball.y - 120;
// Power bar background
var powerBarBg = LK.getAsset('wall', {
anchorX: 0,
anchorY: 0,
scaleX: powerBarWidth / 200,
scaleY: powerBarHeight / 30,
tint: 0x333333
});
powerBarBg.x = powerBarX;
powerBarBg.y = powerBarY;
powerBarBg.alpha = 0.7;
game.addChild(powerBarBg);
// Power bar fill
var fillWidth = powerBarWidth * powerPercent;
if (fillWidth > 0) {
var powerBarFill = LK.getAsset('wall', {
anchorX: 0,
anchorY: 0,
scaleX: fillWidth / 200,
scaleY: powerBarHeight / 30,
tint: powerPercent > 0.8 ? 0xff0000 : powerPercent > 0.5 ? 0xffff00 : 0x00ff00
});
powerBarFill.x = powerBarX;
powerBarFill.y = powerBarY;
powerBarFill.alpha = 0.9;
game.addChild(powerBarFill);
powerBar = [powerBarBg, powerBarFill];
} else {
powerBar = [powerBarBg];
}
}
function clearPowerIndicators() {
if (aimLine) {
aimLine.destroy();
aimLine = null;
}
if (powerBar) {
powerBar.forEach(function (bar) {
bar.destroy();
});
powerBar = null;
}
}
// Power indicator variables
var powerBar = null;
var aimLine = null;
var maxDragDistance = 400; // Maximum pixel distance for full power
// Input handling
game.down = function (x, y, obj) {
if (gameState !== 'playing' || basketball.isLaunched || inputDisabled) {
return;
}
isDragging = true;
dragStartX = x;
dragStartY = y;
};
game.move = function (x, y, obj) {
if (!isDragging || gameState !== 'playing' || basketball.isLaunched || inputDisabled) {
return;
}
// Calculate drag distance and force with increased sensitivity
var dragX = dragStartX - x;
var dragY = dragStartY - y;
var dragDistance = Math.sqrt(dragX * dragX + dragY * dragY);
var dragMultiplier = Math.min(dragDistance / maxDragDistance, 1.0);
var forceX = dragX * 0.08 * dragMultiplier;
var forceY = dragY * 0.08 * dragMultiplier;
// Limit force to maximum power
var magnitude = Math.sqrt(forceX * forceX + forceY * forceY);
if (magnitude > 40) {
forceX = forceX / magnitude * 40;
forceY = forceY / magnitude * 40;
}
// Update visual feedback
updatePowerIndicators(x, y, dragDistance, magnitude);
showTrajectory(forceX, forceY);
};
game.up = function (x, y, obj) {
if (!isDragging || gameState !== 'playing' || basketball.isLaunched || inputDisabled) {
return;
}
isDragging = false;
// Calculate final force with increased sensitivity
var dragX = dragStartX - x;
var dragY = dragStartY - y;
var dragDistance = Math.sqrt(dragX * dragX + dragY * dragY);
var dragMultiplier = Math.min(dragDistance / maxDragDistance, 1.0);
var forceX = dragX * 0.08 * dragMultiplier;
var forceY = dragY * 0.08 * dragMultiplier;
// Limit force to maximum power
var magnitude = Math.sqrt(forceX * forceX + forceY * forceY);
if (magnitude > 40) {
forceX = forceX / magnitude * 40;
forceY = forceY / magnitude * 40;
}
// Clear visual indicators
clearPowerIndicators();
if (magnitude > 1) {
attempts++;
updateAttemptsText();
basketball.launch(forceX, forceY);
clearTrajectory();
// Start global 3-second timer for all levels
ballReleaseTime = LK.ticks;
globalTimerActive = true;
globalTimerStartTime = LK.ticks;
inputDisabled = true;
timerCountdownText.visible = true;
// Check if out of attempts
if (maxAttempts > 0 && attempts >= maxAttempts && !scored) {
LK.setTimeout(function () {
if (!scored) {
currentLevel = 1;
storage.currentLevel = 1;
LK.showGameOver();
}
}, 3000);
}
}
};
// Main update loop
game.update = function () {
if (gameState === 'playing') {
// Handle global 3-second timer countdown for all levels
if (globalTimerActive && globalTimerStartTime > 0) {
var timeSinceRelease = (LK.ticks - globalTimerStartTime) * (1000 / 60); // Convert to milliseconds
var remainingTime = Math.max(0, 3000 - timeSinceRelease);
var secondsLeft = Math.ceil(remainingTime / 1000);
if (timerCountdownText) {
timerCountdownText.setText(secondsLeft.toString());
timerCountdownText.visible = true;
timerCountdownText.tint = 0xFFFFFF; // Reset tint to white
// Animate countdown text
var scale = 1.0 + Math.sin(LK.ticks * 0.3) * 0.2;
timerCountdownText.scaleX = scale;
timerCountdownText.scaleY = scale;
}
// Check if 3 seconds have passed - end level regardless of success/failure
if (remainingTime <= 0) {
globalTimerActive = false;
var level = levels[currentLevel - 1];
var wasSuccessful = scored;
// Check objective completion
if (leftWallBounceRequired && !leftWallBounced) {
wasSuccessful = false;
} else if (level.objective === 'bounce_once' && wallBounces !== 1) {
wasSuccessful = false;
} else if (level.objective === 'bounce_twice' && wallBounces !== 2) {
wasSuccessful = false;
} else if (level.objective === 'no_wall' && touchedWall) {
wasSuccessful = false;
} else if (level.objective === 'timer_challenge') {
// For timer challenge, must score within 1 second
var timeSinceReleaseForTimer = (LK.ticks - ballReleaseTime) * (1000 / 60);
if (!scored || timeSinceReleaseForTimer > 1000) {
wasSuccessful = false;
}
}
if (wasSuccessful) {
// Flash screen with success color
LK.effects.flashScreen(0x00FF00, 500);
// Add sparkle effect to hoop
if (hoop) {
tween(hoop, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 200,
easing: tween.easeOut
});
LK.setTimeout(function () {
tween(hoop, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 300,
easing: tween.bounceOut
});
}, 200);
}
// Hide timer countdown as level is complete
if (timerCountdownText) {
timerCountdownText.visible = false;
}
// Lock input but don't auto-progress - wait for button click
inputDisabled = true;
} else {
// Level failed
if (timerCountdownText) {
timerCountdownText.setText('FAIL');
timerCountdownText.tint = 0xFF0000;
}
LK.effects.flashScreen(0xff0000, 1000);
LK.setTimeout(function () {
currentLevel = 1;
storage.currentLevel = 1;
LK.showGameOver();
}, 2000);
}
return;
}
}
// Track screen edge bounces for objectives (before calling checkCollisions)
if (basketball.isLaunched) {
var ballRadius = 80;
var lastWallBounces = wallBounces;
// Check if ball just bounced off any screen edge this frame
if (basketball.x <= ballRadius && basketball.velocityX > 0 || basketball.x >= 2048 - ballRadius && basketball.velocityX < 0 || basketball.y <= ballRadius && basketball.velocityY > 0 || basketball.y >= 2732 - ballRadius && basketball.velocityY < 0) {
// Ball just bounced off a screen edge
if (wallBounces === lastWallBounces) {
// Prevent double counting
wallBounces++;
touchedWall = true;
// Track non-left wall touches for level 10
if (leftWallBounceRequired && (basketball.x >= 2048 - ballRadius || basketball.y <= ballRadius || basketball.y >= 2732 - ballRadius)) {
touchedNonLeftWall = true;
objectiveFailed = true;
failureReason = 'You must hit the left wall first!';
}
var level = levels[currentLevel - 1];
if (level.objective === 'no_wall') {
objectiveFailed = true;
failureReason = 'You touched the wall!';
}
}
}
}
checkCollisions();
// Check for objective failures
if (objectiveFailed && !scored) {
LK.effects.flashScreen(0xff0000, 1000);
LK.setTimeout(function () {
currentLevel = 1;
storage.currentLevel = 1;
LK.showGameOver();
}, 2000);
return;
}
// Check if ball is off screen (reset)
if (basketball.isLaunched && basketball.y > 2800) {
if (!scored) {
// Ball missed - check if this constitutes objective failure
var level = levels[currentLevel - 1];
var missedObjectiveMessage = 'Missed the hoop!';
if (leftWallBounceRequired && !leftWallBounced) {
missedObjectiveMessage = 'You must hit the left wall first!';
} else if (level.objective === 'bounce_once' && wallBounces !== 1) {
missedObjectiveMessage = wallBounces === 0 ? 'You must bounce off one wall!' : 'You bounced too many times!';
} else if (level.objective === 'bounce_twice' && wallBounces !== 2) {
missedObjectiveMessage = wallBounces === 0 ? 'You must bounce off two walls!' : wallBounces === 1 ? 'You must bounce off one more wall!' : 'You bounced too many times!';
} else if (level.objective === 'no_wall' && touchedWall) {
missedObjectiveMessage = 'You touched the wall!';
}
LK.effects.flashScreen(0xff0000, 1000);
LK.setTimeout(function () {
currentLevel = 1;
storage.currentLevel = 1;
LK.showGameOver();
}, 2000);
} else if (maxAttempts > 0 && attempts >= maxAttempts) {
currentLevel = 1;
storage.currentLevel = 1;
LK.showGameOver();
} else {
resetBall();
}
}
}
};
// Initialize first level
initializeLevel(currentLevel);
Modern App Store icon, high definition, square with rounded corners, for a game titled "Basket Bounce Challenge" and with the description "A colorful basketball puzzle game with slingshot mechanics across 20 challenging levels. Drag and release the ball to score, progressing from simple shots to complex bank shot puzzles with moving hoops and obstacles.". No text on icon!
Fullscreen modern App Store landscape banner, 16:9, high definition, for a game titled "Basket Bounce Challenge" and with the description "A colorful basketball puzzle game with slingshot mechanics across 20 challenging levels. Drag and release the ball to score, progressing from simple shots to complex bank shot puzzles with moving hoops and obstacles.". No text on banner!
A minimalist black and white basketball court background. The court is shown from a side view or slightly isometric angle. It includes clear outlines for the hoop, backboard, half court line, and three-point arc, all in clean black lines on a white surface (or vice versa). The style is modern, simple, and flat — no players, just the environment.. In-Game asset. 2d. High contrast. No shadows