/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Bomb class
var Bomb = Container.expand(function () {
var self = Container.call(this);
// Bomb asset: black circle with red border
var assetId = 'bomb';
var bombGfx = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Add a red border by overlaying a slightly larger ellipse
var borderId = 'bomb_border';
var borderGfx = self.attachAsset(borderId, {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3
});
// Physics
self.vx = 0;
self.vy = 0;
self.gravity = 0.35 + Math.random() * 0.1;
self.sliced = false;
self.slice = function () {
if (self.sliced) return;
self.sliced = true;
// Animate: flash and fade out
tween(self, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 250,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
}
});
};
self.update = function () {
if (self.sliced) return;
self.x += self.vx;
self.y += self.vy;
self.vy += self.gravity;
// Prevent from leaving left/right screen edges
if (self.x < 0 + self.width * 0.5) {
self.x = self.width * 0.5;
self.vx = -self.vx * 0.55;
}
if (self.x > 2048 - self.width * 0.5) {
self.x = 2048 - self.width * 0.5;
self.vx = -self.vx * 0.55;
}
};
return self;
});
// Fruit class
var Fruit = Container.expand(function () {
var self = Container.call(this);
// Fruit types: apple, banana, orange, watermelon, etc.
// Each type has a color and shape
// We'll use 'ellipse' for round fruits, 'box' for banana
// We'll randomize type on creation
var types = [{
id: 'apple',
color: 0xff2d2d,
shape: 'ellipse',
width: 320,
height: 320,
points: 1
}, {
id: 'orange',
color: 0xffa500,
shape: 'ellipse',
width: 300,
height: 300,
points: 1
}, {
id: 'banana',
color: 0xfff700,
shape: 'box',
width: 420,
height: 160,
points: 2
}, {
id: 'watermelon',
color: 0x2ecc40,
shape: 'ellipse',
width: 420,
height: 420,
points: 3
}, {
id: 'kiwi',
color: 0x8ee53f,
shape: 'ellipse',
width: 270,
height: 270,
points: 2
}];
var type = types[Math.floor(Math.random() * types.length)];
// Asset id is unique per type
var assetId = 'fruit_' + type.id;
// Initialize asset if not already
var fruitGfx = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Physics
self.vx = 0;
self.vy = 0;
self.gravity = 0.35 + Math.random() * 0.1;
self.sliced = false;
self.points = type.points;
// For arc motion, we set vx, vy on spawn
// Slicing animation
self.slice = function () {
if (self.sliced) return;
self.sliced = true;
// Play slicing sound
LK.getSound('Ninjasword').play();
// --- Split fruit into two visually distinct halves animation ---
// Top half (slightly lighter, with a white highlight)
var halfTop = LK.getAsset(assetId, {
anchorX: 0.5,
anchorY: 1.0,
x: self.x,
y: self.y,
rotation: self.rotation,
scaleY: 0.52,
tint: 0xffffff // white tint for highlight
});
var halfBottom = LK.getAsset(assetId, {
anchorX: 0.5,
anchorY: 0.0,
x: self.x,
y: self.y,
rotation: self.rotation,
scaleY: 0.52,
tint: type.color // original fruit color for bottom
});
game.addChild(halfTop);
game.addChild(halfBottom);
// Animate halves flying apart, rotating and fading out
tween(halfTop, {
x: self.x - 60,
y: self.y - 80,
rotation: self.rotation - 0.32,
alpha: 0
}, {
duration: 420,
easing: tween.easeOut,
onFinish: function onFinish() {
halfTop.destroy();
}
});
tween(halfBottom, {
x: self.x + 60,
y: self.y + 80,
rotation: self.rotation + 0.32,
alpha: 0
}, {
duration: 420,
easing: tween.easeOut,
onFinish: function onFinish() {
halfBottom.destroy();
}
});
// Juice splash effect (ellipse, color based on fruit)
// More vivid color for splash (slightly saturated)
var vividJuiceColor = function (c) {
// Slightly brighten and saturate the color
var r = Math.min(255, (c >> 16 & 0xff) * 1.15 + 32) | 0;
var g = Math.min(255, (c >> 8 & 0xff) * 1.15 + 32) | 0;
var b = Math.min(255, (c & 0xff) * 1.15 + 32) | 0;
return r << 16 | g << 8 | b;
}(type.color || 0xff2d2d);
// Create multiple juice particles for a juicier effect
var juiceParticles = [];
var juiceParticleCount = 12 + Math.floor(Math.random() * 7); // 12-18 particles
for (var jp = 0; jp < juiceParticleCount; jp++) {
var angle = Math.random() * Math.PI * 2;
var dist = 40 + Math.random() * 60;
var scale = 0.22 + Math.random() * 0.22;
var alpha = 0.55 + Math.random() * 0.25;
var splash = LK.getAsset('juice_splash_' + type.id, {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
scaleX: scale * (0.8 + Math.random() * 0.7),
scaleY: scale * (0.5 + Math.random() * 1.1),
alpha: alpha,
tint: vividJuiceColor
});
game.addChild(splash);
juiceParticles.push(splash);
// Animate each particle outward and fade
tween(splash, {
x: self.x + Math.cos(angle) * dist,
y: self.y + Math.sin(angle) * dist,
scaleX: scale * (1.2 + Math.random() * 1.2),
scaleY: scale * (1.1 + Math.random() * 1.2),
alpha: 0
}, {
duration: 320 + Math.random() * 120,
easing: tween.easeOut,
onFinish: function (s) {
return function () {
s.destroy();
};
}(splash)
});
}
// --- Add stain to background ---
if (typeof background !== "undefined" && background && background.parent) {
// Create a stain asset (ellipse, color based on fruit, random rotation/scale)
var stain = LK.getAsset('juice_stain_' + type.id, {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
scaleX: 0.7 + Math.random() * 0.7,
scaleY: 0.5 + Math.random() * 0.7,
alpha: 0.32 + Math.random() * 0.18,
rotation: Math.random() * Math.PI * 2,
tint: vividJuiceColor
});
// Insert stain just above the background (so it stays under fruit halves/splashes)
var bgParent = background.parent;
var bgIndex = bgParent.getChildIndex(background);
bgParent.addChildAt(stain, bgIndex + 1);
}
// --- End stain ---
// Hide original fruit immediately
self.alpha = 0;
tween(self, {
alpha: 0
}, {
duration: 1,
onFinish: function onFinish() {
self.destroy();
}
});
};
// Update per frame
self.update = function () {
if (self.sliced) return;
self.x += self.vx;
self.y += self.vy;
self.vy += self.gravity;
// Prevent from leaving left/right screen edges
if (self.x < 0 + self.width * 0.5) {
self.x = self.width * 0.5;
self.vx = -self.vx * 0.55; // bounce back with some energy loss
}
if (self.x > 2048 - self.width * 0.5) {
self.x = 2048 - self.width * 0.5;
self.vx = -self.vx * 0.55;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a1a
});
/****
* Game Code
****/
// Add background image
// Game variables
var background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(background);
var fruits = [];
var bombs = [];
var fruitSpawnTimer = 0;
var bombSpawnTimer = 0;
var fruitSpawnInterval = 120; // 2 seconds at 60fps
var bombSpawnInterval = 300; // 5 seconds at 60fps
var score = 0;
var highScore = storage.highScore || 0;
var missed = 0;
var maxMissed = 3;
var isGameOver = false;
// Score display
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Combo system
var comboCount = 0;
var comboTimer = 0;
var comboTimeout = 36; // 0.6s at 60fps
var maxCombo = 0;
var comboTxt = new Text2('', {
size: 90,
fill: 0xFFFA00
});
comboTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(comboTxt);
// High score display (smaller, top right)
var highScoreTxt = new Text2('Best: ' + highScore, {
size: 60,
fill: "#fff"
});
highScoreTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(highScoreTxt);
// Missed fruits display (bottom left, as X icons)
var missTxt = new Text2('', {
size: 90,
fill: 0xFF4444
});
missTxt.anchor.set(0, 1);
LK.gui.bottomLeft.addChild(missTxt);
// Helper: update score
function updateScore(val) {
score = val;
scoreTxt.setText(score);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('Best: ' + highScore);
}
}
// Helper: update missed display
function updateMissed() {
var s = '';
for (var i = 0; i < missed; i++) s += '✗ ';
missTxt.setText(s);
}
// (spawning is now handled by fruit/bomb timers in game.update)
// Slicing logic
var swipePath = [];
var swipeMaxLength = 12; // Number of points to keep in swipe trail
var swipeRadius = 90; // How close swipe must be to slice
// Draw swipe trail (for visual feedback)
var swipeTrail = [];
function drawSwipeTrail() {
// Remove old
for (var i = 0; i < swipeTrail.length; i++) {
swipeTrail[i].destroy();
}
swipeTrail = [];
// Draw new
for (var i = 1; i < swipePath.length; i++) {
var p1 = swipePath[i - 1];
var p2 = swipePath[i];
// Draw a short white ellipse between points
var dx = p2.x - p1.x,
dy = p2.y - p1.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 10) continue;
var angle = Math.atan2(dy, dx);
var id = 'swipe_' + i;
var seg = LK.getAsset(id, {
anchorX: 0,
anchorY: 0.5,
x: p1.x,
y: p1.y,
rotation: angle,
alpha: 0.22
});
game.addChild(seg);
swipeTrail.push(seg);
}
}
// Handle swipe start
game.down = function (x, y, obj) {
if (isGameOver) return;
swipePath = [{
x: x,
y: y
}];
drawSwipeTrail();
};
// Handle swipe move
game.move = function (x, y, obj) {
if (isGameOver) return;
swipePath.push({
x: x,
y: y
});
if (swipePath.length > swipeMaxLength) swipePath.shift();
drawSwipeTrail();
// For each segment in swipe, check for intersection with fruits/bombs
for (var i = 1; i < swipePath.length; i++) {
var p1 = swipePath[i - 1],
p2 = swipePath[i];
// Fruits
for (var f = fruits.length - 1; f >= 0; f--) {
var fruit = fruits[f];
if (fruit.sliced) continue;
// If fruit already off screen, skip
if (fruit.y > 2732 + 120) continue;
// Check if swipe segment is close to fruit center
var cx = fruit.x,
cy = fruit.y;
var dist = pointToSegmentDistance(cx, cy, p1.x, p1.y, p2.x, p2.y);
if (dist < swipeRadius) {
fruit.slice();
updateScore(score + fruit.points); // Increase score by fruit's point value
fruits.splice(f, 1);
// Flash effect
LK.effects.flashObject(fruit, 0xffffff, 120);
// --- Combo logic ---
comboCount++;
comboTimer = comboTimeout;
if (comboCount > maxCombo) maxCombo = comboCount;
if (comboCount > 1) {
comboTxt.setText(comboCount + " Combo!");
comboTxt.alpha = 1;
// Animate combo text alpha in case it was faded out
tween(comboTxt, {
alpha: 1
}, {
duration: 60
});
// Bonus points for combos (e.g. +2 for 3-combo, +4 for 4-combo, etc)
if (comboCount >= 3) {
var bonus = (comboCount - 2) * 2;
updateScore(score + bonus);
// Animate combo text
tween(comboTxt, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 120,
yoyo: true,
repeat: 1
});
}
}
}
}
// Bombs
for (var b = bombs.length - 1; b >= 0; b--) {
var bomb = bombs[b];
if (bomb.sliced) continue;
if (bomb.y > 2732 + 120) continue;
var cx = bomb.x,
cy = bomb.y;
var dist = pointToSegmentDistance(cx, cy, p1.x, p1.y, p2.x, p2.y);
if (dist < swipeRadius) {
bomb.slice();
bombs.splice(b, 1);
// Play bomb sound
LK.getSound('Bomb').play();
// Increase score by 1 when bomb is sliced
updateScore(score + 1);
// Flash screen red, then game over
LK.effects.flashScreen(0xff2222, 800);
isGameOver = true;
LK.setTimeout(function () {
LK.showGameOver();
}, 600);
return;
}
}
}
};
// Handle swipe end
game.up = function (x, y, obj) {
swipePath = [];
drawSwipeTrail();
};
// Helper: distance from point to segment
function pointToSegmentDistance(px, py, x1, y1, x2, y2) {
var dx = x2 - x1,
dy = y2 - y1;
if (dx === 0 && dy === 0) {
dx = px - x1;
dy = py - y1;
return Math.sqrt(dx * dx + dy * dy);
}
var t = ((px - x1) * dx + (py - y1) * dy) / (dx * dx + dy * dy);
t = Math.max(0, Math.min(1, t));
var lx = x1 + t * dx,
ly = y1 + t * dy;
dx = px - lx;
dy = py - ly;
return Math.sqrt(dx * dx + dy * dy);
}
// Main game update
game.update = function () {
if (isGameOver) return;
// --- Dynamic difficulty: speed up game as score increases ---
var minFruitInterval = 36; // minimum interval (0.6s)
var minBombInterval = 120; // minimum interval (2s)
var fruitIntervalStep = Math.max(0, fruitSpawnInterval - Math.floor(score / 10) * 10);
fruitSpawnInterval = Math.max(minFruitInterval, 120 - Math.floor(score / 10) * 10);
bombSpawnInterval = Math.max(minBombInterval, 300 - Math.floor(score / 15) * 15);
// Spawn logic
fruitSpawnTimer++;
bombSpawnTimer++;
if (fruitSpawnTimer >= fruitSpawnInterval) {
// Spawn a fruit only
var obj = new Fruit();
// All fruits spawn at the same Y (bottom), but random X across the screen (leaving margin for fruit width)
var fruitMargin = 200;
var x = fruitMargin + Math.random() * (2048 - 2 * fruitMargin);
var y = 2732 + 60; // All fruits spawn just below the bottom of the screen
// Launch angle is always straight up
var angle = -Math.PI / 2;
var speed = 17 + Math.random() * 5.5 + score * 0.055;
// vx is 0 (no side velocity), vy is always up
var vx = 0;
var vy = -Math.abs(Math.sin(angle) * speed) * 1.32;
obj.x = x;
obj.y = y;
obj.vx = vx;
obj.vy = vy;
obj.scaleX = obj.scaleY = 1;
obj.alpha = 1;
// Increase gravity for fruit as score increases
obj.gravity += Math.min(0.18, score * 0.012);
fruits.push(obj);
game.addChild(obj);
fruitSpawnTimer = 0;
}
if (bombSpawnTimer >= bombSpawnInterval) {
// Spawn a bomb only
var obj = new Bomb();
// All bombs spawn at the same Y (bottom), but random X across the screen (leaving margin for bomb width)
var bombMargin = 200;
var x = bombMargin + Math.random() * (2048 - 2 * bombMargin);
var y = 2732 + 60; // All bombs spawn just below the bottom of the screen
// Launch angle is always straight up
var angle = -Math.PI / 2;
var speed = 17 + Math.random() * 5.5 + score * 0.055;
// vx is 0 (no side velocity), vy is always up
var vx = 0;
var vy = -Math.abs(Math.sin(angle) * speed) * 1.32;
obj.x = x;
obj.y = y;
obj.vx = vx;
obj.vy = vy;
obj.scaleX = obj.scaleY = 1;
obj.alpha = 1;
// Increase gravity for bomb as score increases
obj.gravity += Math.min(0.18, score * 0.012);
bombs.push(obj);
game.addChild(obj);
bombSpawnTimer = 0;
}
// Update fruits
for (var i = fruits.length - 1; i >= 0; i--) {
var fruit = fruits[i];
fruit.update();
// Remove if off screen
if (!fruit.sliced && fruit.y > 2732 + 120) {
// Play lose sound when fruit is missed
LK.getSound('Lose').play();
fruit.destroy();
fruits.splice(i, 1);
missed++;
updateMissed();
// Flash screen if missed
LK.effects.flashScreen(0x2222ff, 200);
if (missed >= maxMissed) {
isGameOver = true;
LK.setTimeout(function () {
LK.showGameOver();
}, 500);
}
// Do NOT increase score here; only bomb slice increases score now
}
}
// Update bombs
for (var i = bombs.length - 1; i >= 0; i--) {
var bomb = bombs[i];
bomb.update();
if (bomb.y > 2732 + 120) {
bomb.destroy();
bombs.splice(i, 1);
}
}
// Fade out swipe trail
for (var i = 0; i < swipeTrail.length; i++) {
swipeTrail[i].alpha *= 0.85;
if (swipeTrail[i].alpha < 0.05) {
swipeTrail[i].destroy();
swipeTrail.splice(i, 1);
i--;
}
}
// --- Combo timer logic ---
if (comboTimer > 0) {
comboTimer--;
if (comboTimer === 0) {
// Combo ended
comboCount = 0;
tween(comboTxt, {
alpha: 0
}, {
duration: 320
});
}
}
};
// Reset game state on new game
game.on('reset', function () {
// Remove all fruits and bombs
for (var i = 0; i < fruits.length; i++) fruits[i].destroy();
for (var i = 0; i < bombs.length; i++) bombs[i].destroy();
fruits = [];
bombs = [];
fruitSpawnTimer = 0;
bombSpawnTimer = 0;
score = 0;
missed = 0;
isGameOver = false;
updateScore(0);
updateMissed();
swipePath = [];
drawSwipeTrail();
comboCount = 0;
comboTimer = 0;
comboTxt.setText('');
comboTxt.alpha = 0;
maxCombo = 0;
highScore = storage.highScore || 0;
highScoreTxt.setText('Best: ' + highScore);
});
// Initial state
updateScore(0);
updateMissed();
// Fruit assets
// Bomb asset: black circle
// Bomb border: red, slightly larger, semi-transparent /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Bomb class
var Bomb = Container.expand(function () {
var self = Container.call(this);
// Bomb asset: black circle with red border
var assetId = 'bomb';
var bombGfx = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Add a red border by overlaying a slightly larger ellipse
var borderId = 'bomb_border';
var borderGfx = self.attachAsset(borderId, {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3
});
// Physics
self.vx = 0;
self.vy = 0;
self.gravity = 0.35 + Math.random() * 0.1;
self.sliced = false;
self.slice = function () {
if (self.sliced) return;
self.sliced = true;
// Animate: flash and fade out
tween(self, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 250,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
}
});
};
self.update = function () {
if (self.sliced) return;
self.x += self.vx;
self.y += self.vy;
self.vy += self.gravity;
// Prevent from leaving left/right screen edges
if (self.x < 0 + self.width * 0.5) {
self.x = self.width * 0.5;
self.vx = -self.vx * 0.55;
}
if (self.x > 2048 - self.width * 0.5) {
self.x = 2048 - self.width * 0.5;
self.vx = -self.vx * 0.55;
}
};
return self;
});
// Fruit class
var Fruit = Container.expand(function () {
var self = Container.call(this);
// Fruit types: apple, banana, orange, watermelon, etc.
// Each type has a color and shape
// We'll use 'ellipse' for round fruits, 'box' for banana
// We'll randomize type on creation
var types = [{
id: 'apple',
color: 0xff2d2d,
shape: 'ellipse',
width: 320,
height: 320,
points: 1
}, {
id: 'orange',
color: 0xffa500,
shape: 'ellipse',
width: 300,
height: 300,
points: 1
}, {
id: 'banana',
color: 0xfff700,
shape: 'box',
width: 420,
height: 160,
points: 2
}, {
id: 'watermelon',
color: 0x2ecc40,
shape: 'ellipse',
width: 420,
height: 420,
points: 3
}, {
id: 'kiwi',
color: 0x8ee53f,
shape: 'ellipse',
width: 270,
height: 270,
points: 2
}];
var type = types[Math.floor(Math.random() * types.length)];
// Asset id is unique per type
var assetId = 'fruit_' + type.id;
// Initialize asset if not already
var fruitGfx = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Physics
self.vx = 0;
self.vy = 0;
self.gravity = 0.35 + Math.random() * 0.1;
self.sliced = false;
self.points = type.points;
// For arc motion, we set vx, vy on spawn
// Slicing animation
self.slice = function () {
if (self.sliced) return;
self.sliced = true;
// Play slicing sound
LK.getSound('Ninjasword').play();
// --- Split fruit into two visually distinct halves animation ---
// Top half (slightly lighter, with a white highlight)
var halfTop = LK.getAsset(assetId, {
anchorX: 0.5,
anchorY: 1.0,
x: self.x,
y: self.y,
rotation: self.rotation,
scaleY: 0.52,
tint: 0xffffff // white tint for highlight
});
var halfBottom = LK.getAsset(assetId, {
anchorX: 0.5,
anchorY: 0.0,
x: self.x,
y: self.y,
rotation: self.rotation,
scaleY: 0.52,
tint: type.color // original fruit color for bottom
});
game.addChild(halfTop);
game.addChild(halfBottom);
// Animate halves flying apart, rotating and fading out
tween(halfTop, {
x: self.x - 60,
y: self.y - 80,
rotation: self.rotation - 0.32,
alpha: 0
}, {
duration: 420,
easing: tween.easeOut,
onFinish: function onFinish() {
halfTop.destroy();
}
});
tween(halfBottom, {
x: self.x + 60,
y: self.y + 80,
rotation: self.rotation + 0.32,
alpha: 0
}, {
duration: 420,
easing: tween.easeOut,
onFinish: function onFinish() {
halfBottom.destroy();
}
});
// Juice splash effect (ellipse, color based on fruit)
// More vivid color for splash (slightly saturated)
var vividJuiceColor = function (c) {
// Slightly brighten and saturate the color
var r = Math.min(255, (c >> 16 & 0xff) * 1.15 + 32) | 0;
var g = Math.min(255, (c >> 8 & 0xff) * 1.15 + 32) | 0;
var b = Math.min(255, (c & 0xff) * 1.15 + 32) | 0;
return r << 16 | g << 8 | b;
}(type.color || 0xff2d2d);
// Create multiple juice particles for a juicier effect
var juiceParticles = [];
var juiceParticleCount = 12 + Math.floor(Math.random() * 7); // 12-18 particles
for (var jp = 0; jp < juiceParticleCount; jp++) {
var angle = Math.random() * Math.PI * 2;
var dist = 40 + Math.random() * 60;
var scale = 0.22 + Math.random() * 0.22;
var alpha = 0.55 + Math.random() * 0.25;
var splash = LK.getAsset('juice_splash_' + type.id, {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
scaleX: scale * (0.8 + Math.random() * 0.7),
scaleY: scale * (0.5 + Math.random() * 1.1),
alpha: alpha,
tint: vividJuiceColor
});
game.addChild(splash);
juiceParticles.push(splash);
// Animate each particle outward and fade
tween(splash, {
x: self.x + Math.cos(angle) * dist,
y: self.y + Math.sin(angle) * dist,
scaleX: scale * (1.2 + Math.random() * 1.2),
scaleY: scale * (1.1 + Math.random() * 1.2),
alpha: 0
}, {
duration: 320 + Math.random() * 120,
easing: tween.easeOut,
onFinish: function (s) {
return function () {
s.destroy();
};
}(splash)
});
}
// --- Add stain to background ---
if (typeof background !== "undefined" && background && background.parent) {
// Create a stain asset (ellipse, color based on fruit, random rotation/scale)
var stain = LK.getAsset('juice_stain_' + type.id, {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
scaleX: 0.7 + Math.random() * 0.7,
scaleY: 0.5 + Math.random() * 0.7,
alpha: 0.32 + Math.random() * 0.18,
rotation: Math.random() * Math.PI * 2,
tint: vividJuiceColor
});
// Insert stain just above the background (so it stays under fruit halves/splashes)
var bgParent = background.parent;
var bgIndex = bgParent.getChildIndex(background);
bgParent.addChildAt(stain, bgIndex + 1);
}
// --- End stain ---
// Hide original fruit immediately
self.alpha = 0;
tween(self, {
alpha: 0
}, {
duration: 1,
onFinish: function onFinish() {
self.destroy();
}
});
};
// Update per frame
self.update = function () {
if (self.sliced) return;
self.x += self.vx;
self.y += self.vy;
self.vy += self.gravity;
// Prevent from leaving left/right screen edges
if (self.x < 0 + self.width * 0.5) {
self.x = self.width * 0.5;
self.vx = -self.vx * 0.55; // bounce back with some energy loss
}
if (self.x > 2048 - self.width * 0.5) {
self.x = 2048 - self.width * 0.5;
self.vx = -self.vx * 0.55;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a1a
});
/****
* Game Code
****/
// Add background image
// Game variables
var background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(background);
var fruits = [];
var bombs = [];
var fruitSpawnTimer = 0;
var bombSpawnTimer = 0;
var fruitSpawnInterval = 120; // 2 seconds at 60fps
var bombSpawnInterval = 300; // 5 seconds at 60fps
var score = 0;
var highScore = storage.highScore || 0;
var missed = 0;
var maxMissed = 3;
var isGameOver = false;
// Score display
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Combo system
var comboCount = 0;
var comboTimer = 0;
var comboTimeout = 36; // 0.6s at 60fps
var maxCombo = 0;
var comboTxt = new Text2('', {
size: 90,
fill: 0xFFFA00
});
comboTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(comboTxt);
// High score display (smaller, top right)
var highScoreTxt = new Text2('Best: ' + highScore, {
size: 60,
fill: "#fff"
});
highScoreTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(highScoreTxt);
// Missed fruits display (bottom left, as X icons)
var missTxt = new Text2('', {
size: 90,
fill: 0xFF4444
});
missTxt.anchor.set(0, 1);
LK.gui.bottomLeft.addChild(missTxt);
// Helper: update score
function updateScore(val) {
score = val;
scoreTxt.setText(score);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('Best: ' + highScore);
}
}
// Helper: update missed display
function updateMissed() {
var s = '';
for (var i = 0; i < missed; i++) s += '✗ ';
missTxt.setText(s);
}
// (spawning is now handled by fruit/bomb timers in game.update)
// Slicing logic
var swipePath = [];
var swipeMaxLength = 12; // Number of points to keep in swipe trail
var swipeRadius = 90; // How close swipe must be to slice
// Draw swipe trail (for visual feedback)
var swipeTrail = [];
function drawSwipeTrail() {
// Remove old
for (var i = 0; i < swipeTrail.length; i++) {
swipeTrail[i].destroy();
}
swipeTrail = [];
// Draw new
for (var i = 1; i < swipePath.length; i++) {
var p1 = swipePath[i - 1];
var p2 = swipePath[i];
// Draw a short white ellipse between points
var dx = p2.x - p1.x,
dy = p2.y - p1.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 10) continue;
var angle = Math.atan2(dy, dx);
var id = 'swipe_' + i;
var seg = LK.getAsset(id, {
anchorX: 0,
anchorY: 0.5,
x: p1.x,
y: p1.y,
rotation: angle,
alpha: 0.22
});
game.addChild(seg);
swipeTrail.push(seg);
}
}
// Handle swipe start
game.down = function (x, y, obj) {
if (isGameOver) return;
swipePath = [{
x: x,
y: y
}];
drawSwipeTrail();
};
// Handle swipe move
game.move = function (x, y, obj) {
if (isGameOver) return;
swipePath.push({
x: x,
y: y
});
if (swipePath.length > swipeMaxLength) swipePath.shift();
drawSwipeTrail();
// For each segment in swipe, check for intersection with fruits/bombs
for (var i = 1; i < swipePath.length; i++) {
var p1 = swipePath[i - 1],
p2 = swipePath[i];
// Fruits
for (var f = fruits.length - 1; f >= 0; f--) {
var fruit = fruits[f];
if (fruit.sliced) continue;
// If fruit already off screen, skip
if (fruit.y > 2732 + 120) continue;
// Check if swipe segment is close to fruit center
var cx = fruit.x,
cy = fruit.y;
var dist = pointToSegmentDistance(cx, cy, p1.x, p1.y, p2.x, p2.y);
if (dist < swipeRadius) {
fruit.slice();
updateScore(score + fruit.points); // Increase score by fruit's point value
fruits.splice(f, 1);
// Flash effect
LK.effects.flashObject(fruit, 0xffffff, 120);
// --- Combo logic ---
comboCount++;
comboTimer = comboTimeout;
if (comboCount > maxCombo) maxCombo = comboCount;
if (comboCount > 1) {
comboTxt.setText(comboCount + " Combo!");
comboTxt.alpha = 1;
// Animate combo text alpha in case it was faded out
tween(comboTxt, {
alpha: 1
}, {
duration: 60
});
// Bonus points for combos (e.g. +2 for 3-combo, +4 for 4-combo, etc)
if (comboCount >= 3) {
var bonus = (comboCount - 2) * 2;
updateScore(score + bonus);
// Animate combo text
tween(comboTxt, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 120,
yoyo: true,
repeat: 1
});
}
}
}
}
// Bombs
for (var b = bombs.length - 1; b >= 0; b--) {
var bomb = bombs[b];
if (bomb.sliced) continue;
if (bomb.y > 2732 + 120) continue;
var cx = bomb.x,
cy = bomb.y;
var dist = pointToSegmentDistance(cx, cy, p1.x, p1.y, p2.x, p2.y);
if (dist < swipeRadius) {
bomb.slice();
bombs.splice(b, 1);
// Play bomb sound
LK.getSound('Bomb').play();
// Increase score by 1 when bomb is sliced
updateScore(score + 1);
// Flash screen red, then game over
LK.effects.flashScreen(0xff2222, 800);
isGameOver = true;
LK.setTimeout(function () {
LK.showGameOver();
}, 600);
return;
}
}
}
};
// Handle swipe end
game.up = function (x, y, obj) {
swipePath = [];
drawSwipeTrail();
};
// Helper: distance from point to segment
function pointToSegmentDistance(px, py, x1, y1, x2, y2) {
var dx = x2 - x1,
dy = y2 - y1;
if (dx === 0 && dy === 0) {
dx = px - x1;
dy = py - y1;
return Math.sqrt(dx * dx + dy * dy);
}
var t = ((px - x1) * dx + (py - y1) * dy) / (dx * dx + dy * dy);
t = Math.max(0, Math.min(1, t));
var lx = x1 + t * dx,
ly = y1 + t * dy;
dx = px - lx;
dy = py - ly;
return Math.sqrt(dx * dx + dy * dy);
}
// Main game update
game.update = function () {
if (isGameOver) return;
// --- Dynamic difficulty: speed up game as score increases ---
var minFruitInterval = 36; // minimum interval (0.6s)
var minBombInterval = 120; // minimum interval (2s)
var fruitIntervalStep = Math.max(0, fruitSpawnInterval - Math.floor(score / 10) * 10);
fruitSpawnInterval = Math.max(minFruitInterval, 120 - Math.floor(score / 10) * 10);
bombSpawnInterval = Math.max(minBombInterval, 300 - Math.floor(score / 15) * 15);
// Spawn logic
fruitSpawnTimer++;
bombSpawnTimer++;
if (fruitSpawnTimer >= fruitSpawnInterval) {
// Spawn a fruit only
var obj = new Fruit();
// All fruits spawn at the same Y (bottom), but random X across the screen (leaving margin for fruit width)
var fruitMargin = 200;
var x = fruitMargin + Math.random() * (2048 - 2 * fruitMargin);
var y = 2732 + 60; // All fruits spawn just below the bottom of the screen
// Launch angle is always straight up
var angle = -Math.PI / 2;
var speed = 17 + Math.random() * 5.5 + score * 0.055;
// vx is 0 (no side velocity), vy is always up
var vx = 0;
var vy = -Math.abs(Math.sin(angle) * speed) * 1.32;
obj.x = x;
obj.y = y;
obj.vx = vx;
obj.vy = vy;
obj.scaleX = obj.scaleY = 1;
obj.alpha = 1;
// Increase gravity for fruit as score increases
obj.gravity += Math.min(0.18, score * 0.012);
fruits.push(obj);
game.addChild(obj);
fruitSpawnTimer = 0;
}
if (bombSpawnTimer >= bombSpawnInterval) {
// Spawn a bomb only
var obj = new Bomb();
// All bombs spawn at the same Y (bottom), but random X across the screen (leaving margin for bomb width)
var bombMargin = 200;
var x = bombMargin + Math.random() * (2048 - 2 * bombMargin);
var y = 2732 + 60; // All bombs spawn just below the bottom of the screen
// Launch angle is always straight up
var angle = -Math.PI / 2;
var speed = 17 + Math.random() * 5.5 + score * 0.055;
// vx is 0 (no side velocity), vy is always up
var vx = 0;
var vy = -Math.abs(Math.sin(angle) * speed) * 1.32;
obj.x = x;
obj.y = y;
obj.vx = vx;
obj.vy = vy;
obj.scaleX = obj.scaleY = 1;
obj.alpha = 1;
// Increase gravity for bomb as score increases
obj.gravity += Math.min(0.18, score * 0.012);
bombs.push(obj);
game.addChild(obj);
bombSpawnTimer = 0;
}
// Update fruits
for (var i = fruits.length - 1; i >= 0; i--) {
var fruit = fruits[i];
fruit.update();
// Remove if off screen
if (!fruit.sliced && fruit.y > 2732 + 120) {
// Play lose sound when fruit is missed
LK.getSound('Lose').play();
fruit.destroy();
fruits.splice(i, 1);
missed++;
updateMissed();
// Flash screen if missed
LK.effects.flashScreen(0x2222ff, 200);
if (missed >= maxMissed) {
isGameOver = true;
LK.setTimeout(function () {
LK.showGameOver();
}, 500);
}
// Do NOT increase score here; only bomb slice increases score now
}
}
// Update bombs
for (var i = bombs.length - 1; i >= 0; i--) {
var bomb = bombs[i];
bomb.update();
if (bomb.y > 2732 + 120) {
bomb.destroy();
bombs.splice(i, 1);
}
}
// Fade out swipe trail
for (var i = 0; i < swipeTrail.length; i++) {
swipeTrail[i].alpha *= 0.85;
if (swipeTrail[i].alpha < 0.05) {
swipeTrail[i].destroy();
swipeTrail.splice(i, 1);
i--;
}
}
// --- Combo timer logic ---
if (comboTimer > 0) {
comboTimer--;
if (comboTimer === 0) {
// Combo ended
comboCount = 0;
tween(comboTxt, {
alpha: 0
}, {
duration: 320
});
}
}
};
// Reset game state on new game
game.on('reset', function () {
// Remove all fruits and bombs
for (var i = 0; i < fruits.length; i++) fruits[i].destroy();
for (var i = 0; i < bombs.length; i++) bombs[i].destroy();
fruits = [];
bombs = [];
fruitSpawnTimer = 0;
bombSpawnTimer = 0;
score = 0;
missed = 0;
isGameOver = false;
updateScore(0);
updateMissed();
swipePath = [];
drawSwipeTrail();
comboCount = 0;
comboTimer = 0;
comboTxt.setText('');
comboTxt.alpha = 0;
maxCombo = 0;
highScore = storage.highScore || 0;
highScoreTxt.setText('Best: ' + highScore);
});
// Initial state
updateScore(0);
updateMissed();
// Fruit assets
// Bomb asset: black circle
// Bomb border: red, slightly larger, semi-transparent