/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Fruit = Container.expand(function (type) {
var self = Container.call(this);
self.type = type;
self.velocityX = 0;
self.velocityY = 0;
self.angularVelocity = 0;
self.gravity = 1.2;
self.bounceX = 0.2;
self.bounceY = 0.1;
self.friction = 0.995;
self.rotationFriction = 0.85;
self.merged = false;
self.lastMergeCheck = false;
// Track if this fruit was created from fruit rain
self.isRainFruit = false;
// Track if this fruit has made contact with ground or another fruit
self.contactMade = false;
// Rainbow mutation - 5% chance for any fruit to be rainbow
self.isRainbow = Math.random() < 0.05;
// Gold mutation - 3% chance for any fruit to be gold
self.isGold = Math.random() < 0.03;
// Celestial mutation - 0.1% chance for any fruit to be celestial (super rare)
self.isCelestial = Math.random() < 0.001;
var fruitAssets = ['cherry', 'strawberry', 'grape', 'orange', 'apple', 'pear', 'peach', 'pineapple', 'melon', 'watermelon', 'lemon', 'coconut', 'dragon', 'kiwi', 'mango', 'papaya', 'durian', 'blueberry', 'pomegranate', 'avocado', 'cranberry'];
var fruitSizes = [120, 160, 200, 240, 280, 320, 360, 400, 440, 480, 520, 560, 600, 640, 580, 620, 660, 380, 700, 720, 340];
var fruitScores = [1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 66, 78, 91, 105, 120, 136, 153, 171, 190, 210, 231];
self.assetName = fruitAssets[type];
self.size = fruitSizes[type];
self.scoreValue = fruitScores[type];
var fruitGraphics = self.attachAsset(self.assetName, {
anchorX: 0.5,
anchorY: 0.5
});
// Apply rainbow effect if this fruit is rainbow
if (self.isRainbow) {
self.rainbowTintIndex = 0;
self.rainbowColors = [0xff0000, 0xff8000, 0xffff00, 0x00ff00, 0x0080ff, 0x8000ff];
fruitGraphics.tint = self.rainbowColors[0];
}
// Apply gold effect if this fruit is gold
if (self.isGold) {
fruitGraphics.tint = 0xffd700; // Gold color
self.shines = [];
// Create 3 shine effects around the fruit
for (var s = 0; s < 3; s++) {
var shine = self.attachAsset('shine', {
anchorX: 0.5,
anchorY: 0.5
});
shine.alpha = 0;
self.shines.push(shine);
}
}
// Apply celestial effect if this fruit is celestial
if (self.isCelestial) {
fruitGraphics.tint = 0x8a2be2; // Purple color
// Create purple ring around the fruit using celestial ring asset
self.celestialRing = self.attachAsset('celestialRing', {
anchorX: 0.5,
anchorY: 0.5
});
self.celestialRing.width = self.size + 80;
self.celestialRing.height = self.size + 80;
self.celestialRing.alpha = 0.6;
self.addChildAt(self.celestialRing, 0); // Add behind the fruit
}
self.getRadius = function () {
return self.size / 2;
};
self.update = function () {
if (typeof fruitsFrozen !== "undefined" && fruitsFrozen) {
return;
}
if (self.merged) {
return;
}
// Apply gravity
self.velocityY += self.gravity;
// Apply velocity
self.x += self.velocityX;
self.y += self.velocityY;
// Calculate current speed
var currentSpeed = Math.sqrt(self.velocityX * self.velocityX + self.velocityY * self.velocityY);
var speedThreshold = 3.0; // Only rotate when moving fast enough
// Always apply rotation
self.rotation += self.angularVelocity;
// Apply friction
self.velocityX *= self.friction;
self.velocityY *= self.friction;
self.angularVelocity *= self.rotationFriction;
// Additional stability check - if fruit is moving very slowly, gradually bring it to rest
var totalSpeed = Math.sqrt(self.velocityX * self.velocityX + self.velocityY * self.velocityY);
if (totalSpeed < 1.0 && totalSpeed > 0) {
var stabilityDamping = 0.8;
self.velocityX *= stabilityDamping;
self.velocityY *= stabilityDamping;
}
// Check if fruit is in cramped conditions
var nearbyFruits = 0;
if (typeof fruits !== "undefined") {
for (var k = 0; k < fruits.length; k++) {
if (fruits[k] !== self) {
var dist = Math.sqrt((self.x - fruits[k].x) * (self.x - fruits[k].x) + (self.y - fruits[k].y) * (self.y - fruits[k].y));
if (dist < self.getRadius() + fruits[k].getRadius() + 30) {
nearbyFruits++;
}
}
}
}
// Apply stronger rotational damping in cramped conditions
if (nearbyFruits >= 3) {
self.angularVelocity *= 0.6; // Much stronger damping when cramped
}
// Cap maximum rotation speed - lower limit for cramped conditions
var maxAngularVelocity = nearbyFruits >= 3 ? 0.05 : 0.1; // Reduced max rotation
if (self.angularVelocity > maxAngularVelocity) {
self.angularVelocity = maxAngularVelocity;
} else if (self.angularVelocity < -maxAngularVelocity) {
self.angularVelocity = -maxAngularVelocity;
}
// Rainbow color cycling for rainbow fruits
if (self.isRainbow && self.rainbowColors && LK.ticks % 30 === 0) {
self.rainbowTintIndex = (self.rainbowTintIndex + 1) % self.rainbowColors.length;
var fruitGraphics = self.children[0];
if (fruitGraphics) {
tween(fruitGraphics, {
tint: self.rainbowColors[self.rainbowTintIndex]
}, {
duration: 500,
easing: tween.easeInOut
});
}
}
// Gold shine animation for gold fruits - always moving and spinning like flies
if (self.isGold && self.shines) {
for (var s = 0; s < self.shines.length; s++) {
var shine = self.shines[s];
// Initialize shine position if not set
if (shine.shineInitialized === undefined) {
shine.shineInitialized = true;
shine.alpha = 0.7;
shine.scaleX = 2.5;
shine.scaleY = 2.5;
var angle = s * 120 * Math.PI / 180;
var radius = self.getRadius() + 20;
shine.x = Math.cos(angle) * radius;
shine.y = Math.sin(angle) * radius;
shine.shineAngle = angle;
shine.shineSpeed = 0.02 + Math.random() * 0.02; // Random speed for each shine
shine.shineRadius = radius + Math.random() * 20;
shine.rotationSpeed = (Math.random() - 0.5) * 0.2; // Random rotation speed
}
// Update shine position in circular/erratic fly-like motion
shine.shineAngle += shine.shineSpeed;
var targetRadius = self.getRadius() + 15 + Math.sin(shine.shineAngle * 3) * 10; // Varying radius
var targetX = Math.cos(shine.shineAngle) * targetRadius + (Math.random() - 0.5) * 5;
var targetY = Math.sin(shine.shineAngle) * targetRadius + (Math.random() - 0.5) * 5;
// Smooth movement towards target position
shine.x += (targetX - shine.x) * 0.1;
shine.y += (targetY - shine.y) * 0.1;
// Continuous spinning
shine.rotation += shine.rotationSpeed;
// Slight alpha variation for flickering effect
shine.alpha = 0.5 + Math.sin(LK.ticks * 0.1 + s) * 0.3;
}
}
// Celestial ring animation for celestial fruits
if (self.isCelestial && self.celestialRing) {
// Slowly rotate the ring
self.celestialRing.rotation += 0.02;
// Pulsing alpha effect
self.celestialRing.alpha = 0.4 + Math.sin(LK.ticks * 0.05) * 0.2;
// Gentle size pulsing
var pulseScale = 1.0 + Math.sin(LK.ticks * 0.03) * 0.05;
self.celestialRing.scaleX = pulseScale;
self.celestialRing.scaleY = pulseScale;
}
// Prevent very small velocities that can cause sticking - increased threshold
if (Math.abs(self.velocityX) < 0.5) {
self.velocityX = 0;
}
if (Math.abs(self.velocityY) < 0.5) {
self.velocityY = 0;
}
// Higher threshold for stopping rotation in cramped conditions
var rotationStopThreshold = nearbyFruits >= 3 ? 0.02 : 0.005;
if (Math.abs(self.angularVelocity) < rotationStopThreshold) {
self.angularVelocity = 0;
// Bounce rotation back to neutral position when angular velocity stops
if (Math.abs(self.rotation) > 0.1) {
tween(self, {
rotation: 0
}, {
duration: nearbyFruits >= 3 ? 400 : 600,
// Faster correction when cramped
easing: tween.bounceOut
});
}
}
var radius = self.getRadius();
var containerLeft = containerX - containerWidth / 2;
var containerRight = containerX + containerWidth / 2;
var containerBottom = containerY + containerHeight / 2;
// Collision with container walls
if (self.x - radius < containerLeft) {
self.x = containerLeft + radius;
self.velocityX = -self.velocityX * self.bounceX;
self.contactMade = true; // Mark contact made when hitting wall
// Add rotational effect from wall collision only if movement is significant
if (Math.abs(self.velocityY) > 2.0) {
self.angularVelocity += self.velocityY * 0.01;
}
}
if (self.x + radius > containerRight) {
self.x = containerRight - radius;
self.velocityX = -self.velocityX * self.bounceX;
self.contactMade = true; // Mark contact made when hitting wall
// Add rotational effect from wall collision only if movement is significant
if (Math.abs(self.velocityY) > 2.0) {
self.angularVelocity -= self.velocityY * 0.01;
}
}
// Collision with container bottom
if (self.y + radius > containerBottom) {
self.y = containerBottom - radius;
self.velocityY = -self.velocityY * self.bounceY;
self.contactMade = true; // Mark contact made when hitting ground
// Add rotational effect from ground collision only if movement is significant
if (Math.abs(self.velocityX) > 2.0) {
self.angularVelocity += self.velocityX * 0.02;
}
if (Math.abs(self.velocityY) < 1) {
self.velocityY = 0;
}
// After ground collision, check speed threshold
var currentSpeed = Math.sqrt(self.velocityX * self.velocityX + self.velocityY * self.velocityY);
var speedThreshold = 3.0;
if (currentSpeed < speedThreshold) {
// Stop movement if below speed threshold after hitting ground
self.velocityX = 0;
self.velocityY = 0;
self.angularVelocity = 0;
// Bounce rotation back to neutral position
if (Math.abs(self.rotation) > 0.1) {
tween(self, {
rotation: 0
}, {
duration: 800,
easing: tween.elasticOut
});
}
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
// No backgroundColor, handled by asset
});
/****
* Game Code
****/
// Blue background asset (full screen)
// Attach blue background asset, ensure it's at the back
var blueBg = game.attachAsset('blueBg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
game.addChildAt(blueBg, 0); // Add as the bottom-most child
// Music selection system (declare before any usage)
var musicTracks = ['music1', 'music2', 'music3', 'music4', 'music5'];
var musicNames = ['Classic Melody', 'Upbeat Bounce', 'Chill Vibes', 'Epic Adventure', 'Peaceful Garden'];
var currentMusicIndex = storage.selectedMusic || 0;
var musicStarted = true;
var musicMenuVisible = false;
// Game state
var gameState = 'playing';
var fruits = [];
var currentFruit = null;
var nextFruitType = 0;
var gameRunning = true;
var dropLine = 400;
var gameOverLine = 300;
var isDragging = false;
var previewFruit = null;
// Freeze fruits when evolution menu is open
var fruitsFrozen = false;
// Track which fruits have been discovered by the player
var discoveredFruits = storage.discoveredFruits || [];
// Initialize array if not in storage
if (discoveredFruits.length === 0) {
for (var i = 0; i < 21; i++) {
discoveredFruits[i] = false;
}
}
// Container dimensions and position
var containerWidth = 1900;
var containerHeight = 2200;
var containerX = 2048 / 2;
var containerY = 1366;
// Create container
var container = game.attachAsset('container', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX,
y: containerY
});
// Score display
var scoreTxt = new Text2('Score: 0', {
size: 80,
fill: 0x000000
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Merge counter (load from storage or default to 0)
var totalMerges = storage.totalMerges || 0;
var mergeCounterTxt = new Text2('Merges: ' + totalMerges, {
size: 60,
fill: 0x000000
});
mergeCounterTxt.anchor.set(0.5, 0.5);
mergeCounterTxt.x = 2048 / 2;
mergeCounterTxt.y = 2732 / 2;
game.addChild(mergeCounterTxt);
// Load discovered fruits from storage, but ensure first 5 are always available
for (var i = 0; i < discoveredFruits.length; i++) {
if (i < 5) {
discoveredFruits[i] = true; // First 5 are always available to drop
} else if (storage.discoveredFruits && storage.discoveredFruits[i]) {
discoveredFruits[i] = true; // Keep unlocked fruits from storage
}
}
// Save to storage
storage.discoveredFruits = discoveredFruits;
// Next fruit preview
var nextFruitPreview = null;
var nextFruitLabel = new Text2('Next:', {
size: 60,
fill: 0x000000
});
nextFruitLabel.anchor.set(1, 0.5);
nextFruitLabel.x = 1580;
nextFruitLabel.y = 120;
game.addChild(nextFruitLabel);
// Drop line indicator
var dropLineIndicator = LK.getAsset('container', {
width: containerWidth,
height: 4,
anchorX: 0.5,
anchorY: 0.5
});
dropLineIndicator.x = containerX;
dropLineIndicator.y = dropLine;
dropLineIndicator.tint = 0x0000FF;
game.addChild(dropLineIndicator);
// Helper functions
function getRandomFruitType() {
return Math.floor(Math.random() * 5); // Only first 5 fruit types can be dropped
}
function createNextFruit() {
nextFruitType = getRandomFruitType();
if (nextFruitPreview) {
// Stop any existing tweens before destroying
tween.stop(nextFruitPreview);
nextFruitPreview.destroy();
}
var fruitAssets = ['cherry', 'strawberry', 'grape', 'orange', 'apple'];
var fruitSizes = [120, 160, 200, 240, 280];
nextFruitPreview = LK.getAsset(fruitAssets[nextFruitType], {
anchorX: 0.5,
anchorY: 0.5
});
nextFruitPreview.x = 1700;
nextFruitPreview.y = 120;
game.addChild(nextFruitPreview);
// Position the label based on fruit size to ensure it's always visible
var fruitRadius = fruitSizes[nextFruitType] / 2;
nextFruitLabel.x = nextFruitPreview.x - fruitRadius - 20; // 20px padding from fruit edge
// Add bobbing animation
var originalY = nextFruitPreview.y;
function bobUp() {
tween(nextFruitPreview, {
y: originalY - 15
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: bobDown
});
}
function bobDown() {
tween(nextFruitPreview, {
y: originalY + 15
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: bobUp
});
}
bobUp();
}
function dropFruit(x) {
if (!gameRunning || currentFruit) {
return;
}
currentFruit = new Fruit(nextFruitType);
currentFruit.x = Math.max(containerX - containerWidth / 2 + currentFruit.getRadius(), Math.min(x, containerX + containerWidth / 2 - currentFruit.getRadius()));
currentFruit.y = dropLine + currentFruit.getRadius() + 60; // Spawn lower below the line with more padding
// Mark this fruit as discovered
var newlyDiscovered = !discoveredFruits[nextFruitType];
discoveredFruits[nextFruitType] = true;
// Save to storage
storage.discoveredFruits = discoveredFruits;
if (newlyDiscovered) {
// Show notification for unlocking new fruit
var evoFruitNames = ['Cherry', 'Strawberry', 'Grape', 'Orange', 'Apple', 'Pear', 'Peach', 'Pineapple', 'Melon', 'Watermelon', 'Lemon', 'Coconut', 'Dragonfruit', 'Kiwi', 'Mango', 'Papaya', 'Durian', 'Blueberry', 'Pomegranate', 'Avocado', 'Cranberry'];
var unlockedName = evoFruitNames[nextFruitType];
var notification = new Text2("You've unlocked " + unlockedName + "!", {
size: 90,
fill: 0x222222
});
notification.anchor.set(0.5, 0.5);
notification.x = 2048 / 2;
notification.y = 900;
notification.alpha = 0.0;
game.addChild(notification);
// Fade in, hold, then fade out
tween(notification, {
alpha: 1
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
LK.setTimeout(function () {
tween(notification, {
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
notification.destroy();
}
});
}, 1200);
}
});
}
// Add small random initial rotation
currentFruit.angularVelocity = (Math.random() - 0.5) * 0.05;
fruits.push(currentFruit);
game.addChild(currentFruit);
// Music and drop sound now start on tap/hold instead of drop
// Reset current fruit after a delay
LK.setTimeout(function () {
currentFruit = null;
}, 100);
createNextFruit();
}
function createPreviewFruit(x) {
if (previewFruit) {
previewFruit.destroy();
}
var fruitAssets = ['cherry', 'strawberry', 'grape', 'orange', 'apple', 'pear', 'peach', 'pineapple', 'melon', 'watermelon', 'lemon', 'coconut', 'dragon', 'kiwi', 'mango', 'papaya', 'durian', 'blueberry', 'pomegranate', 'avocado', 'cranberry'];
previewFruit = LK.getAsset(fruitAssets[nextFruitType], {
anchorX: 0.5,
anchorY: 0.5
});
previewFruit.alpha = 0.7; // Make it semi-transparent
previewFruit.x = x;
previewFruit.y = dropLine;
game.addChild(previewFruit);
}
function updatePreviewFruit(x) {
if (!previewFruit) {
return;
}
var fruitSizes = [120, 160, 200, 240, 280, 320, 360, 400, 440, 480, 520, 560, 600, 640, 580, 620, 660, 380, 700, 720, 340];
var radius = fruitSizes[nextFruitType] / 2;
previewFruit.x = Math.max(containerX - containerWidth / 2 + radius, Math.min(x, containerX + containerWidth / 2 - radius));
}
function destroyPreviewFruit() {
if (previewFruit) {
previewFruit.destroy();
previewFruit = null;
}
}
function checkCollision(fruit1, fruit2) {
var dx = fruit1.x - fruit2.x;
var dy = fruit1.y - fruit2.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var minDistance = fruit1.getRadius() + fruit2.getRadius();
return distance < minDistance;
}
function resolveFruitCollision(fruit1, fruit2) {
var dx = fruit1.x - fruit2.x;
var dy = fruit1.y - fruit2.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var minDistance = fruit1.getRadius() + fruit2.getRadius();
if (distance < minDistance && distance > 0) {
var overlap = minDistance - distance;
// Reduced separation force to prevent oscillation
var separationForce = 0.6;
var separationX = dx / distance * overlap * separationForce;
var separationY = dy / distance * overlap * separationForce;
fruit1.x += separationX;
fruit1.y += separationY;
fruit2.x -= separationX;
fruit2.y -= separationY;
// Apply strong damping when fruits are very close to prevent vibration
var dampingFactor = Math.max(0.1, Math.min(1.0, distance / minDistance));
fruit1.velocityX *= dampingFactor;
fruit1.velocityY *= dampingFactor;
fruit2.velocityX *= dampingFactor;
fruit2.velocityY *= dampingFactor;
// Realistic elastic collision response
var relativeVelX = fruit1.velocityX - fruit2.velocityX;
var relativeVelY = fruit1.velocityY - fruit2.velocityY;
var normalVelX = dx / distance;
var normalVelY = dy / distance;
var separatingVel = relativeVelX * normalVelX + relativeVelY * normalVelY;
// Only resolve if objects are moving toward each other with sufficient velocity
if (separatingVel < -0.5) {
var restitution = 0.05; // Reduced bounciness factor
var impulse = -(1 + restitution) * separatingVel * 0.5; // Reduced impulse strength
var impulseX = impulse * normalVelX;
var impulseY = impulse * normalVelY;
fruit1.velocityX += impulseX;
fruit1.velocityY += impulseY;
fruit2.velocityX -= impulseX;
fruit2.velocityY -= impulseY;
// Add rotational effects from collision only if movement is significant and not in cramped conditions
var movementThreshold = 5.0; // Increased threshold to prevent micro-rotation
var totalImpulse = Math.abs(impulseX) + Math.abs(impulseY);
// Check if fruits are in cramped conditions by counting nearby fruits
var nearbyCount1 = 0;
var nearbyCount2 = 0;
for (var k = 0; k < fruits.length; k++) {
if (fruits[k] !== fruit1 && fruits[k] !== fruit2) {
var dist1 = Math.sqrt((fruit1.x - fruits[k].x) * (fruit1.x - fruits[k].x) + (fruit1.y - fruits[k].y) * (fruit1.y - fruits[k].y));
var dist2 = Math.sqrt((fruit2.x - fruits[k].x) * (fruit2.x - fruits[k].x) + (fruit2.y - fruits[k].y) * (fruit2.y - fruits[k].y));
if (dist1 < fruit1.getRadius() + fruits[k].getRadius() + 50) nearbyCount1++;
if (dist2 < fruit2.getRadius() + fruits[k].getRadius() + 50) nearbyCount2++;
}
}
// Only apply rotation if not cramped and movement is significant
if (totalImpulse > movementThreshold && nearbyCount1 < 3 && nearbyCount2 < 3) {
var rotationFactor = 0.008; // Reduced rotation factor
fruit1.angularVelocity += (impulseX + impulseY) * rotationFactor;
fruit2.angularVelocity -= (impulseX + impulseY) * rotationFactor;
}
}
}
}
function mergeFruits(fruit1, fruit2) {
if (fruit1.type !== fruit2.type || fruit1.merged || fruit2.merged) {
return false;
}
if (fruit1.type >= 20) {
return false;
} // Can't merge cranberry (highest fruit)
// Mark the new fruit type as discovered
var newlyDiscovered = !discoveredFruits[fruit1.type + 1];
discoveredFruits[fruit1.type + 1] = true;
// Save to storage
storage.discoveredFruits = discoveredFruits;
if (newlyDiscovered) {
// Show notification for unlocking new fruit
var evoFruitNames = ['Cherry', 'Strawberry', 'Grape', 'Orange', 'Apple', 'Pear', 'Peach', 'Pineapple', 'Melon', 'Watermelon', 'Lemon', 'Coconut', 'Dragonfruit', 'Kiwi', 'Mango', 'Papaya', 'Durian', 'Blueberry', 'Pomegranate', 'Avocado', 'Cranberry'];
var unlockedName = evoFruitNames[fruit1.type + 1];
var notification = new Text2("You've unlocked " + unlockedName + "!", {
size: 90,
fill: 0x222222
});
notification.anchor.set(0.5, 0.5);
notification.x = 2048 / 2;
notification.y = 900;
notification.alpha = 0.0;
game.addChild(notification);
// Fade in, hold, then fade out
tween(notification, {
alpha: 1
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
LK.setTimeout(function () {
tween(notification, {
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
notification.destroy();
}
});
}, 1200);
}
});
}
// Create new fruit
var newFruit = new Fruit(fruit1.type + 1);
newFruit.x = (fruit1.x + fruit2.x) / 2;
newFruit.y = (fruit1.y + fruit2.y) / 2;
// Inherit contactMade status from parent fruits
newFruit.contactMade = fruit1.contactMade || fruit2.contactMade;
// Only give score if neither fruit is from rain
if (!fruit1.isRainFruit && !fruit2.isRainFruit) {
// Calculate score with rainbow and gold multipliers
var scoreToAdd = newFruit.scoreValue;
var multiplier = 1;
var notificationText = '';
var notificationColor = 0x00ff00;
if (fruit1.isRainbow || fruit2.isRainbow) {
multiplier *= 5;
notificationText = 'RAINBOW x5!';
notificationColor = 0xff00ff;
}
if (fruit1.isGold || fruit2.isGold) {
multiplier *= 3;
if (notificationText) {
notificationText = 'RAINBOW + GOLD x15!';
notificationColor = 0xffd700;
} else {
notificationText = 'GOLD x3!';
notificationColor = 0xffd700;
}
}
if (fruit1.isCelestial || fruit2.isCelestial) {
multiplier *= 15;
if (notificationText) {
if (notificationText.includes('RAINBOW') && notificationText.includes('GOLD')) {
notificationText = 'RAINBOW + GOLD + CELESTIAL x225!';
notificationColor = 0x8a2be2;
} else if (notificationText.includes('RAINBOW')) {
notificationText = 'RAINBOW + CELESTIAL x75!';
notificationColor = 0x8a2be2;
} else if (notificationText.includes('GOLD')) {
notificationText = 'GOLD + CELESTIAL x45!';
notificationColor = 0x8a2be2;
} else {
notificationText = 'CELESTIAL x15!';
notificationColor = 0x8a2be2;
}
} else {
notificationText = 'CELESTIAL x15!';
notificationColor = 0x8a2be2;
}
}
scoreToAdd *= multiplier;
// Show multiplier notification if applicable
if (notificationText) {
var multiplierScoreText = new Text2(notificationText, {
size: 80,
fill: notificationColor
});
multiplierScoreText.anchor.set(0.5, 0.5);
multiplierScoreText.x = newFruit.x;
multiplierScoreText.y = newFruit.y - 100;
multiplierScoreText.alpha = 0;
game.addChild(multiplierScoreText);
// Animate multiplier score text
tween(multiplierScoreText, {
alpha: 1,
y: multiplierScoreText.y - 50
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(multiplierScoreText, {
alpha: 0,
y: multiplierScoreText.y - 50
}, {
duration: 500,
easing: tween.easeIn,
onFinish: function onFinish() {
multiplierScoreText.destroy();
}
});
}
});
}
// Add score
LK.setScore(LK.getScore() + scoreToAdd);
scoreTxt.setText('Score: ' + LK.getScore());
}
// Update merge counter
totalMerges++;
mergeCounterTxt.setText('Merges: ' + totalMerges);
storage.totalMerges = totalMerges;
// Mark old fruits as merged
fruit1.merged = true;
fruit2.merged = true;
// Remove old fruits
for (var i = fruits.length - 1; i >= 0; i--) {
if (fruits[i] === fruit1 || fruits[i] === fruit2) {
fruits[i].destroy();
fruits.splice(i, 1);
}
}
// Add new fruit
fruits.push(newFruit);
game.addChild(newFruit);
LK.getSound('merge').play();
// Flash effect
tween(newFruit, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(newFruit, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200,
easing: tween.easeIn
});
}
});
return true;
}
function performAutoMerge() {
var foundMerge = true;
// Keep merging until no more merges are possible
while (foundMerge) {
foundMerge = false;
// Check all fruit pairs for possible merges
for (var i = 0; i < fruits.length && !foundMerge; i++) {
for (var j = i + 1; j < fruits.length && !foundMerge; j++) {
var fruit1 = fruits[i];
var fruit2 = fruits[j];
// Skip if either fruit is already merged or different types
if (fruit1.merged || fruit2.merged || fruit1.type !== fruit2.type) {
continue;
}
// Skip if already at maximum fruit type (durian)
if (fruit1.type >= 16) {
continue;
}
// Auto merge merges ANY two fruits of the same type regardless of distance
// Perform the merge immediately
if (mergeFruits(fruit1, fruit2)) {
foundMerge = true;
break;
}
}
}
}
}
function checkGameOver() {
for (var i = 0; i < fruits.length; i++) {
var fruit = fruits[i];
// Only check game over for fruits that have made contact with ground or other fruits
if (fruit.contactMade && fruit.y - fruit.getRadius() < gameOverLine) {
return true;
}
}
return false;
}
// Fruit Mover button (leftmost)
var fruitMoverButton = new Text2('FRUIT MOVER!', {
size: 60,
fill: 0x000000
});
fruitMoverButton.anchor.set(0.5, 0.5);
// Place at far left
fruitMoverButton.x = 200;
fruitMoverButton.y = 2732 - 150;
game.addChild(fruitMoverButton);
// Cost text under fruit mover button
var fruitMoverCostText = new Text2('Cost: 125', {
size: 40,
fill: 0x000000
});
fruitMoverCostText.anchor.set(0.5, 0);
// Position just below the fruit mover button
fruitMoverCostText.x = fruitMoverButton.x;
fruitMoverCostText.y = fruitMoverButton.y + 60;
game.addChild(fruitMoverCostText);
// Randomize fruit button (second from left)
var randomizeButton = new Text2('RANDOMIZE!', {
size: 60,
fill: 0x000000
});
randomizeButton.anchor.set(0.5, 0.5);
// Place second from left
randomizeButton.x = 600;
randomizeButton.y = 2732 - 150;
game.addChild(randomizeButton);
// Cost text under randomize button
var randomizeCostText = new Text2('Cost: 300', {
size: 40,
fill: 0x000000
});
randomizeCostText.anchor.set(0.5, 0);
// Position just below the randomize button
randomizeCostText.x = randomizeButton.x;
randomizeCostText.y = randomizeButton.y + 60;
game.addChild(randomizeCostText);
// Shake button (center)
var shakeButton = new Text2('SHAKE!', {
size: 60,
fill: 0x000000
});
shakeButton.anchor.set(0.5, 0.5);
// Place at center
shakeButton.x = 2048 / 2;
shakeButton.y = 2732 - 150;
game.addChild(shakeButton);
// Cost text under shake button
var shakeCostText = new Text2('Cost: 450', {
size: 40,
fill: 0x000000
});
shakeCostText.anchor.set(0.5, 0);
// Position just below the shake button
shakeCostText.x = shakeButton.x;
shakeCostText.y = shakeButton.y + 60;
game.addChild(shakeCostText);
// Auto merge button (second from right)
var autoMergeButton = new Text2('AUTO MERGE!', {
size: 60,
fill: 0x000000
});
autoMergeButton.anchor.set(0.5, 0.5);
// Place second from right
autoMergeButton.x = 1448;
autoMergeButton.y = 2732 - 150;
game.addChild(autoMergeButton);
// Cost text under auto merge button
var autoMergeCostText = new Text2('Cost: 800', {
size: 40,
fill: 0x000000
});
autoMergeCostText.anchor.set(0.5, 0);
// Position just below the auto merge button
autoMergeCostText.x = autoMergeButton.x;
autoMergeCostText.y = autoMergeButton.y + 60;
game.addChild(autoMergeCostText);
// Fruit Rain button (rightmost)
var fruitRainButton = new Text2('FRUIT RAIN!', {
size: 60,
fill: 0x000000
});
fruitRainButton.anchor.set(0.5, 0.5);
// Place at far right
fruitRainButton.x = 1848;
fruitRainButton.y = 2732 - 150;
game.addChild(fruitRainButton);
// Cost text under fruit rain button
var fruitRainCostText = new Text2('Cost: 1000', {
size: 40,
fill: 0x000000
});
fruitRainCostText.anchor.set(0.5, 0);
// Position just below the fruit rain button
fruitRainCostText.x = fruitRainButton.x;
fruitRainCostText.y = fruitRainButton.y + 60;
game.addChild(fruitRainCostText);
// Add auto merge button click handler
autoMergeButton.down = function (x, y, obj) {
if (LK.getScore() >= 800 && fruits.length > 1) {
// Deduct score
LK.setScore(LK.getScore() - 800);
scoreTxt.setText('Score: ' + LK.getScore());
// Perform auto merge
performAutoMerge();
}
};
// Add randomize button click handler
randomizeButton.down = function (x, y, obj) {
if (LK.getScore() >= 300 && fruits.length > 1) {
// Deduct score
LK.setScore(LK.getScore() - 300);
scoreTxt.setText('Score: ' + LK.getScore());
// Store all current fruit positions
var positions = [];
for (var i = 0; i < fruits.length; i++) {
positions.push({
x: fruits[i].x,
y: fruits[i].y
});
}
// Shuffle the positions array
for (var i = positions.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = positions[i];
positions[i] = positions[j];
positions[j] = temp;
}
// Apply shuffled positions to fruits
for (var i = 0; i < fruits.length; i++) {
fruits[i].x = positions[i].x;
fruits[i].y = positions[i].y;
// Reset velocities to prevent physics issues
fruits[i].velocityX = 0;
fruits[i].velocityY = 0;
fruits[i].angularVelocity = 0;
}
}
};
// Add fruit rain button click handler
fruitRainButton.down = function (x, y, obj) {
if (LK.getScore() >= 1000) {
// Deduct score
LK.setScore(LK.getScore() - 1000);
scoreTxt.setText('Score: ' + LK.getScore());
// Create fruit rain effect for 3 seconds
var rainDuration = 3000;
var rainStartTime = LK.ticks;
fruitRainActive = true;
// Create fruits every 100ms (0.1 seconds) during the rain
var rainInterval = LK.setInterval(function () {
if (LK.ticks - rainStartTime >= rainDuration / (1000 / 60)) {
// 3 seconds have passed
LK.clearInterval(rainInterval);
fruitRainActive = false;
return;
}
// Create a random lesser fruit (types 0-4: cherry, strawberry, grape, orange, apple)
var randomType = Math.floor(Math.random() * 5);
var rainFruit = new Fruit(randomType);
// Mark this fruit as a rain fruit (won't give score)
rainFruit.isRainFruit = true;
// Random X position within container bounds
var fruitRadius = rainFruit.getRadius();
var containerLeft = containerX - containerWidth / 2;
var containerRight = containerX + containerWidth / 2;
var randomX = containerLeft + fruitRadius + Math.random() * (containerRight - containerLeft - 2 * fruitRadius);
// Start from above the container
var containerTop = containerY - containerHeight / 2;
rainFruit.x = randomX;
rainFruit.y = containerTop - 200 - Math.random() * 300; // Start higher with some randomness
// Add slight random horizontal velocity for more natural effect
rainFruit.velocityX = (Math.random() - 0.5) * 2;
rainFruit.velocityY = Math.random() * 2 + 1; // Small downward velocity
rainFruit.angularVelocity = (Math.random() - 0.5) * 0.1;
// Mark as discovered
discoveredFruits[randomType] = true;
storage.discoveredFruits = discoveredFruits;
fruits.push(rainFruit);
game.addChild(rainFruit);
}, 100); // Create fruit every 100ms
}
};
// Global variables for fruit mover mode
var fruitMoverActive = false;
var fruitMoverSelectedFruit = null;
// Global variable for fruit rain mode
var fruitRainActive = false;
// Tutorial system variables
var tutorialActive = true; // Always allow tutorial to be available
var tutorialStep = 0;
var tutorialOverlay = null;
var tutorialText = null;
var tutorialArrow = null;
var tutorialVisuals = null; // Container for visual demonstrations
var tutorialTimeouts = []; // Track all tutorial timeouts for cleanup
var tutorialSteps = [{
text: "Welcome to Fruit Drop! Tap and hold to aim your fruit, then release to drop it.",
target: null,
highlight: {
x: 2048 / 2,
y: dropLine,
width: containerWidth,
height: 200
},
visual: "dropDemo"
}, {
text: "When two fruits of the same type touch, they merge into a bigger fruit!",
target: null,
highlight: null,
visual: "mergeDemo"
}, {
text: "Your score is shown here. Merging gives you points!",
target: "score",
highlight: null,
visual: "scoreDemo"
}, {
text: "Check your next fruit here to plan your strategy.",
target: "nextFruit",
highlight: null,
visual: "nextFruitDemo"
}, {
text: "Don't let fruits pile up above this blue line or it's game over!",
target: null,
highlight: {
x: containerX,
y: gameOverLine,
width: containerWidth,
height: 10
},
visual: "gameOverDemo"
}, {
text: "Use power-ups at the bottom to help when you're stuck!",
target: "powerups",
highlight: {
x: 2048 / 2,
y: 2732 - 150,
width: 1800,
height: 150
},
visual: "powerupDemo"
}, {
text: "Tap EVOLUTION to see all fruits you can discover!",
target: "evolution",
highlight: null,
visual: "evolutionDemo"
}, {
text: "Tap SAVE to save your game progress in multiple slots!",
target: "save",
highlight: null,
visual: "saveDemo"
}, {
text: "Tap MUSIC to change the background music anytime!",
target: "music",
highlight: null,
visual: "musicDemo"
}, {
text: "Great! Now you're ready to play. Try dropping your first fruit!",
target: null,
highlight: {
x: 2048 / 2,
y: dropLine,
width: containerWidth,
height: 200
},
visual: "readyDemo"
}];
// Tutorial visual creation functions
function createTutorialVisuals(visualType) {
if (tutorialVisuals) {
// Clean up existing visuals
tween.stop(tutorialVisuals);
tutorialVisuals.destroy();
tutorialVisuals = null;
}
tutorialVisuals = new Container();
if (tutorialOverlay) {
tutorialOverlay.addChild(tutorialVisuals);
}
switch (visualType) {
case "dropDemo":
// Animate dropping motion
var _dropAnimation = function dropAnimation() {
tween(demoFruit, {
y: dropLine + 300
}, {
duration: 1000,
easing: tween.easeIn,
onFinish: function onFinish() {
// Reset position and repeat
demoFruit.y = dropLine - 100;
var dropTimeout = LK.setTimeout(function () {
if (tutorialVisuals && !tutorialVisuals.destroyed) {
_dropAnimation();
}
}, 500);
tutorialTimeouts.push(dropTimeout);
}
});
};
// Show animated fruit dropping
var demoFruit = LK.getAsset('cherry', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX,
y: dropLine - 100
});
tutorialVisuals.addChild(demoFruit);
_dropAnimation();
break;
case "mergeDemo":
var _mergeAnimation = function mergeAnimation() {
// Move cherries together
tween(cherry1, {
x: containerX - 20
}, {
duration: 800,
easing: tween.easeOut
});
tween(cherry2, {
x: containerX + 20
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
// Flash effect
tween(cherry1, {
tint: 0xffffff,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200
});
tween(cherry2, {
tint: 0xffffff,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
// Replace with strawberry
cherry1.destroy();
cherry2.destroy();
var strawberry = LK.getAsset('strawberry', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX,
y: containerY
});
if (tutorialVisuals) {
tutorialVisuals.addChild(strawberry);
}
// Celebration effect
tween(strawberry, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 300,
easing: tween.bounceOut,
onFinish: function onFinish() {
// Reset and repeat
var resetTimeout = LK.setTimeout(function () {
if (tutorialVisuals && !tutorialVisuals.destroyed) {
strawberry.destroy();
cherry1 = LK.getAsset('cherry', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX - 80,
y: containerY
});
cherry2 = LK.getAsset('cherry', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX + 80,
y: containerY
});
tutorialVisuals.addChild(cherry1);
tutorialVisuals.addChild(cherry2);
_mergeAnimation();
}
}, 1000);
tutorialTimeouts.push(resetTimeout);
}
});
}
});
}
});
};
// Show two cherries merging into strawberry
var cherry1 = LK.getAsset('cherry', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX - 80,
y: containerY
});
var cherry2 = LK.getAsset('cherry', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX + 80,
y: containerY
});
tutorialVisuals.addChild(cherry1);
tutorialVisuals.addChild(cherry2);
_mergeAnimation();
break;
case "scoreDemo":
var _scoreAnimation = function scoreAnimation() {
var targetScore = currentScore + 50;
var scoreInterval = LK.setInterval(function () {
currentScore += 2;
scoreDisplay.setText('Score: ' + currentScore);
if (currentScore >= targetScore) {
LK.clearInterval(scoreInterval);
var scoreResetTimeout = LK.setTimeout(function () {
if (tutorialVisuals && !tutorialVisuals.destroyed) {
currentScore = 0;
_scoreAnimation();
}
}, 1000);
tutorialTimeouts.push(scoreResetTimeout);
}
}, 50);
};
// Show animated score counting up
var scoreDisplay = new Text2('Score: 0', {
size: 60,
fill: 0x00ff00
});
scoreDisplay.anchor.set(0.5, 0.5);
scoreDisplay.x = containerX;
scoreDisplay.y = containerY - 200;
tutorialVisuals.addChild(scoreDisplay);
var currentScore = 0;
_scoreAnimation();
break;
case "nextFruitDemo":
var _pulseNext = function pulseNext() {
tween(nextDemo, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0.8
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(nextDemo, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: _pulseNext
});
}
});
};
// Show next fruit preview with pulsing effect
var nextDemo = LK.getAsset('strawberry', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX + 300,
y: containerY - 300
});
tutorialVisuals.addChild(nextDemo);
_pulseNext();
break;
case "gameOverDemo":
var _loop2 = function _loop2() {
pileFruit = LK.getAsset('orange', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX + (i - 1) * 80,
y: gameOverLine - 50 - i * 60
});
pileFruit.tint = 0xff4444; // Red tint to show danger
tutorialVisuals.addChild(pileFruit);
// Shake animation
function shakeAnimation(fruit) {
tween(fruit, {
x: fruit.x + 10
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(fruit, {
x: fruit.x - 10
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
shakeAnimation(fruit);
}
});
}
});
}
shakeAnimation(pileFruit);
},
pileFruit;
// Show fruits piling up above the line
for (var i = 0; i < 3; i++) {
_loop2();
}
break;
case "powerupDemo":
var _miniShake = function miniShake() {
for (var i = 0; i < shakeFruits.length; i++) {
tween(shakeFruits[i], {
x: shakeFruits[i].x + Math.random() * 40 - 20,
y: shakeFruits[i].y + Math.random() * 40 - 20
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (i === shakeFruits.length - 1) {
var shakeTimeout = LK.setTimeout(function () {
if (tutorialVisuals && !tutorialVisuals.destroyed) {
_miniShake();
}
}, 1500);
tutorialTimeouts.push(shakeTimeout);
}
}
});
}
};
// Show mini versions of power-up effects
var demoContainer = new Container();
demoContainer.x = containerX;
demoContainer.y = containerY;
tutorialVisuals.addChild(demoContainer);
// Mini shake effect
var shakeFruits = [];
for (var i = 0; i < 4; i++) {
var shakeFruit = LK.getAsset('grape', {
anchorX: 0.5,
anchorY: 0.5,
x: (i - 1.5) * 60,
y: 0,
width: 40,
height: 40
});
demoContainer.addChild(shakeFruit);
shakeFruits.push(shakeFruit);
}
_miniShake();
break;
case "evolutionDemo":
// Glow effect on fruits
var _glowEvolution = function glowEvolution() {
for (var i = 0; i < tutorialVisuals.children.length; i++) {
var child = tutorialVisuals.children[i];
if (child !== tutorialVisuals.children[0]) {
// Skip first child
tween(child, {
tint: 0xffff88
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(child, {
tint: 0xffffff
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
}
}
var glowTimeout = LK.setTimeout(function () {
if (tutorialVisuals && !tutorialVisuals.destroyed) {
_glowEvolution();
}
}, 1600);
tutorialTimeouts.push(glowTimeout);
};
// Show fruit evolution chain
var evolutionChain = ['cherry', 'strawberry', 'grape'];
for (var i = 0; i < evolutionChain.length; i++) {
var evoFruit = LK.getAsset(evolutionChain[i], {
anchorX: 0.5,
anchorY: 0.5,
x: containerX + (i - 1) * 120,
y: containerY - 100,
width: 80,
height: 80
});
tutorialVisuals.addChild(evoFruit);
if (i > 0) {
// Add arrow
var arrow = LK.getAsset('orangeCircle', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX + (i - 1.5) * 120,
y: containerY - 100,
width: 30,
height: 30
});
arrow.tint = 0xffaa00;
tutorialVisuals.addChild(arrow);
}
}
_glowEvolution();
break;
case "saveDemo":
var _saveAnimation = function saveAnimation() {
tween(saveIcon, {
scaleX: 1.2,
scaleY: 1.2,
tint: 0x4CAF50
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(saveIcon, {
scaleX: 1.0,
scaleY: 1.0,
tint: 0xffffff
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
var saveTimeout = LK.setTimeout(function () {
if (tutorialVisuals && !tutorialVisuals.destroyed) {
_saveAnimation();
}
}, 1000);
tutorialTimeouts.push(saveTimeout);
}
});
}
});
};
// Show save icon with slots
var saveIcon = LK.getAsset('container', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX,
y: containerY - 100,
width: 200,
height: 150
});
saveIcon.tint = 0xdddddd;
tutorialVisuals.addChild(saveIcon);
// Add slot indicators
for (var i = 0; i < 3; i++) {
var slot = LK.getAsset('nextFruit', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX + (i - 1) * 80,
y: containerY + 50,
width: 60,
height: 60
});
slot.tint = 0x4CAF50;
tutorialVisuals.addChild(slot);
}
_saveAnimation();
break;
case "musicDemo":
var _musicAnimation = function musicAnimation() {
for (var i = 0; i < musicNotes.length; i++) {
tween(musicNotes[i], {
y: musicNotes[i].y - 30,
alpha: 1
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(this, {
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
this.y = containerY + 100;
this.alpha = 0.8;
}
});
}
});
}
var musicTimeout = LK.setTimeout(function () {
if (tutorialVisuals && !tutorialVisuals.destroyed) {
_musicAnimation();
}
}, 1200);
tutorialTimeouts.push(musicTimeout);
};
// Show musical notes animation
var musicNotes = [];
for (var i = 0; i < 5; i++) {
var note = LK.getAsset('nextFruit', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX + (i - 2) * 60,
y: containerY + 100,
width: 30,
height: 30
});
note.tint = 0xff9800;
note.alpha = 0.8;
tutorialVisuals.addChild(note);
musicNotes.push(note);
}
_musicAnimation();
break;
case "readyDemo":
var _encourageAnimation = function encourageAnimation() {
tween(readyFruit, {
scaleX: 1.3,
scaleY: 1.3,
rotation: 0.2
}, {
duration: 400,
easing: tween.bounceOut,
onFinish: function onFinish() {
tween(readyFruit, {
scaleX: 1.0,
scaleY: 1.0,
rotation: -0.2
}, {
duration: 400,
easing: tween.bounceOut,
onFinish: function onFinish() {
tween(readyFruit, {
rotation: 0
}, {
duration: 200,
onFinish: function onFinish() {
var encourageTimeout = LK.setTimeout(function () {
if (tutorialVisuals && !tutorialVisuals.destroyed) {
_encourageAnimation();
}
}, 1000);
tutorialTimeouts.push(encourageTimeout);
}
});
}
});
}
});
};
// Show encouraging "thumbs up" style visual
var readyFruit = LK.getAsset('apple', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX,
y: containerY
});
tutorialVisuals.addChild(readyFruit);
_encourageAnimation();
break;
}
}
// Tutorial functions
function createTutorialOverlay() {
if (tutorialOverlay) {
tutorialOverlay.destroy();
}
tutorialOverlay = new Container();
game.addChild(tutorialOverlay);
// Semi-transparent background
var tutorialBg = LK.getAsset('container', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
tutorialBg.alpha = 0.7;
tutorialBg.tint = 0x000000;
tutorialOverlay.addChild(tutorialBg);
// Tutorial text background
var textBg = LK.getAsset('container', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 - 300,
width: 1800,
height: 250
});
textBg.alpha = 0.95;
textBg.tint = 0xffffff;
tutorialOverlay.addChild(textBg);
// Tutorial text
tutorialText = new Text2('', {
size: 50,
fill: 0x000000
});
tutorialText.anchor.set(0.5, 0.5);
tutorialText.x = 2048 / 2;
tutorialText.y = 2732 - 300;
tutorialOverlay.addChild(tutorialText);
// Next button
var nextButton = new Text2('NEXT', {
size: 60,
fill: 0xffffff
});
nextButton.anchor.set(0.5, 0.5);
nextButton.x = 2048 / 2 + 600;
nextButton.y = 2732 - 200;
var nextButtonBg = LK.getAsset('container', {
anchorX: 0.5,
anchorY: 0.5,
x: nextButton.x,
y: nextButton.y,
width: 200,
height: 80
});
nextButtonBg.tint = 0x4CAF50;
tutorialOverlay.addChild(nextButtonBg);
tutorialOverlay.addChild(nextButton);
nextButton.down = function () {
nextTutorialStep();
};
// Skip button
var skipButton = new Text2('SKIP', {
size: 60,
fill: 0xffffff
});
skipButton.anchor.set(0.5, 0.5);
skipButton.x = 2048 / 2 - 600;
skipButton.y = 2732 - 200;
var skipButtonBg = LK.getAsset('container', {
anchorX: 0.5,
anchorY: 0.5,
x: skipButton.x,
y: skipButton.y,
width: 200,
height: 80
});
skipButtonBg.tint = 0xf44336;
tutorialOverlay.addChild(skipButtonBg);
tutorialOverlay.addChild(skipButton);
skipButton.down = function () {
completeTutorial();
};
// Store references to buttons for repositioning
tutorialOverlay.nextButton = nextButton;
tutorialOverlay.nextButtonBg = nextButtonBg;
tutorialOverlay.skipButton = skipButton;
tutorialOverlay.skipButtonBg = skipButtonBg;
}
function showTutorialStep() {
if (!tutorialActive || tutorialStep >= tutorialSteps.length) {
completeTutorial();
return;
}
createTutorialOverlay();
var step = tutorialSteps[tutorialStep];
tutorialText.setText(step.text);
// Determine tutorial text position based on content and targets
var textBg = tutorialOverlay.children[1]; // Text background
var textX = 2048 / 2;
var textY = 2732 - 300;
// Move tutorial text based on what we're explaining
if (step.target === "score") {
textX = 2048 / 2;
textY = 400; // Move down when pointing to score at top
} else if (step.target === "nextFruit") {
textX = 1024; // Move left when pointing to next fruit on right
textY = 400;
} else if (step.target === "evolution") {
textX = 2048 / 2;
textY = 500; // Move down when pointing to evolution button
} else if (step.target === "powerups") {
textX = 2048 / 2;
textY = 2732 - 600; // Move up when pointing to powerups at bottom
} else if (step.highlight && step.highlight.y < 1000) {
textY = 2000; // Move to bottom if highlighting something at top
} else if (step.highlight && step.highlight.y > 2000) {
textY = 800; // Move to top if highlighting something at bottom
}
// Animate tutorial text and background to new position
tween(textBg, {
x: textX,
y: textY
}, {
duration: 500,
easing: tween.easeOut
});
tween(tutorialText, {
x: textX,
y: textY
}, {
duration: 500,
easing: tween.easeOut
});
// Move tutorial buttons to follow the text
var buttonY = textY + 100;
if (tutorialOverlay.nextButton) {
tween(tutorialOverlay.nextButton, {
x: textX + 300,
y: buttonY
}, {
duration: 500,
easing: tween.easeOut
});
tween(tutorialOverlay.nextButtonBg, {
x: textX + 300,
y: buttonY
}, {
duration: 500,
easing: tween.easeOut
});
}
if (tutorialOverlay.skipButton) {
tween(tutorialOverlay.skipButton, {
x: textX - 300,
y: buttonY
}, {
duration: 500,
easing: tween.easeOut
});
tween(tutorialOverlay.skipButtonBg, {
x: textX - 300,
y: buttonY
}, {
duration: 500,
easing: tween.easeOut
});
}
// Add visual demonstration if specified
if (step.visual) {
createTutorialVisuals(step.visual);
}
// Add highlight if specified
if (step.highlight) {
// Pulsing animation
var pulseUp = function pulseUp() {
tween(highlight, {
alpha: 0.3,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: pulseDown
});
};
var pulseDown = function pulseDown() {
tween(highlight, {
alpha: 0.1,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: pulseUp
});
};
var highlight = LK.getAsset('container', {
anchorX: 0.5,
anchorY: 0.5,
x: step.highlight.x,
y: step.highlight.y,
width: step.highlight.width,
height: step.highlight.height
});
highlight.alpha = 0;
highlight.tint = 0xffff00;
tutorialOverlay.addChild(highlight);
pulseUp();
}
// Create arrow pointing to target
if (step.target) {
tutorialArrow = LK.getAsset('orangeCircle', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 80
});
tutorialArrow.tint = 0xff9800;
var targetPos = getTutorialTargetPosition(step.target);
if (targetPos) {
var bounceUp = function bounceUp() {
tween(tutorialArrow, {
y: originalY - 20
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: bounceDown
});
};
var bounceDown = function bounceDown() {
tween(tutorialArrow, {
y: originalY
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: bounceUp
});
};
tutorialArrow.x = targetPos.x;
tutorialArrow.y = targetPos.y - 120;
tutorialOverlay.addChild(tutorialArrow);
// Bouncing animation
var originalY = tutorialArrow.y;
bounceUp();
// Animate arrow sliding in from the side based on target
var startX = tutorialArrow.x;
if (step.target === "nextFruit") {
tutorialArrow.x = 2048 + 100; // Start from right side
} else if (step.target === "score" || step.target === "evolution") {
tutorialArrow.x = -100; // Start from left side
} else {
tutorialArrow.x = 2048 / 2; // Start from center
}
tween(tutorialArrow, {
x: startX
}, {
duration: 600,
easing: tween.bounceOut
});
}
}
}
function getTutorialTargetPosition(target) {
switch (target) {
case "score":
return {
x: scoreTxt.x,
y: 150
};
case "nextFruit":
return {
x: 1700,
y: 120
};
case "evolution":
return {
x: evolutionButton.x,
y: 200
};
case "save":
return {
x: saveMenuButton.x,
y: 200
};
case "music":
return {
x: musicMenuButton.x,
y: 200
};
case "powerups":
return {
x: 2048 / 2,
y: 2732 - 150
};
default:
return null;
}
}
function nextTutorialStep() {
// Clear all tutorial timeouts first
for (var i = 0; i < tutorialTimeouts.length; i++) {
LK.clearTimeout(tutorialTimeouts[i]);
}
tutorialTimeouts = [];
// Clean up current tutorial visuals before moving to next step
if (tutorialVisuals) {
// Stop all tweens on tutorialVisuals and its children recursively
var _stopAllTweensRecursive = function stopAllTweensRecursive(container) {
if (!container) return;
tween.stop(container);
if (container.children && container.children.length > 0) {
for (var i = 0; i < container.children.length; i++) {
if (container.children[i]) {
_stopAllTweensRecursive(container.children[i]);
}
}
}
};
_stopAllTweensRecursive(tutorialVisuals);
// Clear any pending timeouts or intervals that might recreate objects
// Force immediate cleanup by removing from parent first
if (tutorialVisuals.parent) {
tutorialVisuals.parent.removeChild(tutorialVisuals);
}
// Always destroy tutorialVisuals regardless of parent state
tutorialVisuals.destroy();
tutorialVisuals = null;
}
tutorialStep++;
if (tutorialStep >= tutorialSteps.length) {
completeTutorial();
} else {
showTutorialStep();
}
}
function completeTutorial() {
tutorialActive = false;
storage.tutorialCompleted = true;
// Clear all tutorial timeouts
for (var i = 0; i < tutorialTimeouts.length; i++) {
LK.clearTimeout(tutorialTimeouts[i]);
}
tutorialTimeouts = [];
if (tutorialVisuals) {
tween.stop(tutorialVisuals);
tutorialVisuals.destroy();
tutorialVisuals = null;
}
if (tutorialOverlay) {
tutorialOverlay.destroy();
tutorialOverlay = null;
}
gameRunning = true;
fruitsFrozen = false;
}
// Tutorial will start after music selection instead of here
// Add fruit mover button click handler
fruitMoverButton.down = function (x, y, obj) {
if (LK.getScore() >= 125 && fruits.length > 0) {
// Deduct score
LK.setScore(LK.getScore() - 125);
scoreTxt.setText('Score: ' + LK.getScore());
// Activate fruit mover mode
fruitMoverActive = true;
fruitMoverSelectedFruit = null;
// Make all fruits green when fruit mover is used
for (var i = 0; i < fruits.length; i++) {
tween(fruits[i], {
tint: 0x00ff00
}, {
duration: 300,
easing: tween.easeOut
});
}
}
};
// Add shake button click handler
shakeButton.down = function (x, y, obj) {
if (LK.getScore() >= 450) {
// Deduct score
LK.setScore(LK.getScore() - 450);
scoreTxt.setText('Score: ' + LK.getScore());
// Move all fruits to random positions within container bounds
for (var i = 0; i < fruits.length; i++) {
var fruit = fruits[i];
var radius = fruit.getRadius();
var containerLeft = containerX - containerWidth / 2;
var containerRight = containerX + containerWidth / 2;
var containerTop = containerY - containerHeight / 2;
var containerBottom = containerY + containerHeight / 2;
// Calculate valid random position within container bounds
var randomX = containerLeft + radius + Math.random() * (containerRight - containerLeft - 2 * radius);
var randomY = containerTop + radius + Math.random() * (containerBottom - containerTop - 2 * radius);
// Animate fruit to new position with tween
tween(fruit, {
x: randomX,
y: randomY
}, {
duration: 500 + Math.random() * 500,
// Random duration between 500-1000ms
easing: tween.easeOut
});
// Reset velocities to prevent physics issues
fruit.velocityX = 0;
fruit.velocityY = 0;
fruit.angularVelocity = 0;
}
}
};
// Do not spawn a fruit at start; only spawn on click/hold
createNextFruit();
// --- Evolution Button and Menu ---
// Evolution button
var evolutionButton = new Text2('EVOLUTION', {
size: 60,
fill: 0x000000
});
evolutionButton.anchor.set(0.5, 0);
// Place directly under the score display, using LK.gui for vertical stacking
evolutionButton.y = scoreTxt.height + 20;
LK.gui.top.addChild(evolutionButton);
// Evolution menu container (hidden by default)
var evolutionMenu = new Container();
evolutionMenu.visible = false;
// Add evolution menu to the front (highest z-index)
game.addChild(evolutionMenu);
// Semi-transparent background for menu
var menuBg = LK.getAsset('container', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
width: 1400,
height: 1400
});
menuBg.alpha = 0.92;
menuBg.tint = 0xeeeeee;
evolutionMenu.addChild(menuBg);
// Title
var evolutionTitle = new Text2('Fruit Evolution', {
size: 90,
fill: 0x222222,
stroke: 0xffffff,
strokeThickness: 4
});
evolutionTitle.anchor.set(0.5, 0);
evolutionTitle.x = 2048 / 2;
evolutionTitle.y = 2732 / 2 - 650;
evolutionMenu.addChild(evolutionTitle);
// Fruit evolution data
var evoFruitAssets = ['cherry', 'strawberry', 'grape', 'orange', 'apple', 'pear', 'peach', 'pineapple', 'melon', 'watermelon', 'lemon', 'coconut', 'dragon', 'kiwi', 'mango', 'papaya', 'durian', 'blueberry', 'pomegranate', 'avocado', 'cranberry'];
var evoFruitNames = ['Cherry', 'Strawberry', 'Grape', 'Orange', 'Apple', 'Pear', 'Peach', 'Pineapple', 'Melon', 'Watermelon', 'Lemon', 'Coconut', 'Dragonfruit', 'Kiwi', 'Mango', 'Papaya', 'Durian', 'Blueberry', 'Pomegranate', 'Avocado', 'Cranberry'];
var evoFruitSizes = [120, 160, 200, 240, 280, 320, 360, 400, 440, 480, 520, 560, 600, 640, 580, 620, 660, 380, 700];
// Draw fruits in a circle
var centerX = 2048 / 2;
var centerY = 2732 / 2 + 80;
var radius = 500;
for (var i = 0; i < evoFruitAssets.length; i++) {
var angle = Math.PI * 2 / evoFruitAssets.length * i - Math.PI / 2;
var fruitAsset = LK.getAsset(evoFruitAssets[i], {
anchorX: 0.5,
anchorY: 0.5
});
// Make all fruits the same size as the strawberry (160x160)
var strawberrySize = 160;
var fruitRadius = strawberrySize / 2;
fruitAsset.width = strawberrySize;
fruitAsset.height = strawberrySize;
fruitAsset.x = centerX + Math.cos(angle) * radius;
fruitAsset.y = centerY + Math.sin(angle) * radius;
// Black out and hide text for undiscovered fruits
if (!discoveredFruits[i]) {
fruitAsset.tint = 0x222222;
fruitAsset.alpha = 0.7;
}
evolutionMenu.addChild(fruitAsset);
// Fruit name label
var nameLabel = new Text2(discoveredFruits[i] ? evoFruitNames[i] : '', {
size: 38,
fill: 0x333333,
stroke: 0xffffff,
strokeThickness: 3
});
nameLabel.anchor.set(0.5, 1);
nameLabel.x = fruitAsset.x;
nameLabel.y = fruitAsset.y - fruitRadius - 10;
evolutionMenu.addChild(nameLabel);
// Draw arrow to next fruit (except last)
if (i < evoFruitAssets.length - 1) {
var nextAngle = Math.PI * 2 / evoFruitAssets.length * (i + 1) - Math.PI / 2;
var arrowX1 = fruitAsset.x + Math.cos(angle) * (fruitRadius + 20);
var arrowY1 = fruitAsset.y + Math.sin(angle) * (fruitRadius + 20);
var arrowX2 = centerX + Math.cos(nextAngle) * (radius - 40);
var arrowY2 = centerY + Math.sin(nextAngle) * (radius - 40);
// Use a small fruit as an arrow "dot"
var arrowDot = LK.getAsset('nextFruit', {
anchorX: 0.5,
anchorY: 0.5,
x: (arrowX1 + arrowX2) / 2,
y: (arrowY1 + arrowY2) / 2,
width: 32,
height: 32
});
arrowDot.tint = 0xaaaaaa;
evolutionMenu.addChild(arrowDot);
}
}
// Show menu on button press
evolutionButton.down = function (x, y, obj) {
// If menu is open, close it
if (evolutionMenu.visible) {
evolutionMenu.visible = false;
gameRunning = true;
fruitsFrozen = false;
return;
}
// Otherwise, update and open the menu
var childIdx = 0;
for (var i = 0; i < evoFruitAssets.length; i++) {
// Fruit asset is first, label is second, then arrow dot (if not last)
var fruitAsset = evolutionMenu.children[childIdx + 2]; // skip bg and title
var nameLabel = evolutionMenu.children[childIdx + 3];
if (!discoveredFruits[i]) {
fruitAsset.tint = 0x222222;
fruitAsset.alpha = 0.7;
nameLabel.setText('');
} else {
fruitAsset.tint = 0xffffff;
fruitAsset.alpha = 1.0;
nameLabel.setText(evoFruitNames[i]);
// Text2 stroke properties are set directly, not through style object
nameLabel.stroke = 0xffffff;
nameLabel.strokeThickness = 3;
}
childIdx += 2;
if (i < evoFruitAssets.length - 1) childIdx++; // skip arrow dot
}
evolutionMenu.visible = true;
// Move evolution menu to front to ensure it's on top of all game elements
game.removeChild(evolutionMenu);
game.addChild(evolutionMenu);
// Optionally, darken background or disable gameplay while menu is open
gameRunning = false;
fruitsFrozen = true;
};
// Music menu button
var musicMenuButton = new Text2('MUSIC', {
size: 60,
fill: 0x000000
});
musicMenuButton.anchor.set(0.5, 0);
musicMenuButton.x = evolutionButton.x + 300;
musicMenuButton.y = evolutionButton.y;
LK.gui.top.addChild(musicMenuButton);
// Save menu button
var saveMenuButton = new Text2('SAVE', {
size: 60,
fill: 0x000000
});
saveMenuButton.anchor.set(0.5, 0);
saveMenuButton.x = evolutionButton.x - 300;
saveMenuButton.y = musicMenuButton.y;
LK.gui.top.addChild(saveMenuButton);
// Music menu container
var musicMenu = new Container();
musicMenu.visible = false;
game.addChild(musicMenu);
// Save menu container
var saveMenu = new Container();
saveMenu.visible = false;
game.addChild(saveMenu);
// Save menu background
var saveMenuBg = LK.getAsset('container', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
width: 1600,
height: 1000
});
saveMenuBg.alpha = 0.95;
saveMenuBg.tint = 0xdddddd;
saveMenu.addChild(saveMenuBg);
// Save menu title
var saveMenuTitle = new Text2('Save & Load Game', {
size: 80,
fill: 0x222222,
stroke: 0xffffff,
strokeThickness: 3
});
saveMenuTitle.anchor.set(0.5, 0);
saveMenuTitle.x = 2048 / 2;
saveMenuTitle.y = 2732 / 2 - 400;
saveMenu.addChild(saveMenuTitle);
// Save game data function
function saveGameData(slotNumber) {
// Save basic game data directly to storage to avoid nested objects
var slotPrefix = 'slot' + slotNumber + '_';
storage[slotPrefix + 'savedGameScore'] = LK.getScore();
storage[slotPrefix + 'savedGameTotalMerges'] = totalMerges;
storage[slotPrefix + 'savedGameDiscoveredFruits'] = discoveredFruits;
storage[slotPrefix + 'savedGameCurrentMusicIndex'] = currentMusicIndex;
storage[slotPrefix + 'savedGameTutorialCompleted'] = storage.tutorialCompleted || false;
storage[slotPrefix + 'savedGameTime'] = Date.now();
// Save fruits data as separate flat arrays to avoid nested objects
var fruitTypes = [];
var fruitXPositions = [];
var fruitYPositions = [];
var fruitIsRainbow = [];
var fruitIsRainFruit = [];
var fruitIsGold = [];
var fruitIsCelestial = [];
for (var i = 0; i < fruits.length; i++) {
fruitTypes.push(fruits[i].type);
fruitXPositions.push(fruits[i].x);
fruitYPositions.push(fruits[i].y);
fruitIsRainbow.push(fruits[i].isRainbow || false);
fruitIsRainFruit.push(fruits[i].isRainFruit || false);
fruitIsGold.push(fruits[i].isGold || false);
fruitIsCelestial.push(fruits[i].isCelestial || false);
}
storage[slotPrefix + 'savedGameFruitTypes'] = fruitTypes;
storage[slotPrefix + 'savedGameFruitXPositions'] = fruitXPositions;
storage[slotPrefix + 'savedGameFruitYPositions'] = fruitYPositions;
storage[slotPrefix + 'savedGameFruitIsRainbow'] = fruitIsRainbow;
storage[slotPrefix + 'savedGameFruitIsRainFruit'] = fruitIsRainFruit;
storage[slotPrefix + 'savedGameFruitIsGold'] = fruitIsGold;
storage[slotPrefix + 'savedGameFruitIsCelestial'] = fruitIsCelestial;
return {
score: storage[slotPrefix + 'savedGameScore'],
totalMerges: storage[slotPrefix + 'savedGameTotalMerges'],
discoveredFruits: storage[slotPrefix + 'savedGameDiscoveredFruits'],
currentMusicIndex: storage[slotPrefix + 'savedGameCurrentMusicIndex'],
tutorialCompleted: storage[slotPrefix + 'savedGameTutorialCompleted']
};
}
// Helper function to check if a save slot has data
function hasSlotData(slotNumber) {
var slotPrefix = 'slot' + slotNumber + '_';
return storage[slotPrefix + 'savedGameScore'] !== undefined;
}
// Load game data function
function loadGameData(slotNumber) {
// Check if we have saved game data for this slot
var slotPrefix = 'slot' + slotNumber + '_';
if (storage[slotPrefix + 'savedGameScore'] === undefined) return false;
// Restore score and counters
LK.setScore(storage[slotPrefix + 'savedGameScore'] || 0);
scoreTxt.setText('Score: ' + LK.getScore());
totalMerges = storage[slotPrefix + 'savedGameTotalMerges'] || 0;
mergeCounterTxt.setText('Merges: ' + totalMerges);
// Restore discovered fruits
if (storage[slotPrefix + 'savedGameDiscoveredFruits']) {
discoveredFruits = storage[slotPrefix + 'savedGameDiscoveredFruits'];
}
// Restore music selection
if (storage[slotPrefix + 'savedGameCurrentMusicIndex'] !== undefined) {
currentMusicIndex = storage[slotPrefix + 'savedGameCurrentMusicIndex'];
}
// Clear existing fruits
for (var i = fruits.length - 1; i >= 0; i--) {
fruits[i].destroy();
fruits.splice(i, 1);
}
// Restore fruits from flat arrays
var fruitTypes = storage[slotPrefix + 'savedGameFruitTypes'] || [];
var fruitXPositions = storage[slotPrefix + 'savedGameFruitXPositions'] || [];
var fruitYPositions = storage[slotPrefix + 'savedGameFruitYPositions'] || [];
var fruitIsRainbow = storage[slotPrefix + 'savedGameFruitIsRainbow'] || [];
var fruitIsRainFruit = storage[slotPrefix + 'savedGameFruitIsRainFruit'] || [];
var fruitIsGold = storage[slotPrefix + 'savedGameFruitIsGold'] || [];
var fruitIsCelestial = storage[slotPrefix + 'savedGameFruitIsCelestial'] || [];
if (fruitTypes.length > 0) {
for (var i = 0; i < fruitTypes.length; i++) {
var newFruit = new Fruit(fruitTypes[i]);
newFruit.x = fruitXPositions[i] || 0;
newFruit.y = fruitYPositions[i] || 0;
newFruit.isRainbow = fruitIsRainbow[i] || false;
newFruit.isRainFruit = fruitIsRainFruit[i] || false;
newFruit.isGold = fruitIsGold[i] || false;
newFruit.isCelestial = fruitIsCelestial[i] || false;
// Restore rainbow visual effects if this fruit is rainbow
if (newFruit.isRainbow) {
newFruit.rainbowTintIndex = 0;
newFruit.rainbowColors = [0xff0000, 0xff8000, 0xffff00, 0x00ff00, 0x0080ff, 0x8000ff];
var fruitGraphics = newFruit.children[0];
if (fruitGraphics) {
fruitGraphics.tint = newFruit.rainbowColors[0];
}
}
// Restore celestial visual effects if this fruit is celestial
if (newFruit.isCelestial) {
var fruitGraphics = newFruit.children[0];
if (fruitGraphics) {
fruitGraphics.tint = 0x8a2be2; // Purple color
}
// Recreate celestial ring using celestial ring asset
newFruit.celestialRing = newFruit.attachAsset('celestialRing', {
anchorX: 0.5,
anchorY: 0.5
});
newFruit.celestialRing.width = newFruit.size + 80;
newFruit.celestialRing.height = newFruit.size + 80;
newFruit.celestialRing.alpha = 0.6;
newFruit.addChildAt(newFruit.celestialRing, 0); // Add behind the fruit
}
// Restore gold visual effects if this fruit is gold
if (newFruit.isGold) {
var fruitGraphics = newFruit.children[0];
if (fruitGraphics) {
fruitGraphics.tint = 0xffd700; // Gold color
}
newFruit.shines = [];
// Recreate shine effects around the fruit
for (var s = 0; s < 3; s++) {
var shine = newFruit.attachAsset('shine', {
anchorX: 0.5,
anchorY: 0.5
});
shine.alpha = 0;
newFruit.shines.push(shine);
}
}
newFruit.velocityX = 0;
newFruit.velocityY = 0;
newFruit.angularVelocity = 0;
fruits.push(newFruit);
game.addChild(newFruit);
}
}
return true;
}
// Create save slots (3 slots)
var saveSlots = [];
for (var slotNum = 1; slotNum <= 3; slotNum++) {
// Save slot button
var saveSlotButton = new Text2('SLOT ' + slotNum, {
size: 50,
fill: 0xffffff
});
saveSlotButton.anchor.set(0.5, 0.5);
saveSlotButton.x = 2048 / 2 - 400 + (slotNum - 1) * 400;
saveSlotButton.y = 2732 / 2 - 150;
saveSlotButton.slotNumber = slotNum;
var saveSlotButtonBg = LK.getAsset('container', {
anchorX: 0.5,
anchorY: 0.5,
x: saveSlotButton.x,
y: saveSlotButton.y,
width: 320,
height: 80
});
saveSlotButtonBg.tint = 0x4CAF50;
saveMenu.addChild(saveSlotButtonBg);
saveMenu.addChild(saveSlotButton);
// Save slot status text
var saveSlotStatus = new Text2(hasSlotData(slotNum) ? 'HAS DATA' : 'EMPTY', {
size: 30,
fill: hasSlotData(slotNum) ? 0x00aa00 : 0xaaaaaa
});
saveSlotStatus.anchor.set(0.5, 0.5);
saveSlotStatus.x = saveSlotButton.x;
saveSlotStatus.y = saveSlotButton.y - 40;
saveMenu.addChild(saveSlotStatus);
// Load slot button
var loadSlotButton = new Text2('LOAD', {
size: 40,
fill: 0xffffff
});
loadSlotButton.anchor.set(0.5, 0.5);
loadSlotButton.x = saveSlotButton.x;
loadSlotButton.y = saveSlotButton.y + 150;
loadSlotButton.slotNumber = slotNum;
var loadSlotButtonBg = LK.getAsset('container', {
anchorX: 0.5,
anchorY: 0.5,
x: loadSlotButton.x,
y: loadSlotButton.y,
width: 260,
height: 70
});
loadSlotButtonBg.tint = hasSlotData(slotNum) ? 0x2196F3 : 0x666666;
saveMenu.addChild(loadSlotButtonBg);
saveMenu.addChild(loadSlotButton);
// Store references for updating
saveSlots.push({
saveButton: saveSlotButton,
saveButtonBg: saveSlotButtonBg,
statusText: saveSlotStatus,
loadButton: loadSlotButton,
loadButtonBg: loadSlotButtonBg,
slotNumber: slotNum
});
// Save button click handler
saveSlotButton.down = function () {
var slotNum = this.slotNumber;
var gameData = saveGameData(slotNum);
// Update slot status
for (var i = 0; i < saveSlots.length; i++) {
if (saveSlots[i].slotNumber === slotNum) {
saveSlots[i].statusText.setText('HAS DATA');
saveSlots[i].statusText.fill = 0x00aa00;
saveSlots[i].loadButtonBg.tint = 0x2196F3;
break;
}
}
// Show confirmation
var confirmText = new Text2('Saved to Slot ' + slotNum + '!', {
size: 40,
fill: 0x00aa00
});
confirmText.anchor.set(0.5, 0.5);
confirmText.x = this.x;
confirmText.y = this.y + 140;
confirmText.alpha = 0;
saveMenu.addChild(confirmText);
tween(confirmText, {
alpha: 1
}, {
duration: 300,
onFinish: function onFinish() {
LK.setTimeout(function () {
tween(confirmText, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
confirmText.destroy();
}
});
}, 1500);
}
});
};
// Load button click handler
loadSlotButton.down = function () {
var slotNum = this.slotNumber;
var loaded = loadGameData(slotNum);
if (loaded) {
// Show confirmation
var confirmText = new Text2('Loaded Slot ' + slotNum + '!', {
size: 40,
fill: 0x0066aa
});
confirmText.anchor.set(0.5, 0.5);
confirmText.x = this.x;
confirmText.y = this.y + 60;
confirmText.alpha = 0;
saveMenu.addChild(confirmText);
tween(confirmText, {
alpha: 1
}, {
duration: 300,
onFinish: function onFinish() {
LK.setTimeout(function () {
tween(confirmText, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
confirmText.destroy();
saveMenu.visible = false;
gameRunning = true;
fruitsFrozen = false;
}
});
}, 1500);
}
});
} else {
// Show no save found message
var noSaveText = new Text2('Slot ' + slotNum + ' is empty!', {
size: 40,
fill: 0xaa0000
});
noSaveText.anchor.set(0.5, 0.5);
noSaveText.x = this.x;
noSaveText.y = this.y + 60;
noSaveText.alpha = 0;
saveMenu.addChild(noSaveText);
tween(noSaveText, {
alpha: 1
}, {
duration: 300,
onFinish: function onFinish() {
LK.setTimeout(function () {
tween(noSaveText, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
noSaveText.destroy();
}
});
}, 1500);
}
});
}
};
}
// Add tutorial restart button to save menu
var tutorialRestartButton = new Text2('RESTART TUTORIAL', {
size: 50,
fill: 0xffffff
});
tutorialRestartButton.anchor.set(0.5, 0.5);
tutorialRestartButton.x = 2048 / 2 - 300;
tutorialRestartButton.y = 2732 / 2 + 350;
var tutorialRestartBg = LK.getAsset('container', {
anchorX: 0.5,
anchorY: 0.5,
x: tutorialRestartButton.x,
y: tutorialRestartButton.y,
width: 400,
height: 80
});
tutorialRestartBg.tint = 0x2196F3;
saveMenu.addChild(tutorialRestartBg);
saveMenu.addChild(tutorialRestartButton);
tutorialRestartButton.down = function () {
// Close save menu
saveMenu.visible = false;
// Restart tutorial
tutorialActive = true;
tutorialStep = 0;
storage.tutorialCompleted = false;
gameRunning = false;
fruitsFrozen = true;
showTutorialStep();
};
// Close button
var closeSaveMenuButton = new Text2('CLOSE', {
size: 60,
fill: 0xffffff
});
closeSaveMenuButton.anchor.set(0.5, 0.5);
closeSaveMenuButton.x = 2048 / 2 + 300;
closeSaveMenuButton.y = 2732 / 2 + 350;
var closeSaveMenuButtonBg = LK.getAsset('container', {
anchorX: 0.5,
anchorY: 0.5,
x: closeSaveMenuButton.x,
y: closeSaveMenuButton.y,
width: 200,
height: 80
});
closeSaveMenuButtonBg.tint = 0xf44336;
saveMenu.addChild(closeSaveMenuButtonBg);
saveMenu.addChild(closeSaveMenuButton);
closeSaveMenuButton.down = function () {
saveMenu.visible = false;
gameRunning = true;
fruitsFrozen = false;
};
// Show music menu at game start instead of auto-starting music
musicMenu.visible = true;
musicMenuVisible = true;
gameRunning = false;
fruitsFrozen = true;
// Move music menu to front
game.removeChild(musicMenu);
game.addChild(musicMenu);
// Hide music button initially
musicMenuButton.visible = false;
// Hide save button initially
saveMenuButton.visible = false;
// Music menu background
var musicMenuBg = LK.getAsset('container', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
width: 1000,
height: 800
});
musicMenuBg.alpha = 0.95;
musicMenuBg.tint = 0xdddddd;
musicMenu.addChild(musicMenuBg);
// Music menu title
var musicMenuTitle = new Text2('Select Music Track', {
size: 80,
fill: 0x222222,
stroke: 0xffffff,
strokeThickness: 3
});
musicMenuTitle.anchor.set(0.5, 0);
musicMenuTitle.x = 2048 / 2;
musicMenuTitle.y = 2732 / 2 - 300;
musicMenu.addChild(musicMenuTitle);
// Create music selection buttons
var musicButtons = [];
for (var i = 0; i < musicTracks.length; i++) {
var musicButton = new Text2(musicNames[i], {
size: 60,
fill: 0x000000
});
musicButton.anchor.set(0.5, 0.5);
musicButton.x = 2048 / 2;
musicButton.y = 2732 / 2 - 150 + i * 100;
musicButton.musicIndex = i;
// Add click handler for each music button using closure to capture index
(function (index) {
musicButton.down = function (x, y, obj) {
// Update current music selection
currentMusicIndex = index;
storage.selectedMusic = currentMusicIndex;
// Stop current music and play selected track
LK.stopMusic();
LK.playMusic(musicTracks[currentMusicIndex]);
// Close menu
musicMenu.visible = false;
musicMenuVisible = false;
// Show music button now that a song has been picked
musicMenuButton.visible = true;
// Show save button now that a song has been picked
saveMenuButton.visible = true;
// Only start tutorial for new players who haven't completed it
if (!storage.tutorialCompleted) {
gameRunning = false;
fruitsFrozen = true;
LK.setTimeout(function () {
showTutorialStep();
}, 1000);
} else {
// For returning players, start the game immediately
gameRunning = true;
fruitsFrozen = false;
}
};
})(i);
musicButtons.push(musicButton);
musicMenu.addChild(musicButton);
}
// Music menu button click handler
musicMenuButton.down = function (x, y, obj) {
if (musicMenuVisible) {
musicMenu.visible = false;
musicMenuVisible = false;
gameRunning = true;
fruitsFrozen = false;
} else {
musicMenu.visible = true;
musicMenuVisible = true;
// Move music menu to front
game.removeChild(musicMenu);
game.addChild(musicMenu);
gameRunning = false;
fruitsFrozen = true;
}
};
// Save menu button click handler
saveMenuButton.down = function (x, y, obj) {
if (saveMenu.visible) {
saveMenu.visible = false;
gameRunning = true;
fruitsFrozen = false;
} else {
saveMenu.visible = true;
// Move save menu to front
game.removeChild(saveMenu);
game.addChild(saveMenu);
gameRunning = false;
fruitsFrozen = true;
}
};
// Event handlers
game.down = function (x, y, obj) {
if (!gameRunning || currentFruit || saveMenu.visible) {
return;
}
// Handle fruit mover mode
if (fruitMoverActive) {
var _loop = function _loop() {
fruit = fruits[i];
dx = x - fruit.x;
dy = y - fruit.y;
distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= fruit.getRadius()) {
var bobUp = function bobUp() {
tween(nextFruitPreview, {
y: originalY - 15
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: bobDown
});
};
var bobDown = function bobDown() {
tween(nextFruitPreview, {
y: originalY + 15
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: bobUp
});
};
// Fruit clicked - delete it and set as next fruit
fruitMoverSelectedFruit = fruit;
nextFruitType = fruit.type;
// Remove fruit from game
for (j = fruits.length - 1; j >= 0; j--) {
if (fruits[j] === fruit) {
fruits[j].destroy();
fruits.splice(j, 1);
break;
}
}
// Update next fruit preview to show the deleted fruit type
if (nextFruitPreview) {
// Stop any existing tweens before destroying
tween.stop(nextFruitPreview);
nextFruitPreview.destroy();
}
fruitAssets = ['cherry', 'strawberry', 'grape', 'orange', 'apple', 'pear', 'peach', 'pineapple', 'melon', 'watermelon', 'lemon', 'coconut', 'dragon', 'kiwi', 'mango', 'papaya', 'durian', 'blueberry', 'pomegranate', 'avocado', 'cranberry'];
fruitSizes = [120, 160, 200, 240, 280, 320, 360, 400, 440, 480, 520, 560, 600, 640, 580, 620, 660, 380, 700, 720, 340];
nextFruitPreview = LK.getAsset(fruitAssets[nextFruitType], {
anchorX: 0.5,
anchorY: 0.5
});
nextFruitPreview.x = 1700;
nextFruitPreview.y = 120;
game.addChild(nextFruitPreview);
// Position the label based on fruit size to ensure it's always visible
fruitRadius = fruitSizes[nextFruitType] / 2;
nextFruitLabel.x = nextFruitPreview.x - fruitRadius - 20; // 20px padding from fruit edge
// Add bobbing animation
originalY = nextFruitPreview.y;
bobUp();
// Reset all fruits to normal tint
for (k = 0; k < fruits.length; k++) {
tween(fruits[k], {
tint: 0xffffff
}, {
duration: 300,
easing: tween.easeOut
});
}
// Deactivate fruit mover mode
fruitMoverActive = false;
fruitMoverSelectedFruit = null;
return {
v: void 0
};
}
},
fruit,
dx,
dy,
distance,
j,
fruitAssets,
fruitSizes,
fruitRadius,
originalY,
k,
_ret;
// Check if click is on a fruit
for (var i = 0; i < fruits.length; i++) {
_ret = _loop();
if (_ret) return _ret.v;
}
// If no fruit was clicked, cancel fruit mover mode
fruitMoverActive = false;
for (var m = 0; m < fruits.length; m++) {
tween(fruits[m], {
tint: 0xffffff
}, {
duration: 300,
easing: tween.easeOut
});
}
return;
}
// Only allow spawning if the press is inside the container bounds
var radius = 0;
if (x >= containerX - containerWidth / 2 + radius && x <= containerX + containerWidth / 2 - radius && y >= containerY - containerHeight / 2 + radius && y <= containerY + containerHeight / 2 - radius) {
isDragging = true;
createPreviewFruit(x);
// Music now starts automatically at game load
// Play drop sound when starting to aim
LK.getSound('drop').play();
// Progress tutorial if on the last step (first fruit drop)
if (tutorialActive && tutorialStep === tutorialSteps.length - 1) {
completeTutorial();
}
}
};
game.move = function (x, y, obj) {
if (!gameRunning || !isDragging || saveMenu.visible) {
return;
}
updatePreviewFruit(x);
};
game.up = function (x, y, obj) {
if (!gameRunning || !isDragging || saveMenu.visible) {
return;
}
isDragging = false;
var dropX = previewFruit ? previewFruit.x : x;
destroyPreviewFruit();
dropFruit(dropX);
};
// Main game loop
game.update = function () {
if (!gameRunning) {
return;
}
// Update all fruits
if (!fruitsFrozen) {
for (var i = 0; i < fruits.length; i++) {
fruits[i].update();
}
}
// Check collisions and merges
for (var i = 0; i < fruits.length; i++) {
for (var j = i + 1; j < fruits.length; j++) {
var fruit1 = fruits[i];
var fruit2 = fruits[j];
if (fruit1.merged || fruit2.merged) {
continue;
}
if (checkCollision(fruit1, fruit2)) {
// Mark both fruits as having made contact
fruit1.contactMade = true;
fruit2.contactMade = true;
// Always apply physics first to prevent sticking
resolveFruitCollision(fruit1, fruit2);
// Then check for merge
var currentMerging = fruit1.type === fruit2.type;
if (!fruit1.lastMergeCheck && currentMerging) {
if (mergeFruits(fruit1, fruit2)) {
break; // Break out of inner loop as fruits array changed
}
}
fruit1.lastMergeCheck = currentMerging;
fruit2.lastMergeCheck = currentMerging;
} else {
fruit1.lastMergeCheck = false;
fruit2.lastMergeCheck = false;
}
}
}
// Check game over condition (disabled during fruit rain)
if (!fruitRainActive && checkGameOver() && LK.ticks % 60 === 0) {
// Check every second
gameRunning = false;
LK.showGameOver();
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Fruit = Container.expand(function (type) {
var self = Container.call(this);
self.type = type;
self.velocityX = 0;
self.velocityY = 0;
self.angularVelocity = 0;
self.gravity = 1.2;
self.bounceX = 0.2;
self.bounceY = 0.1;
self.friction = 0.995;
self.rotationFriction = 0.85;
self.merged = false;
self.lastMergeCheck = false;
// Track if this fruit was created from fruit rain
self.isRainFruit = false;
// Track if this fruit has made contact with ground or another fruit
self.contactMade = false;
// Rainbow mutation - 5% chance for any fruit to be rainbow
self.isRainbow = Math.random() < 0.05;
// Gold mutation - 3% chance for any fruit to be gold
self.isGold = Math.random() < 0.03;
// Celestial mutation - 0.1% chance for any fruit to be celestial (super rare)
self.isCelestial = Math.random() < 0.001;
var fruitAssets = ['cherry', 'strawberry', 'grape', 'orange', 'apple', 'pear', 'peach', 'pineapple', 'melon', 'watermelon', 'lemon', 'coconut', 'dragon', 'kiwi', 'mango', 'papaya', 'durian', 'blueberry', 'pomegranate', 'avocado', 'cranberry'];
var fruitSizes = [120, 160, 200, 240, 280, 320, 360, 400, 440, 480, 520, 560, 600, 640, 580, 620, 660, 380, 700, 720, 340];
var fruitScores = [1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 66, 78, 91, 105, 120, 136, 153, 171, 190, 210, 231];
self.assetName = fruitAssets[type];
self.size = fruitSizes[type];
self.scoreValue = fruitScores[type];
var fruitGraphics = self.attachAsset(self.assetName, {
anchorX: 0.5,
anchorY: 0.5
});
// Apply rainbow effect if this fruit is rainbow
if (self.isRainbow) {
self.rainbowTintIndex = 0;
self.rainbowColors = [0xff0000, 0xff8000, 0xffff00, 0x00ff00, 0x0080ff, 0x8000ff];
fruitGraphics.tint = self.rainbowColors[0];
}
// Apply gold effect if this fruit is gold
if (self.isGold) {
fruitGraphics.tint = 0xffd700; // Gold color
self.shines = [];
// Create 3 shine effects around the fruit
for (var s = 0; s < 3; s++) {
var shine = self.attachAsset('shine', {
anchorX: 0.5,
anchorY: 0.5
});
shine.alpha = 0;
self.shines.push(shine);
}
}
// Apply celestial effect if this fruit is celestial
if (self.isCelestial) {
fruitGraphics.tint = 0x8a2be2; // Purple color
// Create purple ring around the fruit using celestial ring asset
self.celestialRing = self.attachAsset('celestialRing', {
anchorX: 0.5,
anchorY: 0.5
});
self.celestialRing.width = self.size + 80;
self.celestialRing.height = self.size + 80;
self.celestialRing.alpha = 0.6;
self.addChildAt(self.celestialRing, 0); // Add behind the fruit
}
self.getRadius = function () {
return self.size / 2;
};
self.update = function () {
if (typeof fruitsFrozen !== "undefined" && fruitsFrozen) {
return;
}
if (self.merged) {
return;
}
// Apply gravity
self.velocityY += self.gravity;
// Apply velocity
self.x += self.velocityX;
self.y += self.velocityY;
// Calculate current speed
var currentSpeed = Math.sqrt(self.velocityX * self.velocityX + self.velocityY * self.velocityY);
var speedThreshold = 3.0; // Only rotate when moving fast enough
// Always apply rotation
self.rotation += self.angularVelocity;
// Apply friction
self.velocityX *= self.friction;
self.velocityY *= self.friction;
self.angularVelocity *= self.rotationFriction;
// Additional stability check - if fruit is moving very slowly, gradually bring it to rest
var totalSpeed = Math.sqrt(self.velocityX * self.velocityX + self.velocityY * self.velocityY);
if (totalSpeed < 1.0 && totalSpeed > 0) {
var stabilityDamping = 0.8;
self.velocityX *= stabilityDamping;
self.velocityY *= stabilityDamping;
}
// Check if fruit is in cramped conditions
var nearbyFruits = 0;
if (typeof fruits !== "undefined") {
for (var k = 0; k < fruits.length; k++) {
if (fruits[k] !== self) {
var dist = Math.sqrt((self.x - fruits[k].x) * (self.x - fruits[k].x) + (self.y - fruits[k].y) * (self.y - fruits[k].y));
if (dist < self.getRadius() + fruits[k].getRadius() + 30) {
nearbyFruits++;
}
}
}
}
// Apply stronger rotational damping in cramped conditions
if (nearbyFruits >= 3) {
self.angularVelocity *= 0.6; // Much stronger damping when cramped
}
// Cap maximum rotation speed - lower limit for cramped conditions
var maxAngularVelocity = nearbyFruits >= 3 ? 0.05 : 0.1; // Reduced max rotation
if (self.angularVelocity > maxAngularVelocity) {
self.angularVelocity = maxAngularVelocity;
} else if (self.angularVelocity < -maxAngularVelocity) {
self.angularVelocity = -maxAngularVelocity;
}
// Rainbow color cycling for rainbow fruits
if (self.isRainbow && self.rainbowColors && LK.ticks % 30 === 0) {
self.rainbowTintIndex = (self.rainbowTintIndex + 1) % self.rainbowColors.length;
var fruitGraphics = self.children[0];
if (fruitGraphics) {
tween(fruitGraphics, {
tint: self.rainbowColors[self.rainbowTintIndex]
}, {
duration: 500,
easing: tween.easeInOut
});
}
}
// Gold shine animation for gold fruits - always moving and spinning like flies
if (self.isGold && self.shines) {
for (var s = 0; s < self.shines.length; s++) {
var shine = self.shines[s];
// Initialize shine position if not set
if (shine.shineInitialized === undefined) {
shine.shineInitialized = true;
shine.alpha = 0.7;
shine.scaleX = 2.5;
shine.scaleY = 2.5;
var angle = s * 120 * Math.PI / 180;
var radius = self.getRadius() + 20;
shine.x = Math.cos(angle) * radius;
shine.y = Math.sin(angle) * radius;
shine.shineAngle = angle;
shine.shineSpeed = 0.02 + Math.random() * 0.02; // Random speed for each shine
shine.shineRadius = radius + Math.random() * 20;
shine.rotationSpeed = (Math.random() - 0.5) * 0.2; // Random rotation speed
}
// Update shine position in circular/erratic fly-like motion
shine.shineAngle += shine.shineSpeed;
var targetRadius = self.getRadius() + 15 + Math.sin(shine.shineAngle * 3) * 10; // Varying radius
var targetX = Math.cos(shine.shineAngle) * targetRadius + (Math.random() - 0.5) * 5;
var targetY = Math.sin(shine.shineAngle) * targetRadius + (Math.random() - 0.5) * 5;
// Smooth movement towards target position
shine.x += (targetX - shine.x) * 0.1;
shine.y += (targetY - shine.y) * 0.1;
// Continuous spinning
shine.rotation += shine.rotationSpeed;
// Slight alpha variation for flickering effect
shine.alpha = 0.5 + Math.sin(LK.ticks * 0.1 + s) * 0.3;
}
}
// Celestial ring animation for celestial fruits
if (self.isCelestial && self.celestialRing) {
// Slowly rotate the ring
self.celestialRing.rotation += 0.02;
// Pulsing alpha effect
self.celestialRing.alpha = 0.4 + Math.sin(LK.ticks * 0.05) * 0.2;
// Gentle size pulsing
var pulseScale = 1.0 + Math.sin(LK.ticks * 0.03) * 0.05;
self.celestialRing.scaleX = pulseScale;
self.celestialRing.scaleY = pulseScale;
}
// Prevent very small velocities that can cause sticking - increased threshold
if (Math.abs(self.velocityX) < 0.5) {
self.velocityX = 0;
}
if (Math.abs(self.velocityY) < 0.5) {
self.velocityY = 0;
}
// Higher threshold for stopping rotation in cramped conditions
var rotationStopThreshold = nearbyFruits >= 3 ? 0.02 : 0.005;
if (Math.abs(self.angularVelocity) < rotationStopThreshold) {
self.angularVelocity = 0;
// Bounce rotation back to neutral position when angular velocity stops
if (Math.abs(self.rotation) > 0.1) {
tween(self, {
rotation: 0
}, {
duration: nearbyFruits >= 3 ? 400 : 600,
// Faster correction when cramped
easing: tween.bounceOut
});
}
}
var radius = self.getRadius();
var containerLeft = containerX - containerWidth / 2;
var containerRight = containerX + containerWidth / 2;
var containerBottom = containerY + containerHeight / 2;
// Collision with container walls
if (self.x - radius < containerLeft) {
self.x = containerLeft + radius;
self.velocityX = -self.velocityX * self.bounceX;
self.contactMade = true; // Mark contact made when hitting wall
// Add rotational effect from wall collision only if movement is significant
if (Math.abs(self.velocityY) > 2.0) {
self.angularVelocity += self.velocityY * 0.01;
}
}
if (self.x + radius > containerRight) {
self.x = containerRight - radius;
self.velocityX = -self.velocityX * self.bounceX;
self.contactMade = true; // Mark contact made when hitting wall
// Add rotational effect from wall collision only if movement is significant
if (Math.abs(self.velocityY) > 2.0) {
self.angularVelocity -= self.velocityY * 0.01;
}
}
// Collision with container bottom
if (self.y + radius > containerBottom) {
self.y = containerBottom - radius;
self.velocityY = -self.velocityY * self.bounceY;
self.contactMade = true; // Mark contact made when hitting ground
// Add rotational effect from ground collision only if movement is significant
if (Math.abs(self.velocityX) > 2.0) {
self.angularVelocity += self.velocityX * 0.02;
}
if (Math.abs(self.velocityY) < 1) {
self.velocityY = 0;
}
// After ground collision, check speed threshold
var currentSpeed = Math.sqrt(self.velocityX * self.velocityX + self.velocityY * self.velocityY);
var speedThreshold = 3.0;
if (currentSpeed < speedThreshold) {
// Stop movement if below speed threshold after hitting ground
self.velocityX = 0;
self.velocityY = 0;
self.angularVelocity = 0;
// Bounce rotation back to neutral position
if (Math.abs(self.rotation) > 0.1) {
tween(self, {
rotation: 0
}, {
duration: 800,
easing: tween.elasticOut
});
}
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
// No backgroundColor, handled by asset
});
/****
* Game Code
****/
// Blue background asset (full screen)
// Attach blue background asset, ensure it's at the back
var blueBg = game.attachAsset('blueBg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
game.addChildAt(blueBg, 0); // Add as the bottom-most child
// Music selection system (declare before any usage)
var musicTracks = ['music1', 'music2', 'music3', 'music4', 'music5'];
var musicNames = ['Classic Melody', 'Upbeat Bounce', 'Chill Vibes', 'Epic Adventure', 'Peaceful Garden'];
var currentMusicIndex = storage.selectedMusic || 0;
var musicStarted = true;
var musicMenuVisible = false;
// Game state
var gameState = 'playing';
var fruits = [];
var currentFruit = null;
var nextFruitType = 0;
var gameRunning = true;
var dropLine = 400;
var gameOverLine = 300;
var isDragging = false;
var previewFruit = null;
// Freeze fruits when evolution menu is open
var fruitsFrozen = false;
// Track which fruits have been discovered by the player
var discoveredFruits = storage.discoveredFruits || [];
// Initialize array if not in storage
if (discoveredFruits.length === 0) {
for (var i = 0; i < 21; i++) {
discoveredFruits[i] = false;
}
}
// Container dimensions and position
var containerWidth = 1900;
var containerHeight = 2200;
var containerX = 2048 / 2;
var containerY = 1366;
// Create container
var container = game.attachAsset('container', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX,
y: containerY
});
// Score display
var scoreTxt = new Text2('Score: 0', {
size: 80,
fill: 0x000000
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Merge counter (load from storage or default to 0)
var totalMerges = storage.totalMerges || 0;
var mergeCounterTxt = new Text2('Merges: ' + totalMerges, {
size: 60,
fill: 0x000000
});
mergeCounterTxt.anchor.set(0.5, 0.5);
mergeCounterTxt.x = 2048 / 2;
mergeCounterTxt.y = 2732 / 2;
game.addChild(mergeCounterTxt);
// Load discovered fruits from storage, but ensure first 5 are always available
for (var i = 0; i < discoveredFruits.length; i++) {
if (i < 5) {
discoveredFruits[i] = true; // First 5 are always available to drop
} else if (storage.discoveredFruits && storage.discoveredFruits[i]) {
discoveredFruits[i] = true; // Keep unlocked fruits from storage
}
}
// Save to storage
storage.discoveredFruits = discoveredFruits;
// Next fruit preview
var nextFruitPreview = null;
var nextFruitLabel = new Text2('Next:', {
size: 60,
fill: 0x000000
});
nextFruitLabel.anchor.set(1, 0.5);
nextFruitLabel.x = 1580;
nextFruitLabel.y = 120;
game.addChild(nextFruitLabel);
// Drop line indicator
var dropLineIndicator = LK.getAsset('container', {
width: containerWidth,
height: 4,
anchorX: 0.5,
anchorY: 0.5
});
dropLineIndicator.x = containerX;
dropLineIndicator.y = dropLine;
dropLineIndicator.tint = 0x0000FF;
game.addChild(dropLineIndicator);
// Helper functions
function getRandomFruitType() {
return Math.floor(Math.random() * 5); // Only first 5 fruit types can be dropped
}
function createNextFruit() {
nextFruitType = getRandomFruitType();
if (nextFruitPreview) {
// Stop any existing tweens before destroying
tween.stop(nextFruitPreview);
nextFruitPreview.destroy();
}
var fruitAssets = ['cherry', 'strawberry', 'grape', 'orange', 'apple'];
var fruitSizes = [120, 160, 200, 240, 280];
nextFruitPreview = LK.getAsset(fruitAssets[nextFruitType], {
anchorX: 0.5,
anchorY: 0.5
});
nextFruitPreview.x = 1700;
nextFruitPreview.y = 120;
game.addChild(nextFruitPreview);
// Position the label based on fruit size to ensure it's always visible
var fruitRadius = fruitSizes[nextFruitType] / 2;
nextFruitLabel.x = nextFruitPreview.x - fruitRadius - 20; // 20px padding from fruit edge
// Add bobbing animation
var originalY = nextFruitPreview.y;
function bobUp() {
tween(nextFruitPreview, {
y: originalY - 15
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: bobDown
});
}
function bobDown() {
tween(nextFruitPreview, {
y: originalY + 15
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: bobUp
});
}
bobUp();
}
function dropFruit(x) {
if (!gameRunning || currentFruit) {
return;
}
currentFruit = new Fruit(nextFruitType);
currentFruit.x = Math.max(containerX - containerWidth / 2 + currentFruit.getRadius(), Math.min(x, containerX + containerWidth / 2 - currentFruit.getRadius()));
currentFruit.y = dropLine + currentFruit.getRadius() + 60; // Spawn lower below the line with more padding
// Mark this fruit as discovered
var newlyDiscovered = !discoveredFruits[nextFruitType];
discoveredFruits[nextFruitType] = true;
// Save to storage
storage.discoveredFruits = discoveredFruits;
if (newlyDiscovered) {
// Show notification for unlocking new fruit
var evoFruitNames = ['Cherry', 'Strawberry', 'Grape', 'Orange', 'Apple', 'Pear', 'Peach', 'Pineapple', 'Melon', 'Watermelon', 'Lemon', 'Coconut', 'Dragonfruit', 'Kiwi', 'Mango', 'Papaya', 'Durian', 'Blueberry', 'Pomegranate', 'Avocado', 'Cranberry'];
var unlockedName = evoFruitNames[nextFruitType];
var notification = new Text2("You've unlocked " + unlockedName + "!", {
size: 90,
fill: 0x222222
});
notification.anchor.set(0.5, 0.5);
notification.x = 2048 / 2;
notification.y = 900;
notification.alpha = 0.0;
game.addChild(notification);
// Fade in, hold, then fade out
tween(notification, {
alpha: 1
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
LK.setTimeout(function () {
tween(notification, {
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
notification.destroy();
}
});
}, 1200);
}
});
}
// Add small random initial rotation
currentFruit.angularVelocity = (Math.random() - 0.5) * 0.05;
fruits.push(currentFruit);
game.addChild(currentFruit);
// Music and drop sound now start on tap/hold instead of drop
// Reset current fruit after a delay
LK.setTimeout(function () {
currentFruit = null;
}, 100);
createNextFruit();
}
function createPreviewFruit(x) {
if (previewFruit) {
previewFruit.destroy();
}
var fruitAssets = ['cherry', 'strawberry', 'grape', 'orange', 'apple', 'pear', 'peach', 'pineapple', 'melon', 'watermelon', 'lemon', 'coconut', 'dragon', 'kiwi', 'mango', 'papaya', 'durian', 'blueberry', 'pomegranate', 'avocado', 'cranberry'];
previewFruit = LK.getAsset(fruitAssets[nextFruitType], {
anchorX: 0.5,
anchorY: 0.5
});
previewFruit.alpha = 0.7; // Make it semi-transparent
previewFruit.x = x;
previewFruit.y = dropLine;
game.addChild(previewFruit);
}
function updatePreviewFruit(x) {
if (!previewFruit) {
return;
}
var fruitSizes = [120, 160, 200, 240, 280, 320, 360, 400, 440, 480, 520, 560, 600, 640, 580, 620, 660, 380, 700, 720, 340];
var radius = fruitSizes[nextFruitType] / 2;
previewFruit.x = Math.max(containerX - containerWidth / 2 + radius, Math.min(x, containerX + containerWidth / 2 - radius));
}
function destroyPreviewFruit() {
if (previewFruit) {
previewFruit.destroy();
previewFruit = null;
}
}
function checkCollision(fruit1, fruit2) {
var dx = fruit1.x - fruit2.x;
var dy = fruit1.y - fruit2.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var minDistance = fruit1.getRadius() + fruit2.getRadius();
return distance < minDistance;
}
function resolveFruitCollision(fruit1, fruit2) {
var dx = fruit1.x - fruit2.x;
var dy = fruit1.y - fruit2.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var minDistance = fruit1.getRadius() + fruit2.getRadius();
if (distance < minDistance && distance > 0) {
var overlap = minDistance - distance;
// Reduced separation force to prevent oscillation
var separationForce = 0.6;
var separationX = dx / distance * overlap * separationForce;
var separationY = dy / distance * overlap * separationForce;
fruit1.x += separationX;
fruit1.y += separationY;
fruit2.x -= separationX;
fruit2.y -= separationY;
// Apply strong damping when fruits are very close to prevent vibration
var dampingFactor = Math.max(0.1, Math.min(1.0, distance / minDistance));
fruit1.velocityX *= dampingFactor;
fruit1.velocityY *= dampingFactor;
fruit2.velocityX *= dampingFactor;
fruit2.velocityY *= dampingFactor;
// Realistic elastic collision response
var relativeVelX = fruit1.velocityX - fruit2.velocityX;
var relativeVelY = fruit1.velocityY - fruit2.velocityY;
var normalVelX = dx / distance;
var normalVelY = dy / distance;
var separatingVel = relativeVelX * normalVelX + relativeVelY * normalVelY;
// Only resolve if objects are moving toward each other with sufficient velocity
if (separatingVel < -0.5) {
var restitution = 0.05; // Reduced bounciness factor
var impulse = -(1 + restitution) * separatingVel * 0.5; // Reduced impulse strength
var impulseX = impulse * normalVelX;
var impulseY = impulse * normalVelY;
fruit1.velocityX += impulseX;
fruit1.velocityY += impulseY;
fruit2.velocityX -= impulseX;
fruit2.velocityY -= impulseY;
// Add rotational effects from collision only if movement is significant and not in cramped conditions
var movementThreshold = 5.0; // Increased threshold to prevent micro-rotation
var totalImpulse = Math.abs(impulseX) + Math.abs(impulseY);
// Check if fruits are in cramped conditions by counting nearby fruits
var nearbyCount1 = 0;
var nearbyCount2 = 0;
for (var k = 0; k < fruits.length; k++) {
if (fruits[k] !== fruit1 && fruits[k] !== fruit2) {
var dist1 = Math.sqrt((fruit1.x - fruits[k].x) * (fruit1.x - fruits[k].x) + (fruit1.y - fruits[k].y) * (fruit1.y - fruits[k].y));
var dist2 = Math.sqrt((fruit2.x - fruits[k].x) * (fruit2.x - fruits[k].x) + (fruit2.y - fruits[k].y) * (fruit2.y - fruits[k].y));
if (dist1 < fruit1.getRadius() + fruits[k].getRadius() + 50) nearbyCount1++;
if (dist2 < fruit2.getRadius() + fruits[k].getRadius() + 50) nearbyCount2++;
}
}
// Only apply rotation if not cramped and movement is significant
if (totalImpulse > movementThreshold && nearbyCount1 < 3 && nearbyCount2 < 3) {
var rotationFactor = 0.008; // Reduced rotation factor
fruit1.angularVelocity += (impulseX + impulseY) * rotationFactor;
fruit2.angularVelocity -= (impulseX + impulseY) * rotationFactor;
}
}
}
}
function mergeFruits(fruit1, fruit2) {
if (fruit1.type !== fruit2.type || fruit1.merged || fruit2.merged) {
return false;
}
if (fruit1.type >= 20) {
return false;
} // Can't merge cranberry (highest fruit)
// Mark the new fruit type as discovered
var newlyDiscovered = !discoveredFruits[fruit1.type + 1];
discoveredFruits[fruit1.type + 1] = true;
// Save to storage
storage.discoveredFruits = discoveredFruits;
if (newlyDiscovered) {
// Show notification for unlocking new fruit
var evoFruitNames = ['Cherry', 'Strawberry', 'Grape', 'Orange', 'Apple', 'Pear', 'Peach', 'Pineapple', 'Melon', 'Watermelon', 'Lemon', 'Coconut', 'Dragonfruit', 'Kiwi', 'Mango', 'Papaya', 'Durian', 'Blueberry', 'Pomegranate', 'Avocado', 'Cranberry'];
var unlockedName = evoFruitNames[fruit1.type + 1];
var notification = new Text2("You've unlocked " + unlockedName + "!", {
size: 90,
fill: 0x222222
});
notification.anchor.set(0.5, 0.5);
notification.x = 2048 / 2;
notification.y = 900;
notification.alpha = 0.0;
game.addChild(notification);
// Fade in, hold, then fade out
tween(notification, {
alpha: 1
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
LK.setTimeout(function () {
tween(notification, {
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
notification.destroy();
}
});
}, 1200);
}
});
}
// Create new fruit
var newFruit = new Fruit(fruit1.type + 1);
newFruit.x = (fruit1.x + fruit2.x) / 2;
newFruit.y = (fruit1.y + fruit2.y) / 2;
// Inherit contactMade status from parent fruits
newFruit.contactMade = fruit1.contactMade || fruit2.contactMade;
// Only give score if neither fruit is from rain
if (!fruit1.isRainFruit && !fruit2.isRainFruit) {
// Calculate score with rainbow and gold multipliers
var scoreToAdd = newFruit.scoreValue;
var multiplier = 1;
var notificationText = '';
var notificationColor = 0x00ff00;
if (fruit1.isRainbow || fruit2.isRainbow) {
multiplier *= 5;
notificationText = 'RAINBOW x5!';
notificationColor = 0xff00ff;
}
if (fruit1.isGold || fruit2.isGold) {
multiplier *= 3;
if (notificationText) {
notificationText = 'RAINBOW + GOLD x15!';
notificationColor = 0xffd700;
} else {
notificationText = 'GOLD x3!';
notificationColor = 0xffd700;
}
}
if (fruit1.isCelestial || fruit2.isCelestial) {
multiplier *= 15;
if (notificationText) {
if (notificationText.includes('RAINBOW') && notificationText.includes('GOLD')) {
notificationText = 'RAINBOW + GOLD + CELESTIAL x225!';
notificationColor = 0x8a2be2;
} else if (notificationText.includes('RAINBOW')) {
notificationText = 'RAINBOW + CELESTIAL x75!';
notificationColor = 0x8a2be2;
} else if (notificationText.includes('GOLD')) {
notificationText = 'GOLD + CELESTIAL x45!';
notificationColor = 0x8a2be2;
} else {
notificationText = 'CELESTIAL x15!';
notificationColor = 0x8a2be2;
}
} else {
notificationText = 'CELESTIAL x15!';
notificationColor = 0x8a2be2;
}
}
scoreToAdd *= multiplier;
// Show multiplier notification if applicable
if (notificationText) {
var multiplierScoreText = new Text2(notificationText, {
size: 80,
fill: notificationColor
});
multiplierScoreText.anchor.set(0.5, 0.5);
multiplierScoreText.x = newFruit.x;
multiplierScoreText.y = newFruit.y - 100;
multiplierScoreText.alpha = 0;
game.addChild(multiplierScoreText);
// Animate multiplier score text
tween(multiplierScoreText, {
alpha: 1,
y: multiplierScoreText.y - 50
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(multiplierScoreText, {
alpha: 0,
y: multiplierScoreText.y - 50
}, {
duration: 500,
easing: tween.easeIn,
onFinish: function onFinish() {
multiplierScoreText.destroy();
}
});
}
});
}
// Add score
LK.setScore(LK.getScore() + scoreToAdd);
scoreTxt.setText('Score: ' + LK.getScore());
}
// Update merge counter
totalMerges++;
mergeCounterTxt.setText('Merges: ' + totalMerges);
storage.totalMerges = totalMerges;
// Mark old fruits as merged
fruit1.merged = true;
fruit2.merged = true;
// Remove old fruits
for (var i = fruits.length - 1; i >= 0; i--) {
if (fruits[i] === fruit1 || fruits[i] === fruit2) {
fruits[i].destroy();
fruits.splice(i, 1);
}
}
// Add new fruit
fruits.push(newFruit);
game.addChild(newFruit);
LK.getSound('merge').play();
// Flash effect
tween(newFruit, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(newFruit, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200,
easing: tween.easeIn
});
}
});
return true;
}
function performAutoMerge() {
var foundMerge = true;
// Keep merging until no more merges are possible
while (foundMerge) {
foundMerge = false;
// Check all fruit pairs for possible merges
for (var i = 0; i < fruits.length && !foundMerge; i++) {
for (var j = i + 1; j < fruits.length && !foundMerge; j++) {
var fruit1 = fruits[i];
var fruit2 = fruits[j];
// Skip if either fruit is already merged or different types
if (fruit1.merged || fruit2.merged || fruit1.type !== fruit2.type) {
continue;
}
// Skip if already at maximum fruit type (durian)
if (fruit1.type >= 16) {
continue;
}
// Auto merge merges ANY two fruits of the same type regardless of distance
// Perform the merge immediately
if (mergeFruits(fruit1, fruit2)) {
foundMerge = true;
break;
}
}
}
}
}
function checkGameOver() {
for (var i = 0; i < fruits.length; i++) {
var fruit = fruits[i];
// Only check game over for fruits that have made contact with ground or other fruits
if (fruit.contactMade && fruit.y - fruit.getRadius() < gameOverLine) {
return true;
}
}
return false;
}
// Fruit Mover button (leftmost)
var fruitMoverButton = new Text2('FRUIT MOVER!', {
size: 60,
fill: 0x000000
});
fruitMoverButton.anchor.set(0.5, 0.5);
// Place at far left
fruitMoverButton.x = 200;
fruitMoverButton.y = 2732 - 150;
game.addChild(fruitMoverButton);
// Cost text under fruit mover button
var fruitMoverCostText = new Text2('Cost: 125', {
size: 40,
fill: 0x000000
});
fruitMoverCostText.anchor.set(0.5, 0);
// Position just below the fruit mover button
fruitMoverCostText.x = fruitMoverButton.x;
fruitMoverCostText.y = fruitMoverButton.y + 60;
game.addChild(fruitMoverCostText);
// Randomize fruit button (second from left)
var randomizeButton = new Text2('RANDOMIZE!', {
size: 60,
fill: 0x000000
});
randomizeButton.anchor.set(0.5, 0.5);
// Place second from left
randomizeButton.x = 600;
randomizeButton.y = 2732 - 150;
game.addChild(randomizeButton);
// Cost text under randomize button
var randomizeCostText = new Text2('Cost: 300', {
size: 40,
fill: 0x000000
});
randomizeCostText.anchor.set(0.5, 0);
// Position just below the randomize button
randomizeCostText.x = randomizeButton.x;
randomizeCostText.y = randomizeButton.y + 60;
game.addChild(randomizeCostText);
// Shake button (center)
var shakeButton = new Text2('SHAKE!', {
size: 60,
fill: 0x000000
});
shakeButton.anchor.set(0.5, 0.5);
// Place at center
shakeButton.x = 2048 / 2;
shakeButton.y = 2732 - 150;
game.addChild(shakeButton);
// Cost text under shake button
var shakeCostText = new Text2('Cost: 450', {
size: 40,
fill: 0x000000
});
shakeCostText.anchor.set(0.5, 0);
// Position just below the shake button
shakeCostText.x = shakeButton.x;
shakeCostText.y = shakeButton.y + 60;
game.addChild(shakeCostText);
// Auto merge button (second from right)
var autoMergeButton = new Text2('AUTO MERGE!', {
size: 60,
fill: 0x000000
});
autoMergeButton.anchor.set(0.5, 0.5);
// Place second from right
autoMergeButton.x = 1448;
autoMergeButton.y = 2732 - 150;
game.addChild(autoMergeButton);
// Cost text under auto merge button
var autoMergeCostText = new Text2('Cost: 800', {
size: 40,
fill: 0x000000
});
autoMergeCostText.anchor.set(0.5, 0);
// Position just below the auto merge button
autoMergeCostText.x = autoMergeButton.x;
autoMergeCostText.y = autoMergeButton.y + 60;
game.addChild(autoMergeCostText);
// Fruit Rain button (rightmost)
var fruitRainButton = new Text2('FRUIT RAIN!', {
size: 60,
fill: 0x000000
});
fruitRainButton.anchor.set(0.5, 0.5);
// Place at far right
fruitRainButton.x = 1848;
fruitRainButton.y = 2732 - 150;
game.addChild(fruitRainButton);
// Cost text under fruit rain button
var fruitRainCostText = new Text2('Cost: 1000', {
size: 40,
fill: 0x000000
});
fruitRainCostText.anchor.set(0.5, 0);
// Position just below the fruit rain button
fruitRainCostText.x = fruitRainButton.x;
fruitRainCostText.y = fruitRainButton.y + 60;
game.addChild(fruitRainCostText);
// Add auto merge button click handler
autoMergeButton.down = function (x, y, obj) {
if (LK.getScore() >= 800 && fruits.length > 1) {
// Deduct score
LK.setScore(LK.getScore() - 800);
scoreTxt.setText('Score: ' + LK.getScore());
// Perform auto merge
performAutoMerge();
}
};
// Add randomize button click handler
randomizeButton.down = function (x, y, obj) {
if (LK.getScore() >= 300 && fruits.length > 1) {
// Deduct score
LK.setScore(LK.getScore() - 300);
scoreTxt.setText('Score: ' + LK.getScore());
// Store all current fruit positions
var positions = [];
for (var i = 0; i < fruits.length; i++) {
positions.push({
x: fruits[i].x,
y: fruits[i].y
});
}
// Shuffle the positions array
for (var i = positions.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = positions[i];
positions[i] = positions[j];
positions[j] = temp;
}
// Apply shuffled positions to fruits
for (var i = 0; i < fruits.length; i++) {
fruits[i].x = positions[i].x;
fruits[i].y = positions[i].y;
// Reset velocities to prevent physics issues
fruits[i].velocityX = 0;
fruits[i].velocityY = 0;
fruits[i].angularVelocity = 0;
}
}
};
// Add fruit rain button click handler
fruitRainButton.down = function (x, y, obj) {
if (LK.getScore() >= 1000) {
// Deduct score
LK.setScore(LK.getScore() - 1000);
scoreTxt.setText('Score: ' + LK.getScore());
// Create fruit rain effect for 3 seconds
var rainDuration = 3000;
var rainStartTime = LK.ticks;
fruitRainActive = true;
// Create fruits every 100ms (0.1 seconds) during the rain
var rainInterval = LK.setInterval(function () {
if (LK.ticks - rainStartTime >= rainDuration / (1000 / 60)) {
// 3 seconds have passed
LK.clearInterval(rainInterval);
fruitRainActive = false;
return;
}
// Create a random lesser fruit (types 0-4: cherry, strawberry, grape, orange, apple)
var randomType = Math.floor(Math.random() * 5);
var rainFruit = new Fruit(randomType);
// Mark this fruit as a rain fruit (won't give score)
rainFruit.isRainFruit = true;
// Random X position within container bounds
var fruitRadius = rainFruit.getRadius();
var containerLeft = containerX - containerWidth / 2;
var containerRight = containerX + containerWidth / 2;
var randomX = containerLeft + fruitRadius + Math.random() * (containerRight - containerLeft - 2 * fruitRadius);
// Start from above the container
var containerTop = containerY - containerHeight / 2;
rainFruit.x = randomX;
rainFruit.y = containerTop - 200 - Math.random() * 300; // Start higher with some randomness
// Add slight random horizontal velocity for more natural effect
rainFruit.velocityX = (Math.random() - 0.5) * 2;
rainFruit.velocityY = Math.random() * 2 + 1; // Small downward velocity
rainFruit.angularVelocity = (Math.random() - 0.5) * 0.1;
// Mark as discovered
discoveredFruits[randomType] = true;
storage.discoveredFruits = discoveredFruits;
fruits.push(rainFruit);
game.addChild(rainFruit);
}, 100); // Create fruit every 100ms
}
};
// Global variables for fruit mover mode
var fruitMoverActive = false;
var fruitMoverSelectedFruit = null;
// Global variable for fruit rain mode
var fruitRainActive = false;
// Tutorial system variables
var tutorialActive = true; // Always allow tutorial to be available
var tutorialStep = 0;
var tutorialOverlay = null;
var tutorialText = null;
var tutorialArrow = null;
var tutorialVisuals = null; // Container for visual demonstrations
var tutorialTimeouts = []; // Track all tutorial timeouts for cleanup
var tutorialSteps = [{
text: "Welcome to Fruit Drop! Tap and hold to aim your fruit, then release to drop it.",
target: null,
highlight: {
x: 2048 / 2,
y: dropLine,
width: containerWidth,
height: 200
},
visual: "dropDemo"
}, {
text: "When two fruits of the same type touch, they merge into a bigger fruit!",
target: null,
highlight: null,
visual: "mergeDemo"
}, {
text: "Your score is shown here. Merging gives you points!",
target: "score",
highlight: null,
visual: "scoreDemo"
}, {
text: "Check your next fruit here to plan your strategy.",
target: "nextFruit",
highlight: null,
visual: "nextFruitDemo"
}, {
text: "Don't let fruits pile up above this blue line or it's game over!",
target: null,
highlight: {
x: containerX,
y: gameOverLine,
width: containerWidth,
height: 10
},
visual: "gameOverDemo"
}, {
text: "Use power-ups at the bottom to help when you're stuck!",
target: "powerups",
highlight: {
x: 2048 / 2,
y: 2732 - 150,
width: 1800,
height: 150
},
visual: "powerupDemo"
}, {
text: "Tap EVOLUTION to see all fruits you can discover!",
target: "evolution",
highlight: null,
visual: "evolutionDemo"
}, {
text: "Tap SAVE to save your game progress in multiple slots!",
target: "save",
highlight: null,
visual: "saveDemo"
}, {
text: "Tap MUSIC to change the background music anytime!",
target: "music",
highlight: null,
visual: "musicDemo"
}, {
text: "Great! Now you're ready to play. Try dropping your first fruit!",
target: null,
highlight: {
x: 2048 / 2,
y: dropLine,
width: containerWidth,
height: 200
},
visual: "readyDemo"
}];
// Tutorial visual creation functions
function createTutorialVisuals(visualType) {
if (tutorialVisuals) {
// Clean up existing visuals
tween.stop(tutorialVisuals);
tutorialVisuals.destroy();
tutorialVisuals = null;
}
tutorialVisuals = new Container();
if (tutorialOverlay) {
tutorialOverlay.addChild(tutorialVisuals);
}
switch (visualType) {
case "dropDemo":
// Animate dropping motion
var _dropAnimation = function dropAnimation() {
tween(demoFruit, {
y: dropLine + 300
}, {
duration: 1000,
easing: tween.easeIn,
onFinish: function onFinish() {
// Reset position and repeat
demoFruit.y = dropLine - 100;
var dropTimeout = LK.setTimeout(function () {
if (tutorialVisuals && !tutorialVisuals.destroyed) {
_dropAnimation();
}
}, 500);
tutorialTimeouts.push(dropTimeout);
}
});
};
// Show animated fruit dropping
var demoFruit = LK.getAsset('cherry', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX,
y: dropLine - 100
});
tutorialVisuals.addChild(demoFruit);
_dropAnimation();
break;
case "mergeDemo":
var _mergeAnimation = function mergeAnimation() {
// Move cherries together
tween(cherry1, {
x: containerX - 20
}, {
duration: 800,
easing: tween.easeOut
});
tween(cherry2, {
x: containerX + 20
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
// Flash effect
tween(cherry1, {
tint: 0xffffff,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200
});
tween(cherry2, {
tint: 0xffffff,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
// Replace with strawberry
cherry1.destroy();
cherry2.destroy();
var strawberry = LK.getAsset('strawberry', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX,
y: containerY
});
if (tutorialVisuals) {
tutorialVisuals.addChild(strawberry);
}
// Celebration effect
tween(strawberry, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 300,
easing: tween.bounceOut,
onFinish: function onFinish() {
// Reset and repeat
var resetTimeout = LK.setTimeout(function () {
if (tutorialVisuals && !tutorialVisuals.destroyed) {
strawberry.destroy();
cherry1 = LK.getAsset('cherry', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX - 80,
y: containerY
});
cherry2 = LK.getAsset('cherry', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX + 80,
y: containerY
});
tutorialVisuals.addChild(cherry1);
tutorialVisuals.addChild(cherry2);
_mergeAnimation();
}
}, 1000);
tutorialTimeouts.push(resetTimeout);
}
});
}
});
}
});
};
// Show two cherries merging into strawberry
var cherry1 = LK.getAsset('cherry', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX - 80,
y: containerY
});
var cherry2 = LK.getAsset('cherry', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX + 80,
y: containerY
});
tutorialVisuals.addChild(cherry1);
tutorialVisuals.addChild(cherry2);
_mergeAnimation();
break;
case "scoreDemo":
var _scoreAnimation = function scoreAnimation() {
var targetScore = currentScore + 50;
var scoreInterval = LK.setInterval(function () {
currentScore += 2;
scoreDisplay.setText('Score: ' + currentScore);
if (currentScore >= targetScore) {
LK.clearInterval(scoreInterval);
var scoreResetTimeout = LK.setTimeout(function () {
if (tutorialVisuals && !tutorialVisuals.destroyed) {
currentScore = 0;
_scoreAnimation();
}
}, 1000);
tutorialTimeouts.push(scoreResetTimeout);
}
}, 50);
};
// Show animated score counting up
var scoreDisplay = new Text2('Score: 0', {
size: 60,
fill: 0x00ff00
});
scoreDisplay.anchor.set(0.5, 0.5);
scoreDisplay.x = containerX;
scoreDisplay.y = containerY - 200;
tutorialVisuals.addChild(scoreDisplay);
var currentScore = 0;
_scoreAnimation();
break;
case "nextFruitDemo":
var _pulseNext = function pulseNext() {
tween(nextDemo, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0.8
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(nextDemo, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: _pulseNext
});
}
});
};
// Show next fruit preview with pulsing effect
var nextDemo = LK.getAsset('strawberry', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX + 300,
y: containerY - 300
});
tutorialVisuals.addChild(nextDemo);
_pulseNext();
break;
case "gameOverDemo":
var _loop2 = function _loop2() {
pileFruit = LK.getAsset('orange', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX + (i - 1) * 80,
y: gameOverLine - 50 - i * 60
});
pileFruit.tint = 0xff4444; // Red tint to show danger
tutorialVisuals.addChild(pileFruit);
// Shake animation
function shakeAnimation(fruit) {
tween(fruit, {
x: fruit.x + 10
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(fruit, {
x: fruit.x - 10
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
shakeAnimation(fruit);
}
});
}
});
}
shakeAnimation(pileFruit);
},
pileFruit;
// Show fruits piling up above the line
for (var i = 0; i < 3; i++) {
_loop2();
}
break;
case "powerupDemo":
var _miniShake = function miniShake() {
for (var i = 0; i < shakeFruits.length; i++) {
tween(shakeFruits[i], {
x: shakeFruits[i].x + Math.random() * 40 - 20,
y: shakeFruits[i].y + Math.random() * 40 - 20
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (i === shakeFruits.length - 1) {
var shakeTimeout = LK.setTimeout(function () {
if (tutorialVisuals && !tutorialVisuals.destroyed) {
_miniShake();
}
}, 1500);
tutorialTimeouts.push(shakeTimeout);
}
}
});
}
};
// Show mini versions of power-up effects
var demoContainer = new Container();
demoContainer.x = containerX;
demoContainer.y = containerY;
tutorialVisuals.addChild(demoContainer);
// Mini shake effect
var shakeFruits = [];
for (var i = 0; i < 4; i++) {
var shakeFruit = LK.getAsset('grape', {
anchorX: 0.5,
anchorY: 0.5,
x: (i - 1.5) * 60,
y: 0,
width: 40,
height: 40
});
demoContainer.addChild(shakeFruit);
shakeFruits.push(shakeFruit);
}
_miniShake();
break;
case "evolutionDemo":
// Glow effect on fruits
var _glowEvolution = function glowEvolution() {
for (var i = 0; i < tutorialVisuals.children.length; i++) {
var child = tutorialVisuals.children[i];
if (child !== tutorialVisuals.children[0]) {
// Skip first child
tween(child, {
tint: 0xffff88
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(child, {
tint: 0xffffff
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
}
}
var glowTimeout = LK.setTimeout(function () {
if (tutorialVisuals && !tutorialVisuals.destroyed) {
_glowEvolution();
}
}, 1600);
tutorialTimeouts.push(glowTimeout);
};
// Show fruit evolution chain
var evolutionChain = ['cherry', 'strawberry', 'grape'];
for (var i = 0; i < evolutionChain.length; i++) {
var evoFruit = LK.getAsset(evolutionChain[i], {
anchorX: 0.5,
anchorY: 0.5,
x: containerX + (i - 1) * 120,
y: containerY - 100,
width: 80,
height: 80
});
tutorialVisuals.addChild(evoFruit);
if (i > 0) {
// Add arrow
var arrow = LK.getAsset('orangeCircle', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX + (i - 1.5) * 120,
y: containerY - 100,
width: 30,
height: 30
});
arrow.tint = 0xffaa00;
tutorialVisuals.addChild(arrow);
}
}
_glowEvolution();
break;
case "saveDemo":
var _saveAnimation = function saveAnimation() {
tween(saveIcon, {
scaleX: 1.2,
scaleY: 1.2,
tint: 0x4CAF50
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(saveIcon, {
scaleX: 1.0,
scaleY: 1.0,
tint: 0xffffff
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
var saveTimeout = LK.setTimeout(function () {
if (tutorialVisuals && !tutorialVisuals.destroyed) {
_saveAnimation();
}
}, 1000);
tutorialTimeouts.push(saveTimeout);
}
});
}
});
};
// Show save icon with slots
var saveIcon = LK.getAsset('container', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX,
y: containerY - 100,
width: 200,
height: 150
});
saveIcon.tint = 0xdddddd;
tutorialVisuals.addChild(saveIcon);
// Add slot indicators
for (var i = 0; i < 3; i++) {
var slot = LK.getAsset('nextFruit', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX + (i - 1) * 80,
y: containerY + 50,
width: 60,
height: 60
});
slot.tint = 0x4CAF50;
tutorialVisuals.addChild(slot);
}
_saveAnimation();
break;
case "musicDemo":
var _musicAnimation = function musicAnimation() {
for (var i = 0; i < musicNotes.length; i++) {
tween(musicNotes[i], {
y: musicNotes[i].y - 30,
alpha: 1
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(this, {
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
this.y = containerY + 100;
this.alpha = 0.8;
}
});
}
});
}
var musicTimeout = LK.setTimeout(function () {
if (tutorialVisuals && !tutorialVisuals.destroyed) {
_musicAnimation();
}
}, 1200);
tutorialTimeouts.push(musicTimeout);
};
// Show musical notes animation
var musicNotes = [];
for (var i = 0; i < 5; i++) {
var note = LK.getAsset('nextFruit', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX + (i - 2) * 60,
y: containerY + 100,
width: 30,
height: 30
});
note.tint = 0xff9800;
note.alpha = 0.8;
tutorialVisuals.addChild(note);
musicNotes.push(note);
}
_musicAnimation();
break;
case "readyDemo":
var _encourageAnimation = function encourageAnimation() {
tween(readyFruit, {
scaleX: 1.3,
scaleY: 1.3,
rotation: 0.2
}, {
duration: 400,
easing: tween.bounceOut,
onFinish: function onFinish() {
tween(readyFruit, {
scaleX: 1.0,
scaleY: 1.0,
rotation: -0.2
}, {
duration: 400,
easing: tween.bounceOut,
onFinish: function onFinish() {
tween(readyFruit, {
rotation: 0
}, {
duration: 200,
onFinish: function onFinish() {
var encourageTimeout = LK.setTimeout(function () {
if (tutorialVisuals && !tutorialVisuals.destroyed) {
_encourageAnimation();
}
}, 1000);
tutorialTimeouts.push(encourageTimeout);
}
});
}
});
}
});
};
// Show encouraging "thumbs up" style visual
var readyFruit = LK.getAsset('apple', {
anchorX: 0.5,
anchorY: 0.5,
x: containerX,
y: containerY
});
tutorialVisuals.addChild(readyFruit);
_encourageAnimation();
break;
}
}
// Tutorial functions
function createTutorialOverlay() {
if (tutorialOverlay) {
tutorialOverlay.destroy();
}
tutorialOverlay = new Container();
game.addChild(tutorialOverlay);
// Semi-transparent background
var tutorialBg = LK.getAsset('container', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
tutorialBg.alpha = 0.7;
tutorialBg.tint = 0x000000;
tutorialOverlay.addChild(tutorialBg);
// Tutorial text background
var textBg = LK.getAsset('container', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 - 300,
width: 1800,
height: 250
});
textBg.alpha = 0.95;
textBg.tint = 0xffffff;
tutorialOverlay.addChild(textBg);
// Tutorial text
tutorialText = new Text2('', {
size: 50,
fill: 0x000000
});
tutorialText.anchor.set(0.5, 0.5);
tutorialText.x = 2048 / 2;
tutorialText.y = 2732 - 300;
tutorialOverlay.addChild(tutorialText);
// Next button
var nextButton = new Text2('NEXT', {
size: 60,
fill: 0xffffff
});
nextButton.anchor.set(0.5, 0.5);
nextButton.x = 2048 / 2 + 600;
nextButton.y = 2732 - 200;
var nextButtonBg = LK.getAsset('container', {
anchorX: 0.5,
anchorY: 0.5,
x: nextButton.x,
y: nextButton.y,
width: 200,
height: 80
});
nextButtonBg.tint = 0x4CAF50;
tutorialOverlay.addChild(nextButtonBg);
tutorialOverlay.addChild(nextButton);
nextButton.down = function () {
nextTutorialStep();
};
// Skip button
var skipButton = new Text2('SKIP', {
size: 60,
fill: 0xffffff
});
skipButton.anchor.set(0.5, 0.5);
skipButton.x = 2048 / 2 - 600;
skipButton.y = 2732 - 200;
var skipButtonBg = LK.getAsset('container', {
anchorX: 0.5,
anchorY: 0.5,
x: skipButton.x,
y: skipButton.y,
width: 200,
height: 80
});
skipButtonBg.tint = 0xf44336;
tutorialOverlay.addChild(skipButtonBg);
tutorialOverlay.addChild(skipButton);
skipButton.down = function () {
completeTutorial();
};
// Store references to buttons for repositioning
tutorialOverlay.nextButton = nextButton;
tutorialOverlay.nextButtonBg = nextButtonBg;
tutorialOverlay.skipButton = skipButton;
tutorialOverlay.skipButtonBg = skipButtonBg;
}
function showTutorialStep() {
if (!tutorialActive || tutorialStep >= tutorialSteps.length) {
completeTutorial();
return;
}
createTutorialOverlay();
var step = tutorialSteps[tutorialStep];
tutorialText.setText(step.text);
// Determine tutorial text position based on content and targets
var textBg = tutorialOverlay.children[1]; // Text background
var textX = 2048 / 2;
var textY = 2732 - 300;
// Move tutorial text based on what we're explaining
if (step.target === "score") {
textX = 2048 / 2;
textY = 400; // Move down when pointing to score at top
} else if (step.target === "nextFruit") {
textX = 1024; // Move left when pointing to next fruit on right
textY = 400;
} else if (step.target === "evolution") {
textX = 2048 / 2;
textY = 500; // Move down when pointing to evolution button
} else if (step.target === "powerups") {
textX = 2048 / 2;
textY = 2732 - 600; // Move up when pointing to powerups at bottom
} else if (step.highlight && step.highlight.y < 1000) {
textY = 2000; // Move to bottom if highlighting something at top
} else if (step.highlight && step.highlight.y > 2000) {
textY = 800; // Move to top if highlighting something at bottom
}
// Animate tutorial text and background to new position
tween(textBg, {
x: textX,
y: textY
}, {
duration: 500,
easing: tween.easeOut
});
tween(tutorialText, {
x: textX,
y: textY
}, {
duration: 500,
easing: tween.easeOut
});
// Move tutorial buttons to follow the text
var buttonY = textY + 100;
if (tutorialOverlay.nextButton) {
tween(tutorialOverlay.nextButton, {
x: textX + 300,
y: buttonY
}, {
duration: 500,
easing: tween.easeOut
});
tween(tutorialOverlay.nextButtonBg, {
x: textX + 300,
y: buttonY
}, {
duration: 500,
easing: tween.easeOut
});
}
if (tutorialOverlay.skipButton) {
tween(tutorialOverlay.skipButton, {
x: textX - 300,
y: buttonY
}, {
duration: 500,
easing: tween.easeOut
});
tween(tutorialOverlay.skipButtonBg, {
x: textX - 300,
y: buttonY
}, {
duration: 500,
easing: tween.easeOut
});
}
// Add visual demonstration if specified
if (step.visual) {
createTutorialVisuals(step.visual);
}
// Add highlight if specified
if (step.highlight) {
// Pulsing animation
var pulseUp = function pulseUp() {
tween(highlight, {
alpha: 0.3,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: pulseDown
});
};
var pulseDown = function pulseDown() {
tween(highlight, {
alpha: 0.1,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: pulseUp
});
};
var highlight = LK.getAsset('container', {
anchorX: 0.5,
anchorY: 0.5,
x: step.highlight.x,
y: step.highlight.y,
width: step.highlight.width,
height: step.highlight.height
});
highlight.alpha = 0;
highlight.tint = 0xffff00;
tutorialOverlay.addChild(highlight);
pulseUp();
}
// Create arrow pointing to target
if (step.target) {
tutorialArrow = LK.getAsset('orangeCircle', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 80
});
tutorialArrow.tint = 0xff9800;
var targetPos = getTutorialTargetPosition(step.target);
if (targetPos) {
var bounceUp = function bounceUp() {
tween(tutorialArrow, {
y: originalY - 20
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: bounceDown
});
};
var bounceDown = function bounceDown() {
tween(tutorialArrow, {
y: originalY
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: bounceUp
});
};
tutorialArrow.x = targetPos.x;
tutorialArrow.y = targetPos.y - 120;
tutorialOverlay.addChild(tutorialArrow);
// Bouncing animation
var originalY = tutorialArrow.y;
bounceUp();
// Animate arrow sliding in from the side based on target
var startX = tutorialArrow.x;
if (step.target === "nextFruit") {
tutorialArrow.x = 2048 + 100; // Start from right side
} else if (step.target === "score" || step.target === "evolution") {
tutorialArrow.x = -100; // Start from left side
} else {
tutorialArrow.x = 2048 / 2; // Start from center
}
tween(tutorialArrow, {
x: startX
}, {
duration: 600,
easing: tween.bounceOut
});
}
}
}
function getTutorialTargetPosition(target) {
switch (target) {
case "score":
return {
x: scoreTxt.x,
y: 150
};
case "nextFruit":
return {
x: 1700,
y: 120
};
case "evolution":
return {
x: evolutionButton.x,
y: 200
};
case "save":
return {
x: saveMenuButton.x,
y: 200
};
case "music":
return {
x: musicMenuButton.x,
y: 200
};
case "powerups":
return {
x: 2048 / 2,
y: 2732 - 150
};
default:
return null;
}
}
function nextTutorialStep() {
// Clear all tutorial timeouts first
for (var i = 0; i < tutorialTimeouts.length; i++) {
LK.clearTimeout(tutorialTimeouts[i]);
}
tutorialTimeouts = [];
// Clean up current tutorial visuals before moving to next step
if (tutorialVisuals) {
// Stop all tweens on tutorialVisuals and its children recursively
var _stopAllTweensRecursive = function stopAllTweensRecursive(container) {
if (!container) return;
tween.stop(container);
if (container.children && container.children.length > 0) {
for (var i = 0; i < container.children.length; i++) {
if (container.children[i]) {
_stopAllTweensRecursive(container.children[i]);
}
}
}
};
_stopAllTweensRecursive(tutorialVisuals);
// Clear any pending timeouts or intervals that might recreate objects
// Force immediate cleanup by removing from parent first
if (tutorialVisuals.parent) {
tutorialVisuals.parent.removeChild(tutorialVisuals);
}
// Always destroy tutorialVisuals regardless of parent state
tutorialVisuals.destroy();
tutorialVisuals = null;
}
tutorialStep++;
if (tutorialStep >= tutorialSteps.length) {
completeTutorial();
} else {
showTutorialStep();
}
}
function completeTutorial() {
tutorialActive = false;
storage.tutorialCompleted = true;
// Clear all tutorial timeouts
for (var i = 0; i < tutorialTimeouts.length; i++) {
LK.clearTimeout(tutorialTimeouts[i]);
}
tutorialTimeouts = [];
if (tutorialVisuals) {
tween.stop(tutorialVisuals);
tutorialVisuals.destroy();
tutorialVisuals = null;
}
if (tutorialOverlay) {
tutorialOverlay.destroy();
tutorialOverlay = null;
}
gameRunning = true;
fruitsFrozen = false;
}
// Tutorial will start after music selection instead of here
// Add fruit mover button click handler
fruitMoverButton.down = function (x, y, obj) {
if (LK.getScore() >= 125 && fruits.length > 0) {
// Deduct score
LK.setScore(LK.getScore() - 125);
scoreTxt.setText('Score: ' + LK.getScore());
// Activate fruit mover mode
fruitMoverActive = true;
fruitMoverSelectedFruit = null;
// Make all fruits green when fruit mover is used
for (var i = 0; i < fruits.length; i++) {
tween(fruits[i], {
tint: 0x00ff00
}, {
duration: 300,
easing: tween.easeOut
});
}
}
};
// Add shake button click handler
shakeButton.down = function (x, y, obj) {
if (LK.getScore() >= 450) {
// Deduct score
LK.setScore(LK.getScore() - 450);
scoreTxt.setText('Score: ' + LK.getScore());
// Move all fruits to random positions within container bounds
for (var i = 0; i < fruits.length; i++) {
var fruit = fruits[i];
var radius = fruit.getRadius();
var containerLeft = containerX - containerWidth / 2;
var containerRight = containerX + containerWidth / 2;
var containerTop = containerY - containerHeight / 2;
var containerBottom = containerY + containerHeight / 2;
// Calculate valid random position within container bounds
var randomX = containerLeft + radius + Math.random() * (containerRight - containerLeft - 2 * radius);
var randomY = containerTop + radius + Math.random() * (containerBottom - containerTop - 2 * radius);
// Animate fruit to new position with tween
tween(fruit, {
x: randomX,
y: randomY
}, {
duration: 500 + Math.random() * 500,
// Random duration between 500-1000ms
easing: tween.easeOut
});
// Reset velocities to prevent physics issues
fruit.velocityX = 0;
fruit.velocityY = 0;
fruit.angularVelocity = 0;
}
}
};
// Do not spawn a fruit at start; only spawn on click/hold
createNextFruit();
// --- Evolution Button and Menu ---
// Evolution button
var evolutionButton = new Text2('EVOLUTION', {
size: 60,
fill: 0x000000
});
evolutionButton.anchor.set(0.5, 0);
// Place directly under the score display, using LK.gui for vertical stacking
evolutionButton.y = scoreTxt.height + 20;
LK.gui.top.addChild(evolutionButton);
// Evolution menu container (hidden by default)
var evolutionMenu = new Container();
evolutionMenu.visible = false;
// Add evolution menu to the front (highest z-index)
game.addChild(evolutionMenu);
// Semi-transparent background for menu
var menuBg = LK.getAsset('container', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
width: 1400,
height: 1400
});
menuBg.alpha = 0.92;
menuBg.tint = 0xeeeeee;
evolutionMenu.addChild(menuBg);
// Title
var evolutionTitle = new Text2('Fruit Evolution', {
size: 90,
fill: 0x222222,
stroke: 0xffffff,
strokeThickness: 4
});
evolutionTitle.anchor.set(0.5, 0);
evolutionTitle.x = 2048 / 2;
evolutionTitle.y = 2732 / 2 - 650;
evolutionMenu.addChild(evolutionTitle);
// Fruit evolution data
var evoFruitAssets = ['cherry', 'strawberry', 'grape', 'orange', 'apple', 'pear', 'peach', 'pineapple', 'melon', 'watermelon', 'lemon', 'coconut', 'dragon', 'kiwi', 'mango', 'papaya', 'durian', 'blueberry', 'pomegranate', 'avocado', 'cranberry'];
var evoFruitNames = ['Cherry', 'Strawberry', 'Grape', 'Orange', 'Apple', 'Pear', 'Peach', 'Pineapple', 'Melon', 'Watermelon', 'Lemon', 'Coconut', 'Dragonfruit', 'Kiwi', 'Mango', 'Papaya', 'Durian', 'Blueberry', 'Pomegranate', 'Avocado', 'Cranberry'];
var evoFruitSizes = [120, 160, 200, 240, 280, 320, 360, 400, 440, 480, 520, 560, 600, 640, 580, 620, 660, 380, 700];
// Draw fruits in a circle
var centerX = 2048 / 2;
var centerY = 2732 / 2 + 80;
var radius = 500;
for (var i = 0; i < evoFruitAssets.length; i++) {
var angle = Math.PI * 2 / evoFruitAssets.length * i - Math.PI / 2;
var fruitAsset = LK.getAsset(evoFruitAssets[i], {
anchorX: 0.5,
anchorY: 0.5
});
// Make all fruits the same size as the strawberry (160x160)
var strawberrySize = 160;
var fruitRadius = strawberrySize / 2;
fruitAsset.width = strawberrySize;
fruitAsset.height = strawberrySize;
fruitAsset.x = centerX + Math.cos(angle) * radius;
fruitAsset.y = centerY + Math.sin(angle) * radius;
// Black out and hide text for undiscovered fruits
if (!discoveredFruits[i]) {
fruitAsset.tint = 0x222222;
fruitAsset.alpha = 0.7;
}
evolutionMenu.addChild(fruitAsset);
// Fruit name label
var nameLabel = new Text2(discoveredFruits[i] ? evoFruitNames[i] : '', {
size: 38,
fill: 0x333333,
stroke: 0xffffff,
strokeThickness: 3
});
nameLabel.anchor.set(0.5, 1);
nameLabel.x = fruitAsset.x;
nameLabel.y = fruitAsset.y - fruitRadius - 10;
evolutionMenu.addChild(nameLabel);
// Draw arrow to next fruit (except last)
if (i < evoFruitAssets.length - 1) {
var nextAngle = Math.PI * 2 / evoFruitAssets.length * (i + 1) - Math.PI / 2;
var arrowX1 = fruitAsset.x + Math.cos(angle) * (fruitRadius + 20);
var arrowY1 = fruitAsset.y + Math.sin(angle) * (fruitRadius + 20);
var arrowX2 = centerX + Math.cos(nextAngle) * (radius - 40);
var arrowY2 = centerY + Math.sin(nextAngle) * (radius - 40);
// Use a small fruit as an arrow "dot"
var arrowDot = LK.getAsset('nextFruit', {
anchorX: 0.5,
anchorY: 0.5,
x: (arrowX1 + arrowX2) / 2,
y: (arrowY1 + arrowY2) / 2,
width: 32,
height: 32
});
arrowDot.tint = 0xaaaaaa;
evolutionMenu.addChild(arrowDot);
}
}
// Show menu on button press
evolutionButton.down = function (x, y, obj) {
// If menu is open, close it
if (evolutionMenu.visible) {
evolutionMenu.visible = false;
gameRunning = true;
fruitsFrozen = false;
return;
}
// Otherwise, update and open the menu
var childIdx = 0;
for (var i = 0; i < evoFruitAssets.length; i++) {
// Fruit asset is first, label is second, then arrow dot (if not last)
var fruitAsset = evolutionMenu.children[childIdx + 2]; // skip bg and title
var nameLabel = evolutionMenu.children[childIdx + 3];
if (!discoveredFruits[i]) {
fruitAsset.tint = 0x222222;
fruitAsset.alpha = 0.7;
nameLabel.setText('');
} else {
fruitAsset.tint = 0xffffff;
fruitAsset.alpha = 1.0;
nameLabel.setText(evoFruitNames[i]);
// Text2 stroke properties are set directly, not through style object
nameLabel.stroke = 0xffffff;
nameLabel.strokeThickness = 3;
}
childIdx += 2;
if (i < evoFruitAssets.length - 1) childIdx++; // skip arrow dot
}
evolutionMenu.visible = true;
// Move evolution menu to front to ensure it's on top of all game elements
game.removeChild(evolutionMenu);
game.addChild(evolutionMenu);
// Optionally, darken background or disable gameplay while menu is open
gameRunning = false;
fruitsFrozen = true;
};
// Music menu button
var musicMenuButton = new Text2('MUSIC', {
size: 60,
fill: 0x000000
});
musicMenuButton.anchor.set(0.5, 0);
musicMenuButton.x = evolutionButton.x + 300;
musicMenuButton.y = evolutionButton.y;
LK.gui.top.addChild(musicMenuButton);
// Save menu button
var saveMenuButton = new Text2('SAVE', {
size: 60,
fill: 0x000000
});
saveMenuButton.anchor.set(0.5, 0);
saveMenuButton.x = evolutionButton.x - 300;
saveMenuButton.y = musicMenuButton.y;
LK.gui.top.addChild(saveMenuButton);
// Music menu container
var musicMenu = new Container();
musicMenu.visible = false;
game.addChild(musicMenu);
// Save menu container
var saveMenu = new Container();
saveMenu.visible = false;
game.addChild(saveMenu);
// Save menu background
var saveMenuBg = LK.getAsset('container', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
width: 1600,
height: 1000
});
saveMenuBg.alpha = 0.95;
saveMenuBg.tint = 0xdddddd;
saveMenu.addChild(saveMenuBg);
// Save menu title
var saveMenuTitle = new Text2('Save & Load Game', {
size: 80,
fill: 0x222222,
stroke: 0xffffff,
strokeThickness: 3
});
saveMenuTitle.anchor.set(0.5, 0);
saveMenuTitle.x = 2048 / 2;
saveMenuTitle.y = 2732 / 2 - 400;
saveMenu.addChild(saveMenuTitle);
// Save game data function
function saveGameData(slotNumber) {
// Save basic game data directly to storage to avoid nested objects
var slotPrefix = 'slot' + slotNumber + '_';
storage[slotPrefix + 'savedGameScore'] = LK.getScore();
storage[slotPrefix + 'savedGameTotalMerges'] = totalMerges;
storage[slotPrefix + 'savedGameDiscoveredFruits'] = discoveredFruits;
storage[slotPrefix + 'savedGameCurrentMusicIndex'] = currentMusicIndex;
storage[slotPrefix + 'savedGameTutorialCompleted'] = storage.tutorialCompleted || false;
storage[slotPrefix + 'savedGameTime'] = Date.now();
// Save fruits data as separate flat arrays to avoid nested objects
var fruitTypes = [];
var fruitXPositions = [];
var fruitYPositions = [];
var fruitIsRainbow = [];
var fruitIsRainFruit = [];
var fruitIsGold = [];
var fruitIsCelestial = [];
for (var i = 0; i < fruits.length; i++) {
fruitTypes.push(fruits[i].type);
fruitXPositions.push(fruits[i].x);
fruitYPositions.push(fruits[i].y);
fruitIsRainbow.push(fruits[i].isRainbow || false);
fruitIsRainFruit.push(fruits[i].isRainFruit || false);
fruitIsGold.push(fruits[i].isGold || false);
fruitIsCelestial.push(fruits[i].isCelestial || false);
}
storage[slotPrefix + 'savedGameFruitTypes'] = fruitTypes;
storage[slotPrefix + 'savedGameFruitXPositions'] = fruitXPositions;
storage[slotPrefix + 'savedGameFruitYPositions'] = fruitYPositions;
storage[slotPrefix + 'savedGameFruitIsRainbow'] = fruitIsRainbow;
storage[slotPrefix + 'savedGameFruitIsRainFruit'] = fruitIsRainFruit;
storage[slotPrefix + 'savedGameFruitIsGold'] = fruitIsGold;
storage[slotPrefix + 'savedGameFruitIsCelestial'] = fruitIsCelestial;
return {
score: storage[slotPrefix + 'savedGameScore'],
totalMerges: storage[slotPrefix + 'savedGameTotalMerges'],
discoveredFruits: storage[slotPrefix + 'savedGameDiscoveredFruits'],
currentMusicIndex: storage[slotPrefix + 'savedGameCurrentMusicIndex'],
tutorialCompleted: storage[slotPrefix + 'savedGameTutorialCompleted']
};
}
// Helper function to check if a save slot has data
function hasSlotData(slotNumber) {
var slotPrefix = 'slot' + slotNumber + '_';
return storage[slotPrefix + 'savedGameScore'] !== undefined;
}
// Load game data function
function loadGameData(slotNumber) {
// Check if we have saved game data for this slot
var slotPrefix = 'slot' + slotNumber + '_';
if (storage[slotPrefix + 'savedGameScore'] === undefined) return false;
// Restore score and counters
LK.setScore(storage[slotPrefix + 'savedGameScore'] || 0);
scoreTxt.setText('Score: ' + LK.getScore());
totalMerges = storage[slotPrefix + 'savedGameTotalMerges'] || 0;
mergeCounterTxt.setText('Merges: ' + totalMerges);
// Restore discovered fruits
if (storage[slotPrefix + 'savedGameDiscoveredFruits']) {
discoveredFruits = storage[slotPrefix + 'savedGameDiscoveredFruits'];
}
// Restore music selection
if (storage[slotPrefix + 'savedGameCurrentMusicIndex'] !== undefined) {
currentMusicIndex = storage[slotPrefix + 'savedGameCurrentMusicIndex'];
}
// Clear existing fruits
for (var i = fruits.length - 1; i >= 0; i--) {
fruits[i].destroy();
fruits.splice(i, 1);
}
// Restore fruits from flat arrays
var fruitTypes = storage[slotPrefix + 'savedGameFruitTypes'] || [];
var fruitXPositions = storage[slotPrefix + 'savedGameFruitXPositions'] || [];
var fruitYPositions = storage[slotPrefix + 'savedGameFruitYPositions'] || [];
var fruitIsRainbow = storage[slotPrefix + 'savedGameFruitIsRainbow'] || [];
var fruitIsRainFruit = storage[slotPrefix + 'savedGameFruitIsRainFruit'] || [];
var fruitIsGold = storage[slotPrefix + 'savedGameFruitIsGold'] || [];
var fruitIsCelestial = storage[slotPrefix + 'savedGameFruitIsCelestial'] || [];
if (fruitTypes.length > 0) {
for (var i = 0; i < fruitTypes.length; i++) {
var newFruit = new Fruit(fruitTypes[i]);
newFruit.x = fruitXPositions[i] || 0;
newFruit.y = fruitYPositions[i] || 0;
newFruit.isRainbow = fruitIsRainbow[i] || false;
newFruit.isRainFruit = fruitIsRainFruit[i] || false;
newFruit.isGold = fruitIsGold[i] || false;
newFruit.isCelestial = fruitIsCelestial[i] || false;
// Restore rainbow visual effects if this fruit is rainbow
if (newFruit.isRainbow) {
newFruit.rainbowTintIndex = 0;
newFruit.rainbowColors = [0xff0000, 0xff8000, 0xffff00, 0x00ff00, 0x0080ff, 0x8000ff];
var fruitGraphics = newFruit.children[0];
if (fruitGraphics) {
fruitGraphics.tint = newFruit.rainbowColors[0];
}
}
// Restore celestial visual effects if this fruit is celestial
if (newFruit.isCelestial) {
var fruitGraphics = newFruit.children[0];
if (fruitGraphics) {
fruitGraphics.tint = 0x8a2be2; // Purple color
}
// Recreate celestial ring using celestial ring asset
newFruit.celestialRing = newFruit.attachAsset('celestialRing', {
anchorX: 0.5,
anchorY: 0.5
});
newFruit.celestialRing.width = newFruit.size + 80;
newFruit.celestialRing.height = newFruit.size + 80;
newFruit.celestialRing.alpha = 0.6;
newFruit.addChildAt(newFruit.celestialRing, 0); // Add behind the fruit
}
// Restore gold visual effects if this fruit is gold
if (newFruit.isGold) {
var fruitGraphics = newFruit.children[0];
if (fruitGraphics) {
fruitGraphics.tint = 0xffd700; // Gold color
}
newFruit.shines = [];
// Recreate shine effects around the fruit
for (var s = 0; s < 3; s++) {
var shine = newFruit.attachAsset('shine', {
anchorX: 0.5,
anchorY: 0.5
});
shine.alpha = 0;
newFruit.shines.push(shine);
}
}
newFruit.velocityX = 0;
newFruit.velocityY = 0;
newFruit.angularVelocity = 0;
fruits.push(newFruit);
game.addChild(newFruit);
}
}
return true;
}
// Create save slots (3 slots)
var saveSlots = [];
for (var slotNum = 1; slotNum <= 3; slotNum++) {
// Save slot button
var saveSlotButton = new Text2('SLOT ' + slotNum, {
size: 50,
fill: 0xffffff
});
saveSlotButton.anchor.set(0.5, 0.5);
saveSlotButton.x = 2048 / 2 - 400 + (slotNum - 1) * 400;
saveSlotButton.y = 2732 / 2 - 150;
saveSlotButton.slotNumber = slotNum;
var saveSlotButtonBg = LK.getAsset('container', {
anchorX: 0.5,
anchorY: 0.5,
x: saveSlotButton.x,
y: saveSlotButton.y,
width: 320,
height: 80
});
saveSlotButtonBg.tint = 0x4CAF50;
saveMenu.addChild(saveSlotButtonBg);
saveMenu.addChild(saveSlotButton);
// Save slot status text
var saveSlotStatus = new Text2(hasSlotData(slotNum) ? 'HAS DATA' : 'EMPTY', {
size: 30,
fill: hasSlotData(slotNum) ? 0x00aa00 : 0xaaaaaa
});
saveSlotStatus.anchor.set(0.5, 0.5);
saveSlotStatus.x = saveSlotButton.x;
saveSlotStatus.y = saveSlotButton.y - 40;
saveMenu.addChild(saveSlotStatus);
// Load slot button
var loadSlotButton = new Text2('LOAD', {
size: 40,
fill: 0xffffff
});
loadSlotButton.anchor.set(0.5, 0.5);
loadSlotButton.x = saveSlotButton.x;
loadSlotButton.y = saveSlotButton.y + 150;
loadSlotButton.slotNumber = slotNum;
var loadSlotButtonBg = LK.getAsset('container', {
anchorX: 0.5,
anchorY: 0.5,
x: loadSlotButton.x,
y: loadSlotButton.y,
width: 260,
height: 70
});
loadSlotButtonBg.tint = hasSlotData(slotNum) ? 0x2196F3 : 0x666666;
saveMenu.addChild(loadSlotButtonBg);
saveMenu.addChild(loadSlotButton);
// Store references for updating
saveSlots.push({
saveButton: saveSlotButton,
saveButtonBg: saveSlotButtonBg,
statusText: saveSlotStatus,
loadButton: loadSlotButton,
loadButtonBg: loadSlotButtonBg,
slotNumber: slotNum
});
// Save button click handler
saveSlotButton.down = function () {
var slotNum = this.slotNumber;
var gameData = saveGameData(slotNum);
// Update slot status
for (var i = 0; i < saveSlots.length; i++) {
if (saveSlots[i].slotNumber === slotNum) {
saveSlots[i].statusText.setText('HAS DATA');
saveSlots[i].statusText.fill = 0x00aa00;
saveSlots[i].loadButtonBg.tint = 0x2196F3;
break;
}
}
// Show confirmation
var confirmText = new Text2('Saved to Slot ' + slotNum + '!', {
size: 40,
fill: 0x00aa00
});
confirmText.anchor.set(0.5, 0.5);
confirmText.x = this.x;
confirmText.y = this.y + 140;
confirmText.alpha = 0;
saveMenu.addChild(confirmText);
tween(confirmText, {
alpha: 1
}, {
duration: 300,
onFinish: function onFinish() {
LK.setTimeout(function () {
tween(confirmText, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
confirmText.destroy();
}
});
}, 1500);
}
});
};
// Load button click handler
loadSlotButton.down = function () {
var slotNum = this.slotNumber;
var loaded = loadGameData(slotNum);
if (loaded) {
// Show confirmation
var confirmText = new Text2('Loaded Slot ' + slotNum + '!', {
size: 40,
fill: 0x0066aa
});
confirmText.anchor.set(0.5, 0.5);
confirmText.x = this.x;
confirmText.y = this.y + 60;
confirmText.alpha = 0;
saveMenu.addChild(confirmText);
tween(confirmText, {
alpha: 1
}, {
duration: 300,
onFinish: function onFinish() {
LK.setTimeout(function () {
tween(confirmText, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
confirmText.destroy();
saveMenu.visible = false;
gameRunning = true;
fruitsFrozen = false;
}
});
}, 1500);
}
});
} else {
// Show no save found message
var noSaveText = new Text2('Slot ' + slotNum + ' is empty!', {
size: 40,
fill: 0xaa0000
});
noSaveText.anchor.set(0.5, 0.5);
noSaveText.x = this.x;
noSaveText.y = this.y + 60;
noSaveText.alpha = 0;
saveMenu.addChild(noSaveText);
tween(noSaveText, {
alpha: 1
}, {
duration: 300,
onFinish: function onFinish() {
LK.setTimeout(function () {
tween(noSaveText, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
noSaveText.destroy();
}
});
}, 1500);
}
});
}
};
}
// Add tutorial restart button to save menu
var tutorialRestartButton = new Text2('RESTART TUTORIAL', {
size: 50,
fill: 0xffffff
});
tutorialRestartButton.anchor.set(0.5, 0.5);
tutorialRestartButton.x = 2048 / 2 - 300;
tutorialRestartButton.y = 2732 / 2 + 350;
var tutorialRestartBg = LK.getAsset('container', {
anchorX: 0.5,
anchorY: 0.5,
x: tutorialRestartButton.x,
y: tutorialRestartButton.y,
width: 400,
height: 80
});
tutorialRestartBg.tint = 0x2196F3;
saveMenu.addChild(tutorialRestartBg);
saveMenu.addChild(tutorialRestartButton);
tutorialRestartButton.down = function () {
// Close save menu
saveMenu.visible = false;
// Restart tutorial
tutorialActive = true;
tutorialStep = 0;
storage.tutorialCompleted = false;
gameRunning = false;
fruitsFrozen = true;
showTutorialStep();
};
// Close button
var closeSaveMenuButton = new Text2('CLOSE', {
size: 60,
fill: 0xffffff
});
closeSaveMenuButton.anchor.set(0.5, 0.5);
closeSaveMenuButton.x = 2048 / 2 + 300;
closeSaveMenuButton.y = 2732 / 2 + 350;
var closeSaveMenuButtonBg = LK.getAsset('container', {
anchorX: 0.5,
anchorY: 0.5,
x: closeSaveMenuButton.x,
y: closeSaveMenuButton.y,
width: 200,
height: 80
});
closeSaveMenuButtonBg.tint = 0xf44336;
saveMenu.addChild(closeSaveMenuButtonBg);
saveMenu.addChild(closeSaveMenuButton);
closeSaveMenuButton.down = function () {
saveMenu.visible = false;
gameRunning = true;
fruitsFrozen = false;
};
// Show music menu at game start instead of auto-starting music
musicMenu.visible = true;
musicMenuVisible = true;
gameRunning = false;
fruitsFrozen = true;
// Move music menu to front
game.removeChild(musicMenu);
game.addChild(musicMenu);
// Hide music button initially
musicMenuButton.visible = false;
// Hide save button initially
saveMenuButton.visible = false;
// Music menu background
var musicMenuBg = LK.getAsset('container', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
width: 1000,
height: 800
});
musicMenuBg.alpha = 0.95;
musicMenuBg.tint = 0xdddddd;
musicMenu.addChild(musicMenuBg);
// Music menu title
var musicMenuTitle = new Text2('Select Music Track', {
size: 80,
fill: 0x222222,
stroke: 0xffffff,
strokeThickness: 3
});
musicMenuTitle.anchor.set(0.5, 0);
musicMenuTitle.x = 2048 / 2;
musicMenuTitle.y = 2732 / 2 - 300;
musicMenu.addChild(musicMenuTitle);
// Create music selection buttons
var musicButtons = [];
for (var i = 0; i < musicTracks.length; i++) {
var musicButton = new Text2(musicNames[i], {
size: 60,
fill: 0x000000
});
musicButton.anchor.set(0.5, 0.5);
musicButton.x = 2048 / 2;
musicButton.y = 2732 / 2 - 150 + i * 100;
musicButton.musicIndex = i;
// Add click handler for each music button using closure to capture index
(function (index) {
musicButton.down = function (x, y, obj) {
// Update current music selection
currentMusicIndex = index;
storage.selectedMusic = currentMusicIndex;
// Stop current music and play selected track
LK.stopMusic();
LK.playMusic(musicTracks[currentMusicIndex]);
// Close menu
musicMenu.visible = false;
musicMenuVisible = false;
// Show music button now that a song has been picked
musicMenuButton.visible = true;
// Show save button now that a song has been picked
saveMenuButton.visible = true;
// Only start tutorial for new players who haven't completed it
if (!storage.tutorialCompleted) {
gameRunning = false;
fruitsFrozen = true;
LK.setTimeout(function () {
showTutorialStep();
}, 1000);
} else {
// For returning players, start the game immediately
gameRunning = true;
fruitsFrozen = false;
}
};
})(i);
musicButtons.push(musicButton);
musicMenu.addChild(musicButton);
}
// Music menu button click handler
musicMenuButton.down = function (x, y, obj) {
if (musicMenuVisible) {
musicMenu.visible = false;
musicMenuVisible = false;
gameRunning = true;
fruitsFrozen = false;
} else {
musicMenu.visible = true;
musicMenuVisible = true;
// Move music menu to front
game.removeChild(musicMenu);
game.addChild(musicMenu);
gameRunning = false;
fruitsFrozen = true;
}
};
// Save menu button click handler
saveMenuButton.down = function (x, y, obj) {
if (saveMenu.visible) {
saveMenu.visible = false;
gameRunning = true;
fruitsFrozen = false;
} else {
saveMenu.visible = true;
// Move save menu to front
game.removeChild(saveMenu);
game.addChild(saveMenu);
gameRunning = false;
fruitsFrozen = true;
}
};
// Event handlers
game.down = function (x, y, obj) {
if (!gameRunning || currentFruit || saveMenu.visible) {
return;
}
// Handle fruit mover mode
if (fruitMoverActive) {
var _loop = function _loop() {
fruit = fruits[i];
dx = x - fruit.x;
dy = y - fruit.y;
distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= fruit.getRadius()) {
var bobUp = function bobUp() {
tween(nextFruitPreview, {
y: originalY - 15
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: bobDown
});
};
var bobDown = function bobDown() {
tween(nextFruitPreview, {
y: originalY + 15
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: bobUp
});
};
// Fruit clicked - delete it and set as next fruit
fruitMoverSelectedFruit = fruit;
nextFruitType = fruit.type;
// Remove fruit from game
for (j = fruits.length - 1; j >= 0; j--) {
if (fruits[j] === fruit) {
fruits[j].destroy();
fruits.splice(j, 1);
break;
}
}
// Update next fruit preview to show the deleted fruit type
if (nextFruitPreview) {
// Stop any existing tweens before destroying
tween.stop(nextFruitPreview);
nextFruitPreview.destroy();
}
fruitAssets = ['cherry', 'strawberry', 'grape', 'orange', 'apple', 'pear', 'peach', 'pineapple', 'melon', 'watermelon', 'lemon', 'coconut', 'dragon', 'kiwi', 'mango', 'papaya', 'durian', 'blueberry', 'pomegranate', 'avocado', 'cranberry'];
fruitSizes = [120, 160, 200, 240, 280, 320, 360, 400, 440, 480, 520, 560, 600, 640, 580, 620, 660, 380, 700, 720, 340];
nextFruitPreview = LK.getAsset(fruitAssets[nextFruitType], {
anchorX: 0.5,
anchorY: 0.5
});
nextFruitPreview.x = 1700;
nextFruitPreview.y = 120;
game.addChild(nextFruitPreview);
// Position the label based on fruit size to ensure it's always visible
fruitRadius = fruitSizes[nextFruitType] / 2;
nextFruitLabel.x = nextFruitPreview.x - fruitRadius - 20; // 20px padding from fruit edge
// Add bobbing animation
originalY = nextFruitPreview.y;
bobUp();
// Reset all fruits to normal tint
for (k = 0; k < fruits.length; k++) {
tween(fruits[k], {
tint: 0xffffff
}, {
duration: 300,
easing: tween.easeOut
});
}
// Deactivate fruit mover mode
fruitMoverActive = false;
fruitMoverSelectedFruit = null;
return {
v: void 0
};
}
},
fruit,
dx,
dy,
distance,
j,
fruitAssets,
fruitSizes,
fruitRadius,
originalY,
k,
_ret;
// Check if click is on a fruit
for (var i = 0; i < fruits.length; i++) {
_ret = _loop();
if (_ret) return _ret.v;
}
// If no fruit was clicked, cancel fruit mover mode
fruitMoverActive = false;
for (var m = 0; m < fruits.length; m++) {
tween(fruits[m], {
tint: 0xffffff
}, {
duration: 300,
easing: tween.easeOut
});
}
return;
}
// Only allow spawning if the press is inside the container bounds
var radius = 0;
if (x >= containerX - containerWidth / 2 + radius && x <= containerX + containerWidth / 2 - radius && y >= containerY - containerHeight / 2 + radius && y <= containerY + containerHeight / 2 - radius) {
isDragging = true;
createPreviewFruit(x);
// Music now starts automatically at game load
// Play drop sound when starting to aim
LK.getSound('drop').play();
// Progress tutorial if on the last step (first fruit drop)
if (tutorialActive && tutorialStep === tutorialSteps.length - 1) {
completeTutorial();
}
}
};
game.move = function (x, y, obj) {
if (!gameRunning || !isDragging || saveMenu.visible) {
return;
}
updatePreviewFruit(x);
};
game.up = function (x, y, obj) {
if (!gameRunning || !isDragging || saveMenu.visible) {
return;
}
isDragging = false;
var dropX = previewFruit ? previewFruit.x : x;
destroyPreviewFruit();
dropFruit(dropX);
};
// Main game loop
game.update = function () {
if (!gameRunning) {
return;
}
// Update all fruits
if (!fruitsFrozen) {
for (var i = 0; i < fruits.length; i++) {
fruits[i].update();
}
}
// Check collisions and merges
for (var i = 0; i < fruits.length; i++) {
for (var j = i + 1; j < fruits.length; j++) {
var fruit1 = fruits[i];
var fruit2 = fruits[j];
if (fruit1.merged || fruit2.merged) {
continue;
}
if (checkCollision(fruit1, fruit2)) {
// Mark both fruits as having made contact
fruit1.contactMade = true;
fruit2.contactMade = true;
// Always apply physics first to prevent sticking
resolveFruitCollision(fruit1, fruit2);
// Then check for merge
var currentMerging = fruit1.type === fruit2.type;
if (!fruit1.lastMergeCheck && currentMerging) {
if (mergeFruits(fruit1, fruit2)) {
break; // Break out of inner loop as fruits array changed
}
}
fruit1.lastMergeCheck = currentMerging;
fruit2.lastMergeCheck = currentMerging;
} else {
fruit1.lastMergeCheck = false;
fruit2.lastMergeCheck = false;
}
}
}
// Check game over condition (disabled during fruit rain)
if (!fruitRainActive && checkGameOver() && LK.ticks % 60 === 0) {
// Check every second
gameRunning = false;
LK.showGameOver();
}
};
Apple with cute face. In-Game asset. 2d. High contrast. No shadows
Cherry with cute face. In-Game asset. 2d. High contrast. No shadows
Greyish blue square. In-Game asset. 2d. High contrast. No shadows
Grapes with a cute face. In-Game asset. 2d. High contrast. No shadows
Circle water melon with cute face. In-Game asset. 2d. High contrast. No shadows
Circle cantaloupe with cute face. In-Game asset. 2d. High contrast. No shadows
Pear with cute face. In-Game asset. 2d. High contrast. No shadows
Strawberry with cute face. In-Game asset. 2d. High contrast. No shadows
Peach with cute face. In-Game asset. 2d. High contrast. No shadows
Pineapple with cute face. In-Game asset. 2d. High contrast. No shadows
Dragon fruit with cute face. In-Game asset. 2d. High contrast. No shadows
Lemon with cute face. In-Game asset. 2d. High contrast. No shadows
Coconut with cute face. In-Game asset. 2d. High contrast. No shadows
Kiwi with cute face. In-Game asset. 2d. High contrast. No shadows
Durian with cute face. In-Game asset. 2d. High contrast. No shadows
Mango with cute face. In-Game asset. 2d. High contrast. No shadows
Papaya with cute face. In-Game asset. 2d. High contrast. No shadows
Arrow pointing down. In-Game asset. 2d. High contrast. No shadows
Shine. In-Game asset. 2d. High contrast. No shadows
Purple galaxy. In-Game asset. 2d. High contrast. No shadows
Blueberry with cute face. In-Game asset. 2d. High contrast. No shadows
Open pomegranate with cute face. In-Game asset. 2d. High contrast. No shadows
A cut in half avocado with cute face. In-Game asset. 2d. High contrast. No shadows
Cranberry’s with cute face. In-Game asset. 2d. High contrast. No shadows