/**** * 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