/****
* 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.merged = false;
self.velocityY = 0;
self.velocityX = 0;
self.gravity = 0.5;
self.friction = 0.98;
self.bounce = 0.3;
self.hasSettled = false;
self.settleTimer = 0;
self.hasPassedDangerLine = false; // Track if fruit completed initial drop
// Weight system based on fruit size
var fruitWeights = {
'cherry': 1,
'grape': 2,
'strawberry': 3,
'greenplum': 4,
'apricot': 5,
'kiwi': 6,
'plum': 7,
'apple': 8,
'orange': 9,
'peach': 10,
'quince': 11,
'melon': 12,
'watermelon': 15
};
self.weight = fruitWeights[type] || 1;
var fruitGraphics = self.attachAsset(type, {
anchorX: 0.5,
anchorY: 0.5
});
self.getRadius = function () {
return fruitGraphics.width / 2;
};
// Method to apply external force (like being hit by another fruit)
self.applyImpulse = function (impulseX, impulseY) {
// Heavier fruits are less affected by external forces
var forceMultiplier = 1 / Math.sqrt(self.weight / 2);
self.velocityX += impulseX * forceMultiplier;
self.velocityY += impulseY * forceMultiplier;
self.hasSettled = false;
self.settleTimer = 0;
};
self.update = function () {
if (self.merged) {
return;
}
// Apply weight-based physics
var weightedGravity = self.gravity * Math.sqrt(self.weight / 5); // Heavier fruits fall faster
self.velocityY += weightedGravity;
self.x += self.velocityX;
self.y += self.velocityY;
// Ground collision with weight-based bouncing
var groundY = 2732 - 100;
if (self.y + self.getRadius() > groundY) {
self.y = groundY - self.getRadius();
// Heavier fruits bounce less
var weightedBounce = self.bounce * (1 / Math.sqrt(self.weight / 3));
self.velocityY *= -weightedBounce;
self.velocityX *= self.friction;
// Apply weight-based settling
var settleThreshold = 2 - self.weight * 0.1;
if (Math.abs(self.velocityY) < settleThreshold) {
self.velocityY = 0;
}
}
// Track if fruit has completed initial drop phase
if (!self.hasPassedDangerLine && self.y + self.getRadius() > dangerLineY + 20) {
self.hasPassedDangerLine = true;
}
// Check if fruit has settled (low velocity for some time)
if (Math.abs(self.velocityY) < 2 && Math.abs(self.velocityX) < 2) {
self.settleTimer++;
if (self.settleTimer > 10) {
// 10 frames = 0.17 seconds at 60fps - faster settling detection
self.hasSettled = true;
}
} else {
self.settleTimer = 0;
self.hasSettled = false; // Reset settled state when fruit starts moving again
}
// Wall collisions with weight-based bouncing
if (self.x - self.getRadius() < 20) {
self.x = 20 + self.getRadius();
var weightedBounce = self.bounce * (1 / Math.sqrt(self.weight / 3));
self.velocityX *= -weightedBounce;
}
if (self.x + self.getRadius() > 2048 - 20) {
self.x = 2048 - 20 - self.getRadius();
var weightedBounce = self.bounce * (1 / Math.sqrt(self.weight / 3));
self.velocityX *= -weightedBounce;
}
// Fruit collision detection with weight-based physics
for (var i = 0; i < fruits.length; i++) {
var otherFruit = fruits[i];
if (otherFruit === self || otherFruit.merged) {
continue;
}
var dx = self.x - otherFruit.x;
var dy = self.y - otherFruit.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var minDistance = self.getRadius() + otherFruit.getRadius();
if (distance < minDistance && distance > 0) {
// Calculate collision response with weight-based physics
var overlap = minDistance - distance;
var normalX = dx / distance;
var normalY = dy / distance;
// Weight-based separation (heavier fruits move less)
var totalWeight = self.weight + otherFruit.weight;
var selfSeparationRatio = otherFruit.weight / totalWeight;
var otherSeparationRatio = self.weight / totalWeight;
var separationX = normalX * overlap * selfSeparationRatio;
var separationY = normalY * overlap * selfSeparationRatio;
var otherSeparationX = -normalX * overlap * otherSeparationRatio;
var otherSeparationY = -normalY * overlap * otherSeparationRatio;
// Apply weight-based separation
self.x += separationX;
self.y += separationY;
otherFruit.x += otherSeparationX;
otherFruit.y += otherSeparationY;
// Calculate relative velocity
var relativeVelocityX = self.velocityX - otherFruit.velocityX;
var relativeVelocityY = self.velocityY - otherFruit.velocityY;
var velocityAlongNormal = relativeVelocityX * normalX + relativeVelocityY * normalY;
// Don't resolve if velocities are separating
if (velocityAlongNormal > 0) {
continue;
}
// Calculate restitution (bounciness)
var restitution = Math.min(self.bounce, otherFruit.bounce);
var impulseScalar = -(1 + restitution) * velocityAlongNormal;
impulseScalar /= 1 / self.weight + 1 / otherFruit.weight;
// Apply impulse with weight consideration
var impulseX = impulseScalar * normalX;
var impulseY = impulseScalar * normalY;
self.velocityX += impulseX / self.weight;
self.velocityY += impulseY / self.weight;
otherFruit.velocityX -= impulseX / otherFruit.weight;
otherFruit.velocityY -= impulseY / otherFruit.weight;
// Weight-based stacking effect - lighter fruits get pushed more
if (self.weight < otherFruit.weight) {
// Lighter fruit on top gets pushed sideways
var pushForce = (otherFruit.weight - self.weight) * 0.2;
self.velocityX += normalX * pushForce;
} else if (otherFruit.weight < self.weight) {
// Other lighter fruit gets pushed
var pushForce = (self.weight - otherFruit.weight) * 0.2;
otherFruit.velocityX -= normalX * pushForce;
}
}
}
// Check if fruits touch top red line (danger line) - immediate game over
if (self.y - self.getRadius() <= dangerLineY + 1) {
LK.effects.flashScreen(0xFF0000, 1000);
LK.showGameOver();
}
};
return self;
});
var FruitParticle = Container.expand(function (x, y, fruitType) {
var self = Container.call(this);
self.x = x;
self.y = y;
// Create smaller piece of the original fruit
var particleGraphics = self.attachAsset(fruitType, {
anchorX: 0.5,
anchorY: 0.5
});
// Make it smaller like a fragment
self.scaleX = 0.3 + Math.random() * 0.2;
self.scaleY = 0.3 + Math.random() * 0.2;
// Random velocity for explosion effect
self.velocityX = (Math.random() - 0.5) * 20;
self.velocityY = (Math.random() - 0.5) * 20 - 10; // Bias upward
self.velocityY -= Math.random() * 10; // Extra upward force
// Physics properties
self.gravity = 0.8;
self.friction = 0.95;
self.bounce = 0.3;
self.life = 120; // Particle life in frames
// Rotation for spinning effect
self.rotationSpeed = (Math.random() - 0.5) * 0.3;
self.update = function () {
// Apply physics
self.velocityY += self.gravity;
self.x += self.velocityX;
self.y += self.velocityY;
self.rotation += self.rotationSpeed;
// Apply friction
self.velocityX *= self.friction;
// Ground collision
var groundY = 2732 - 100;
if (self.y + 20 > groundY) {
self.y = groundY - 20;
self.velocityY *= -self.bounce;
self.velocityX *= self.friction;
}
// Wall collisions
if (self.x < 20) {
self.x = 20;
self.velocityX *= -self.bounce;
}
if (self.x > 2048 - 20) {
self.x = 2048 - 20;
self.velocityX *= -self.bounce;
}
// Fade out over time
self.life--;
if (self.life < 60) {
self.alpha = self.life / 60;
}
// Remove when life is over
if (self.life <= 0) {
self.destroy();
// Remove from particles array
for (var i = particles.length - 1; i >= 0; i--) {
if (particles[i] === self) {
particles.splice(i, 1);
break;
}
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
var fruitTypes = ['cherry', 'grape', 'strawberry', 'greenplum', 'apricot', 'kiwi', 'plum', 'apple', 'orange', 'peach', 'quince', 'melon', 'watermelon'];
var fruitScores = [5, 25, 10, 35, 40, 50, 75, 120, 180, 500, 300, 1000, 2000];
var watermelonMergeCount = 0;
var fruits = [];
var particles = [];
var dropX = 1024;
var currentFruitType = 'cherry';
var dangerLineY = 250;
var canDrop = true;
var gameStartTime = Date.now();
var totalDrops = 0;
var highScore = 0;
var dangerLineTouches = 0; // Counter for danger line touches
// Create walls
var leftWall = game.addChild(LK.getAsset('walls', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 20,
height: 2732
}));
var rightWall = game.addChild(LK.getAsset('walls', {
anchorX: 0,
anchorY: 0,
x: 2048 - 20,
y: 0,
width: 20,
height: 2732
}));
// Create ground
var ground = game.addChild(LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 2732 - 100,
width: 2048,
height: 20
}));
// Create danger line
var dangerLine = game.addChild(LK.getAsset('dangerLine', {
anchorX: 0,
anchorY: 0,
x: 0,
y: dangerLineY
}));
// Create score text
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFD700,
fontWeight: 'bold'
});
scoreTxt.anchor.set(1, 0);
scoreTxt.x = -60; // Move score text 150 pixels left from right edge
scoreTxt.y = 30; // Move score text to top right corner
LK.gui.topRight.addChild(scoreTxt);
// Create next fruit indicator
var nextFruitTxt = new Text2('Next: Pineapple', {
size: 50,
fill: 0x000000,
fontWeight: 'bold'
});
nextFruitTxt.anchor.set(0, 0);
nextFruitTxt.x = 50;
nextFruitTxt.y = 90;
LK.gui.topLeft.addChild(nextFruitTxt);
// Create high score text
var highScoreTxt = new Text2('Best Score: ' + highScore, {
size: 50,
fill: 0x000000,
fontWeight: 'bold'
});
highScoreTxt.anchor.set(0, 0);
highScoreTxt.x = 50;
highScoreTxt.y = 30;
LK.gui.topLeft.addChild(highScoreTxt);
// Create preview fruit
var previewFruit = game.addChild(LK.getAsset(currentFruitType, {
anchorX: 0.5,
anchorY: 0.5,
x: dropX,
y: dangerLineY + 314,
alpha: 0.7
}));
function getRandomFruitType() {
var gameTimeSeconds = (Date.now() - gameStartTime) / 1000;
var currentScore = LK.getScore();
// Progressive difficulty based on time and score
var maxFruitIndex = 0; // Start with only cherry
var availableFruits = 1;
// Time-based progression (every 30 seconds, unlock next fruit type)
var timeBasedUnlocks = Math.floor(gameTimeSeconds / 30);
availableFruits = Math.min(availableFruits + timeBasedUnlocks, fruitTypes.length);
// Score-based progression (unlock fruits based on score milestones)
var scoreBasedUnlocks = 0;
if (currentScore >= 30) {
scoreBasedUnlocks = 1;
} // grape
if (currentScore >= 60) {
scoreBasedUnlocks = 2;
} // strawberry
if (currentScore >= 100) {
scoreBasedUnlocks = 3;
} // green plum
if (currentScore >= 150) {
scoreBasedUnlocks = 4;
} // apricot
if (currentScore >= 210) {
scoreBasedUnlocks = 5;
} // kiwi
if (currentScore >= 280) {
scoreBasedUnlocks = 6;
} // plum
if (currentScore >= 360) {
scoreBasedUnlocks = 7;
} // apple
if (currentScore >= 460) {
scoreBasedUnlocks = 8;
} // orange
if (currentScore >= 580) {
scoreBasedUnlocks = 9;
} // peach
if (currentScore >= 720) {
scoreBasedUnlocks = 10;
} // quince
if (currentScore >= 890) {
scoreBasedUnlocks = 11;
} // melon
if (currentScore >= 1100) {
scoreBasedUnlocks = 12;
} // watermelon
availableFruits = Math.max(availableFruits, scoreBasedUnlocks + 1);
maxFruitIndex = Math.min(availableFruits - 1, fruitTypes.length - 1);
// Early game weighting - favor smaller fruits
var weights = [];
for (var i = 0; i <= maxFruitIndex; i++) {
// Weight smaller fruits more heavily early in the game
var baseWeight = maxFruitIndex - i + 1;
// Additional weighting for very early game
if (totalDrops < 20) {
if (i === 0) {
baseWeight *= 4;
} // Heavy favor for cherry
else if (i === 1) {
baseWeight *= 2;
} // Medium favor for strawberry
} else if (totalDrops < 50) {
if (i <= 1) {
baseWeight *= 2;
} // Favor first two fruits
}
weights.push(baseWeight);
}
// Weighted random selection
var totalWeight = 0;
for (var i = 0; i < weights.length; i++) {
totalWeight += weights[i];
}
var randomValue = Math.random() * totalWeight;
var currentWeight = 0;
for (var i = 0; i < weights.length; i++) {
currentWeight += weights[i];
if (randomValue <= currentWeight) {
return fruitTypes[i];
}
}
// Fallback
return fruitTypes[0];
}
function updatePreviewFruit() {
game.removeChild(previewFruit);
previewFruit = game.addChild(LK.getAsset(currentFruitType, {
anchorX: 0.5,
anchorY: 0.5,
x: dropX,
y: dangerLineY + 314,
alpha: 0.7
}));
var capitalizedType = currentFruitType.charAt(0).toUpperCase() + currentFruitType.slice(1);
nextFruitTxt.setText('Next: ' + capitalizedType);
}
function dropFruit() {
if (!canDrop) {
return;
}
var newFruit = new Fruit(currentFruitType);
newFruit.x = dropX;
newFruit.y = dangerLineY + 364; // Start fruits even further below the danger line
fruits.push(newFruit);
game.addChild(newFruit);
LK.getSound('drop').play();
totalDrops++;
currentFruitType = getRandomFruitType();
updatePreviewFruit();
canDrop = false;
LK.setTimeout(function () {
canDrop = true;
}, 500);
}
function checkMerges() {
for (var i = 0; i < fruits.length; i++) {
if (fruits[i].merged) {
continue;
}
for (var j = i + 1; j < fruits.length; j++) {
if (fruits[j].merged) {
continue;
}
var fruit1 = fruits[i];
var fruit2 = fruits[j];
if (fruit1.type === fruit2.type) {
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();
// Merge immediately when fruits touch (with small buffer for instant merging)
if (distance <= minDistance + 5) {
mergeFruits(fruit1, fruit2);
break;
}
}
}
}
}
function mergeFruits(fruit1, fruit2) {
var currentTypeIndex = fruitTypes.indexOf(fruit1.type);
// Handle watermelon merging - they can merge up to 10 times then get destroyed
if (fruit1.type === 'watermelon') {
watermelonMergeCount++;
if (watermelonMergeCount >= 10) {
// Create explosive particle effect for destruction
var particleCount = 15 + Math.floor(Math.random() * 10); // 15-25 particles
for (var p = 0; p < particleCount; p++) {
var particle = new FruitParticle((fruit1.x + fruit2.x) / 2 + (Math.random() - 0.5) * 100, (fruit1.y + fruit2.y) / 2 + (Math.random() - 0.5) * 100, fruit1.type);
particles.push(particle);
game.addChild(particle);
}
// Add score for destruction
LK.setScore(LK.getScore() + 2000);
scoreTxt.setText(LK.getScore());
// Play merge sound
LK.getSound('merge').play();
// Mark fruits as merged and remove them
fruit1.merged = true;
fruit2.merged = true;
// Remove from fruits array
for (var i = fruits.length - 1; i >= 0; i--) {
if (fruits[i].merged) {
fruits[i].destroy();
fruits.splice(i, 1);
}
}
return;
}
}
if (currentTypeIndex < fruitTypes.length - 1) {
var nextType = fruitTypes[currentTypeIndex + 1];
// Create new merged fruit
var mergedFruit = new Fruit(nextType);
mergedFruit.x = (fruit1.x + fruit2.x) / 2;
mergedFruit.y = (fruit1.y + fruit2.y) / 2;
// Create explosive particle effect - fragments of the original fruits
var particleCount = 8 + Math.floor(Math.random() * 6); // 8-14 particles
for (var p = 0; p < particleCount; p++) {
var particle = new FruitParticle((fruit1.x + fruit2.x) / 2 + (Math.random() - 0.5) * 60, (fruit1.y + fruit2.y) / 2 + (Math.random() - 0.5) * 60, fruit1.type);
particles.push(particle);
game.addChild(particle);
}
// Create explosive merge effect
mergedFruit.scaleX = 0.1;
mergedFruit.scaleY = 0.1;
// First explosion - quick scale up
tween(mergedFruit, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
// Second bounce - scale down to normal
tween(mergedFruit, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.bounceOut
});
}
});
// Add stronger screen shake effect for explosion
var originalX = game.x;
var originalY = game.y;
var shakeAmount = 20; // Increased shake
var shakeDuration = 30; // Shorter, more intense shake
// Multiple shake cycles for more impact
for (var shakeCount = 0; shakeCount < 6; shakeCount++) {
(function (count) {
LK.setTimeout(function () {
var currentShake = shakeAmount * (1 - count / 6); // Diminishing shake
tween(game, {
x: originalX + (Math.random() - 0.5) * currentShake * 2,
y: originalY + (Math.random() - 0.5) * currentShake * 2
}, {
duration: shakeDuration,
easing: tween.easeOut
});
}, count * shakeDuration);
})(shakeCount);
}
// Return to original position
LK.setTimeout(function () {
tween(game, {
x: originalX,
y: originalY
}, {
duration: 100,
easing: tween.easeOut
});
}, 200);
// Add rotation effect for extra visual impact
tween(mergedFruit, {
rotation: Math.PI * 2
}, {
duration: 350,
easing: tween.easeOut
});
// Add tint effect that fades from bright to normal
mergedFruit.tint = 0xFFFF00;
tween(mergedFruit, {
tint: 0xFFFFFF
}, {
duration: 500,
easing: tween.easeOut
});
fruits.push(mergedFruit);
game.addChild(mergedFruit);
// Add score
var scoreToAdd = fruitScores[currentTypeIndex + 1];
LK.setScore(LK.getScore() + scoreToAdd);
scoreTxt.setText(LK.getScore());
// Check and update high score
var currentScore = LK.getScore();
if (currentScore > highScore) {
highScore = currentScore;
highScoreTxt.setText('Best Score: ' + highScore);
// Flash high score text when broken
LK.effects.flashObject(highScoreTxt, 0xFFD700, 1000);
}
// Continue playing even after watermelon - no win condition
// Play merge sound
LK.getSound('merge').play();
// Flash effect
LK.effects.flashObject(mergedFruit, 0xFFFF00, 500);
// Push away nearby fruits from explosion
var mergeX = mergedFruit.x;
var mergeY = mergedFruit.y;
var explosionRadius = 200;
for (var k = 0; k < fruits.length; k++) {
if (fruits[k] !== mergedFruit && !fruits[k].merged) {
var dx = fruits[k].x - mergeX;
var dy = fruits[k].y - mergeY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < explosionRadius && distance > 0) {
var force = (explosionRadius - distance) / explosionRadius * 15;
var normalX = dx / distance;
var normalY = dy / distance;
fruits[k].applyImpulse(normalX * force, normalY * force);
}
}
}
}
// Mark fruits as merged and remove them
fruit1.merged = true;
fruit2.merged = true;
// Remove from fruits array
for (var i = fruits.length - 1; i >= 0; i--) {
if (fruits[i].merged) {
fruits[i].destroy();
fruits.splice(i, 1);
}
}
}
var isDragging = false;
game.move = function (x, y, obj) {
if (canDrop && isDragging) {
dropX = Math.max(40, Math.min(2048 - 40, x));
previewFruit.x = dropX;
}
};
game.down = function (x, y, obj) {
if (canDrop) {
isDragging = true;
dropX = Math.max(40, Math.min(2048 - 40, x));
previewFruit.x = dropX;
}
};
game.up = function (x, y, obj) {
if (canDrop && isDragging) {
isDragging = false;
dropFruit();
}
};
game.update = function () {
for (var i = 0; i < fruits.length; i++) {
fruits[i].update();
}
// Update particles
for (var i = 0; i < particles.length; i++) {
particles[i].update();
}
checkMerges();
};
// Initialize first fruit type
updatePreviewFruit(); /****
* 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.merged = false;
self.velocityY = 0;
self.velocityX = 0;
self.gravity = 0.5;
self.friction = 0.98;
self.bounce = 0.3;
self.hasSettled = false;
self.settleTimer = 0;
self.hasPassedDangerLine = false; // Track if fruit completed initial drop
// Weight system based on fruit size
var fruitWeights = {
'cherry': 1,
'grape': 2,
'strawberry': 3,
'greenplum': 4,
'apricot': 5,
'kiwi': 6,
'plum': 7,
'apple': 8,
'orange': 9,
'peach': 10,
'quince': 11,
'melon': 12,
'watermelon': 15
};
self.weight = fruitWeights[type] || 1;
var fruitGraphics = self.attachAsset(type, {
anchorX: 0.5,
anchorY: 0.5
});
self.getRadius = function () {
return fruitGraphics.width / 2;
};
// Method to apply external force (like being hit by another fruit)
self.applyImpulse = function (impulseX, impulseY) {
// Heavier fruits are less affected by external forces
var forceMultiplier = 1 / Math.sqrt(self.weight / 2);
self.velocityX += impulseX * forceMultiplier;
self.velocityY += impulseY * forceMultiplier;
self.hasSettled = false;
self.settleTimer = 0;
};
self.update = function () {
if (self.merged) {
return;
}
// Apply weight-based physics
var weightedGravity = self.gravity * Math.sqrt(self.weight / 5); // Heavier fruits fall faster
self.velocityY += weightedGravity;
self.x += self.velocityX;
self.y += self.velocityY;
// Ground collision with weight-based bouncing
var groundY = 2732 - 100;
if (self.y + self.getRadius() > groundY) {
self.y = groundY - self.getRadius();
// Heavier fruits bounce less
var weightedBounce = self.bounce * (1 / Math.sqrt(self.weight / 3));
self.velocityY *= -weightedBounce;
self.velocityX *= self.friction;
// Apply weight-based settling
var settleThreshold = 2 - self.weight * 0.1;
if (Math.abs(self.velocityY) < settleThreshold) {
self.velocityY = 0;
}
}
// Track if fruit has completed initial drop phase
if (!self.hasPassedDangerLine && self.y + self.getRadius() > dangerLineY + 20) {
self.hasPassedDangerLine = true;
}
// Check if fruit has settled (low velocity for some time)
if (Math.abs(self.velocityY) < 2 && Math.abs(self.velocityX) < 2) {
self.settleTimer++;
if (self.settleTimer > 10) {
// 10 frames = 0.17 seconds at 60fps - faster settling detection
self.hasSettled = true;
}
} else {
self.settleTimer = 0;
self.hasSettled = false; // Reset settled state when fruit starts moving again
}
// Wall collisions with weight-based bouncing
if (self.x - self.getRadius() < 20) {
self.x = 20 + self.getRadius();
var weightedBounce = self.bounce * (1 / Math.sqrt(self.weight / 3));
self.velocityX *= -weightedBounce;
}
if (self.x + self.getRadius() > 2048 - 20) {
self.x = 2048 - 20 - self.getRadius();
var weightedBounce = self.bounce * (1 / Math.sqrt(self.weight / 3));
self.velocityX *= -weightedBounce;
}
// Fruit collision detection with weight-based physics
for (var i = 0; i < fruits.length; i++) {
var otherFruit = fruits[i];
if (otherFruit === self || otherFruit.merged) {
continue;
}
var dx = self.x - otherFruit.x;
var dy = self.y - otherFruit.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var minDistance = self.getRadius() + otherFruit.getRadius();
if (distance < minDistance && distance > 0) {
// Calculate collision response with weight-based physics
var overlap = minDistance - distance;
var normalX = dx / distance;
var normalY = dy / distance;
// Weight-based separation (heavier fruits move less)
var totalWeight = self.weight + otherFruit.weight;
var selfSeparationRatio = otherFruit.weight / totalWeight;
var otherSeparationRatio = self.weight / totalWeight;
var separationX = normalX * overlap * selfSeparationRatio;
var separationY = normalY * overlap * selfSeparationRatio;
var otherSeparationX = -normalX * overlap * otherSeparationRatio;
var otherSeparationY = -normalY * overlap * otherSeparationRatio;
// Apply weight-based separation
self.x += separationX;
self.y += separationY;
otherFruit.x += otherSeparationX;
otherFruit.y += otherSeparationY;
// Calculate relative velocity
var relativeVelocityX = self.velocityX - otherFruit.velocityX;
var relativeVelocityY = self.velocityY - otherFruit.velocityY;
var velocityAlongNormal = relativeVelocityX * normalX + relativeVelocityY * normalY;
// Don't resolve if velocities are separating
if (velocityAlongNormal > 0) {
continue;
}
// Calculate restitution (bounciness)
var restitution = Math.min(self.bounce, otherFruit.bounce);
var impulseScalar = -(1 + restitution) * velocityAlongNormal;
impulseScalar /= 1 / self.weight + 1 / otherFruit.weight;
// Apply impulse with weight consideration
var impulseX = impulseScalar * normalX;
var impulseY = impulseScalar * normalY;
self.velocityX += impulseX / self.weight;
self.velocityY += impulseY / self.weight;
otherFruit.velocityX -= impulseX / otherFruit.weight;
otherFruit.velocityY -= impulseY / otherFruit.weight;
// Weight-based stacking effect - lighter fruits get pushed more
if (self.weight < otherFruit.weight) {
// Lighter fruit on top gets pushed sideways
var pushForce = (otherFruit.weight - self.weight) * 0.2;
self.velocityX += normalX * pushForce;
} else if (otherFruit.weight < self.weight) {
// Other lighter fruit gets pushed
var pushForce = (self.weight - otherFruit.weight) * 0.2;
otherFruit.velocityX -= normalX * pushForce;
}
}
}
// Check if fruits touch top red line (danger line) - immediate game over
if (self.y - self.getRadius() <= dangerLineY + 1) {
LK.effects.flashScreen(0xFF0000, 1000);
LK.showGameOver();
}
};
return self;
});
var FruitParticle = Container.expand(function (x, y, fruitType) {
var self = Container.call(this);
self.x = x;
self.y = y;
// Create smaller piece of the original fruit
var particleGraphics = self.attachAsset(fruitType, {
anchorX: 0.5,
anchorY: 0.5
});
// Make it smaller like a fragment
self.scaleX = 0.3 + Math.random() * 0.2;
self.scaleY = 0.3 + Math.random() * 0.2;
// Random velocity for explosion effect
self.velocityX = (Math.random() - 0.5) * 20;
self.velocityY = (Math.random() - 0.5) * 20 - 10; // Bias upward
self.velocityY -= Math.random() * 10; // Extra upward force
// Physics properties
self.gravity = 0.8;
self.friction = 0.95;
self.bounce = 0.3;
self.life = 120; // Particle life in frames
// Rotation for spinning effect
self.rotationSpeed = (Math.random() - 0.5) * 0.3;
self.update = function () {
// Apply physics
self.velocityY += self.gravity;
self.x += self.velocityX;
self.y += self.velocityY;
self.rotation += self.rotationSpeed;
// Apply friction
self.velocityX *= self.friction;
// Ground collision
var groundY = 2732 - 100;
if (self.y + 20 > groundY) {
self.y = groundY - 20;
self.velocityY *= -self.bounce;
self.velocityX *= self.friction;
}
// Wall collisions
if (self.x < 20) {
self.x = 20;
self.velocityX *= -self.bounce;
}
if (self.x > 2048 - 20) {
self.x = 2048 - 20;
self.velocityX *= -self.bounce;
}
// Fade out over time
self.life--;
if (self.life < 60) {
self.alpha = self.life / 60;
}
// Remove when life is over
if (self.life <= 0) {
self.destroy();
// Remove from particles array
for (var i = particles.length - 1; i >= 0; i--) {
if (particles[i] === self) {
particles.splice(i, 1);
break;
}
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
var fruitTypes = ['cherry', 'grape', 'strawberry', 'greenplum', 'apricot', 'kiwi', 'plum', 'apple', 'orange', 'peach', 'quince', 'melon', 'watermelon'];
var fruitScores = [5, 25, 10, 35, 40, 50, 75, 120, 180, 500, 300, 1000, 2000];
var watermelonMergeCount = 0;
var fruits = [];
var particles = [];
var dropX = 1024;
var currentFruitType = 'cherry';
var dangerLineY = 250;
var canDrop = true;
var gameStartTime = Date.now();
var totalDrops = 0;
var highScore = 0;
var dangerLineTouches = 0; // Counter for danger line touches
// Create walls
var leftWall = game.addChild(LK.getAsset('walls', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 20,
height: 2732
}));
var rightWall = game.addChild(LK.getAsset('walls', {
anchorX: 0,
anchorY: 0,
x: 2048 - 20,
y: 0,
width: 20,
height: 2732
}));
// Create ground
var ground = game.addChild(LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 2732 - 100,
width: 2048,
height: 20
}));
// Create danger line
var dangerLine = game.addChild(LK.getAsset('dangerLine', {
anchorX: 0,
anchorY: 0,
x: 0,
y: dangerLineY
}));
// Create score text
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFD700,
fontWeight: 'bold'
});
scoreTxt.anchor.set(1, 0);
scoreTxt.x = -60; // Move score text 150 pixels left from right edge
scoreTxt.y = 30; // Move score text to top right corner
LK.gui.topRight.addChild(scoreTxt);
// Create next fruit indicator
var nextFruitTxt = new Text2('Next: Pineapple', {
size: 50,
fill: 0x000000,
fontWeight: 'bold'
});
nextFruitTxt.anchor.set(0, 0);
nextFruitTxt.x = 50;
nextFruitTxt.y = 90;
LK.gui.topLeft.addChild(nextFruitTxt);
// Create high score text
var highScoreTxt = new Text2('Best Score: ' + highScore, {
size: 50,
fill: 0x000000,
fontWeight: 'bold'
});
highScoreTxt.anchor.set(0, 0);
highScoreTxt.x = 50;
highScoreTxt.y = 30;
LK.gui.topLeft.addChild(highScoreTxt);
// Create preview fruit
var previewFruit = game.addChild(LK.getAsset(currentFruitType, {
anchorX: 0.5,
anchorY: 0.5,
x: dropX,
y: dangerLineY + 314,
alpha: 0.7
}));
function getRandomFruitType() {
var gameTimeSeconds = (Date.now() - gameStartTime) / 1000;
var currentScore = LK.getScore();
// Progressive difficulty based on time and score
var maxFruitIndex = 0; // Start with only cherry
var availableFruits = 1;
// Time-based progression (every 30 seconds, unlock next fruit type)
var timeBasedUnlocks = Math.floor(gameTimeSeconds / 30);
availableFruits = Math.min(availableFruits + timeBasedUnlocks, fruitTypes.length);
// Score-based progression (unlock fruits based on score milestones)
var scoreBasedUnlocks = 0;
if (currentScore >= 30) {
scoreBasedUnlocks = 1;
} // grape
if (currentScore >= 60) {
scoreBasedUnlocks = 2;
} // strawberry
if (currentScore >= 100) {
scoreBasedUnlocks = 3;
} // green plum
if (currentScore >= 150) {
scoreBasedUnlocks = 4;
} // apricot
if (currentScore >= 210) {
scoreBasedUnlocks = 5;
} // kiwi
if (currentScore >= 280) {
scoreBasedUnlocks = 6;
} // plum
if (currentScore >= 360) {
scoreBasedUnlocks = 7;
} // apple
if (currentScore >= 460) {
scoreBasedUnlocks = 8;
} // orange
if (currentScore >= 580) {
scoreBasedUnlocks = 9;
} // peach
if (currentScore >= 720) {
scoreBasedUnlocks = 10;
} // quince
if (currentScore >= 890) {
scoreBasedUnlocks = 11;
} // melon
if (currentScore >= 1100) {
scoreBasedUnlocks = 12;
} // watermelon
availableFruits = Math.max(availableFruits, scoreBasedUnlocks + 1);
maxFruitIndex = Math.min(availableFruits - 1, fruitTypes.length - 1);
// Early game weighting - favor smaller fruits
var weights = [];
for (var i = 0; i <= maxFruitIndex; i++) {
// Weight smaller fruits more heavily early in the game
var baseWeight = maxFruitIndex - i + 1;
// Additional weighting for very early game
if (totalDrops < 20) {
if (i === 0) {
baseWeight *= 4;
} // Heavy favor for cherry
else if (i === 1) {
baseWeight *= 2;
} // Medium favor for strawberry
} else if (totalDrops < 50) {
if (i <= 1) {
baseWeight *= 2;
} // Favor first two fruits
}
weights.push(baseWeight);
}
// Weighted random selection
var totalWeight = 0;
for (var i = 0; i < weights.length; i++) {
totalWeight += weights[i];
}
var randomValue = Math.random() * totalWeight;
var currentWeight = 0;
for (var i = 0; i < weights.length; i++) {
currentWeight += weights[i];
if (randomValue <= currentWeight) {
return fruitTypes[i];
}
}
// Fallback
return fruitTypes[0];
}
function updatePreviewFruit() {
game.removeChild(previewFruit);
previewFruit = game.addChild(LK.getAsset(currentFruitType, {
anchorX: 0.5,
anchorY: 0.5,
x: dropX,
y: dangerLineY + 314,
alpha: 0.7
}));
var capitalizedType = currentFruitType.charAt(0).toUpperCase() + currentFruitType.slice(1);
nextFruitTxt.setText('Next: ' + capitalizedType);
}
function dropFruit() {
if (!canDrop) {
return;
}
var newFruit = new Fruit(currentFruitType);
newFruit.x = dropX;
newFruit.y = dangerLineY + 364; // Start fruits even further below the danger line
fruits.push(newFruit);
game.addChild(newFruit);
LK.getSound('drop').play();
totalDrops++;
currentFruitType = getRandomFruitType();
updatePreviewFruit();
canDrop = false;
LK.setTimeout(function () {
canDrop = true;
}, 500);
}
function checkMerges() {
for (var i = 0; i < fruits.length; i++) {
if (fruits[i].merged) {
continue;
}
for (var j = i + 1; j < fruits.length; j++) {
if (fruits[j].merged) {
continue;
}
var fruit1 = fruits[i];
var fruit2 = fruits[j];
if (fruit1.type === fruit2.type) {
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();
// Merge immediately when fruits touch (with small buffer for instant merging)
if (distance <= minDistance + 5) {
mergeFruits(fruit1, fruit2);
break;
}
}
}
}
}
function mergeFruits(fruit1, fruit2) {
var currentTypeIndex = fruitTypes.indexOf(fruit1.type);
// Handle watermelon merging - they can merge up to 10 times then get destroyed
if (fruit1.type === 'watermelon') {
watermelonMergeCount++;
if (watermelonMergeCount >= 10) {
// Create explosive particle effect for destruction
var particleCount = 15 + Math.floor(Math.random() * 10); // 15-25 particles
for (var p = 0; p < particleCount; p++) {
var particle = new FruitParticle((fruit1.x + fruit2.x) / 2 + (Math.random() - 0.5) * 100, (fruit1.y + fruit2.y) / 2 + (Math.random() - 0.5) * 100, fruit1.type);
particles.push(particle);
game.addChild(particle);
}
// Add score for destruction
LK.setScore(LK.getScore() + 2000);
scoreTxt.setText(LK.getScore());
// Play merge sound
LK.getSound('merge').play();
// Mark fruits as merged and remove them
fruit1.merged = true;
fruit2.merged = true;
// Remove from fruits array
for (var i = fruits.length - 1; i >= 0; i--) {
if (fruits[i].merged) {
fruits[i].destroy();
fruits.splice(i, 1);
}
}
return;
}
}
if (currentTypeIndex < fruitTypes.length - 1) {
var nextType = fruitTypes[currentTypeIndex + 1];
// Create new merged fruit
var mergedFruit = new Fruit(nextType);
mergedFruit.x = (fruit1.x + fruit2.x) / 2;
mergedFruit.y = (fruit1.y + fruit2.y) / 2;
// Create explosive particle effect - fragments of the original fruits
var particleCount = 8 + Math.floor(Math.random() * 6); // 8-14 particles
for (var p = 0; p < particleCount; p++) {
var particle = new FruitParticle((fruit1.x + fruit2.x) / 2 + (Math.random() - 0.5) * 60, (fruit1.y + fruit2.y) / 2 + (Math.random() - 0.5) * 60, fruit1.type);
particles.push(particle);
game.addChild(particle);
}
// Create explosive merge effect
mergedFruit.scaleX = 0.1;
mergedFruit.scaleY = 0.1;
// First explosion - quick scale up
tween(mergedFruit, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
// Second bounce - scale down to normal
tween(mergedFruit, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.bounceOut
});
}
});
// Add stronger screen shake effect for explosion
var originalX = game.x;
var originalY = game.y;
var shakeAmount = 20; // Increased shake
var shakeDuration = 30; // Shorter, more intense shake
// Multiple shake cycles for more impact
for (var shakeCount = 0; shakeCount < 6; shakeCount++) {
(function (count) {
LK.setTimeout(function () {
var currentShake = shakeAmount * (1 - count / 6); // Diminishing shake
tween(game, {
x: originalX + (Math.random() - 0.5) * currentShake * 2,
y: originalY + (Math.random() - 0.5) * currentShake * 2
}, {
duration: shakeDuration,
easing: tween.easeOut
});
}, count * shakeDuration);
})(shakeCount);
}
// Return to original position
LK.setTimeout(function () {
tween(game, {
x: originalX,
y: originalY
}, {
duration: 100,
easing: tween.easeOut
});
}, 200);
// Add rotation effect for extra visual impact
tween(mergedFruit, {
rotation: Math.PI * 2
}, {
duration: 350,
easing: tween.easeOut
});
// Add tint effect that fades from bright to normal
mergedFruit.tint = 0xFFFF00;
tween(mergedFruit, {
tint: 0xFFFFFF
}, {
duration: 500,
easing: tween.easeOut
});
fruits.push(mergedFruit);
game.addChild(mergedFruit);
// Add score
var scoreToAdd = fruitScores[currentTypeIndex + 1];
LK.setScore(LK.getScore() + scoreToAdd);
scoreTxt.setText(LK.getScore());
// Check and update high score
var currentScore = LK.getScore();
if (currentScore > highScore) {
highScore = currentScore;
highScoreTxt.setText('Best Score: ' + highScore);
// Flash high score text when broken
LK.effects.flashObject(highScoreTxt, 0xFFD700, 1000);
}
// Continue playing even after watermelon - no win condition
// Play merge sound
LK.getSound('merge').play();
// Flash effect
LK.effects.flashObject(mergedFruit, 0xFFFF00, 500);
// Push away nearby fruits from explosion
var mergeX = mergedFruit.x;
var mergeY = mergedFruit.y;
var explosionRadius = 200;
for (var k = 0; k < fruits.length; k++) {
if (fruits[k] !== mergedFruit && !fruits[k].merged) {
var dx = fruits[k].x - mergeX;
var dy = fruits[k].y - mergeY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < explosionRadius && distance > 0) {
var force = (explosionRadius - distance) / explosionRadius * 15;
var normalX = dx / distance;
var normalY = dy / distance;
fruits[k].applyImpulse(normalX * force, normalY * force);
}
}
}
}
// Mark fruits as merged and remove them
fruit1.merged = true;
fruit2.merged = true;
// Remove from fruits array
for (var i = fruits.length - 1; i >= 0; i--) {
if (fruits[i].merged) {
fruits[i].destroy();
fruits.splice(i, 1);
}
}
}
var isDragging = false;
game.move = function (x, y, obj) {
if (canDrop && isDragging) {
dropX = Math.max(40, Math.min(2048 - 40, x));
previewFruit.x = dropX;
}
};
game.down = function (x, y, obj) {
if (canDrop) {
isDragging = true;
dropX = Math.max(40, Math.min(2048 - 40, x));
previewFruit.x = dropX;
}
};
game.up = function (x, y, obj) {
if (canDrop && isDragging) {
isDragging = false;
dropFruit();
}
};
game.update = function () {
for (var i = 0; i < fruits.length; i++) {
fruits[i].update();
}
// Update particles
for (var i = 0; i < particles.length; i++) {
particles[i].update();
}
checkMerges();
};
// Initialize first fruit type
updatePreviewFruit();