/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Fruit class var Fruit = Container.expand(function () { var self = Container.call(this); // Properties self.fruitType = 0; // index in FRUIT_LIST self.radius = 60; // default, will be set below self.vx = 0; self.vy = 0; self.isDropping = false; // true if falling, false if held at top self.isMerging = false; // true if currently merging (to avoid double merges) self.lastMergedTick = -1; // to prevent double merges in one tick // Attach fruit asset self.setType = function (typeIdx) { self.fruitType = typeIdx; if (self.fruitAsset) { self.removeChild(self.fruitAsset); } var fruitId = FRUIT_LIST[typeIdx].id; self.fruitAsset = self.attachAsset(fruitId, { anchorX: 0.5, anchorY: 0.5 }); self.radius = self.fruitAsset.width / 2; }; // Set initial type self.setType(0); // Set position self.setPosition = function (x, y) { self.x = x; self.y = y; }; // Physics update self.update = function () { if (!self.isDropping) return; // Gravity self.vy += 0.62; // reduced gravity for more stable stacking and less shake // Clamp vy if (self.vy > 16) self.vy = 16; // Move self.x += self.vx; self.y += self.vy; // Dampen tiny velocities to avoid jitter if (Math.abs(self.vx) < 0.03) self.vx = 0; if (Math.abs(self.vy) < 0.03) self.vy = 0; // Wall collision if (self.x - self.radius < containerLeftX + wallThickness) { self.x = containerLeftX + wallThickness + self.radius; self.vx *= -0.5; } if (self.x + self.radius > containerRightX - wallThickness) { self.x = containerRightX - wallThickness - self.radius; self.vx *= -0.5; } // Floor collision if (self.y + self.radius > containerBottomY) { self.y = containerBottomY - self.radius; if (Math.abs(self.vy) > 2) { self.vy *= -0.45; } else { self.vy = 0; } // Friction self.vx *= 0.98; if (Math.abs(self.vx) < 0.1) self.vx = 0; } }; // Merge animation self.playMergeAnim = function () { tween(self, { scaleX: 1.25, scaleY: 1.25 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeIn }); } }); }; // Destroy self.destroyFruit = function () { if (self.parent) self.parent.removeChild(self); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222233 }); /**** * Game Code ****/ /**** * Game Code // --- Shake Button --- var shakeBtn = new Text2("Shake!", { size: 110, fill: "#fff", font: "Impact, Arial Black, Tahoma" }); shakeBtn.anchor.set(0.5, 1); // Place at bottom center, above the very bottom (safe for mobile) shakeBtn.x = 2048 / 2; shakeBtn.y = 2732 - 80; shakeBtn.interactive = true; shakeBtn.buttonMode = true; LK.gui.bottom.addChild(shakeBtn); var isShaking = false; function shakeBottle() { if (isShaking) return; isShaking = true; // Animate containerBody, leftWall, rightWall, and all fruits var shakeAmount = 48; var shakeTime = 60; var shakeSeq = [ {x: -shakeAmount, duration: shakeTime}, {x: shakeAmount, duration: shakeTime * 2}, {x: -shakeAmount, duration: shakeTime * 2}, {x: 0, duration: shakeTime} ]; var targets = [containerBody, leftWall, rightWall]; // Save original X for all var origX = []; for (var i = 0; i < targets.length; i++) origX[i] = targets[i].x; // Fruits: only those inside the bottle var fruitOrigX = []; for (var i = 0; i < fruits.length; i++) fruitOrigX[i] = fruits[i].x; var step = 0; function doShakeStep() { if (step >= shakeSeq.length) { // Restore all X for (var i = 0; i < targets.length; i++) targets[i].x = origX[i]; for (var i = 0; i < fruits.length; i++) fruits[i].x = fruitOrigX[i]; isShaking = false; return; } var dx = shakeSeq[step].x; var dur = shakeSeq[step].duration; // Tween container and walls for (var i = 0; i < targets.length; i++) { tween(targets[i], {x: origX[i] + dx}, {duration: dur, easing: tween.cubicInOut}); } // Tween fruits for (var i = 0; i < fruits.length; i++) { tween(fruits[i], {x: fruitOrigX[i] + dx}, {duration: dur, easing: tween.cubicInOut}); // Add a little random nudge to vx for more realism fruits[i].vx += (Math.random() - 0.5) * 2.5; } LK.setTimeout(function () { step++; doShakeStep(); }, dur); } doShakeStep(); } // Button event: shake on down/tap shakeBtn.down = function(x, y, obj) { shakeBottle(); }; /**** * Fruit Progression Data ****/ // Container (the bin) // We'll use colored ellipses for each fruit, with increasing size and distinct colors. // Fruit progression: cherry → strawberry → grape → dekopon → orange → apple → pear → peach → pineapple → melon → watermelon // Container dimensions var FRUIT_LIST = [{ id: 'fruit_cherry', name: 'Cherry', score: 1 }, { id: 'fruit_strawberry', name: 'Strawberry', score: 2 }, { id: 'fruit_grape', name: 'Grape', score: 4 }, { id: 'fruit_dekopon', name: 'Dekopon', score: 8 }, { id: 'fruit_orange', name: 'Orange', score: 16 }, { id: 'fruit_apple', name: 'Apple', score: 32 }, { id: 'fruit_pear', name: 'Pear', score: 64 }, { id: 'fruit_peach', name: 'Peach', score: 128 }, { id: 'fruit_pineapple', name: 'Pineapple', score: 256 }, { id: 'fruit_melon', name: 'Melon', score: 512 }, { id: 'fruit_watermelon', name: 'Watermelon', score: 1024 }]; var containerWidth = 1200; var containerHeight = 1600; var wallThickness = 40; var containerLeftX = (2048 - containerWidth) / 2; var containerRightX = containerLeftX + containerWidth; var containerTopY = 400; var containerBottomY = containerTopY + containerHeight; // Draw container var containerBody = LK.getAsset('container_body', { anchorX: 0, anchorY: 0, x: containerLeftX, y: containerTopY }); game.addChild(containerBody); var leftWall = LK.getAsset('container_wall', { anchorX: 0, anchorY: 0, x: containerLeftX, y: containerTopY }); game.addChild(leftWall); var rightWall = LK.getAsset('container_wall', { anchorX: 0, anchorY: 0, x: containerRightX - wallThickness, y: containerTopY }); game.addChild(rightWall); // Fruits array var fruits = []; // Score var score = 0; var scoreTxt = new Text2('0', { size: 120, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Next fruit preview // The biggest dropable fruit is fruit_grape (index 2) var MAX_DROP_FRUIT_INDEX = 2; var nextFruitType = 0; var nextFruitAsset = LK.getAsset(FRUIT_LIST[nextFruitType].id, { anchorX: 0.5, anchorY: 0.5, // Place in right top corner, but not in the top left 100x100 area x: 2048 - 200, y: 180 }); game.addChild(nextFruitAsset); // Drop spot indicator line var dropLine = LK.getAsset('container_wall', { anchorX: 0.5, anchorY: 0, width: 8, height: containerHeight + 60, color: 0xffffff, x: 2048 / 2, y: containerTopY - 30 }); dropLine.alpha = 0.5; game.addChild(dropLine); // Current dropping fruit var currentFruit = null; // Helper: get random fruit type (up to fruit_grape) function getRandomFruitType() { // Only allow cherry, strawberry, grape (index 0,1,2) return Math.floor(Math.random() * (MAX_DROP_FRUIT_INDEX + 1)); } // Helper: spawn new fruit at top function spawnFruit() { var typeIdx = nextFruitType; currentFruit = new Fruit(); currentFruit.setType(typeIdx); currentFruit.setPosition(2048 / 2, containerTopY - 80); currentFruit.isDropping = false; currentFruit.vx = 0; currentFruit.vy = 0; currentFruit.scaleX = 1; currentFruit.scaleY = 1; game.addChild(currentFruit); // Move dropLine to center for new fruit if (dropLine) dropLine.x = 2048 / 2; // Next fruit nextFruitType = getRandomFruitType(); if (nextFruitAsset && nextFruitAsset.parent) nextFruitAsset.parent.removeChild(nextFruitAsset); nextFruitAsset = LK.getAsset(FRUIT_LIST[nextFruitType].id, { anchorX: 0.5, anchorY: 0.5, x: 2048 - 200, y: 180 }); game.addChild(nextFruitAsset); } // Helper: check collision between two fruits function fruitsCollide(f1, f2) { var dx = f1.x - f2.x; var dy = f1.y - f2.y; var dist = Math.sqrt(dx * dx + dy * dy); return dist < (f1.radius + f2.radius) * 0.98; } // Helper: resolve collision between two fruits (physics bounce) function resolveFruitCollision(f1, f2, pushStrength) { var dx = f1.x - f2.x; var dy = f1.y - f2.y; var dist = Math.sqrt(dx * dx + dy * dy); var minDist = f1.radius + f2.radius; if (dist < minDist && dist > 0.1) { var overlap = minDist - dist; var nx = dx / dist; var ny = dy / dist; // Push fruits apart (stronger separation for stability) var push = overlap / (pushStrength || 1.1); f1.x += nx * push / 2; f1.y += ny * push / 2; f2.x -= nx * push / 2; f2.y -= ny * push / 2; // Exchange velocity (dampen for stacking) var k = 0.45; var tx = nx; var ty = ny; var dp = (f1.vx - f2.vx) * tx + (f1.vy - f2.vy) * ty; if (dp < 0) { f1.vx -= dp * tx * k; f1.vy -= dp * ty * k; f2.vx += dp * tx * k; f2.vy += dp * ty * k; // Dampen vertical velocity for stacking f1.vy *= 0.82; f2.vy *= 0.82; } } } // Helper: merge two fruits (returns new fruit or null) function tryMergeFruits(f1, f2, tick) { if (f1.isMerging || f2.isMerging) return null; if (f1.fruitType !== f2.fruitType) return null; if (f1.fruitType >= FRUIT_LIST.length - 1) return null; // Prevent double merge in one tick if (f1.lastMergedTick === tick || f2.lastMergedTick === tick) return null; // Mark as merging f1.isMerging = true; f2.isMerging = true; f1.lastMergedTick = tick; f2.lastMergedTick = tick; // New fruit at average position var newFruit = new Fruit(); newFruit.setType(f1.fruitType + 1); newFruit.setPosition((f1.x + f2.x) / 2, (f1.y + f2.y) / 2); newFruit.vx = (f1.vx + f2.vx) / 2; newFruit.vy = (f1.vy + f2.vy) / 2 - 8; // bounce up a bit newFruit.isDropping = true; newFruit.scaleX = 0.7; newFruit.scaleY = 0.7; game.addChild(newFruit); // Merge animation newFruit.playMergeAnim(); // Particle effect for (var p = 0; p < 18; p++) { var particle = LK.getAsset(FRUIT_LIST[newFruit.fruitType].id, { anchorX: 0.5, anchorY: 0.5, x: newFruit.x, y: newFruit.y, scaleX: 0.18 + Math.random() * 0.12, scaleY: 0.18 + Math.random() * 0.12, alpha: 0.85 }); game.addChild(particle); var angle = Math.PI * 2 * p / 18 + Math.random() * 0.3; var dist = newFruit.radius * (1.1 + Math.random() * 0.7); var tx = newFruit.x + Math.cos(angle) * dist; var ty = newFruit.y + Math.sin(angle) * dist; tween(particle, { x: tx, y: ty, alpha: 0 }, { duration: 420 + Math.random() * 180, easing: tween.cubicOut, onFinish: function (pt) { return function () { if (pt.parent) pt.parent.removeChild(pt); }; }(particle) }); } // Explode nearby fruits outward for (var i = 0; i < fruits.length; i++) { var other = fruits[i]; if (other !== f1 && other !== f2 && other.isDropping) { var dx = other.x - newFruit.x; var dy = other.y - newFruit.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < newFruit.radius * 2.5 && dist > 0.1) { // Push away with random force var force = 18 + Math.random() * 8; var nx = dx / dist; var ny = dy / dist; other.vx += nx * force; other.vy += ny * force; } } } // Remove old fruits f1.destroyFruit(); f2.destroyFruit(); // Remove from fruits array for (var i = fruits.length - 1; i >= 0; i--) { if (fruits[i] === f1 || fruits[i] === f2) fruits.splice(i, 1); } fruits.push(newFruit); // Add score score += FRUIT_LIST[newFruit.fruitType].score; scoreTxt.setText(score); return newFruit; } // Helper: check for game over (if any fruit is above containerTopY) with 3-2-1 countdown (1 second per count) var gameOverCounter = 0; var gameOverCountdownActive = false; var countdownText = null; var gameOverInterval = null; function clearGameOverInterval() { if (gameOverInterval !== null) { LK.clearInterval(gameOverInterval); gameOverInterval = null; } } function checkGameOver() { var anyAbove = false; for (var i = 0; i < fruits.length; i++) { if (fruits[i].y - fruits[i].radius < containerTopY + 10) { anyAbove = true; break; } } if (anyAbove) { if (!gameOverCountdownActive) { gameOverCounter = 3; gameOverCountdownActive = true; // Show countdown text if (!countdownText) { countdownText = new Text2('3', { size: 220, fill: 0xFF3333 }); countdownText.anchor.set(0.5, 0.5); LK.gui.center.addChild(countdownText); } countdownText.setText("3"); countdownText.visible = true; // Start 1-second interval countdown clearGameOverInterval(); gameOverInterval = LK.setInterval(function () { // Check if still any fruit above var stillAbove = false; for (var i = 0; i < fruits.length; i++) { if (fruits[i].y - fruits[i].radius < containerTopY + 10) { stillAbove = true; break; } } if (!stillAbove) { // Reset if no fruit above clearGameOverInterval(); gameOverCounter = 0; gameOverCountdownActive = false; if (countdownText) countdownText.visible = false; return; } gameOverCounter--; if (countdownText) countdownText.setText(gameOverCounter > 0 ? String(gameOverCounter) : "0"); if (gameOverCounter <= 0) { clearGameOverInterval(); if (countdownText) countdownText.setText("0"); LK.effects.flashScreen(0xff0000, 1000); LK.setTimeout(function () { if (countdownText) countdownText.visible = false; LK.showGameOver(); }, 400); } }, 1000); } // else: already counting down, do nothing (interval handles decrement) } else { // No fruit above, reset everything clearGameOverInterval(); gameOverCounter = 0; gameOverCountdownActive = false; if (countdownText) countdownText.visible = false; } return false; } // Drag and drop for current fruit var isDragging = false; var dragOffsetX = 0; // Only allow drop inside container function clampFruitX(x, fruit) { var minX = containerLeftX + wallThickness + fruit.radius; var maxX = containerRightX - wallThickness - fruit.radius; if (x < minX) x = minX; if (x > maxX) x = maxX; return x; } // Touch/mouse down: start drag if on current fruit // Machine gun drop: hold to drop fruits rapidly var dropInterval = null; game.down = function (x, y, obj) { if (!currentFruit || currentFruit.isDropping) return; // Start interval for machine gun drop (hold to drop repeatedly) if (dropInterval) LK.clearInterval(dropInterval); dropInterval = LK.setInterval(function () { if (!currentFruit || currentFruit.isDropping) { LK.clearInterval(dropInterval); dropInterval = null; return; } // Drop and spawn on interval if holding currentFruit.isDropping = true; currentFruit.vx = 0; currentFruit.vy = 0; fruits.push(currentFruit); // Hide dropLine while fruit is falling if (dropLine) dropLine.visible = false; currentFruit = null; // Spawn next fruit after short delay LK.setTimeout(function () { if (dropLine) dropLine.visible = true; spawnFruit(); }, 120); }, 120); }; // Touch/mouse move: drag fruit horizontally game.move = function (x, y, obj) { if (!currentFruit || currentFruit.isDropping) return; // If dragging, keep old drag logic if (isDragging) { var nx = clampFruitX(x + dragOffsetX, currentFruit); currentFruit.x = nx; if (dropLine) dropLine.x = nx; } else { // Not dragging: always follow mouse/touch X axis var nx = clampFruitX(x, currentFruit); currentFruit.x = nx; if (dropLine) dropLine.x = nx; } }; // Touch/mouse up: drop fruit game.up = function (x, y, obj) { // Stop machine gun drop on release if (dropInterval) { LK.clearInterval(dropInterval); dropInterval = null; } if (!currentFruit || currentFruit.isDropping) return; // Always drop fruit on up, regardless of dragging isDragging = false; currentFruit.isDropping = true; currentFruit.vx = 0; currentFruit.vy = 0; fruits.push(currentFruit); // Hide dropLine while fruit is falling if (dropLine) dropLine.visible = false; currentFruit = null; // Spawn next fruit after short delay LK.setTimeout(function () { if (dropLine) dropLine.visible = true; spawnFruit(); }, 120); }; // Main update loop game.update = function () { // Update all fruits for (var i = 0; i < fruits.length; i++) { fruits[i].update(); } // Fruit-to-fruit collision and merging var tick = LK.ticks; // Run collision resolution more times per frame for much better separation for (var repeat = 0; repeat < 16; repeat++) { for (var i = 0; i < fruits.length; i++) { var f1 = fruits[i]; if (!f1.isDropping) continue; for (var j = i + 1; j < fruits.length; j++) { var f2 = fruits[j]; if (!f2.isDropping) continue; if (fruitsCollide(f1, f2)) { // Try merge only on first pass if (repeat === 0) { var merged = tryMergeFruits(f1, f2, tick); if (merged) break; // f1 is gone, break inner loop } // Always resolve collision // Use a much stronger push for the first several passes resolveFruitCollision(f1, f2, repeat < 6 ? 2.8 : 1.2); } } } } // Check for game over // Only check for game over if there is no currentFruit (i.e., all fruits are dropped) // And only after a 3 second grace period after a fruit is dropped if (!currentFruit) { if (typeof lastFruitDropTime === "undefined") lastFruitDropTime = 0; if (typeof gameOverGraceActive === "undefined") gameOverGraceActive = false; if (!gameOverGraceActive) { // Start grace period after fruit is dropped lastFruitDropTime = Date.now(); gameOverGraceActive = true; } // Wait 3 seconds after drop before checking game over if (Date.now() - lastFruitDropTime > 3000) { checkGameOver(); } } else { // Reset grace period if a new fruit is being held gameOverGraceActive = false; } }; // Start game score = 0; scoreTxt.setText(score); fruits = []; gameOverCounter = 0; gameOverCountdownActive = false; if (typeof countdownText !== "undefined" && countdownText && countdownText.parent) { countdownText.visible = false; } if (nextFruitAsset && nextFruitAsset.parent) nextFruitAsset.parent.removeChild(nextFruitAsset); nextFruitType = getRandomFruitType(); nextFruitAsset = LK.getAsset(FRUIT_LIST[nextFruitType].id, { anchorX: 0.5, anchorY: 0.5, x: 2048 - 200, y: 180 }); game.addChild(nextFruitAsset); spawnFruit();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Fruit class
var Fruit = Container.expand(function () {
var self = Container.call(this);
// Properties
self.fruitType = 0; // index in FRUIT_LIST
self.radius = 60; // default, will be set below
self.vx = 0;
self.vy = 0;
self.isDropping = false; // true if falling, false if held at top
self.isMerging = false; // true if currently merging (to avoid double merges)
self.lastMergedTick = -1; // to prevent double merges in one tick
// Attach fruit asset
self.setType = function (typeIdx) {
self.fruitType = typeIdx;
if (self.fruitAsset) {
self.removeChild(self.fruitAsset);
}
var fruitId = FRUIT_LIST[typeIdx].id;
self.fruitAsset = self.attachAsset(fruitId, {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = self.fruitAsset.width / 2;
};
// Set initial type
self.setType(0);
// Set position
self.setPosition = function (x, y) {
self.x = x;
self.y = y;
};
// Physics update
self.update = function () {
if (!self.isDropping) return;
// Gravity
self.vy += 0.62; // reduced gravity for more stable stacking and less shake
// Clamp vy
if (self.vy > 16) self.vy = 16;
// Move
self.x += self.vx;
self.y += self.vy;
// Dampen tiny velocities to avoid jitter
if (Math.abs(self.vx) < 0.03) self.vx = 0;
if (Math.abs(self.vy) < 0.03) self.vy = 0;
// Wall collision
if (self.x - self.radius < containerLeftX + wallThickness) {
self.x = containerLeftX + wallThickness + self.radius;
self.vx *= -0.5;
}
if (self.x + self.radius > containerRightX - wallThickness) {
self.x = containerRightX - wallThickness - self.radius;
self.vx *= -0.5;
}
// Floor collision
if (self.y + self.radius > containerBottomY) {
self.y = containerBottomY - self.radius;
if (Math.abs(self.vy) > 2) {
self.vy *= -0.45;
} else {
self.vy = 0;
}
// Friction
self.vx *= 0.98;
if (Math.abs(self.vx) < 0.1) self.vx = 0;
}
};
// Merge animation
self.playMergeAnim = function () {
tween(self, {
scaleX: 1.25,
scaleY: 1.25
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeIn
});
}
});
};
// Destroy
self.destroyFruit = function () {
if (self.parent) self.parent.removeChild(self);
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222233
});
/****
* Game Code
****/
/****
* Game Code
// --- Shake Button ---
var shakeBtn = new Text2("Shake!", {
size: 110,
fill: "#fff",
font: "Impact, Arial Black, Tahoma"
});
shakeBtn.anchor.set(0.5, 1);
// Place at bottom center, above the very bottom (safe for mobile)
shakeBtn.x = 2048 / 2;
shakeBtn.y = 2732 - 80;
shakeBtn.interactive = true;
shakeBtn.buttonMode = true;
LK.gui.bottom.addChild(shakeBtn);
var isShaking = false;
function shakeBottle() {
if (isShaking) return;
isShaking = true;
// Animate containerBody, leftWall, rightWall, and all fruits
var shakeAmount = 48;
var shakeTime = 60;
var shakeSeq = [
{x: -shakeAmount, duration: shakeTime},
{x: shakeAmount, duration: shakeTime * 2},
{x: -shakeAmount, duration: shakeTime * 2},
{x: 0, duration: shakeTime}
];
var targets = [containerBody, leftWall, rightWall];
// Save original X for all
var origX = [];
for (var i = 0; i < targets.length; i++) origX[i] = targets[i].x;
// Fruits: only those inside the bottle
var fruitOrigX = [];
for (var i = 0; i < fruits.length; i++) fruitOrigX[i] = fruits[i].x;
var step = 0;
function doShakeStep() {
if (step >= shakeSeq.length) {
// Restore all X
for (var i = 0; i < targets.length; i++) targets[i].x = origX[i];
for (var i = 0; i < fruits.length; i++) fruits[i].x = fruitOrigX[i];
isShaking = false;
return;
}
var dx = shakeSeq[step].x;
var dur = shakeSeq[step].duration;
// Tween container and walls
for (var i = 0; i < targets.length; i++) {
tween(targets[i], {x: origX[i] + dx}, {duration: dur, easing: tween.cubicInOut});
}
// Tween fruits
for (var i = 0; i < fruits.length; i++) {
tween(fruits[i], {x: fruitOrigX[i] + dx}, {duration: dur, easing: tween.cubicInOut});
// Add a little random nudge to vx for more realism
fruits[i].vx += (Math.random() - 0.5) * 2.5;
}
LK.setTimeout(function () {
step++;
doShakeStep();
}, dur);
}
doShakeStep();
}
// Button event: shake on down/tap
shakeBtn.down = function(x, y, obj) {
shakeBottle();
};
/****
* Fruit Progression Data
****/
// Container (the bin)
// We'll use colored ellipses for each fruit, with increasing size and distinct colors.
// Fruit progression: cherry → strawberry → grape → dekopon → orange → apple → pear → peach → pineapple → melon → watermelon
// Container dimensions
var FRUIT_LIST = [{
id: 'fruit_cherry',
name: 'Cherry',
score: 1
}, {
id: 'fruit_strawberry',
name: 'Strawberry',
score: 2
}, {
id: 'fruit_grape',
name: 'Grape',
score: 4
}, {
id: 'fruit_dekopon',
name: 'Dekopon',
score: 8
}, {
id: 'fruit_orange',
name: 'Orange',
score: 16
}, {
id: 'fruit_apple',
name: 'Apple',
score: 32
}, {
id: 'fruit_pear',
name: 'Pear',
score: 64
}, {
id: 'fruit_peach',
name: 'Peach',
score: 128
}, {
id: 'fruit_pineapple',
name: 'Pineapple',
score: 256
}, {
id: 'fruit_melon',
name: 'Melon',
score: 512
}, {
id: 'fruit_watermelon',
name: 'Watermelon',
score: 1024
}];
var containerWidth = 1200;
var containerHeight = 1600;
var wallThickness = 40;
var containerLeftX = (2048 - containerWidth) / 2;
var containerRightX = containerLeftX + containerWidth;
var containerTopY = 400;
var containerBottomY = containerTopY + containerHeight;
// Draw container
var containerBody = LK.getAsset('container_body', {
anchorX: 0,
anchorY: 0,
x: containerLeftX,
y: containerTopY
});
game.addChild(containerBody);
var leftWall = LK.getAsset('container_wall', {
anchorX: 0,
anchorY: 0,
x: containerLeftX,
y: containerTopY
});
game.addChild(leftWall);
var rightWall = LK.getAsset('container_wall', {
anchorX: 0,
anchorY: 0,
x: containerRightX - wallThickness,
y: containerTopY
});
game.addChild(rightWall);
// Fruits array
var fruits = [];
// Score
var score = 0;
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Next fruit preview
// The biggest dropable fruit is fruit_grape (index 2)
var MAX_DROP_FRUIT_INDEX = 2;
var nextFruitType = 0;
var nextFruitAsset = LK.getAsset(FRUIT_LIST[nextFruitType].id, {
anchorX: 0.5,
anchorY: 0.5,
// Place in right top corner, but not in the top left 100x100 area
x: 2048 - 200,
y: 180
});
game.addChild(nextFruitAsset);
// Drop spot indicator line
var dropLine = LK.getAsset('container_wall', {
anchorX: 0.5,
anchorY: 0,
width: 8,
height: containerHeight + 60,
color: 0xffffff,
x: 2048 / 2,
y: containerTopY - 30
});
dropLine.alpha = 0.5;
game.addChild(dropLine);
// Current dropping fruit
var currentFruit = null;
// Helper: get random fruit type (up to fruit_grape)
function getRandomFruitType() {
// Only allow cherry, strawberry, grape (index 0,1,2)
return Math.floor(Math.random() * (MAX_DROP_FRUIT_INDEX + 1));
}
// Helper: spawn new fruit at top
function spawnFruit() {
var typeIdx = nextFruitType;
currentFruit = new Fruit();
currentFruit.setType(typeIdx);
currentFruit.setPosition(2048 / 2, containerTopY - 80);
currentFruit.isDropping = false;
currentFruit.vx = 0;
currentFruit.vy = 0;
currentFruit.scaleX = 1;
currentFruit.scaleY = 1;
game.addChild(currentFruit);
// Move dropLine to center for new fruit
if (dropLine) dropLine.x = 2048 / 2;
// Next fruit
nextFruitType = getRandomFruitType();
if (nextFruitAsset && nextFruitAsset.parent) nextFruitAsset.parent.removeChild(nextFruitAsset);
nextFruitAsset = LK.getAsset(FRUIT_LIST[nextFruitType].id, {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 200,
y: 180
});
game.addChild(nextFruitAsset);
}
// Helper: check collision between two fruits
function fruitsCollide(f1, f2) {
var dx = f1.x - f2.x;
var dy = f1.y - f2.y;
var dist = Math.sqrt(dx * dx + dy * dy);
return dist < (f1.radius + f2.radius) * 0.98;
}
// Helper: resolve collision between two fruits (physics bounce)
function resolveFruitCollision(f1, f2, pushStrength) {
var dx = f1.x - f2.x;
var dy = f1.y - f2.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var minDist = f1.radius + f2.radius;
if (dist < minDist && dist > 0.1) {
var overlap = minDist - dist;
var nx = dx / dist;
var ny = dy / dist;
// Push fruits apart (stronger separation for stability)
var push = overlap / (pushStrength || 1.1);
f1.x += nx * push / 2;
f1.y += ny * push / 2;
f2.x -= nx * push / 2;
f2.y -= ny * push / 2;
// Exchange velocity (dampen for stacking)
var k = 0.45;
var tx = nx;
var ty = ny;
var dp = (f1.vx - f2.vx) * tx + (f1.vy - f2.vy) * ty;
if (dp < 0) {
f1.vx -= dp * tx * k;
f1.vy -= dp * ty * k;
f2.vx += dp * tx * k;
f2.vy += dp * ty * k;
// Dampen vertical velocity for stacking
f1.vy *= 0.82;
f2.vy *= 0.82;
}
}
}
// Helper: merge two fruits (returns new fruit or null)
function tryMergeFruits(f1, f2, tick) {
if (f1.isMerging || f2.isMerging) return null;
if (f1.fruitType !== f2.fruitType) return null;
if (f1.fruitType >= FRUIT_LIST.length - 1) return null;
// Prevent double merge in one tick
if (f1.lastMergedTick === tick || f2.lastMergedTick === tick) return null;
// Mark as merging
f1.isMerging = true;
f2.isMerging = true;
f1.lastMergedTick = tick;
f2.lastMergedTick = tick;
// New fruit at average position
var newFruit = new Fruit();
newFruit.setType(f1.fruitType + 1);
newFruit.setPosition((f1.x + f2.x) / 2, (f1.y + f2.y) / 2);
newFruit.vx = (f1.vx + f2.vx) / 2;
newFruit.vy = (f1.vy + f2.vy) / 2 - 8; // bounce up a bit
newFruit.isDropping = true;
newFruit.scaleX = 0.7;
newFruit.scaleY = 0.7;
game.addChild(newFruit);
// Merge animation
newFruit.playMergeAnim();
// Particle effect
for (var p = 0; p < 18; p++) {
var particle = LK.getAsset(FRUIT_LIST[newFruit.fruitType].id, {
anchorX: 0.5,
anchorY: 0.5,
x: newFruit.x,
y: newFruit.y,
scaleX: 0.18 + Math.random() * 0.12,
scaleY: 0.18 + Math.random() * 0.12,
alpha: 0.85
});
game.addChild(particle);
var angle = Math.PI * 2 * p / 18 + Math.random() * 0.3;
var dist = newFruit.radius * (1.1 + Math.random() * 0.7);
var tx = newFruit.x + Math.cos(angle) * dist;
var ty = newFruit.y + Math.sin(angle) * dist;
tween(particle, {
x: tx,
y: ty,
alpha: 0
}, {
duration: 420 + Math.random() * 180,
easing: tween.cubicOut,
onFinish: function (pt) {
return function () {
if (pt.parent) pt.parent.removeChild(pt);
};
}(particle)
});
}
// Explode nearby fruits outward
for (var i = 0; i < fruits.length; i++) {
var other = fruits[i];
if (other !== f1 && other !== f2 && other.isDropping) {
var dx = other.x - newFruit.x;
var dy = other.y - newFruit.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < newFruit.radius * 2.5 && dist > 0.1) {
// Push away with random force
var force = 18 + Math.random() * 8;
var nx = dx / dist;
var ny = dy / dist;
other.vx += nx * force;
other.vy += ny * force;
}
}
}
// Remove old fruits
f1.destroyFruit();
f2.destroyFruit();
// Remove from fruits array
for (var i = fruits.length - 1; i >= 0; i--) {
if (fruits[i] === f1 || fruits[i] === f2) fruits.splice(i, 1);
}
fruits.push(newFruit);
// Add score
score += FRUIT_LIST[newFruit.fruitType].score;
scoreTxt.setText(score);
return newFruit;
}
// Helper: check for game over (if any fruit is above containerTopY) with 3-2-1 countdown (1 second per count)
var gameOverCounter = 0;
var gameOverCountdownActive = false;
var countdownText = null;
var gameOverInterval = null;
function clearGameOverInterval() {
if (gameOverInterval !== null) {
LK.clearInterval(gameOverInterval);
gameOverInterval = null;
}
}
function checkGameOver() {
var anyAbove = false;
for (var i = 0; i < fruits.length; i++) {
if (fruits[i].y - fruits[i].radius < containerTopY + 10) {
anyAbove = true;
break;
}
}
if (anyAbove) {
if (!gameOverCountdownActive) {
gameOverCounter = 3;
gameOverCountdownActive = true;
// Show countdown text
if (!countdownText) {
countdownText = new Text2('3', {
size: 220,
fill: 0xFF3333
});
countdownText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(countdownText);
}
countdownText.setText("3");
countdownText.visible = true;
// Start 1-second interval countdown
clearGameOverInterval();
gameOverInterval = LK.setInterval(function () {
// Check if still any fruit above
var stillAbove = false;
for (var i = 0; i < fruits.length; i++) {
if (fruits[i].y - fruits[i].radius < containerTopY + 10) {
stillAbove = true;
break;
}
}
if (!stillAbove) {
// Reset if no fruit above
clearGameOverInterval();
gameOverCounter = 0;
gameOverCountdownActive = false;
if (countdownText) countdownText.visible = false;
return;
}
gameOverCounter--;
if (countdownText) countdownText.setText(gameOverCounter > 0 ? String(gameOverCounter) : "0");
if (gameOverCounter <= 0) {
clearGameOverInterval();
if (countdownText) countdownText.setText("0");
LK.effects.flashScreen(0xff0000, 1000);
LK.setTimeout(function () {
if (countdownText) countdownText.visible = false;
LK.showGameOver();
}, 400);
}
}, 1000);
}
// else: already counting down, do nothing (interval handles decrement)
} else {
// No fruit above, reset everything
clearGameOverInterval();
gameOverCounter = 0;
gameOverCountdownActive = false;
if (countdownText) countdownText.visible = false;
}
return false;
}
// Drag and drop for current fruit
var isDragging = false;
var dragOffsetX = 0;
// Only allow drop inside container
function clampFruitX(x, fruit) {
var minX = containerLeftX + wallThickness + fruit.radius;
var maxX = containerRightX - wallThickness - fruit.radius;
if (x < minX) x = minX;
if (x > maxX) x = maxX;
return x;
}
// Touch/mouse down: start drag if on current fruit
// Machine gun drop: hold to drop fruits rapidly
var dropInterval = null;
game.down = function (x, y, obj) {
if (!currentFruit || currentFruit.isDropping) return;
// Start interval for machine gun drop (hold to drop repeatedly)
if (dropInterval) LK.clearInterval(dropInterval);
dropInterval = LK.setInterval(function () {
if (!currentFruit || currentFruit.isDropping) {
LK.clearInterval(dropInterval);
dropInterval = null;
return;
}
// Drop and spawn on interval if holding
currentFruit.isDropping = true;
currentFruit.vx = 0;
currentFruit.vy = 0;
fruits.push(currentFruit);
// Hide dropLine while fruit is falling
if (dropLine) dropLine.visible = false;
currentFruit = null;
// Spawn next fruit after short delay
LK.setTimeout(function () {
if (dropLine) dropLine.visible = true;
spawnFruit();
}, 120);
}, 120);
};
// Touch/mouse move: drag fruit horizontally
game.move = function (x, y, obj) {
if (!currentFruit || currentFruit.isDropping) return;
// If dragging, keep old drag logic
if (isDragging) {
var nx = clampFruitX(x + dragOffsetX, currentFruit);
currentFruit.x = nx;
if (dropLine) dropLine.x = nx;
} else {
// Not dragging: always follow mouse/touch X axis
var nx = clampFruitX(x, currentFruit);
currentFruit.x = nx;
if (dropLine) dropLine.x = nx;
}
};
// Touch/mouse up: drop fruit
game.up = function (x, y, obj) {
// Stop machine gun drop on release
if (dropInterval) {
LK.clearInterval(dropInterval);
dropInterval = null;
}
if (!currentFruit || currentFruit.isDropping) return;
// Always drop fruit on up, regardless of dragging
isDragging = false;
currentFruit.isDropping = true;
currentFruit.vx = 0;
currentFruit.vy = 0;
fruits.push(currentFruit);
// Hide dropLine while fruit is falling
if (dropLine) dropLine.visible = false;
currentFruit = null;
// Spawn next fruit after short delay
LK.setTimeout(function () {
if (dropLine) dropLine.visible = true;
spawnFruit();
}, 120);
};
// Main update loop
game.update = function () {
// Update all fruits
for (var i = 0; i < fruits.length; i++) {
fruits[i].update();
}
// Fruit-to-fruit collision and merging
var tick = LK.ticks;
// Run collision resolution more times per frame for much better separation
for (var repeat = 0; repeat < 16; repeat++) {
for (var i = 0; i < fruits.length; i++) {
var f1 = fruits[i];
if (!f1.isDropping) continue;
for (var j = i + 1; j < fruits.length; j++) {
var f2 = fruits[j];
if (!f2.isDropping) continue;
if (fruitsCollide(f1, f2)) {
// Try merge only on first pass
if (repeat === 0) {
var merged = tryMergeFruits(f1, f2, tick);
if (merged) break; // f1 is gone, break inner loop
}
// Always resolve collision
// Use a much stronger push for the first several passes
resolveFruitCollision(f1, f2, repeat < 6 ? 2.8 : 1.2);
}
}
}
}
// Check for game over
// Only check for game over if there is no currentFruit (i.e., all fruits are dropped)
// And only after a 3 second grace period after a fruit is dropped
if (!currentFruit) {
if (typeof lastFruitDropTime === "undefined") lastFruitDropTime = 0;
if (typeof gameOverGraceActive === "undefined") gameOverGraceActive = false;
if (!gameOverGraceActive) {
// Start grace period after fruit is dropped
lastFruitDropTime = Date.now();
gameOverGraceActive = true;
}
// Wait 3 seconds after drop before checking game over
if (Date.now() - lastFruitDropTime > 3000) {
checkGameOver();
}
} else {
// Reset grace period if a new fruit is being held
gameOverGraceActive = false;
}
};
// Start game
score = 0;
scoreTxt.setText(score);
fruits = [];
gameOverCounter = 0;
gameOverCountdownActive = false;
if (typeof countdownText !== "undefined" && countdownText && countdownText.parent) {
countdownText.visible = false;
}
if (nextFruitAsset && nextFruitAsset.parent) nextFruitAsset.parent.removeChild(nextFruitAsset);
nextFruitType = getRandomFruitType();
nextFruitAsset = LK.getAsset(FRUIT_LIST[nextFruitType].id, {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 200,
y: 180
});
game.addChild(nextFruitAsset);
spawnFruit();
“A cute cartoon-style baby chick standing upright, designed as a game character. Big round eyes, small orange beak, fluffy yellow feathers, small wings, tiny feet. Bright and happy expression, simple colorful background, high-quality digital illustration, ideal for a mobile game character.. In-Game asset. 2d. High contrast. No shadows. cartoon style
“A cute cartoon-style frog character for a mobile game. Bright green skin, big round eyes, wide smiling mouth, small chubby body, sitting or standing upright. Friendly and playful expression, simple colorful background. High-quality digital illustration, perfect for a fun kids game.”. In-Game asset. 2d. High contrast. No shadows. cartoon style
“A cute cartoon-style chicken character designed for a mobile game. White fluffy feathers, red comb and wattle, small yellow beak, round black eyes, tiny orange feet, standing upright with a cheerful expression. Colorful, simple background. High-quality digital illustration, ideal for a fun kids game.”. In-Game asset. 2d. High contrast. No shadows. cartoon style
“A cute cartoon-style snail character for a mobile game. Soft pastel-colored shell (spiral-shaped), smooth shiny body in light beige or green, big round eyes on long eye stalks, small smiling mouth, friendly and playful expression. Standing on a leaf or simple colorful background. High-quality digital illustration, perfect for a kids game.”. In-Game asset. 2d. High contrast. No shadows. cartoon style
A cute cartoon-style panda character for a mobile game. Chubby body with classic black and white fur, big round eyes with a sparkle, small ears, sitting or standing upright with a happy, playful expression. Holding a bamboo stick or waving. Colorful, simple background. High-quality digital illustration, ideal for a children’s game.”. In-Game asset. 2d. High contrast. No shadows. cartoon style
A cute cartoon-style cat character for a mobile game. Soft fluffy fur, big round eyes, small triangular ears, pink nose, and a curled tail. Light gray or orange color, standing or sitting with a playful and happy expression. High-quality digital illustration with a simple, colorful background. Ideal for a fun and friendly kids game.”. In-Game asset. 2d. High contrast. No shadows. cartoon style
A cute cartoon-style dog character designed for a mobile game. Fluffy fur, big round eyes, floppy ears, small black nose, wagging tail, standing or sitting with a joyful and friendly expression. Light brown or beige color, simple colorful background. High-quality digital illustration, perfect for a kids game.. In-Game asset. 2d. High contrast. No shadows. cartoon style
add bambu right hand
A cute cartoon-style dolphin character designed for a mobile game. Smooth, shiny skin, big round eyes full of curiosity, a friendly smile, and a playful pose as if leaping or floating in water. Light blue or aqua color with soft highlights, small flippers, and a sleek body. Joyful and approachable expression. Simple, colorful underwater background with bubbles and coral. High-quality digital illustration, perfect for a kids game.. In-Game asset. 2d. High contrast. No shadows. cartoon style
A cute cartoon-style shark character designed for a mobile game. Smooth and slightly chubby body, big round eyes with a friendly sparkle, a wide cheerful grin with small, non-scary teeth. Light gray or blue-gray color with a soft white belly. Playful pose, as if swimming happily or waving a fin. Simple colorful underwater background with bubbles, coral, or treasure chests. Joyful and friendly expression, perfect for kids. High-quality digital illustration, ideal for a children’s game.. In-Game asset. 2d. High contrast. No shadows. cartoon style
A beautiful flower wall made of a lush mix of vibrant, colorful flowers like roses, peonies, hydrangeas, and orchids. The wall is densely packed with blossoms in shades of pink, red, white, purple, and pastel tones. Greenery like ivy and leaves peek through the blooms, adding depth and contrast. The arrangement is elegant and symmetrical, ideal as a photo backdrop for events like weddings or parties. High-resolution, soft lighting, and dreamy atmosphere.. In-Game asset. 2d. High contrast. No shadows. cartoon style