/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Fruit: Apple (extra points)
var Apple = Container.expand(function () {
var self = Container.call(this);
var appleAsset = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
appleAsset.tint = 0xff2222; // Red
appleAsset.scaleX = 1.1;
appleAsset.scaleY = 1.1;
tween(appleAsset, {
alpha: 0.85
}, {
duration: 350,
yoyo: true,
repeat: Infinity,
easing: tween.easeInOut
});
var baseSpeed = 10 + Math.random() * 3;
if (typeof LK !== "undefined" && typeof LK.getScore === "function") {
baseSpeed += Math.min(8, Math.floor(LK.getScore() / 12));
}
self.speed = baseSpeed;
self.type = "apple";
self.update = function () {
self.y += self.speed;
};
return self;
});
// Ball class
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballAsset = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
// Neon color palette for balls
var colors = [0x00ffff, 0xff00ff, 0xffff00, 0x00ff00, 0xff8800, 0x00ffea, 0xff0088];
ballAsset.color = colors[Math.floor(Math.random() * colors.length)];
ballAsset.tint = ballAsset.color;
// Neon glow effect: animate alpha for a pulsing glow
tween(ballAsset, {
alpha: 0.7
}, {
duration: 400,
yoyo: true,
repeat: Infinity,
easing: tween.easeInOut
});
// Adjusted for human reflexes: start slower, increase with score, cap max speed
var baseSpeed = 10 + Math.random() * 4;
if (typeof LK !== "undefined" && typeof LK.getScore === "function") {
baseSpeed += Math.min(10, Math.floor(LK.getScore() / 8)); // increase 1 per 8 points, max +10
}
self.speed = baseSpeed;
self.vx = (Math.random() - 0.5) * 10; // random horizontal speed
self.update = function () {
self.y += self.speed;
self.x += self.vx;
// Bounce off left/right walls
if (self.x < 60 && self.vx < 0) {
self.x = 60;
self.vx *= -1;
}
if (self.x > 2048 - 60 && self.vx > 0) {
self.x = 2048 - 60;
self.vx *= -1;
}
};
return self;
});
// Fruit: Banana (wider catch area)
var Banana = Container.expand(function () {
var self = Container.call(this);
var bananaAsset = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
bananaAsset.tint = 0xfff700; // Yellow
bananaAsset.scaleX = 1.5;
bananaAsset.scaleY = 0.8;
tween(bananaAsset, {
alpha: 0.8
}, {
duration: 400,
yoyo: true,
repeat: Infinity,
easing: tween.easeInOut
});
var baseSpeed = 9 + Math.random() * 2;
if (typeof LK !== "undefined" && typeof LK.getScore === "function") {
baseSpeed += Math.min(7, Math.floor(LK.getScore() / 14));
}
self.speed = baseSpeed;
self.type = "banana";
self.update = function () {
self.y += self.speed;
};
return self;
});
// Basket class
var Basket = Container.expand(function () {
var self = Container.call(this);
var basketAsset = self.attachAsset('basket', {
anchorX: 0.5,
anchorY: 0.5
});
// Neon basket: random neon color and glow
var basketColors = [0x00ffff, 0xff00ff, 0xffff00, 0x00ff00, 0xff8800, 0x00ffea, 0xff0088];
basketAsset.tint = basketColors[Math.floor(Math.random() * basketColors.length)];
tween(basketAsset, {
alpha: 0.7
}, {
duration: 600,
yoyo: true,
repeat: Infinity,
easing: tween.easeInOut
});
return self;
});
// Bomb class
var Bomb = Container.expand(function () {
var self = Container.call(this);
var bombAsset = self.attachAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5
});
// Neon bomb: magenta or cyan
var bombColors = [0xff00ff, 0x00ffff, 0xffff00];
bombAsset.tint = bombColors[Math.floor(Math.random() * bombColors.length)];
tween(bombAsset, {
alpha: 0.6
}, {
duration: 400,
yoyo: true,
repeat: Infinity,
easing: tween.easeInOut
});
// Adjusted for human reflexes: start slower, increase with score, cap max speed
var baseBombSpeed = 12 + Math.random() * 5;
if (typeof LK !== "undefined" && typeof LK.getScore === "function") {
baseBombSpeed += Math.min(12, Math.floor(LK.getScore() / 7)); // increase 1 per 7 points, max +12
}
self.speed = baseBombSpeed;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Fruit: Grape (slows bombs for a short time)
var Grape = Container.expand(function () {
var self = Container.call(this);
var grapeAsset = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
grapeAsset.tint = 0x9900ff; // Purple
grapeAsset.scaleX = 1.0;
grapeAsset.scaleY = 1.0;
tween(grapeAsset, {
alpha: 0.7
}, {
duration: 300,
yoyo: true,
repeat: Infinity,
easing: tween.easeInOut
});
var baseSpeed = 11 + Math.random() * 2;
if (typeof LK !== "undefined" && typeof LK.getScore === "function") {
baseSpeed += Math.min(7, Math.floor(LK.getScore() / 13));
}
self.speed = baseSpeed;
self.type = "grape";
self.update = function () {
self.y += self.speed;
};
return self;
});
// PowerUp class (neon star)
var PowerUp = Container.expand(function () {
var self = Container.call(this);
// Use a ball asset for now, but tint and scale to look like a power-up
var starAsset = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
// Neon star color
var starColors = [0xFFD700, 0x00ffea, 0xff00ff, 0x00ff00];
starAsset.tint = starColors[Math.floor(Math.random() * starColors.length)];
starAsset.scaleX = 1.3;
starAsset.scaleY = 1.3;
// Animate alpha for a pulsing glow
tween(starAsset, {
alpha: 0.8
}, {
duration: 300,
yoyo: true,
repeat: Infinity,
easing: tween.easeInOut
});
// Adjusted for human reflexes: start slower, increase with score, cap max speed
var basePowerSpeed = 9 + Math.random() * 3;
if (typeof LK !== "undefined" && typeof LK.getScore === "function") {
basePowerSpeed += Math.min(8, Math.floor(LK.getScore() / 10)); // increase 1 per 10 points, max +8
}
self.speed = basePowerSpeed;
self.type = "score"; // default type
self.update = function () {
self.y += self.speed;
};
return self;
});
// ShieldPowerUp class (neon blue ring)
var ShieldPowerUp = Container.expand(function () {
var self = Container.call(this);
var shieldAsset = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
shieldAsset.tint = 0x00ffff;
shieldAsset.scaleX = 1.5;
shieldAsset.scaleY = 1.5;
tween(shieldAsset, {
alpha: 0.6
}, {
duration: 200,
yoyo: true,
repeat: Infinity,
easing: tween.easeInOut
});
// Adjusted for human reflexes: start slower, increase with score, cap max speed
var baseShieldSpeed = 8 + Math.random() * 2;
if (typeof LK !== "undefined" && typeof LK.getScore === "function") {
baseShieldSpeed += Math.min(7, Math.floor(LK.getScore() / 12)); // increase 1 per 12 points, max +7
}
self.speed = baseShieldSpeed;
self.type = "shield";
self.update = function () {
self.y += self.speed;
};
return self;
});
// Fruit: Watermelon (big, gives shield)
var Watermelon = Container.expand(function () {
var self = Container.call(this);
var melonAsset = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
melonAsset.tint = 0x22ff44; // Green
melonAsset.scaleX = 1.7;
melonAsset.scaleY = 1.3;
tween(melonAsset, {
alpha: 0.8
}, {
duration: 500,
yoyo: true,
repeat: Infinity,
easing: tween.easeInOut
});
var baseSpeed = 8 + Math.random() * 2;
if (typeof LK !== "undefined" && typeof LK.getScore === "function") {
baseSpeed += Math.min(6, Math.floor(LK.getScore() / 15));
}
self.speed = baseSpeed;
self.type = "watermelon";
self.update = function () {
self.y += self.speed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Sound for missing a ball
// Sound for catching a bomb
// Sound for catching a ball
// Bomb: black ellipse
// Ball: colored ellipse
// Basket: wide rectangle
// Game area: 2048x2732
// No background image, only black background
// No background image, only black background
game.setBackgroundColor(0x000000);
// Neon-style game objective text at the start (small, English, subtle neon)
var objectiveTxt = new Text2('Goal: Catch the balls, avoid the bombs!', {
size: 54,
// smaller text
fill: 0x00ffff,
stroke: 0x222222,
strokeThickness: 8,
dropShadow: true,
dropShadowColor: 0x00ffff,
dropShadowBlur: 12,
dropShadowDistance: 0,
align: "center"
});
objectiveTxt.anchor.set(0.5, 0);
objectiveTxt.y = 90;
LK.gui.top.addChild(objectiveTxt);
// Add leaderboard button for competition
var leaderboardBtn = new Text2('🏆 Leaderboard', {
size: 60,
fill: 0xFFD700,
stroke: 0x00ffff,
strokeThickness: 8,
dropShadow: true,
dropShadowColor: 0xFFD700,
dropShadowBlur: 10,
dropShadowDistance: 0,
align: "center"
});
leaderboardBtn.anchor.set(0.5, 0);
leaderboardBtn.x = 2048 - 250;
leaderboardBtn.y = 90;
leaderboardBtn.interactive = true;
leaderboardBtn.buttonMode = true;
leaderboardBtn.down = function () {
if (typeof LK.showLeaderboard === "function") {
LK.showLeaderboard();
}
};
LK.gui.top.addChild(leaderboardBtn);
// Hide objective after 2.5 seconds
LK.setTimeout(function () {
objectiveTxt.visible = false;
}, 2500);
// Score text
var scoreTxt = new Text2('0', {
size: 120,
fill: 0x00FFEA,
stroke: 0xFF00FF,
strokeThickness: 18,
dropShadow: true,
dropShadowColor: 0x00FFFF,
dropShadowBlur: 24,
dropShadowDistance: 0,
align: "center"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Health bar UI
var maxHealth = 3;
var health = maxHealth;
var healthBarWidth = 320;
var healthBarHeight = 38;
var healthBarBg = LK.getAsset('ball', {
anchorX: 0,
anchorY: 0.5,
scaleX: healthBarWidth / 100,
scaleY: healthBarHeight / 100,
x: 120,
y: 160
});
healthBarBg.tint = 0x222222;
LK.gui.top.addChild(healthBarBg);
var healthBar = LK.getAsset('ball', {
anchorX: 0,
anchorY: 0.5,
scaleX: healthBarWidth / 100,
scaleY: healthBarHeight / 100,
x: 120,
y: 160
});
healthBar.tint = 0x00ff00;
LK.gui.top.addChild(healthBar);
// Health icon hearts
var healthHearts = [];
for (var h = 0; h < maxHealth; h++) {
var heart = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.32,
scaleY: 0.32,
x: 120 + 40 + h * 54,
y: 160
});
heart.tint = 0xff2222;
LK.gui.top.addChild(heart);
healthHearts.push(heart);
}
// Misses text (shows "Missed!" when a ball is missed)
var missTxt = new Text2('', {
size: 90,
fill: 0x00FFFF,
stroke: 0xFF00FF,
strokeThickness: 14,
dropShadow: true,
dropShadowColor: 0x00FFFF,
dropShadowBlur: 18,
dropShadowDistance: 0,
align: "center"
});
missTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(missTxt);
missTxt.visible = false;
// Basket
var basket = new Basket();
game.addChild(basket);
basket.y = 2732 - 180;
basket.x = 2048 / 2;
// Ball, bomb, and power-up arrays
var balls = [];
var bombs = [];
var powerups = [];
// Dragging
var dragNode = null;
// Touch indicator for user-friendly feedback
var touchIndicator = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
touchIndicator.tint = 0xFFD700;
touchIndicator.scaleX = 0.4;
touchIndicator.scaleY = 0.4;
touchIndicator.alpha = 0;
game.addChild(touchIndicator);
// Spawning control
var spawnTimer = 0;
var spawnInterval = 55; // frames between spawns, will decrease as score increases
// State
var lastScore = 0;
var gameOver = false;
// Combo scoring
var comboCount = 0;
var comboTimeout = null;
var comboTxt = new Text2('', {
size: 80,
fill: 0xFFD700,
stroke: 0x00ffff,
strokeThickness: 10,
dropShadow: true,
dropShadowColor: 0xFFD700,
dropShadowBlur: 12,
dropShadowDistance: 0,
align: "center"
});
comboTxt.anchor.set(0.5, 0);
comboTxt.y = 200;
comboTxt.visible = false;
LK.gui.top.addChild(comboTxt);
// Helper: spawn a ball, bomb, or power-up
function spawnFallingObject() {
// 80% ball, 20% bomb, 5% power-up, 3% shield power-up, plus fruits
var rand = Math.random();
var x = 150 + Math.random() * (2048 - 300);
if (rand < 0.10) {
var bomb = new Bomb();
bomb.x = x;
bomb.y = -60;
bomb.lastIntersecting = false;
// Add "-" indicator for bomb
var minusTxt = new Text2('-', {
size: 70,
fill: 0xff2222,
stroke: 0x000000,
strokeThickness: 8,
align: "center"
});
minusTxt.anchor.set(0.5, 1);
minusTxt.y = -60;
bomb.addChild(minusTxt);
bomb.indicatorTxt = minusTxt;
bombs.push(bomb);
game.addChild(bomb);
} else if (rand < 0.13) {
// 3% chance for super bomb
var superBomb = new Bomb();
superBomb.x = x;
superBomb.y = -60;
superBomb.lastIntersecting = false;
superBomb.isSuper = true;
if (superBomb.children && superBomb.children.length > 0) {
superBomb.children[0].tint = 0xffffff;
}
// Add "-" indicator for super bomb
var minusTxt = new Text2('-', {
size: 70,
fill: 0xff2222,
stroke: 0x000000,
strokeThickness: 8,
align: "center"
});
minusTxt.anchor.set(0.5, 1);
minusTxt.y = -60;
superBomb.addChild(minusTxt);
superBomb.indicatorTxt = minusTxt;
bombs.push(superBomb);
game.addChild(superBomb);
} else if (rand > 0.98) {
// 2% chance for slow motion power-up
var slowPower = new PowerUp();
slowPower.x = x;
slowPower.y = -60;
slowPower.lastIntersecting = false;
slowPower.type = "slow";
if (slowPower.children && slowPower.children.length > 0) {
slowPower.children[0].tint = 0x3b6eea;
}
// Add "+" indicator for slow powerup
var plusTxt = new Text2('+', {
size: 70,
fill: 0x00ffea,
stroke: 0x000000,
strokeThickness: 8,
align: "center"
});
plusTxt.anchor.set(0.5, 1);
plusTxt.y = -60;
slowPower.addChild(plusTxt);
slowPower.indicatorTxt = plusTxt;
powerups.push(slowPower);
game.addChild(slowPower);
} else if (rand > 0.96) {
// 4% chance for shield power-up
var shield = new ShieldPowerUp();
shield.x = x;
shield.y = -60;
shield.lastIntersecting = false;
// Add "+" indicator for shield
var plusTxt = new Text2('+', {
size: 70,
fill: 0x00ffff,
stroke: 0x000000,
strokeThickness: 8,
align: "center"
});
plusTxt.anchor.set(0.5, 1);
plusTxt.y = -60;
shield.addChild(plusTxt);
shield.indicatorTxt = plusTxt;
powerups.push(shield);
game.addChild(shield);
} else if (rand > 0.94) {
// 2% chance for score power-up
var powerup = new PowerUp();
powerup.x = x;
powerup.y = -60;
powerup.lastIntersecting = false;
// Add "+" indicator for score powerup
var plusTxt = new Text2('+', {
size: 70,
fill: 0xFFD700,
stroke: 0x000000,
strokeThickness: 8,
align: "center"
});
plusTxt.anchor.set(0.5, 1);
plusTxt.y = -60;
powerup.addChild(plusTxt);
powerup.indicatorTxt = plusTxt;
powerups.push(powerup);
game.addChild(powerup);
} else if (rand > 0.92) {
// 2% chance for Watermelon (shield fruit)
var watermelon = new Watermelon();
watermelon.x = x;
watermelon.y = -60;
watermelon.lastIntersecting = false;
// Add "+" indicator for watermelon
var plusTxt = new Text2('+', {
size: 70,
fill: 0x22ff44,
stroke: 0x000000,
strokeThickness: 8,
align: "center"
});
plusTxt.anchor.set(0.5, 1);
plusTxt.y = -60;
watermelon.addChild(plusTxt);
watermelon.indicatorTxt = plusTxt;
powerups.push(watermelon);
game.addChild(watermelon);
} else if (rand > 0.89) {
// 3% chance for Grape (slows bombs)
var grape = new Grape();
grape.x = x;
grape.y = -60;
grape.lastIntersecting = false;
// Add "+" indicator for grape
var plusTxt = new Text2('+', {
size: 70,
fill: 0x9900ff,
stroke: 0x000000,
strokeThickness: 8,
align: "center"
});
plusTxt.anchor.set(0.5, 1);
plusTxt.y = -60;
grape.addChild(plusTxt);
grape.indicatorTxt = plusTxt;
powerups.push(grape);
game.addChild(grape);
} else if (rand > 0.86) {
// 3% chance for Banana (wider catch area)
var banana = new Banana();
banana.x = x;
banana.y = -60;
banana.lastIntersecting = false;
// Add "+" indicator for banana
var plusTxt = new Text2('+', {
size: 70,
fill: 0xfff700,
stroke: 0x000000,
strokeThickness: 8,
align: "center"
});
plusTxt.anchor.set(0.5, 1);
plusTxt.y = -60;
banana.addChild(plusTxt);
banana.indicatorTxt = plusTxt;
powerups.push(banana);
game.addChild(banana);
} else if (rand > 0.83) {
// 3% chance for Apple (extra points)
var apple = new Apple();
apple.x = x;
apple.y = -60;
apple.lastIntersecting = false;
// Add "+" indicator for apple
var plusTxt = new Text2('+', {
size: 70,
fill: 0xff2222,
stroke: 0x000000,
strokeThickness: 8,
align: "center"
});
plusTxt.anchor.set(0.5, 1);
plusTxt.y = -60;
apple.addChild(plusTxt);
apple.indicatorTxt = plusTxt;
powerups.push(apple);
game.addChild(apple);
} else {
var ball = new Ball();
ball.x = x;
ball.y = -60;
ball.lastIntersecting = false;
// Add "+" indicator for normal ball
var plusTxt = new Text2('+', {
size: 70,
fill: 0x00ffff,
stroke: 0x000000,
strokeThickness: 8,
align: "center"
});
plusTxt.anchor.set(0.5, 1);
plusTxt.y = -60;
ball.addChild(plusTxt);
ball.indicatorTxt = plusTxt;
balls.push(ball);
game.addChild(ball);
}
}
// Move handler for dragging basket
function handleMove(x, y, obj) {
if (dragNode) {
// Clamp basket within screen
var halfWidth = dragNode.width / 2;
var minX = halfWidth;
var maxX = 2048 - halfWidth;
var newX = Math.max(minX, Math.min(maxX, x));
// Only update basket.x if it is not at the edge, or if moving away from the edge
// This prevents the basket from "burning" or flashing at the edge
if (!(dragNode.x <= minX && newX <= minX) &&
// not stuck at left edge
!(dragNode.x >= maxX && newX >= maxX) // not stuck at right edge
) {
dragNode.x = newX;
}
// Move touch indicator with finger
touchIndicator.x = x;
touchIndicator.y = y;
touchIndicator.alpha = 0.7;
}
}
// Touch/mouse events
game.down = function (x, y, obj) {
// Allow drag from anywhere in lower 1/3 of screen for easier control
if (y > 2732 * 2 / 3) {
dragNode = basket;
handleMove(x, y, obj);
// Show and animate touch indicator
touchIndicator.x = x;
touchIndicator.y = y;
touchIndicator.alpha = 1;
tween(touchIndicator, {
alpha: 0.7,
scaleX: 0.6,
scaleY: 0.6
}, {
duration: 120,
yoyo: true,
repeat: 1,
onFinish: function onFinish() {
// Keep indicator visible while dragging
if (!dragNode) {
touchIndicator.alpha = 0;
touchIndicator.scaleX = 0.4;
touchIndicator.scaleY = 0.4;
}
}
});
}
};
game.move = handleMove;
game.up = function (x, y, obj) {
dragNode = null;
touchIndicator.alpha = 0;
};
// Main update loop
game.update = function () {
if (gameOver) return;
// Change background color at each 5 points (neon palette)
var neonBgColors = [0x000000, 0x0ff0fc, 0x1a0033, 0x3b6eea, 0x00ffea, 0xff00ff, 0x00ff00, 0xf75e5e, 0xf7c325];
var score = LK.getScore();
var bgIndex = Math.floor(score / 5) % neonBgColors.length;
game.setBackgroundColor(neonBgColors[bgIndex]);
// Challenge mode: basket moves left/right automatically every 20 points
if (score > 0 && score % 20 === 0 && !basket.challengeActive) {
basket.challengeActive = true;
basket.challengeDir = Math.random() > 0.5 ? 1 : -1;
basket.challengeTween = tween(basket, {
x: basket.challengeDir > 0 ? 2048 - basket.width / 2 : basket.width / 2
}, {
duration: 2200,
yoyo: true,
repeat: 2,
easing: tween.easeInOut,
onFinish: function onFinish() {
basket.challengeActive = false;
}
});
}
// Spawn balls/bombs
spawnTimer++;
// Decrease interval as score increases (min 35 for human reflexes)
var interval = Math.max(35, spawnInterval - Math.floor(LK.getScore() / 10) * 3);
if (spawnTimer >= interval) {
spawnFallingObject();
spawnTimer = 0;
}
// Speed up all balls every 10 points
if (score > 0 && score % 10 === 0 && !game.lastSpeedupScore) {
for (var si = 0; si < balls.length; si++) {
balls[si].speed *= 1.08; // less aggressive speedup for human reflexes
balls[si].vx *= 1.05;
}
game.lastSpeedupScore = score;
}
if (score % 10 !== 0) {
game.lastSpeedupScore = null;
}
// Update balls
// Use a local array to collect indices to remove, then remove after loop to avoid array shifting
var ballsToRemove = [];
for (var i = balls.length - 1; i >= 0; i--) {
var ball = balls[i];
if (ball.lastY === undefined) ball.lastY = ball.y;
if (ball.lastIntersecting === undefined) ball.lastIntersecting = false;
ball.update();
// Check for catch
var intersecting = ball.intersects(basket);
if (!ball.lastIntersecting && intersecting) {
// Caught!
comboCount++;
if (comboTimeout) {
LK.clearTimeout(comboTimeout);
comboTimeout = null;
}
if (comboCount > 1) {
comboTxt.setText('Combo x' + comboCount + '!');
comboTxt.visible = true;
comboTxt.alpha = 1;
tween(comboTxt, {
alpha: 0
}, {
duration: 900,
onFinish: function onFinish() {
comboTxt.visible = false;
}
});
}
// Combo bonus: +1 for first, +2 for 2nd, +3 for 3rd, etc.
var comboBonus = comboCount > 1 ? comboCount : 1;
LK.setScore(LK.getScore() + comboBonus);
// Streak bonus: every 5 consecutive catches, +10 bonus
if (comboCount > 0 && comboCount % 5 === 0) {
LK.setScore(LK.getScore() + 10);
comboTxt.setText('Streak! +10');
comboTxt.visible = true;
comboTxt.alpha = 1;
tween(comboTxt, {
alpha: 0
}, {
duration: 900,
onFinish: function onFinish() {
comboTxt.visible = false;
}
});
}
// Edge catch bonus: if ball is caught at the left/right 20% of basket
var basketLeft = basket.x - basket.width / 2;
var basketRight = basket.x + basket.width / 2;
if (ball.x < basketLeft + basket.width * 0.2 || ball.x > basketRight - basket.width * 0.2) {
LK.setScore(LK.getScore() + 3);
comboTxt.setText('Edge Catch! +3');
comboTxt.visible = true;
comboTxt.alpha = 1;
tween(comboTxt, {
alpha: 0
}, {
duration: 900,
onFinish: function onFinish() {
comboTxt.visible = false;
}
});
}
scoreTxt.setText(LK.getScore());
LK.getSound('catch').play();
var flashColors = [0x4ad991, 0xf75e5e, 0x3b6eea, 0xf7a325, 0xFFD700];
LK.effects.flashScreen(flashColors[Math.floor(Math.random() * flashColors.length)], 200);
LK.effects.flashObject(basket, 0x4ad991, 200);
tween(ball, {
y: basket.y,
alpha: 0
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function (ballRef) {
return function () {
if (ballRef && ballRef.destroy) ballRef.destroy();
};
}(ball)
});
ballsToRemove.push(i);
// Reset combo if no catch in 1.2s
comboTimeout = LK.setTimeout(function () {
comboCount = 0;
comboTxt.visible = false;
comboTimeout = null;
}, 1200);
continue;
}
// Missed (goes below basket)
if (ball.lastY < 2732 && ball.y >= 2732 - 80) {
LK.getSound('miss').play();
missTxt.setText('Missed!');
missTxt.visible = true;
comboCount = 0;
if (comboTimeout) {
LK.clearTimeout(comboTimeout);
comboTimeout = null;
}
comboTxt.visible = false;
LK.effects.flashScreen(0xff0000, 600);
health = Math.max(0, health - 1);
var healthFrac = health / maxHealth;
healthBar.scaleX = healthBarWidth * healthFrac / 100;
if (healthFrac > 0.66) {
healthBar.tint = 0x00ff00;
} else if (healthFrac > 0.33) {
healthBar.tint = 0xffc700;
} else {
healthBar.tint = 0xff2222;
}
for (var hi = 0; hi < healthHearts.length; hi++) {
healthHearts[hi].alpha = hi < health ? 1 : 0.25;
healthHearts[hi].tint = hi < health ? 0xff2222 : 0x444444;
}
if (health <= 0) {
LK.setTimeout(function () {
LK.showGameOver();
// Show leaderboard with other players' scores after game over
if (typeof LK.showLeaderboard === "function") {
LK.showLeaderboard({
showOthers: true
});
}
}, 600);
gameOver = true;
return;
}
if (ball && ball.destroy) ball.destroy();
ballsToRemove.push(i);
continue;
}
// Off screen (cleanup)
if (ball.y > 2800) {
if (ball && ball.destroy) ball.destroy();
ballsToRemove.push(i);
continue;
}
ball.lastY = ball.y;
ball.lastIntersecting = intersecting;
}
// Remove balls after loop to avoid array shifting
// Remove from highest index to lowest to avoid shifting issues
ballsToRemove.sort(function (a, b) {
return b - a;
});
for (var btr = 0; btr < ballsToRemove.length; btr++) {
balls.splice(ballsToRemove[btr], 1);
}
// Update bombs
var bombsToRemove = [];
for (var j = bombs.length - 1; j >= 0; j--) {
var bomb = bombs[j];
if (bomb.lastY === undefined) bomb.lastY = bomb.y;
if (bomb.lastIntersecting === undefined) bomb.lastIntersecting = false;
bomb.update();
// Check for catch
var intersecting = bomb.intersects(basket);
if (!bomb.lastIntersecting && intersecting) {
if (basket.hasShield) {
basket.hasShield = false;
if (basket.shieldTween) {
basket.shieldTween.stop();
basket.shieldTween = null;
}
LK.effects.flashObject(basket, 0x00ffff, 400);
LK.effects.flashScreen(0x00ffff, 200);
if (bomb.isSuper) {
for (var bi = balls.length - 1; bi >= 0; bi--) {
balls[bi].destroy();
}
balls = [];
LK.setScore(LK.getScore() + 15);
scoreTxt.setText(LK.getScore());
missTxt.setText('Super Bomb! Cleared!');
missTxt.visible = true;
if (missTxt.hideTimeout) {
LK.clearTimeout(missTxt.hideTimeout);
missTxt.hideTimeout = null;
}
missTxt.hideTimeout = LK.setTimeout(function () {
missTxt.visible = false;
missTxt.hideTimeout = null;
}, 1200);
}
if (bomb && bomb.destroy) bomb.destroy();
bombsToRemove.push(j);
continue;
}
LK.getSound('boom').play();
LK.effects.flashObject(basket, 0xff0000, 600);
comboCount = 0;
if (comboTimeout) {
LK.clearTimeout(comboTimeout);
comboTimeout = null;
}
comboTxt.visible = false;
LK.effects.flashScreen(0x000000, 600);
health = Math.max(0, health - 1);
var healthFrac = health / maxHealth;
healthBar.scaleX = healthBarWidth * healthFrac / 100;
if (healthFrac > 0.66) {
healthBar.tint = 0x00ff00;
} else if (healthFrac > 0.33) {
healthBar.tint = 0xffc700;
} else {
healthBar.tint = 0xff2222;
}
for (var hi = 0; hi < healthHearts.length; hi++) {
healthHearts[hi].alpha = hi < health ? 1 : 0.25;
healthHearts[hi].tint = hi < health ? 0xff2222 : 0x444444;
}
if (health <= 0) {
LK.setTimeout(function () {
LK.showGameOver();
if (typeof LK.showLeaderboard === "function") {
LK.showLeaderboard({
showOthers: true
});
}
}, 600);
gameOver = true;
return;
}
if (bomb && bomb.destroy) bomb.destroy();
bombsToRemove.push(j);
continue;
}
// Off screen (cleanup)
if (bomb.y > 2800) {
if (bomb && bomb.destroy) bomb.destroy();
bombsToRemove.push(j);
continue;
}
bomb.lastY = bomb.y;
bomb.lastIntersecting = intersecting;
}
// Remove bombs after loop
// Remove from highest index to lowest to avoid shifting issues
bombsToRemove.sort(function (a, b) {
return b - a;
});
for (var btr = 0; btr < bombsToRemove.length; btr++) {
bombs.splice(bombsToRemove[btr], 1);
}
// Update power-ups
var powerupsToRemove = [];
for (var k = powerups.length - 1; k >= 0; k--) {
var powerup = powerups[k];
if (powerup.lastY === undefined) powerup.lastY = powerup.y;
if (powerup.lastIntersecting === undefined) powerup.lastIntersecting = false;
powerup.update();
// Check for catch
var intersecting = powerup.intersects(basket);
if (!powerup.lastIntersecting && intersecting) {
if (powerup.type === "shield" || powerup instanceof ShieldPowerUp || powerup instanceof Watermelon) {
basket.hasShield = true;
LK.effects.flashObject(basket, 0x00ffff, 400);
LK.effects.flashScreen(0x00ffff, 200);
if (!basket.shieldTween) {
basket.shieldTween = tween(basket, {
alpha: 1
}, {
duration: 200,
yoyo: true,
repeat: Infinity,
easing: tween.easeInOut
});
}
tween(powerup, {
y: basket.y,
alpha: 0
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function (puRef) {
return function () {
if (puRef && puRef.destroy) puRef.destroy();
};
}(powerup)
});
powerupsToRemove.push(k);
continue;
} else if (powerup.type === "slow" || powerup instanceof Grape) {
for (var bi = 0; bi < balls.length; bi++) {
balls[bi].speed *= 0.4;
balls[bi].vx *= 0.4;
}
for (var bj = 0; bj < bombs.length; bj++) {
bombs[bj].speed *= 0.4;
}
for (var bp = 0; bp < powerups.length; bp++) {
powerups[bp].speed *= 0.4;
}
missTxt.setText('Slow Motion!');
missTxt.visible = true;
if (missTxt.hideTimeout) {
LK.clearTimeout(missTxt.hideTimeout);
missTxt.hideTimeout = null;
}
missTxt.hideTimeout = LK.setTimeout(function () {
missTxt.visible = false;
missTxt.hideTimeout = null;
}, 1200);
LK.setTimeout(function () {
for (var bi = 0; bi < balls.length; bi++) {
balls[bi].speed /= 0.4;
balls[bi].vx /= 0.4;
}
for (var bj = 0; bj < bombs.length; bj++) {
bombs[bj].speed /= 0.4;
}
for (var bp = 0; bp < powerups.length; bp++) {
powerups[bp].speed /= 0.4;
}
}, 3000);
tween(powerup, {
y: basket.y,
alpha: 0
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function (puRef) {
return function () {
if (puRef && puRef.destroy) puRef.destroy();
};
}(powerup)
});
powerupsToRemove.push(k);
continue;
} else if (powerup.type === "banana" || powerup instanceof Banana) {
var originalScale = basket.scaleX || 1;
basket.scaleX = originalScale * 1.5;
basket.scaleY = (basket.scaleY || 1) * 1.1;
LK.effects.flashObject(basket, 0xfff700, 300);
missTxt.setText('Wide Basket!');
missTxt.visible = true;
if (missTxt.hideTimeout) {
LK.clearTimeout(missTxt.hideTimeout);
missTxt.hideTimeout = null;
}
missTxt.hideTimeout = LK.setTimeout(function () {
missTxt.visible = false;
missTxt.hideTimeout = null;
}, 1200);
LK.setTimeout(function () {
basket.scaleX = originalScale;
basket.scaleY = 1;
}, 4000);
tween(powerup, {
y: basket.y,
alpha: 0
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function (puRef) {
return function () {
if (puRef && puRef.destroy) puRef.destroy();
};
}(powerup)
});
powerupsToRemove.push(k);
continue;
} else if (powerup.type === "apple" || powerup instanceof Apple) {
LK.setScore(LK.getScore() + 5);
scoreTxt.setText(LK.getScore());
LK.getSound('catch').play();
LK.effects.flashScreen(0xff2222, 350);
LK.effects.flashObject(basket, 0xff2222, 300);
missTxt.setText('Apple! +5');
missTxt.visible = true;
if (missTxt.hideTimeout) {
LK.clearTimeout(missTxt.hideTimeout);
missTxt.hideTimeout = null;
}
missTxt.hideTimeout = LK.setTimeout(function () {
missTxt.visible = false;
missTxt.hideTimeout = null;
}, 1200);
tween(powerup, {
y: basket.y,
alpha: 0
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function (puRef) {
return function () {
if (puRef && puRef.destroy) puRef.destroy();
};
}(powerup)
});
powerupsToRemove.push(k);
continue;
} else {
LK.setScore(LK.getScore() + 2);
scoreTxt.setText(LK.getScore());
LK.getSound('catch').play();
LK.effects.flashScreen(0xFFD700, 350);
LK.effects.flashObject(basket, 0xFFD700, 300);
tween(powerup, {
y: basket.y,
alpha: 0
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function (puRef) {
return function () {
if (puRef && puRef.destroy) puRef.destroy();
};
}(powerup)
});
powerupsToRemove.push(k);
continue;
}
}
// Off screen (cleanup)
if (powerup.y > 2800) {
if (powerup && powerup.destroy) powerup.destroy();
powerupsToRemove.push(k);
continue;
}
powerup.lastY = powerup.y;
powerup.lastIntersecting = intersecting;
}
// Remove powerups after loop
// Remove from highest index to lowest to avoid shifting issues
powerupsToRemove.sort(function (a, b) {
return b - a;
});
for (var putr = 0; putr < powerupsToRemove.length; putr++) {
powerups.splice(powerupsToRemove[putr], 1);
}
// Hide miss text after a short time
if (missTxt.visible) {
if (!missTxt.hideTimeout) {
missTxt.hideTimeout = LK.setTimeout(function () {
missTxt.visible = false;
missTxt.hideTimeout = null;
}, 900);
}
}
// Draw shield icon above basket if shield is active
if (basket.hasShield) {
if (!basket.shieldIcon) {
basket.shieldIcon = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
basket.shieldIcon.tint = 0x00ffff;
basket.shieldIcon.scaleX = 0.5;
basket.shieldIcon.scaleY = 0.5;
game.addChild(basket.shieldIcon);
}
basket.shieldIcon.x = basket.x;
basket.shieldIcon.y = basket.y - 70;
basket.shieldIcon.visible = true;
} else if (basket.shieldIcon) {
basket.shieldIcon.visible = false;
}
// Rainbow basket after 40 points
if (LK.getScore() >= 40 && basket.children && basket.children.length > 0) {
var t = LK.ticks % 60 / 60;
var r = Math.floor(127 * Math.sin(2 * Math.PI * t) + 128);
var g = Math.floor(127 * Math.sin(2 * Math.PI * t + 2) + 128);
var b = Math.floor(127 * Math.sin(2 * Math.PI * t + 4) + 128);
basket.children[0].tint = r << 16 | g << 8 | b;
}
// Defensive: Always update lastX and lastY for basket to avoid undefined errors
if (basket) {
if (basket.lastX === undefined) basket.lastX = basket.x;
if (basket.lastY === undefined) basket.lastY = basket.y;
basket.lastX = basket.x;
basket.lastY = basket.y;
}
};
// Reset state on new game
LK.on('gameStart', function () {
// Remove all balls, bombs, and powerups
for (var i = 0; i < balls.length; i++) balls[i].destroy();
for (var j = 0; j < bombs.length; j++) bombs[j].destroy();
for (var k = 0; k < powerups.length; k++) powerups[k].destroy();
balls = [];
bombs = [];
powerups = [];
basket.x = 2048 / 2;
basket.y = 2732 - 180;
// Reset challenge mode state
basket.challengeActive = false;
basket.challengeDir = null;
if (basket.challengeTween) {
basket.challengeTween.stop();
basket.challengeTween = null;
}
// Reset shield state
basket.hasShield = false;
if (basket.shieldTween) {
basket.shieldTween.stop();
basket.shieldTween = null;
}
// Randomize basket color on new game
var basketColors = [0x3b6eea, 0x4ad991, 0xf75e5e, 0xf7a325, 0xFFD700];
if (basket.children && basket.children.length > 0) {
basket.children[0].tint = basketColors[Math.floor(Math.random() * basketColors.length)];
}
LK.setScore(0);
scoreTxt.setText('0');
missTxt.setText('');
missTxt.visible = false;
if (missTxt.hideTimeout) {
LK.clearTimeout(missTxt.hideTimeout);
missTxt.hideTimeout = null;
}
spawnTimer = 0;
gameOver = false;
game.missForgiven = false;
// Reset combo
comboCount = 0;
if (comboTimeout) {
LK.clearTimeout(comboTimeout);
comboTimeout = null;
}
comboTxt.visible = false;
// Reset health bar and hearts
health = maxHealth;
healthBar.scaleX = healthBarWidth / 100;
healthBar.tint = 0x00ff00;
for (var hi = 0; hi < healthHearts.length; hi++) {
healthHearts[hi].alpha = 1;
healthHearts[hi].tint = 0xff2222;
}
}); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Fruit: Apple (extra points)
var Apple = Container.expand(function () {
var self = Container.call(this);
var appleAsset = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
appleAsset.tint = 0xff2222; // Red
appleAsset.scaleX = 1.1;
appleAsset.scaleY = 1.1;
tween(appleAsset, {
alpha: 0.85
}, {
duration: 350,
yoyo: true,
repeat: Infinity,
easing: tween.easeInOut
});
var baseSpeed = 10 + Math.random() * 3;
if (typeof LK !== "undefined" && typeof LK.getScore === "function") {
baseSpeed += Math.min(8, Math.floor(LK.getScore() / 12));
}
self.speed = baseSpeed;
self.type = "apple";
self.update = function () {
self.y += self.speed;
};
return self;
});
// Ball class
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballAsset = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
// Neon color palette for balls
var colors = [0x00ffff, 0xff00ff, 0xffff00, 0x00ff00, 0xff8800, 0x00ffea, 0xff0088];
ballAsset.color = colors[Math.floor(Math.random() * colors.length)];
ballAsset.tint = ballAsset.color;
// Neon glow effect: animate alpha for a pulsing glow
tween(ballAsset, {
alpha: 0.7
}, {
duration: 400,
yoyo: true,
repeat: Infinity,
easing: tween.easeInOut
});
// Adjusted for human reflexes: start slower, increase with score, cap max speed
var baseSpeed = 10 + Math.random() * 4;
if (typeof LK !== "undefined" && typeof LK.getScore === "function") {
baseSpeed += Math.min(10, Math.floor(LK.getScore() / 8)); // increase 1 per 8 points, max +10
}
self.speed = baseSpeed;
self.vx = (Math.random() - 0.5) * 10; // random horizontal speed
self.update = function () {
self.y += self.speed;
self.x += self.vx;
// Bounce off left/right walls
if (self.x < 60 && self.vx < 0) {
self.x = 60;
self.vx *= -1;
}
if (self.x > 2048 - 60 && self.vx > 0) {
self.x = 2048 - 60;
self.vx *= -1;
}
};
return self;
});
// Fruit: Banana (wider catch area)
var Banana = Container.expand(function () {
var self = Container.call(this);
var bananaAsset = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
bananaAsset.tint = 0xfff700; // Yellow
bananaAsset.scaleX = 1.5;
bananaAsset.scaleY = 0.8;
tween(bananaAsset, {
alpha: 0.8
}, {
duration: 400,
yoyo: true,
repeat: Infinity,
easing: tween.easeInOut
});
var baseSpeed = 9 + Math.random() * 2;
if (typeof LK !== "undefined" && typeof LK.getScore === "function") {
baseSpeed += Math.min(7, Math.floor(LK.getScore() / 14));
}
self.speed = baseSpeed;
self.type = "banana";
self.update = function () {
self.y += self.speed;
};
return self;
});
// Basket class
var Basket = Container.expand(function () {
var self = Container.call(this);
var basketAsset = self.attachAsset('basket', {
anchorX: 0.5,
anchorY: 0.5
});
// Neon basket: random neon color and glow
var basketColors = [0x00ffff, 0xff00ff, 0xffff00, 0x00ff00, 0xff8800, 0x00ffea, 0xff0088];
basketAsset.tint = basketColors[Math.floor(Math.random() * basketColors.length)];
tween(basketAsset, {
alpha: 0.7
}, {
duration: 600,
yoyo: true,
repeat: Infinity,
easing: tween.easeInOut
});
return self;
});
// Bomb class
var Bomb = Container.expand(function () {
var self = Container.call(this);
var bombAsset = self.attachAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5
});
// Neon bomb: magenta or cyan
var bombColors = [0xff00ff, 0x00ffff, 0xffff00];
bombAsset.tint = bombColors[Math.floor(Math.random() * bombColors.length)];
tween(bombAsset, {
alpha: 0.6
}, {
duration: 400,
yoyo: true,
repeat: Infinity,
easing: tween.easeInOut
});
// Adjusted for human reflexes: start slower, increase with score, cap max speed
var baseBombSpeed = 12 + Math.random() * 5;
if (typeof LK !== "undefined" && typeof LK.getScore === "function") {
baseBombSpeed += Math.min(12, Math.floor(LK.getScore() / 7)); // increase 1 per 7 points, max +12
}
self.speed = baseBombSpeed;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Fruit: Grape (slows bombs for a short time)
var Grape = Container.expand(function () {
var self = Container.call(this);
var grapeAsset = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
grapeAsset.tint = 0x9900ff; // Purple
grapeAsset.scaleX = 1.0;
grapeAsset.scaleY = 1.0;
tween(grapeAsset, {
alpha: 0.7
}, {
duration: 300,
yoyo: true,
repeat: Infinity,
easing: tween.easeInOut
});
var baseSpeed = 11 + Math.random() * 2;
if (typeof LK !== "undefined" && typeof LK.getScore === "function") {
baseSpeed += Math.min(7, Math.floor(LK.getScore() / 13));
}
self.speed = baseSpeed;
self.type = "grape";
self.update = function () {
self.y += self.speed;
};
return self;
});
// PowerUp class (neon star)
var PowerUp = Container.expand(function () {
var self = Container.call(this);
// Use a ball asset for now, but tint and scale to look like a power-up
var starAsset = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
// Neon star color
var starColors = [0xFFD700, 0x00ffea, 0xff00ff, 0x00ff00];
starAsset.tint = starColors[Math.floor(Math.random() * starColors.length)];
starAsset.scaleX = 1.3;
starAsset.scaleY = 1.3;
// Animate alpha for a pulsing glow
tween(starAsset, {
alpha: 0.8
}, {
duration: 300,
yoyo: true,
repeat: Infinity,
easing: tween.easeInOut
});
// Adjusted for human reflexes: start slower, increase with score, cap max speed
var basePowerSpeed = 9 + Math.random() * 3;
if (typeof LK !== "undefined" && typeof LK.getScore === "function") {
basePowerSpeed += Math.min(8, Math.floor(LK.getScore() / 10)); // increase 1 per 10 points, max +8
}
self.speed = basePowerSpeed;
self.type = "score"; // default type
self.update = function () {
self.y += self.speed;
};
return self;
});
// ShieldPowerUp class (neon blue ring)
var ShieldPowerUp = Container.expand(function () {
var self = Container.call(this);
var shieldAsset = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
shieldAsset.tint = 0x00ffff;
shieldAsset.scaleX = 1.5;
shieldAsset.scaleY = 1.5;
tween(shieldAsset, {
alpha: 0.6
}, {
duration: 200,
yoyo: true,
repeat: Infinity,
easing: tween.easeInOut
});
// Adjusted for human reflexes: start slower, increase with score, cap max speed
var baseShieldSpeed = 8 + Math.random() * 2;
if (typeof LK !== "undefined" && typeof LK.getScore === "function") {
baseShieldSpeed += Math.min(7, Math.floor(LK.getScore() / 12)); // increase 1 per 12 points, max +7
}
self.speed = baseShieldSpeed;
self.type = "shield";
self.update = function () {
self.y += self.speed;
};
return self;
});
// Fruit: Watermelon (big, gives shield)
var Watermelon = Container.expand(function () {
var self = Container.call(this);
var melonAsset = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
melonAsset.tint = 0x22ff44; // Green
melonAsset.scaleX = 1.7;
melonAsset.scaleY = 1.3;
tween(melonAsset, {
alpha: 0.8
}, {
duration: 500,
yoyo: true,
repeat: Infinity,
easing: tween.easeInOut
});
var baseSpeed = 8 + Math.random() * 2;
if (typeof LK !== "undefined" && typeof LK.getScore === "function") {
baseSpeed += Math.min(6, Math.floor(LK.getScore() / 15));
}
self.speed = baseSpeed;
self.type = "watermelon";
self.update = function () {
self.y += self.speed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Sound for missing a ball
// Sound for catching a bomb
// Sound for catching a ball
// Bomb: black ellipse
// Ball: colored ellipse
// Basket: wide rectangle
// Game area: 2048x2732
// No background image, only black background
// No background image, only black background
game.setBackgroundColor(0x000000);
// Neon-style game objective text at the start (small, English, subtle neon)
var objectiveTxt = new Text2('Goal: Catch the balls, avoid the bombs!', {
size: 54,
// smaller text
fill: 0x00ffff,
stroke: 0x222222,
strokeThickness: 8,
dropShadow: true,
dropShadowColor: 0x00ffff,
dropShadowBlur: 12,
dropShadowDistance: 0,
align: "center"
});
objectiveTxt.anchor.set(0.5, 0);
objectiveTxt.y = 90;
LK.gui.top.addChild(objectiveTxt);
// Add leaderboard button for competition
var leaderboardBtn = new Text2('🏆 Leaderboard', {
size: 60,
fill: 0xFFD700,
stroke: 0x00ffff,
strokeThickness: 8,
dropShadow: true,
dropShadowColor: 0xFFD700,
dropShadowBlur: 10,
dropShadowDistance: 0,
align: "center"
});
leaderboardBtn.anchor.set(0.5, 0);
leaderboardBtn.x = 2048 - 250;
leaderboardBtn.y = 90;
leaderboardBtn.interactive = true;
leaderboardBtn.buttonMode = true;
leaderboardBtn.down = function () {
if (typeof LK.showLeaderboard === "function") {
LK.showLeaderboard();
}
};
LK.gui.top.addChild(leaderboardBtn);
// Hide objective after 2.5 seconds
LK.setTimeout(function () {
objectiveTxt.visible = false;
}, 2500);
// Score text
var scoreTxt = new Text2('0', {
size: 120,
fill: 0x00FFEA,
stroke: 0xFF00FF,
strokeThickness: 18,
dropShadow: true,
dropShadowColor: 0x00FFFF,
dropShadowBlur: 24,
dropShadowDistance: 0,
align: "center"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Health bar UI
var maxHealth = 3;
var health = maxHealth;
var healthBarWidth = 320;
var healthBarHeight = 38;
var healthBarBg = LK.getAsset('ball', {
anchorX: 0,
anchorY: 0.5,
scaleX: healthBarWidth / 100,
scaleY: healthBarHeight / 100,
x: 120,
y: 160
});
healthBarBg.tint = 0x222222;
LK.gui.top.addChild(healthBarBg);
var healthBar = LK.getAsset('ball', {
anchorX: 0,
anchorY: 0.5,
scaleX: healthBarWidth / 100,
scaleY: healthBarHeight / 100,
x: 120,
y: 160
});
healthBar.tint = 0x00ff00;
LK.gui.top.addChild(healthBar);
// Health icon hearts
var healthHearts = [];
for (var h = 0; h < maxHealth; h++) {
var heart = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.32,
scaleY: 0.32,
x: 120 + 40 + h * 54,
y: 160
});
heart.tint = 0xff2222;
LK.gui.top.addChild(heart);
healthHearts.push(heart);
}
// Misses text (shows "Missed!" when a ball is missed)
var missTxt = new Text2('', {
size: 90,
fill: 0x00FFFF,
stroke: 0xFF00FF,
strokeThickness: 14,
dropShadow: true,
dropShadowColor: 0x00FFFF,
dropShadowBlur: 18,
dropShadowDistance: 0,
align: "center"
});
missTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(missTxt);
missTxt.visible = false;
// Basket
var basket = new Basket();
game.addChild(basket);
basket.y = 2732 - 180;
basket.x = 2048 / 2;
// Ball, bomb, and power-up arrays
var balls = [];
var bombs = [];
var powerups = [];
// Dragging
var dragNode = null;
// Touch indicator for user-friendly feedback
var touchIndicator = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
touchIndicator.tint = 0xFFD700;
touchIndicator.scaleX = 0.4;
touchIndicator.scaleY = 0.4;
touchIndicator.alpha = 0;
game.addChild(touchIndicator);
// Spawning control
var spawnTimer = 0;
var spawnInterval = 55; // frames between spawns, will decrease as score increases
// State
var lastScore = 0;
var gameOver = false;
// Combo scoring
var comboCount = 0;
var comboTimeout = null;
var comboTxt = new Text2('', {
size: 80,
fill: 0xFFD700,
stroke: 0x00ffff,
strokeThickness: 10,
dropShadow: true,
dropShadowColor: 0xFFD700,
dropShadowBlur: 12,
dropShadowDistance: 0,
align: "center"
});
comboTxt.anchor.set(0.5, 0);
comboTxt.y = 200;
comboTxt.visible = false;
LK.gui.top.addChild(comboTxt);
// Helper: spawn a ball, bomb, or power-up
function spawnFallingObject() {
// 80% ball, 20% bomb, 5% power-up, 3% shield power-up, plus fruits
var rand = Math.random();
var x = 150 + Math.random() * (2048 - 300);
if (rand < 0.10) {
var bomb = new Bomb();
bomb.x = x;
bomb.y = -60;
bomb.lastIntersecting = false;
// Add "-" indicator for bomb
var minusTxt = new Text2('-', {
size: 70,
fill: 0xff2222,
stroke: 0x000000,
strokeThickness: 8,
align: "center"
});
minusTxt.anchor.set(0.5, 1);
minusTxt.y = -60;
bomb.addChild(minusTxt);
bomb.indicatorTxt = minusTxt;
bombs.push(bomb);
game.addChild(bomb);
} else if (rand < 0.13) {
// 3% chance for super bomb
var superBomb = new Bomb();
superBomb.x = x;
superBomb.y = -60;
superBomb.lastIntersecting = false;
superBomb.isSuper = true;
if (superBomb.children && superBomb.children.length > 0) {
superBomb.children[0].tint = 0xffffff;
}
// Add "-" indicator for super bomb
var minusTxt = new Text2('-', {
size: 70,
fill: 0xff2222,
stroke: 0x000000,
strokeThickness: 8,
align: "center"
});
minusTxt.anchor.set(0.5, 1);
minusTxt.y = -60;
superBomb.addChild(minusTxt);
superBomb.indicatorTxt = minusTxt;
bombs.push(superBomb);
game.addChild(superBomb);
} else if (rand > 0.98) {
// 2% chance for slow motion power-up
var slowPower = new PowerUp();
slowPower.x = x;
slowPower.y = -60;
slowPower.lastIntersecting = false;
slowPower.type = "slow";
if (slowPower.children && slowPower.children.length > 0) {
slowPower.children[0].tint = 0x3b6eea;
}
// Add "+" indicator for slow powerup
var plusTxt = new Text2('+', {
size: 70,
fill: 0x00ffea,
stroke: 0x000000,
strokeThickness: 8,
align: "center"
});
plusTxt.anchor.set(0.5, 1);
plusTxt.y = -60;
slowPower.addChild(plusTxt);
slowPower.indicatorTxt = plusTxt;
powerups.push(slowPower);
game.addChild(slowPower);
} else if (rand > 0.96) {
// 4% chance for shield power-up
var shield = new ShieldPowerUp();
shield.x = x;
shield.y = -60;
shield.lastIntersecting = false;
// Add "+" indicator for shield
var plusTxt = new Text2('+', {
size: 70,
fill: 0x00ffff,
stroke: 0x000000,
strokeThickness: 8,
align: "center"
});
plusTxt.anchor.set(0.5, 1);
plusTxt.y = -60;
shield.addChild(plusTxt);
shield.indicatorTxt = plusTxt;
powerups.push(shield);
game.addChild(shield);
} else if (rand > 0.94) {
// 2% chance for score power-up
var powerup = new PowerUp();
powerup.x = x;
powerup.y = -60;
powerup.lastIntersecting = false;
// Add "+" indicator for score powerup
var plusTxt = new Text2('+', {
size: 70,
fill: 0xFFD700,
stroke: 0x000000,
strokeThickness: 8,
align: "center"
});
plusTxt.anchor.set(0.5, 1);
plusTxt.y = -60;
powerup.addChild(plusTxt);
powerup.indicatorTxt = plusTxt;
powerups.push(powerup);
game.addChild(powerup);
} else if (rand > 0.92) {
// 2% chance for Watermelon (shield fruit)
var watermelon = new Watermelon();
watermelon.x = x;
watermelon.y = -60;
watermelon.lastIntersecting = false;
// Add "+" indicator for watermelon
var plusTxt = new Text2('+', {
size: 70,
fill: 0x22ff44,
stroke: 0x000000,
strokeThickness: 8,
align: "center"
});
plusTxt.anchor.set(0.5, 1);
plusTxt.y = -60;
watermelon.addChild(plusTxt);
watermelon.indicatorTxt = plusTxt;
powerups.push(watermelon);
game.addChild(watermelon);
} else if (rand > 0.89) {
// 3% chance for Grape (slows bombs)
var grape = new Grape();
grape.x = x;
grape.y = -60;
grape.lastIntersecting = false;
// Add "+" indicator for grape
var plusTxt = new Text2('+', {
size: 70,
fill: 0x9900ff,
stroke: 0x000000,
strokeThickness: 8,
align: "center"
});
plusTxt.anchor.set(0.5, 1);
plusTxt.y = -60;
grape.addChild(plusTxt);
grape.indicatorTxt = plusTxt;
powerups.push(grape);
game.addChild(grape);
} else if (rand > 0.86) {
// 3% chance for Banana (wider catch area)
var banana = new Banana();
banana.x = x;
banana.y = -60;
banana.lastIntersecting = false;
// Add "+" indicator for banana
var plusTxt = new Text2('+', {
size: 70,
fill: 0xfff700,
stroke: 0x000000,
strokeThickness: 8,
align: "center"
});
plusTxt.anchor.set(0.5, 1);
plusTxt.y = -60;
banana.addChild(plusTxt);
banana.indicatorTxt = plusTxt;
powerups.push(banana);
game.addChild(banana);
} else if (rand > 0.83) {
// 3% chance for Apple (extra points)
var apple = new Apple();
apple.x = x;
apple.y = -60;
apple.lastIntersecting = false;
// Add "+" indicator for apple
var plusTxt = new Text2('+', {
size: 70,
fill: 0xff2222,
stroke: 0x000000,
strokeThickness: 8,
align: "center"
});
plusTxt.anchor.set(0.5, 1);
plusTxt.y = -60;
apple.addChild(plusTxt);
apple.indicatorTxt = plusTxt;
powerups.push(apple);
game.addChild(apple);
} else {
var ball = new Ball();
ball.x = x;
ball.y = -60;
ball.lastIntersecting = false;
// Add "+" indicator for normal ball
var plusTxt = new Text2('+', {
size: 70,
fill: 0x00ffff,
stroke: 0x000000,
strokeThickness: 8,
align: "center"
});
plusTxt.anchor.set(0.5, 1);
plusTxt.y = -60;
ball.addChild(plusTxt);
ball.indicatorTxt = plusTxt;
balls.push(ball);
game.addChild(ball);
}
}
// Move handler for dragging basket
function handleMove(x, y, obj) {
if (dragNode) {
// Clamp basket within screen
var halfWidth = dragNode.width / 2;
var minX = halfWidth;
var maxX = 2048 - halfWidth;
var newX = Math.max(minX, Math.min(maxX, x));
// Only update basket.x if it is not at the edge, or if moving away from the edge
// This prevents the basket from "burning" or flashing at the edge
if (!(dragNode.x <= minX && newX <= minX) &&
// not stuck at left edge
!(dragNode.x >= maxX && newX >= maxX) // not stuck at right edge
) {
dragNode.x = newX;
}
// Move touch indicator with finger
touchIndicator.x = x;
touchIndicator.y = y;
touchIndicator.alpha = 0.7;
}
}
// Touch/mouse events
game.down = function (x, y, obj) {
// Allow drag from anywhere in lower 1/3 of screen for easier control
if (y > 2732 * 2 / 3) {
dragNode = basket;
handleMove(x, y, obj);
// Show and animate touch indicator
touchIndicator.x = x;
touchIndicator.y = y;
touchIndicator.alpha = 1;
tween(touchIndicator, {
alpha: 0.7,
scaleX: 0.6,
scaleY: 0.6
}, {
duration: 120,
yoyo: true,
repeat: 1,
onFinish: function onFinish() {
// Keep indicator visible while dragging
if (!dragNode) {
touchIndicator.alpha = 0;
touchIndicator.scaleX = 0.4;
touchIndicator.scaleY = 0.4;
}
}
});
}
};
game.move = handleMove;
game.up = function (x, y, obj) {
dragNode = null;
touchIndicator.alpha = 0;
};
// Main update loop
game.update = function () {
if (gameOver) return;
// Change background color at each 5 points (neon palette)
var neonBgColors = [0x000000, 0x0ff0fc, 0x1a0033, 0x3b6eea, 0x00ffea, 0xff00ff, 0x00ff00, 0xf75e5e, 0xf7c325];
var score = LK.getScore();
var bgIndex = Math.floor(score / 5) % neonBgColors.length;
game.setBackgroundColor(neonBgColors[bgIndex]);
// Challenge mode: basket moves left/right automatically every 20 points
if (score > 0 && score % 20 === 0 && !basket.challengeActive) {
basket.challengeActive = true;
basket.challengeDir = Math.random() > 0.5 ? 1 : -1;
basket.challengeTween = tween(basket, {
x: basket.challengeDir > 0 ? 2048 - basket.width / 2 : basket.width / 2
}, {
duration: 2200,
yoyo: true,
repeat: 2,
easing: tween.easeInOut,
onFinish: function onFinish() {
basket.challengeActive = false;
}
});
}
// Spawn balls/bombs
spawnTimer++;
// Decrease interval as score increases (min 35 for human reflexes)
var interval = Math.max(35, spawnInterval - Math.floor(LK.getScore() / 10) * 3);
if (spawnTimer >= interval) {
spawnFallingObject();
spawnTimer = 0;
}
// Speed up all balls every 10 points
if (score > 0 && score % 10 === 0 && !game.lastSpeedupScore) {
for (var si = 0; si < balls.length; si++) {
balls[si].speed *= 1.08; // less aggressive speedup for human reflexes
balls[si].vx *= 1.05;
}
game.lastSpeedupScore = score;
}
if (score % 10 !== 0) {
game.lastSpeedupScore = null;
}
// Update balls
// Use a local array to collect indices to remove, then remove after loop to avoid array shifting
var ballsToRemove = [];
for (var i = balls.length - 1; i >= 0; i--) {
var ball = balls[i];
if (ball.lastY === undefined) ball.lastY = ball.y;
if (ball.lastIntersecting === undefined) ball.lastIntersecting = false;
ball.update();
// Check for catch
var intersecting = ball.intersects(basket);
if (!ball.lastIntersecting && intersecting) {
// Caught!
comboCount++;
if (comboTimeout) {
LK.clearTimeout(comboTimeout);
comboTimeout = null;
}
if (comboCount > 1) {
comboTxt.setText('Combo x' + comboCount + '!');
comboTxt.visible = true;
comboTxt.alpha = 1;
tween(comboTxt, {
alpha: 0
}, {
duration: 900,
onFinish: function onFinish() {
comboTxt.visible = false;
}
});
}
// Combo bonus: +1 for first, +2 for 2nd, +3 for 3rd, etc.
var comboBonus = comboCount > 1 ? comboCount : 1;
LK.setScore(LK.getScore() + comboBonus);
// Streak bonus: every 5 consecutive catches, +10 bonus
if (comboCount > 0 && comboCount % 5 === 0) {
LK.setScore(LK.getScore() + 10);
comboTxt.setText('Streak! +10');
comboTxt.visible = true;
comboTxt.alpha = 1;
tween(comboTxt, {
alpha: 0
}, {
duration: 900,
onFinish: function onFinish() {
comboTxt.visible = false;
}
});
}
// Edge catch bonus: if ball is caught at the left/right 20% of basket
var basketLeft = basket.x - basket.width / 2;
var basketRight = basket.x + basket.width / 2;
if (ball.x < basketLeft + basket.width * 0.2 || ball.x > basketRight - basket.width * 0.2) {
LK.setScore(LK.getScore() + 3);
comboTxt.setText('Edge Catch! +3');
comboTxt.visible = true;
comboTxt.alpha = 1;
tween(comboTxt, {
alpha: 0
}, {
duration: 900,
onFinish: function onFinish() {
comboTxt.visible = false;
}
});
}
scoreTxt.setText(LK.getScore());
LK.getSound('catch').play();
var flashColors = [0x4ad991, 0xf75e5e, 0x3b6eea, 0xf7a325, 0xFFD700];
LK.effects.flashScreen(flashColors[Math.floor(Math.random() * flashColors.length)], 200);
LK.effects.flashObject(basket, 0x4ad991, 200);
tween(ball, {
y: basket.y,
alpha: 0
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function (ballRef) {
return function () {
if (ballRef && ballRef.destroy) ballRef.destroy();
};
}(ball)
});
ballsToRemove.push(i);
// Reset combo if no catch in 1.2s
comboTimeout = LK.setTimeout(function () {
comboCount = 0;
comboTxt.visible = false;
comboTimeout = null;
}, 1200);
continue;
}
// Missed (goes below basket)
if (ball.lastY < 2732 && ball.y >= 2732 - 80) {
LK.getSound('miss').play();
missTxt.setText('Missed!');
missTxt.visible = true;
comboCount = 0;
if (comboTimeout) {
LK.clearTimeout(comboTimeout);
comboTimeout = null;
}
comboTxt.visible = false;
LK.effects.flashScreen(0xff0000, 600);
health = Math.max(0, health - 1);
var healthFrac = health / maxHealth;
healthBar.scaleX = healthBarWidth * healthFrac / 100;
if (healthFrac > 0.66) {
healthBar.tint = 0x00ff00;
} else if (healthFrac > 0.33) {
healthBar.tint = 0xffc700;
} else {
healthBar.tint = 0xff2222;
}
for (var hi = 0; hi < healthHearts.length; hi++) {
healthHearts[hi].alpha = hi < health ? 1 : 0.25;
healthHearts[hi].tint = hi < health ? 0xff2222 : 0x444444;
}
if (health <= 0) {
LK.setTimeout(function () {
LK.showGameOver();
// Show leaderboard with other players' scores after game over
if (typeof LK.showLeaderboard === "function") {
LK.showLeaderboard({
showOthers: true
});
}
}, 600);
gameOver = true;
return;
}
if (ball && ball.destroy) ball.destroy();
ballsToRemove.push(i);
continue;
}
// Off screen (cleanup)
if (ball.y > 2800) {
if (ball && ball.destroy) ball.destroy();
ballsToRemove.push(i);
continue;
}
ball.lastY = ball.y;
ball.lastIntersecting = intersecting;
}
// Remove balls after loop to avoid array shifting
// Remove from highest index to lowest to avoid shifting issues
ballsToRemove.sort(function (a, b) {
return b - a;
});
for (var btr = 0; btr < ballsToRemove.length; btr++) {
balls.splice(ballsToRemove[btr], 1);
}
// Update bombs
var bombsToRemove = [];
for (var j = bombs.length - 1; j >= 0; j--) {
var bomb = bombs[j];
if (bomb.lastY === undefined) bomb.lastY = bomb.y;
if (bomb.lastIntersecting === undefined) bomb.lastIntersecting = false;
bomb.update();
// Check for catch
var intersecting = bomb.intersects(basket);
if (!bomb.lastIntersecting && intersecting) {
if (basket.hasShield) {
basket.hasShield = false;
if (basket.shieldTween) {
basket.shieldTween.stop();
basket.shieldTween = null;
}
LK.effects.flashObject(basket, 0x00ffff, 400);
LK.effects.flashScreen(0x00ffff, 200);
if (bomb.isSuper) {
for (var bi = balls.length - 1; bi >= 0; bi--) {
balls[bi].destroy();
}
balls = [];
LK.setScore(LK.getScore() + 15);
scoreTxt.setText(LK.getScore());
missTxt.setText('Super Bomb! Cleared!');
missTxt.visible = true;
if (missTxt.hideTimeout) {
LK.clearTimeout(missTxt.hideTimeout);
missTxt.hideTimeout = null;
}
missTxt.hideTimeout = LK.setTimeout(function () {
missTxt.visible = false;
missTxt.hideTimeout = null;
}, 1200);
}
if (bomb && bomb.destroy) bomb.destroy();
bombsToRemove.push(j);
continue;
}
LK.getSound('boom').play();
LK.effects.flashObject(basket, 0xff0000, 600);
comboCount = 0;
if (comboTimeout) {
LK.clearTimeout(comboTimeout);
comboTimeout = null;
}
comboTxt.visible = false;
LK.effects.flashScreen(0x000000, 600);
health = Math.max(0, health - 1);
var healthFrac = health / maxHealth;
healthBar.scaleX = healthBarWidth * healthFrac / 100;
if (healthFrac > 0.66) {
healthBar.tint = 0x00ff00;
} else if (healthFrac > 0.33) {
healthBar.tint = 0xffc700;
} else {
healthBar.tint = 0xff2222;
}
for (var hi = 0; hi < healthHearts.length; hi++) {
healthHearts[hi].alpha = hi < health ? 1 : 0.25;
healthHearts[hi].tint = hi < health ? 0xff2222 : 0x444444;
}
if (health <= 0) {
LK.setTimeout(function () {
LK.showGameOver();
if (typeof LK.showLeaderboard === "function") {
LK.showLeaderboard({
showOthers: true
});
}
}, 600);
gameOver = true;
return;
}
if (bomb && bomb.destroy) bomb.destroy();
bombsToRemove.push(j);
continue;
}
// Off screen (cleanup)
if (bomb.y > 2800) {
if (bomb && bomb.destroy) bomb.destroy();
bombsToRemove.push(j);
continue;
}
bomb.lastY = bomb.y;
bomb.lastIntersecting = intersecting;
}
// Remove bombs after loop
// Remove from highest index to lowest to avoid shifting issues
bombsToRemove.sort(function (a, b) {
return b - a;
});
for (var btr = 0; btr < bombsToRemove.length; btr++) {
bombs.splice(bombsToRemove[btr], 1);
}
// Update power-ups
var powerupsToRemove = [];
for (var k = powerups.length - 1; k >= 0; k--) {
var powerup = powerups[k];
if (powerup.lastY === undefined) powerup.lastY = powerup.y;
if (powerup.lastIntersecting === undefined) powerup.lastIntersecting = false;
powerup.update();
// Check for catch
var intersecting = powerup.intersects(basket);
if (!powerup.lastIntersecting && intersecting) {
if (powerup.type === "shield" || powerup instanceof ShieldPowerUp || powerup instanceof Watermelon) {
basket.hasShield = true;
LK.effects.flashObject(basket, 0x00ffff, 400);
LK.effects.flashScreen(0x00ffff, 200);
if (!basket.shieldTween) {
basket.shieldTween = tween(basket, {
alpha: 1
}, {
duration: 200,
yoyo: true,
repeat: Infinity,
easing: tween.easeInOut
});
}
tween(powerup, {
y: basket.y,
alpha: 0
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function (puRef) {
return function () {
if (puRef && puRef.destroy) puRef.destroy();
};
}(powerup)
});
powerupsToRemove.push(k);
continue;
} else if (powerup.type === "slow" || powerup instanceof Grape) {
for (var bi = 0; bi < balls.length; bi++) {
balls[bi].speed *= 0.4;
balls[bi].vx *= 0.4;
}
for (var bj = 0; bj < bombs.length; bj++) {
bombs[bj].speed *= 0.4;
}
for (var bp = 0; bp < powerups.length; bp++) {
powerups[bp].speed *= 0.4;
}
missTxt.setText('Slow Motion!');
missTxt.visible = true;
if (missTxt.hideTimeout) {
LK.clearTimeout(missTxt.hideTimeout);
missTxt.hideTimeout = null;
}
missTxt.hideTimeout = LK.setTimeout(function () {
missTxt.visible = false;
missTxt.hideTimeout = null;
}, 1200);
LK.setTimeout(function () {
for (var bi = 0; bi < balls.length; bi++) {
balls[bi].speed /= 0.4;
balls[bi].vx /= 0.4;
}
for (var bj = 0; bj < bombs.length; bj++) {
bombs[bj].speed /= 0.4;
}
for (var bp = 0; bp < powerups.length; bp++) {
powerups[bp].speed /= 0.4;
}
}, 3000);
tween(powerup, {
y: basket.y,
alpha: 0
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function (puRef) {
return function () {
if (puRef && puRef.destroy) puRef.destroy();
};
}(powerup)
});
powerupsToRemove.push(k);
continue;
} else if (powerup.type === "banana" || powerup instanceof Banana) {
var originalScale = basket.scaleX || 1;
basket.scaleX = originalScale * 1.5;
basket.scaleY = (basket.scaleY || 1) * 1.1;
LK.effects.flashObject(basket, 0xfff700, 300);
missTxt.setText('Wide Basket!');
missTxt.visible = true;
if (missTxt.hideTimeout) {
LK.clearTimeout(missTxt.hideTimeout);
missTxt.hideTimeout = null;
}
missTxt.hideTimeout = LK.setTimeout(function () {
missTxt.visible = false;
missTxt.hideTimeout = null;
}, 1200);
LK.setTimeout(function () {
basket.scaleX = originalScale;
basket.scaleY = 1;
}, 4000);
tween(powerup, {
y: basket.y,
alpha: 0
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function (puRef) {
return function () {
if (puRef && puRef.destroy) puRef.destroy();
};
}(powerup)
});
powerupsToRemove.push(k);
continue;
} else if (powerup.type === "apple" || powerup instanceof Apple) {
LK.setScore(LK.getScore() + 5);
scoreTxt.setText(LK.getScore());
LK.getSound('catch').play();
LK.effects.flashScreen(0xff2222, 350);
LK.effects.flashObject(basket, 0xff2222, 300);
missTxt.setText('Apple! +5');
missTxt.visible = true;
if (missTxt.hideTimeout) {
LK.clearTimeout(missTxt.hideTimeout);
missTxt.hideTimeout = null;
}
missTxt.hideTimeout = LK.setTimeout(function () {
missTxt.visible = false;
missTxt.hideTimeout = null;
}, 1200);
tween(powerup, {
y: basket.y,
alpha: 0
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function (puRef) {
return function () {
if (puRef && puRef.destroy) puRef.destroy();
};
}(powerup)
});
powerupsToRemove.push(k);
continue;
} else {
LK.setScore(LK.getScore() + 2);
scoreTxt.setText(LK.getScore());
LK.getSound('catch').play();
LK.effects.flashScreen(0xFFD700, 350);
LK.effects.flashObject(basket, 0xFFD700, 300);
tween(powerup, {
y: basket.y,
alpha: 0
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function (puRef) {
return function () {
if (puRef && puRef.destroy) puRef.destroy();
};
}(powerup)
});
powerupsToRemove.push(k);
continue;
}
}
// Off screen (cleanup)
if (powerup.y > 2800) {
if (powerup && powerup.destroy) powerup.destroy();
powerupsToRemove.push(k);
continue;
}
powerup.lastY = powerup.y;
powerup.lastIntersecting = intersecting;
}
// Remove powerups after loop
// Remove from highest index to lowest to avoid shifting issues
powerupsToRemove.sort(function (a, b) {
return b - a;
});
for (var putr = 0; putr < powerupsToRemove.length; putr++) {
powerups.splice(powerupsToRemove[putr], 1);
}
// Hide miss text after a short time
if (missTxt.visible) {
if (!missTxt.hideTimeout) {
missTxt.hideTimeout = LK.setTimeout(function () {
missTxt.visible = false;
missTxt.hideTimeout = null;
}, 900);
}
}
// Draw shield icon above basket if shield is active
if (basket.hasShield) {
if (!basket.shieldIcon) {
basket.shieldIcon = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
basket.shieldIcon.tint = 0x00ffff;
basket.shieldIcon.scaleX = 0.5;
basket.shieldIcon.scaleY = 0.5;
game.addChild(basket.shieldIcon);
}
basket.shieldIcon.x = basket.x;
basket.shieldIcon.y = basket.y - 70;
basket.shieldIcon.visible = true;
} else if (basket.shieldIcon) {
basket.shieldIcon.visible = false;
}
// Rainbow basket after 40 points
if (LK.getScore() >= 40 && basket.children && basket.children.length > 0) {
var t = LK.ticks % 60 / 60;
var r = Math.floor(127 * Math.sin(2 * Math.PI * t) + 128);
var g = Math.floor(127 * Math.sin(2 * Math.PI * t + 2) + 128);
var b = Math.floor(127 * Math.sin(2 * Math.PI * t + 4) + 128);
basket.children[0].tint = r << 16 | g << 8 | b;
}
// Defensive: Always update lastX and lastY for basket to avoid undefined errors
if (basket) {
if (basket.lastX === undefined) basket.lastX = basket.x;
if (basket.lastY === undefined) basket.lastY = basket.y;
basket.lastX = basket.x;
basket.lastY = basket.y;
}
};
// Reset state on new game
LK.on('gameStart', function () {
// Remove all balls, bombs, and powerups
for (var i = 0; i < balls.length; i++) balls[i].destroy();
for (var j = 0; j < bombs.length; j++) bombs[j].destroy();
for (var k = 0; k < powerups.length; k++) powerups[k].destroy();
balls = [];
bombs = [];
powerups = [];
basket.x = 2048 / 2;
basket.y = 2732 - 180;
// Reset challenge mode state
basket.challengeActive = false;
basket.challengeDir = null;
if (basket.challengeTween) {
basket.challengeTween.stop();
basket.challengeTween = null;
}
// Reset shield state
basket.hasShield = false;
if (basket.shieldTween) {
basket.shieldTween.stop();
basket.shieldTween = null;
}
// Randomize basket color on new game
var basketColors = [0x3b6eea, 0x4ad991, 0xf75e5e, 0xf7a325, 0xFFD700];
if (basket.children && basket.children.length > 0) {
basket.children[0].tint = basketColors[Math.floor(Math.random() * basketColors.length)];
}
LK.setScore(0);
scoreTxt.setText('0');
missTxt.setText('');
missTxt.visible = false;
if (missTxt.hideTimeout) {
LK.clearTimeout(missTxt.hideTimeout);
missTxt.hideTimeout = null;
}
spawnTimer = 0;
gameOver = false;
game.missForgiven = false;
// Reset combo
comboCount = 0;
if (comboTimeout) {
LK.clearTimeout(comboTimeout);
comboTimeout = null;
}
comboTxt.visible = false;
// Reset health bar and hearts
health = maxHealth;
healthBar.scaleX = healthBarWidth / 100;
healthBar.tint = 0x00ff00;
for (var hi = 0; hi < healthHearts.length; hi++) {
healthHearts[hi].alpha = 1;
healthHearts[hi].tint = 0xff2222;
}
});