/****
* 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