User prompt
make the balls move slower
User prompt
Update the fruit init method to use level-based speed multiplier
User prompt
make some balls fly from left to right and straight down. let thembounce of the walls if they have not been sliced
User prompt
decrease how high the balls go by a quarter. spawn new balls from random points on the bottom of the screen and in random directions
User prompt
double the sizes of the fruits and make them go much higher and slower
User prompt
make the fruits bounce higher and slower
Code edit (1 edits merged)
Please save this source code
User prompt
Fruit Ninja Physics
Initial prompt
a fruit slashing game where player swipes to slash fruit. 5 types of fruit of different sizes, the smallest being 200px and increasing with 50 pixels hence. use real physics
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var Blade = Container.expand(function () { var self = Container.call(this); self.active = false; self.points = []; self.maxPoints = 40; // Increased from 30 to 40 to track more points for even smoother detection self.lastUpdateTime = 0; // Track time between updates for velocity calculation self.trail = []; for (var i = 0; i < self.maxPoints; i++) { var trailPart = self.attachAsset('blade', { anchorX: 0.5, anchorY: 0.5, alpha: 0 }); self.trail.push(trailPart); } self.update = function () { // Update trail visuals for (var i = 0; i < self.trail.length; i++) { if (i < self.points.length) { var point = self.points[i]; var trailPart = self.trail[i]; trailPart.x = point.x; trailPart.y = point.y; trailPart.alpha = 0; // Keep blade invisible if (i > 0) { var prevPoint = self.points[i - 1]; var angle = Math.atan2(point.y - prevPoint.y, point.x - prevPoint.x); trailPart.rotation = angle; } } else { self.trail[i].alpha = 0; } } }; self.addPoint = function (x, y) { var currentTime = Date.now(); var velocity = 0; var angle = 0; // Calculate velocity and angle if we have previous points if (self.points.length > 0) { var prevPoint = self.points[0]; var dx = x - prevPoint.x; var dy = y - prevPoint.y; var timeDiff = currentTime - self.lastUpdateTime; // Prevent division by zero if (timeDiff > 0) { var distance = Math.sqrt(dx * dx + dy * dy); velocity = distance / timeDiff * 1000; // pixels per second } angle = Math.atan2(dy, dx); } self.lastUpdateTime = currentTime; self.points.unshift({ x: x, y: y, time: currentTime, velocity: velocity, angle: angle }); if (self.points.length > self.maxPoints) { self.points.pop(); } }; self.reset = function () { self.points = []; self.active = false; for (var i = 0; i < self.trail.length; i++) { self.trail[i].alpha = 0; } }; return self; }); var BlitzPowerup = Container.expand(function () { var self = Container.call(this); self.type = 'blitz'; self.sliced = false; self.width = 300; self.height = 300; self.points = 0; self.baseSpeed = 1.5; // Create a red lightning bolt-shaped fruit for blitz var blitzGraphics = self.attachAsset('blitz', { anchorX: 0.5, anchorY: 0.5 }); // No color tint needed as we're using the blitz image asset // No shape needed as we're using the blitz image asset self.vx = 0; self.vy = 0; self.gravity = 0.01; self.rotationSpeed = (Math.random() - 0.5) * 0.015; self.init = function (x, y, direction) { self.x = x; self.y = y; var angle = direction * (Math.PI / 4) + Math.random() * Math.PI / 8 - Math.PI / 16; var speed = self.baseSpeed + Math.random() * 0.8; self.vx = Math.cos(angle) * speed * 1.15; self.vy = -Math.sin(angle) * speed - 8; }; self.slice = function () { if (self.sliced) { return; } self.sliced = true; // Play Blitzkrieg sound LK.getSound('Blitzkrieg').play(); // Display "Blitzkrieg!" text in the middle of screen with large text that blinks red and yellow var blitzText = new Text2('BLITZKRIEG!', { size: 180, // Larger size fill: 0xFF0000, // Start with red weight: 'bold' }); blitzText.anchor.set(0.5, 0.5); blitzText.x = GAME_WIDTH / 2; blitzText.y = GAME_HEIGHT / 2; game.addChild(blitzText); // Make the text shake and blink red/yellow for 1 second var shakeCount = 0; var maxShakes = 20; // More shakes for more intensity var shakeIntensity = 25; // Higher intensity shake var originalX = blitzText.x; var originalY = blitzText.y; var colorToggle = false; // Shake and blink for 1 second (50ms * 20 = 1000ms) var shakeInterval = LK.setInterval(function () { if (shakeCount >= maxShakes) { LK.clearInterval(shakeInterval); blitzText.x = originalX; blitzText.y = originalY; // After 1 second of shaking, play blitzsquash LK.getSound('blitzsquash').play(); // Then continue with the rest of the effect // Flash effect tween(blitzGraphics, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { // No need to reset tint as we're not using tint } }); // Fade out the text tween(blitzText, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 500, easing: tween.elasticOut, onFinish: function onFinish() { blitzText.destroy(); } }); // Convert all fruits to explosion assets first, then after 1 second, destroy them for (var i = fruits.length - 1; i >= 0; i--) { var fruit = fruits[i]; if (fruit.type !== 'lifefruit' && fruit.type !== 'bullettime' && fruit.type !== 'blitz') { // Skip if already converted if (fruit.converted) { continue; } fruit.converted = true; // Get the current fruit graphic var fruitGraphics = fruit.children[0]; // Replace with explosion asset var explosionGraphic = LK.getAsset('explosion', { anchorX: 0.5, anchorY: 0.5 }); explosionGraphic.x = fruitGraphics.x; explosionGraphic.y = fruitGraphics.y; explosionGraphic.scale.set(fruitGraphics.scale.x, fruitGraphics.scale.y); fruit.removeChild(fruitGraphics); fruit.addChild(explosionGraphic); // Add glow effect tween(explosionGraphic, { scaleX: explosionGraphic.scale.x * 1.2, scaleY: explosionGraphic.scale.y * 1.2 }, { duration: 500, easing: tween.easeOut }); } } // Shake the screen effect var gameOriginalX = game.x; var gameOriginalY = game.y; var gameShakeCount = 0; var gameMaxShakes = 10; var gameShakeIntensity = 20; var gameShakeInterval = LK.setInterval(function () { if (gameShakeCount >= gameMaxShakes) { LK.clearInterval(gameShakeInterval); game.x = gameOriginalX; game.y = gameOriginalY; // After 1 second and shake is done, destroy all fruits LK.setTimeout(function () { // Destroy all fruits on screen (including bombs) for (var i = fruits.length - 1; i >= 0; i--) { var fruit = fruits[i]; if (fruit.type !== 'lifefruit' && fruit.type !== 'bullettime' && fruit.type !== 'blitz') { // Add points for each destroyed fruit if (fruit.type !== 'bomb') { LK.setScore(LK.getScore() + fruit.points); } // Animate explosion effect var explosionGraphic = fruit.children[0]; tween(explosionGraphic, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 200, easing: tween.easeOut }); // Remove fruit fruit.destroy(); fruits.splice(i, 1); } } // Update score display scoreTxt.setText(LK.getScore()); }, 1000); // Wait 1 second before clearing return; } // Apply random shake offset game.x = gameOriginalX + (Math.random() - 0.5) * gameShakeIntensity; game.y = gameOriginalY + (Math.random() - 0.5) * gameShakeIntensity; gameShakeCount++; }, 50); return; } // Toggle text color between red and yellow for blinking effect colorToggle = !colorToggle; // Direct property access for Text2 objects instead of using style blitzText.fill = colorToggle ? 0xFFFF00 : 0xFF0000; // Apply random shake offset with higher intensity blitzText.x = originalX + (Math.random() - 0.5) * shakeIntensity * 2; blitzText.y = originalY + (Math.random() - 0.5) * shakeIntensity * 1.5; shakeCount++; }, 50); // Slower interval for more visible blinks }; self.down = function (x, y, obj) { if (!self.sliced) { self.slice(); // Update combo comboCount++; comboTimer = Date.now() + comboTimeout; } }; self.update = function () { // Don't update movement if gameActive is false (leaderboard is visible) if (!gameActive) { return; } if (!self.sliced) { // Update whole fruit self.vy += self.gravity; self.x += self.vx; self.y += self.vy; self.rotation += self.rotationSpeed; // Bounce off walls if not sliced if (self.x < self.width / 2 && self.vx < 0) { self.vx = -self.vx * 0.8; self.x = self.width / 2; } if (self.x > GAME_WIDTH - self.width / 2 && self.vx > 0) { self.vx = -self.vx * 0.8; self.x = GAME_WIDTH - self.width / 2; } } }; self.isOffScreen = function () { return self.y > 2732 + self.height || self.x > GAME_WIDTH + self.width; }; return self; }); var Boss = Container.expand(function (level) { var self = Container.call(this); // Boss properties self.level = level || 1; self.health = 5 + self.level; // Health increases with level self.maxHealth = self.health; self.width = 800; self.height = 800; self.active = false; self.attackTimer = 0; self.attackInterval = self.level === 5 ? 950 : self.level === 4 ? 600 : self.level === 1 ? 5000 : self.level === 2 ? 2000 : 3000 - self.level * 250; // Level 5 boss spawn speed at 950ms (increased from 833ms), Level 4 boss at 600ms, Level 1 boss at 5000ms (increased from 3000ms), Level 2 boss at 2000ms // Ensure level is within valid range (1-5) if (self.level < 1) { self.level = 1; } if (self.level > 5) { self.level = 5; } // Determine boss type based on level var bossType = BOSS_FRUIT_TYPES[self.level - 1] || 'strawberry'; // Create boss visual var bossGraphic = self.attachAsset(bossType, { anchorX: 0.5, anchorY: 0.5, scaleX: self.level === 1 ? 1.95 : self.level === 2 ? 2.8 : self.level === 5 ? 3.5 : 4, // Make level 1 boss smaller by 35%, level 2 boss smaller by 30%, level 5 boss 30% smaller (from 5 to 3.5) scaleY: self.level === 1 ? 1.95 : self.level === 2 ? 2.8 : self.level === 5 ? 3.5 : 4 // Make level 1 boss smaller by 35%, level 2 boss smaller by 30%, level 5 boss 30% smaller (from 5 to 3.5) }); // Health bar background var healthBarBg = new Container(); healthBarBg.x = -200; healthBarBg.y = -450; var healthBarBack = LK.getAsset('blade', { anchorX: 0, anchorY: 0.5, width: 400, height: 30 }); healthBarBack.tint = 0x333333; // Health bar fill var healthBarFill = LK.getAsset('blade', { anchorX: 0, anchorY: 0.5, width: 400, height: 30 }); healthBarFill.tint = 0xFF0000; healthBarBg.addChild(healthBarBack); healthBarBg.addChild(healthBarFill); self.addChild(healthBarBg); self.healthBar = healthBarFill; // Boss movement self.vx = self.level === 5 ? 8 : 2 + self.level * 0.5; // Level 5 boss moves much faster self.targetX = GAME_WIDTH / 2; self.activate = function () { self.active = true; self.health = self.maxHealth; self.x = GAME_WIDTH / 2; // Position boss below the top boundary (320px) with enough space to not overlap // Boss dimensions depend on the asset scale (4x) and boss type // Account for boss height and health bar to ensure it appears fully below the boundary // Position bosses at different heights based on level to avoid overlapping the top boundary if (self.level === 2) { self.y = 320 + 570; // Level 2 boss: 60px lower } else if (self.level === 3) { self.y = 320 + 690; // Level 3 boss: 170px lower (moved additional 30px lower) } else if (self.level === 4) { self.y = 320 + 750; // Level 4 boss: 200px lower } else if (self.level === 5) { // For level 5 boss, require 65 slashes to defeat self.health -= self.maxHealth / 65; self.y = 320 + 900; // Level 5 boss: 300px lower (moved from 600 to 900) } else { self.y = 320 + 570; // Default position for level 1 boss } self.updateHealthBar(); }; self.updateHealthBar = function () { self.healthBar.width = self.health / self.maxHealth * 400; }; self.hit = function () { if (!self.active) { return; } if (self.lastHitTime && Date.now() - self.lastHitTime < 100) { return false; } self.lastHitTime = Date.now(); // For level 1 boss, require 15 slashes to defeat if (self.level === 1) { // Decrement by 1/21 of health for level 1 boss self.health -= self.maxHealth / 21; } else if (self.level === 2) { // For level 2 boss, require 35 slashes to defeat self.health -= self.maxHealth / 35; } else if (self.level === 3) { // For level 3 boss, require 45 slashes to defeat self.health -= self.maxHealth / 45; } else if (self.level === 4) { // For level 4 boss, require 50 slashes to defeat self.health -= self.maxHealth / 50; } else if (self.level === 5) { // For level 5 boss, require 65 slashes to defeat self.health -= self.maxHealth / 65; } else { // Normal behavior for other bosses self.health--; } self.updateHealthBar(); // Play owww sound when boss is hit LK.getSound('owww').play(); // Flash the boss red bossGraphic.tint = 0xFF0000; LK.setTimeout(function () { bossGraphic.tint = 0xFFFFFF; }, 200); // Check if boss is defeated if (self.health <= 0) { self.defeat(); return true; } return false; }; self.defeat = function () { self.active = false; // Make the health bar transparent healthBarBg.alpha = 0; // Create explosion effect bossGraphic.tint = 0xFFFFFF; tween(bossGraphic, { alpha: 0, scaleX: 3, scaleY: 3, rotation: Math.PI * 2 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { // Add bonus points (increased for higher level bosses) var bonus = 25 * self.level; LK.setScore(LK.getScore() + bonus); scoreTxt.setText(LK.getScore()); LK.effects.flashScreen(0x00FF00, 500); } }); // Play explosion sound LK.getSound('explosion').play(); // Play nooo sound when boss is killed LK.getSound('nooo').play(); }; self.throwFruit = function () { if (!self.active) { return; } // Launch multiple bombs at the player var bombCount = self.level === 5 ? Math.floor(Math.random() * 3) + 2 : // Level 5: 2-4 bombs (reduced from 3-6) self.level === 4 ? Math.floor(Math.random() * 2) + 1 : // Level 4: 1-2 bombs (reduced from 1-4) Math.floor(Math.random() * self.level) + 1; // Other levels: 1-level bombs // For level 5, also spawn random fruits alongside bombs // Also spawn random fruits with level 1 and level 2 bosses var fruitCount = self.level === 5 || self.level === 1 || self.level === 2 ? Math.floor(Math.random() * 2) : 0; // Reduced from 1-2 to 0-1 // Spawn bombs for (var i = 0; i < bombCount; i++) { var fruit = new Fruit('bomb'); // Generate random position on the boss perimeter var spawnAngle = Math.random() * Math.PI * 2; // Full 360 degrees var spawnRadius = self.width / 3; // Radius to spawn from (edge of boss) // Position the bomb on the perimeter fruit.x = self.x + Math.cos(spawnAngle) * spawnRadius; fruit.y = self.y + Math.sin(spawnAngle) * spawnRadius; // Aim outward from the spawn point with spread var angle = spawnAngle + Math.PI + (Math.random() - 0.5); // Outward direction with randomness var speed = self.level === 5 ? 5 + Math.random() * 3 : // Level 5: Much faster bombs 3 + Math.random() * 2 + self.level * 0.5; // Other levels: Normal speed fruit.vx = Math.cos(angle) * speed; fruit.vy = Math.sin(angle) * speed; game.addChild(fruit); fruits.push(fruit); } // For level 5, level 1, level 2, or level 3, also spawn random fruits if (self.level === 5 || self.level === 1 || self.level === 2 || self.level === 3) { // Level 2 boss spawns fewer fruits (1-2 instead of 2-3) var fruitCount = self.level === 2 ? Math.floor(Math.random() * 2) + 1 : self.level === 3 ? Math.floor(Math.random() * 3) + 2 : // Level 3 boss: 2-4 fruits self.level === 5 || self.level === 1 ? Math.floor(Math.random() * 2) : 0; for (var j = 0; j < fruitCount; j++) { // Select a random fruit type // For level 1, only use level 1 fruits (strawberry) // For level 2, use level 2 fruits (strawberry, kiwi) // For level 3, use level 3 fruits (strawberry, kiwi, orange) // For level 4, use level 4 fruits (strawberry, kiwi, orange, apple) var availableFruits; if (self.level === 1) { availableFruits = ['strawberry']; // Level 1 fruits } else if (self.level === 2) { availableFruits = ['strawberry', 'kiwi']; // Level 2 fruits } else if (self.level === 3) { availableFruits = ['strawberry', 'kiwi', 'orange']; // Level 3 fruits } else if (self.level === 4) { availableFruits = ['strawberry', 'kiwi', 'orange', 'apple']; // Level 4 fruits // Spawn more fruits for level 4 boss fruitCount = Math.floor(Math.random() * 6) + 10; // Level 4 boss: 10-15 fruits (increased from 6-10) // Ensure we spawn level 4 fruits // Make the boss spawn each fruit type at least once, then random selections var guaranteedFruits = ['strawberry', 'kiwi', 'orange', 'apple']; for (var j = 0; j < fruitCount; j++) { var fruitType; if (j < guaranteedFruits.length) { // Guarantee each fruit type appears at least once fruitType = guaranteedFruits[j]; } else { // Then pick random fruits fruitType = availableFruits[Math.floor(Math.random() * availableFruits.length)]; } var randomFruit = new Fruit(fruitType); // Generate random position on the boss perimeter var fruitAngle = Math.random() * Math.PI * 2; var fruitRadius = self.width / 3; // Position the fruit on the perimeter randomFruit.x = self.x + Math.cos(fruitAngle) * fruitRadius; randomFruit.y = self.y + Math.sin(fruitAngle) * fruitRadius; // Aim outward with different trajectory var fruitDirection = fruitAngle + Math.PI + (Math.random() - 0.5) * 0.5; var fruitSpeed = 4 + Math.random() * 2; randomFruit.vx = Math.cos(fruitDirection) * fruitSpeed; randomFruit.vy = Math.sin(fruitDirection) * fruitSpeed; game.addChild(randomFruit); fruits.push(randomFruit); } return; // Skip the regular fruit spawning below } else if (self.level === 5) { availableFruits = ['strawberry', 'kiwi', 'orange', 'apple', 'watermelon']; // Level 5 fruits // Spawn more fruits for level 5 boss fruitCount = Math.floor(Math.random() * 3) + 4; // Level 5 boss: 4-6 fruits // Ensure we spawn level 5 fruits for (var j = 0; j < fruitCount; j++) { var fruitType = availableFruits[Math.floor(Math.random() * availableFruits.length)]; var randomFruit = new Fruit(fruitType); // Generate random position on the boss perimeter var fruitAngle = Math.random() * Math.PI * 2; var fruitRadius = self.width / 3; // Position the fruit on the perimeter randomFruit.x = self.x + Math.cos(fruitAngle) * fruitRadius; randomFruit.y = self.y + Math.sin(fruitAngle) * fruitRadius; // Aim outward with different trajectory var fruitDirection = fruitAngle + Math.PI + (Math.random() - 0.5) * 0.5; var fruitSpeed = 4 + Math.random() * 2; randomFruit.vx = Math.cos(fruitDirection) * fruitSpeed; randomFruit.vy = Math.sin(fruitDirection) * fruitSpeed; game.addChild(randomFruit); fruits.push(randomFruit); } return; // Skip the regular fruit spawning below } else { availableFruits = ['strawberry', 'kiwi', 'orange', 'apple', 'watermelon']; // Level 5 fruits } var fruitType = availableFruits[Math.floor(Math.random() * availableFruits.length)]; var randomFruit = new Fruit(fruitType); // Generate random position on the boss perimeter var fruitAngle = Math.random() * Math.PI * 2; var fruitRadius = self.width / 3; // Position the fruit on the perimeter randomFruit.x = self.x + Math.cos(fruitAngle) * fruitRadius; randomFruit.y = self.y + Math.sin(fruitAngle) * fruitRadius; // Aim outward with different trajectory var fruitDirection = fruitAngle + Math.PI + (Math.random() - 0.5) * 0.5; var fruitSpeed = 4 + Math.random() * 2; randomFruit.vx = Math.cos(fruitDirection) * fruitSpeed; randomFruit.vy = Math.sin(fruitDirection) * fruitSpeed; game.addChild(randomFruit); fruits.push(randomFruit); } } }; self.update = function (delta) { if (!self.active) { return; } // Apply bullet time effect if active (slow down by 75%) var speedMultiplier = bulletTimeActive ? 0.25 : 1; // Move boss back and forth if (Math.abs(self.x - self.targetX) < 10) { self.targetX = Math.random() * (GAME_WIDTH - 400) + 200; } var dirX = self.targetX - self.x; self.x += dirX / Math.abs(dirX) * self.vx * speedMultiplier; // Attack on timer var currentTime = Date.now(); if (currentTime >= self.attackTimer) { self.throwFruit(); // Extend attack interval during bullet time // For level 1 boss, spawn a fruit every 1 second (reduced from 2 seconds) var interval = self.level === 1 ? 1000 : self.attackInterval; self.attackTimer = currentTime + interval * (bulletTimeActive ? 4 : 1); } }; self.down = function (x, y, obj) { // Allow clicking on boss to damage it if (self.active) { self.hit(); } }; return self; }); var BulletTimePowerup = Container.expand(function () { var self = Container.call(this); self.type = 'bullettime'; self.sliced = false; self.width = 300; self.height = 300; self.points = 0; self.baseSpeed = 1.5; // Create a blue hourglass-shaped fruit for bullet time var bulletTimeGraphics = self.attachAsset('bullettime', { anchorX: 0.5, anchorY: 0.5 }); // No color tint needed as we're using the bullettime image asset // No shapes needed as we're using the bullettime image asset self.vx = 0; self.vy = 0; self.gravity = 0.01; self.rotationSpeed = (Math.random() - 0.5) * 0.015; self.init = function (x, y, direction) { self.x = x; self.y = y; var angle = direction * (Math.PI / 4) + Math.random() * Math.PI / 8 - Math.PI / 16; var speed = self.baseSpeed + Math.random() * 0.8; self.vx = Math.cos(angle) * speed * 1.15; self.vy = -Math.sin(angle) * speed - 8; }; self.slice = function () { if (self.sliced) { return; } self.sliced = true; // Activate bullet time effect activateBulletTime(); // Create bullet time text display instead of Blitzkrieg text var bulletTimeText = new Text2('BULLET TIME!', { size: 120, fill: 0x00BFFF, // Blue color for bullet time weight: 'bold' }); bulletTimeText.anchor.set(0.5, 0.5); bulletTimeText.x = GAME_WIDTH / 2; bulletTimeText.y = GAME_HEIGHT / 2; game.addChild(bulletTimeText); // Play bullet time sound LK.getSound('bullet').play(); // Flash effect tween(bulletTimeGraphics, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { // No need to reset tint as we're not using tint } }); // Animate text to fade out tween(bulletTimeText, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 1000, easing: tween.elasticOut, onFinish: function onFinish() { bulletTimeText.destroy(); } }); }; self.down = function (x, y, obj) { if (!self.sliced) { self.slice(); // Update combo comboCount++; comboTimer = Date.now() + comboTimeout; } }; self.update = function () { // Don't update movement if gameActive is false (leaderboard is visible) if (!gameActive) { return; } if (!self.sliced) { // Update whole fruit self.vy += self.gravity; self.x += self.vx; self.y += self.vy; self.rotation += self.rotationSpeed; // Bounce off walls if not sliced if (self.x < self.width / 2 && self.vx < 0) { self.vx = -self.vx * 0.8; self.x = self.width / 2; } if (self.x > GAME_WIDTH - self.width / 2 && self.vx > 0) { self.vx = -self.vx * 0.8; self.x = GAME_WIDTH - self.width / 2; } } }; self.isOffScreen = function () { return self.y > 2732 + self.height || self.x > GAME_WIDTH + self.width; }; return self; }); var Fruit = Container.expand(function (type) { var self = Container.call(this); self.type = type || 'apple'; self.sliced = false; self.width = 0; self.height = 0; self.points = 0; self.baseSpeed = 0; var fruitGraphics; switch (self.type) { case 'watermelon': self.width = 200; self.height = 200; self.points = 5; self.baseSpeed = 1.6; // Faster base speed break; case 'apple': self.width = 240; self.height = 240; self.points = 4; self.baseSpeed = 1.7; // Faster base speed break; case 'orange': self.width = 280; self.height = 280; self.points = 3; self.baseSpeed = 1.8; // Faster base speed break; case 'kiwi': self.width = 320; self.height = 320; self.points = 2; self.baseSpeed = 2.0; // Faster base speed break; case 'passion_fruit': self.width = 360; self.height = 360; self.points = 6; self.baseSpeed = 2.3; // Even faster base speed break; case 'strawberry': self.width = 480; self.height = 480; self.points = 1; self.baseSpeed = 2.2; // Faster base speed break; case 'bomb': self.width = 320; self.height = 320; self.points = -10; self.baseSpeed = 1.7; // Faster base speed break; } fruitGraphics = self.attachAsset(self.type, { anchorX: 0.5, anchorY: 0.5 }); self.vx = 0; self.vy = 0; self.gravity = 0.01; // Reduced gravity to make fruits go higher self.rotationSpeed = (Math.random() - 0.5) * 0.018; // Slightly faster rotation self.init = function (x, y, direction) { self.x = x; self.y = y; var angle = direction * (Math.PI / 4) + Math.random() * Math.PI / 8 - Math.PI / 16; var currentLevel = Math.floor(LK.getScore() / 50) + 1; var levelMultiplier = 1 + (currentLevel - 1) * 0.1; // 10% increase per level // Use constant base speed without random factor to keep speed consistent within level var speed = self.baseSpeed * levelMultiplier; // Apply 30% speed reduction for level 1 if (currentLevel === 1) { speed *= 0.7; // 30% slower for level 1 } self.vx = Math.cos(angle) * speed * 1.15; // Slightly faster horizontal movement self.vy = -Math.sin(angle) * speed - 8; // Higher initial upward velocity }; self.slice = function () { if (self.sliced) { return; } self.sliced = true; // Create explosive flash effect fruitGraphics.tint = 0xFFFFFF; fruitGraphics.alpha = 1; // Animate explosive flash effect with scaling tween(fruitGraphics, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 300, easing: tween.easeOut, onUpdate: function onUpdate(progress) { // Create a pulsing effect with rotation during explosion fruitGraphics.rotation += 0.1; }, onFinish: function onFinish() { // Clean up after animation fruitGraphics.tint = 0xFFFFFF; fruitGraphics.scaleX = 1; fruitGraphics.scaleY = 1; } }); // Play slice and squash sounds LK.getSound('slice').play(); LK.getSound('squash').play(); return self.points; }; self.down = function (x, y, obj) { if (!self.sliced) { // Slice the fruit when clicked var points = self.slice(); // Check if it's a bomb if (self.type === 'bomb') { // Lose a life when clicking a bomb LK.getSound('explosion').play(); LK.effects.flashScreen(0xFF0000, 500); playerLives--; // Reduce lives by 1 updateLivesDisplay(); // Update hearts display // Make bomb disappear self.sliced = true; // Create explosive flash effect for bomb fruitGraphics.tint = 0xFFFFFF; // Animate bomb disappearing tween(fruitGraphics, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { // Remove bomb from game self.destroy(); // Find and remove from fruits array var index = fruits.indexOf(self); if (index > -1) { fruits.splice(index, 1); } } }); // If no lives left, game over if (playerLives <= 0) { // Update high score if current score is higher if (LK.getScore() > highScore) { highScore = LK.getScore(); storage.highScore = highScore; } // Add score to global leaderboard addScoreToLeaderboard(LK.getScore()); // Pass the current score and high score to game over display LK.showGameOver({ score: LK.getScore(), highScore: highScore }); } return; } // Add points to score LK.setScore(LK.getScore() + points); scoreTxt.setText(LK.getScore()); // Increment sliced fruits counter for current level slicedFruitsInLevel++; // Update combo comboCount++; comboTimer = Date.now() + comboTimeout; } }; self.update = function () { // Don't update movement if gameActive is false (leaderboard is visible) if (!gameActive) { return; } if (!self.sliced) { // Update whole fruit // Apply bullet time effect if active (slow down by 75%) var speedMultiplier = bulletTimeActive ? 0.25 : 1; self.vy += self.gravity * speedMultiplier; self.x += self.vx * speedMultiplier; self.y += self.vy * speedMultiplier; self.rotation += self.rotationSpeed * speedMultiplier; // Bounce off walls if not sliced if (self.x < self.width / 2 && self.vx < 0) { self.vx = -self.vx * 0.8; // Bounce with some energy loss self.x = self.width / 2; } if (self.x > GAME_WIDTH - self.width / 2 && self.vx > 0) { self.vx = -self.vx * 0.8; // Bounce with some energy loss self.x = GAME_WIDTH - self.width / 2; } } else { // No update for sliced fruit since we're just flashing and fading them } }; self.isOffScreen = function () { return self.y > 2732 + self.height || self.x > GAME_WIDTH + self.width; }; return self; }); var Heart = Container.expand(function () { var self = Container.call(this); // Create a red heart shape (using blade asset tinted red) var heartShape = self.attachAsset('blade', { anchorX: 0.5, anchorY: 0.5, width: 60, height: 60 }); heartShape.tint = 0xFF0000; return self; }); var Leaderboard = Container.expand(function () { var self = Container.call(this); self.visible = false; // Background panel var panel = self.attachAsset('blade', { anchorX: 0.5, anchorY: 0.5, width: 1500, height: 1800 }); panel.tint = 0x111111; // Very dark color (almost black) for better contrast panel.alpha = 0.9; // Slightly transparent for better visibility // Title var title = new Text2('GLOBAL LEADERBOARD', { size: 100, fill: 0xFFFFFF }); title.anchor.set(0.5, 0); title.y = -800; self.addChild(title); // Scores container var scoresContainer = new Container(); scoresContainer.y = -650; self.addChild(scoresContainer); self.scoresContainer = scoresContainer; // Close button var closeBtn = self.attachAsset('blade', { anchorX: 0.5, anchorY: 0.5, width: 120, height: 120 }); closeBtn.tint = 0xFF0000; closeBtn.x = 700; closeBtn.y = -800; // X symbol on close button var closeX1 = self.attachAsset('blade', { anchorX: 0.5, anchorY: 0.5, width: 60, height: 12 }); closeX1.tint = 0xFFFFFF; closeX1.rotation = Math.PI / 4; closeX1.x = 700; closeX1.y = -800; var closeX2 = self.attachAsset('blade', { anchorX: 0.5, anchorY: 0.5, width: 60, height: 12 }); closeX2.tint = 0xFFFFFF; closeX2.rotation = -Math.PI / 4; closeX2.x = 700; closeX2.y = -800; // Close button interaction closeBtn.interactive = true; closeBtn.down = function (x, y, obj) { self.hide(); }; // Show leaderboard self.show = function (scores) { self.visible = true; self.updateScores(scores); // Pause the game when leaderboard is visible gameActive = false; // Make game area 50% transparent game.alpha = 0.5; // Make leaderboard panel fully opaque (0% transparent) panel.alpha = 1.0; // Bring leaderboard to front layer if (self.parent) { var parent = self.parent; parent.removeChild(self); parent.addChild(self); } }; // Hide leaderboard self.hide = function () { self.visible = false; // Resume the game when leaderboard is hidden gameActive = true; // Restore game area opacity game.alpha = 1.0; }; // Update scores display self.updateScores = function (scores) { // Clear existing score entries while (scoresContainer.children.length > 0) { scoresContainer.removeChildAt(0); } // Add score entries for (var i = 0; i < scores.length; i++) { var entry = scores[i]; var yPos = i * 120; // Rank var rank = new Text2(i + 1 + '.', { size: 80, fill: 0xFFFFFF }); rank.anchor.set(1, 0); rank.x = -600; rank.y = yPos; scoresContainer.addChild(rank); // Username var username = entry.username || "Player"; var usernameText = new Text2(username, { size: 60, fill: 0xFFFFFF }); usernameText.anchor.set(0, 0); usernameText.x = -500; usernameText.y = yPos; scoresContainer.addChild(usernameText); // Score value var scoreText = new Text2(entry.score.toString(), { size: 80, fill: 0xFFF200 // Brighter yellow color for better visibility }); scoreText.anchor.set(0, 0); scoreText.x = 0; scoreText.y = yPos; scoresContainer.addChild(scoreText); } // Add message if no scores if (scores.length === 0) { var noScores = new Text2("No scores yet!", { size: 80, fill: 0x00FFFF //{3Y} // Bright cyan color for better visibility }); noScores.anchor.set(0.5, 0); noScores.y = 100; scoresContainer.addChild(noScores); } }; return self; }); var LeaderboardButton = Container.expand(function () { var self = Container.call(this); // Create button background var buttonBg = self.attachAsset('blade', { anchorX: 0.5, anchorY: 0.5, width: 400, height: 120 }); buttonBg.tint = 0x3498db; // Create button text that says LEADERBOARD var buttonText = new Text2('LEADERBOARD', { size: 60, fill: 0xFFFFFF }); buttonText.anchor.set(0.5, 0.5); self.addChild(buttonText); // Button interaction self.down = function (x, y, obj) { // Scale down effect on press tween(self, { scaleX: 0.9, scaleY: 0.9 }, { duration: 100 }); // Toggle leaderboard visibility if (leaderboard && leaderboard.visible) { leaderboard.hide(); } else { showLeaderboard(); } }; self.up = function (x, y, obj) { // Scale back to normal on release tween(self, { scaleX: 1, scaleY: 1 }, { duration: 100 }); }; return self; }); var LifeFruit = Container.expand(function () { var self = Container.call(this); self.type = 'lifefruit'; self.sliced = false; self.width = 320; self.height = 320; self.points = 0; self.baseSpeed = 1.5; // Create a white fruit with heart shape for extra life var lifeGraphics = self.attachAsset('lifefruit_img', { anchorX: 0.5, anchorY: 0.5 }); // No color tint needed as we're using the lifefruit_img asset // No heart shape needed as we're using the lifefruit_img asset self.vx = 0; self.vy = 0; self.gravity = 0.01; self.rotationSpeed = (Math.random() - 0.5) * 0.015; self.init = function (x, y, direction) { self.x = x; self.y = y; var angle = direction * (Math.PI / 4) + Math.random() * Math.PI / 8 - Math.PI / 16; var speed = self.baseSpeed + Math.random() * 0.8; self.vx = Math.cos(angle) * speed * 1.15; self.vy = -Math.sin(angle) * speed - 8; }; self.slice = function () { if (self.sliced) { return; } self.sliced = true; // Add an extra life but cap at 12 maximum lives playerLives = Math.min(12, playerLives + 1); // Cap lives at 12 updateLivesDisplay(); // Flash effect tween(lifeGraphics, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { // No need to reset tint as we're not using tint } }); // Play fruit squash sound before lifesquash sound LK.getSound('squash').play(); LK.getSound('lifesquash').play(); }; self.down = function (x, y, obj) { if (!self.sliced) { self.slice(); // Update combo comboCount++; comboTimer = Date.now() + comboTimeout; } }; self.update = function () { // Don't update movement if gameActive is false (leaderboard is visible) if (!gameActive) { return; } if (!self.sliced) { // Update whole fruit // Apply bullet time effect if active (slow down by 75%) var speedMultiplier = bulletTimeActive ? 0.25 : 1; self.vy += self.gravity * speedMultiplier; self.x += self.vx * speedMultiplier; self.y += self.vy * speedMultiplier; self.rotation += self.rotationSpeed * speedMultiplier; // Bounce off walls if not sliced if (self.x < self.width / 2 && self.vx < 0) { self.vx = -self.vx * 0.8; self.x = self.width / 2; } if (self.x > GAME_WIDTH - self.width / 2 && self.vx > 0) { self.vx = -self.vx * 0.8; self.x = GAME_WIDTH - self.width / 2; } } }; self.isOffScreen = function () { return self.y > 2732 + self.height || self.x > GAME_WIDTH + self.width; }; return self; }); var PlayAreaBoundary = Container.expand(function (isTop) { var self = Container.call(this); var line = self.attachAsset('blade', { anchorX: 0, anchorY: 0.5, width: GAME_WIDTH, height: 10 }); line.tint = 0xFFFFFF; line.alpha = 0.7; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x3498DB }); /**** * Game Code ****/ // Game constants var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; var SPAWN_INTERVAL_MIN = 1000; // Decreased spawn interval minimum for more frequent spawning var SPAWN_INTERVAL_MAX = 2000; // Decreased spawn interval maximum for more frequent spawning var SPAWN_COUNT_MIN = 1; var SPAWN_COUNT_MAX = 2; // Increased to spawn more fruits at once var FRUIT_TYPES = ['watermelon', 'apple', 'orange', 'kiwi', 'strawberry', 'passion_fruit']; var BOSS_FRUIT_TYPES = ['strawberry', 'kiwi', 'orange', 'apple', 'passion_fruit']; // Boss type matches newest fruit type introduced in each level var LEVEL_THRESHOLDS = [5, 50, 150, 300, 450]; // Score thresholds for each level boss var LEVEL_FRUITS = [['strawberry'], // Level 1 fruits ['strawberry', 'kiwi'], // Level 2 fruits ['strawberry', 'kiwi', 'orange'], // Level 3 fruits ['strawberry', 'kiwi', 'orange', 'apple'], // Level 4 fruits ['strawberry', 'kiwi', 'orange', 'apple', 'watermelon'] // Level 5 fruits ]; var BOMB_PROBABILITY = 0.2; // Increased bomb probability // Game variables var fruits = []; var blade = null; // Boss-related variables var boss = null; var bossActivated = false; var slicedFruitsInLevel = 0; // Track how many fruits sliced in current level for boss activation var bossFlashed = false; // Track if boss has been flashed in current level // Bullet time variables var bulletTimeActive = false; var bulletTimeEndTime = 0; var bulletTimeBar; // Progress bar for bullet time var bulletTimeDuration = 10000; // 10 seconds // Level tracking var currentLevel = 1; var nextLevelNotified = false; var showingLevelText = false; var levelTextTimer = 0; var levelText; // Level notification text var levelDisplay; // Text to display current level // Spawn timers var lastSpawnTime = 0; var nextSpawnTime = 0; // Combo system var comboCount = 0; var comboTimer = 0; var comboTimeout = 1000; // ms to reset combo var comboTxt; // Text to display combo // Player state var gameActive = true; var playerLives = 3; // Player starts with 3 lives var heartsDisplay; // Container for heart icons var missedFruits = 0; // Counter for missed fruits var missedText; // Text to display missed count var lifeFruitSpawned = 0; // Track how many life fruits we've spawned // Score-related var scoreTxt; // Text to display score var highScore = storage.highScore || 0; // Get high score from storage or default to 0 var highScoreText; // Text to display high score // Leaderboard var leaderboardButton; // Button to open leaderboard var leaderboard; // Leaderboard UI component // Parse leaderboard scores from storage or use empty array if none exists var leaderboardScores = []; if (storage.leaderboardScores) { var scoresArray = storage.leaderboardScores.split(';'); for (var i = 0; i < scoresArray.length; i++) { if (scoresArray[i]) { var parts = scoresArray[i].split(','); var scoreObj = { score: parseInt(parts[0]) }; if (parts.length > 1) { scoreObj.username = parts[1]; } leaderboardScores.push(scoreObj); } } } // Global leaderboard data var playerName = storage.playerName || "Player"; // Get saved player name or use default // UI elements var scoreTxt; var comboTxt; function setupGame() { // Prompt for player name if not already stored if (!storage.playerName) { var defaultName = "Player"; // Skip popup for now and just use default name // LK.showPopup is not available in the current version storage.playerName = defaultName; playerName = defaultName; } // Create blade blade = game.addChild(new Blade()); // Create boss reference but don't create the actual boss instance yet // We'll create it only when needed for the boss fight boss = null; // Set up score display scoreTxt = new Text2('0', { size: 100, fill: 0xFFFFFF, weight: 'bold' // Make text bold }); scoreTxt.anchor.set(0.5, 0); scoreTxt.y = 30; LK.gui.top.addChild(scoreTxt); // Set up combo display comboTxt = new Text2('', { size: 60, fill: 0xFFFF00 }); comboTxt.anchor.set(0.5, 0); comboTxt.y = 140; comboTxt.alpha = 0; LK.gui.top.addChild(comboTxt); // Set up level text display levelText = new Text2('Level 1', { size: 120, fill: 0xFFFFFF }); levelText.anchor.set(0.5, 0.5); levelText.x = GAME_WIDTH / 2; levelText.y = GAME_HEIGHT / 2; levelText.alpha = 0; game.addChild(levelText); // Add level display to top right levelDisplay = new Text2('Level: 1', { size: 100, fill: 0xFFFFFF, weight: 'bold' // Make text bold }); levelDisplay.anchor.set(1, 0); levelDisplay.x = GAME_WIDTH - 50; // Right justified 50px from right wall levelDisplay.y = 100; game.addChild(levelDisplay); // Initialize missed counter missedFruits = 0; // Initialize the missed counter display through the dedicated function updateMissedCounter(); // Set up high score display highScoreText = new Text2('Your High Score: ' + highScore, { size: 60, fill: 0xFFFFFF }); highScoreText.anchor.set(0, 1); highScoreText.x = 50; highScoreText.y = -30; // Position at the bottom, moved 20 pixels below LK.gui.bottomLeft.addChild(highScoreText); // Create bullet time bar (initially hidden) bulletTimeBar = new Container(); bulletTimeBar.x = GAME_WIDTH / 2; bulletTimeBar.y = 280; // Moved 80px down from the top bulletTimeBar.visible = false; game.addChild(bulletTimeBar); // Bullet time bar background var bulletTimeBarBg = LK.getAsset('blade', { anchorX: 0.5, anchorY: 0.5, width: 1000, height: 30 }); bulletTimeBarBg.tint = 0x333333; // Bullet time bar fill var bulletTimeBarFill = LK.getAsset('blade', { anchorX: 0, anchorY: 0.5, width: 1000, height: 30 }); bulletTimeBarFill.tint = 0x00BFFF; // Match bullet time powerup color bulletTimeBarFill.x = -500; // Center the bar // Bullet time text var bulletTimeText = new Text2('BULLET TIME', { size: 60, fill: 0xFFFFFF }); bulletTimeText.anchor.set(0.5, 0.5); bulletTimeText.y = -60; // Add elements to bullet time bar bulletTimeBar.addChild(bulletTimeBarBg); bulletTimeBar.addChild(bulletTimeBarFill); bulletTimeBar.addChild(bulletTimeText); bulletTimeBar.barFill = bulletTimeBarFill; // Store reference to the fill for updates // Create hearts display for lives heartsDisplay = new Container(); heartsDisplay.x = GAME_WIDTH - 100; // Right justified 100px from right wall heartsDisplay.y = 250; // 250px from top game.addChild(heartsDisplay); // Add initial hearts (3 lives) updateLivesDisplay(); // Leaderboard button is hidden // Create and position leaderboard UI leaderboard = new Leaderboard(); leaderboard.x = GAME_WIDTH / 2; leaderboard.y = GAME_HEIGHT / 2; leaderboard.visible = false; game.addChild(leaderboard); // Set initial spawn time nextSpawnTime = Date.now() + Math.random() * (SPAWN_INTERVAL_MAX - SPAWN_INTERVAL_MIN) + SPAWN_INTERVAL_MIN; // Special level 1 powerup spawning removed // Play background music LK.playMusic('gameMusic'); // Reset all game state currentLevel = 1; bossActivated = false; nextLevelNotified = false; LK.setScore(0); missedFruits = 0; playerLives = 3; // Reset lives to 3 lifeFruitSpawned = 0; // Reset life fruit spawn counter slicedFruitsInLevel = 0; // Reset sliced fruits counter comboCount = 0; // Reset combo counter comboTimer = 0; // Reset combo timer // Add play area boundary lines var topBoundary = new PlayAreaBoundary(true); topBoundary.y = 320; // Position 320px from the top game.addChild(topBoundary); var bottomBoundary = new PlayAreaBoundary(false); bottomBoundary.y = GAME_HEIGHT - 200; // Position 200px from the bottom game.addChild(bottomBoundary); // Show level 1 text at start showLevelText(1); } // Function to display level text and reset level-specific state function showLevelText(level) { // Set level text content levelText.setText('Level ' + level); levelText.alpha = 0; showingLevelText = true; // Reset level-specific counters missedFruits = 0; updateMissedCounter(); slicedFruitsInLevel = 0; // Reset boss state for new level bossActivated = false; bossFlashed = false; // Reset boss flash state for new level // Reset bullet time bulletTimeActive = false; bulletTimeBar.visible = false; // Update level display in top right levelDisplay.setText('Level: ' + level); // Clear all remaining fruits on screen for (var i = fruits.length - 1; i >= 0; i--) { fruits[i].destroy(); fruits.splice(i, 1); } // Animate the level text tween(levelText, { alpha: 1 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { // Hold the text visible for a moment levelTextTimer = Date.now() + 2000; } }); } function spawnFruits() { var count = currentLevel === 1 ? Math.ceil((Math.floor(Math.random() * (SPAWN_COUNT_MAX - SPAWN_COUNT_MIN + 1)) + SPAWN_COUNT_MIN) / 2) : // 50% reduction for level 1 Math.floor(Math.random() * (SPAWN_COUNT_MAX - SPAWN_COUNT_MIN + 1)) + SPAWN_COUNT_MIN; // Get appropriate fruit types for current level var availableFruits = LEVEL_FRUITS[currentLevel - 1] || ['strawberry']; // Check if we should spawn a life fruit based on the current level var spawnLifeFruit = false; var spawnBulletTime = false; var spawnBlitz = false; var maxLifeFruits = currentLevel; if (currentLevel === 4) { maxLifeFruits = 21; // Level 4 gets 21 life fruits (increased from 17) } else if (currentLevel === 5) { maxLifeFruits = 20; // Level 5 gets 20 life fruits (increased from 14) } // Level 4 gets 21 life fruits if (currentLevel === 1) { maxLifeFruits = 5; // Level 1 gets 5 life fruits (unchanged) } else if (currentLevel === 2) { maxLifeFruits = 12; // Level 2 gets 12 life fruits (increased from 9) } else if (currentLevel === 3) { maxLifeFruits = 10; // Level 3 gets 10 life fruits (reduced from 17) } // Increase spawn chances for all levels if (lifeFruitSpawned < maxLifeFruits && Math.random() < (currentLevel === 1 ? 0.2 : currentLevel === 2 ? 0.25 : currentLevel === 3 ? 0.3 : currentLevel === 4 ? 0.25 : currentLevel === 5 ? 0.3 : 0.1)) { // 30% chance to spawn a life fruit in level 5 and level 3, 25% chance in level 2 and level 4, 20% chance in level 1 spawnLifeFruit = true; lifeFruitSpawned++; } else if (!bulletTimeActive && Math.random() < (currentLevel === 3 ? 0.12 : currentLevel === 4 ? 0.15 : currentLevel === 5 ? 0.2 : 0.05)) { // 20% chance to spawn bullet time powerup in level 5, 15% in level 4, 12% in level 3, 5% in other levels spawnBulletTime = true; } else if (Math.random() < (currentLevel === 1 ? 0.12 : currentLevel === 3 ? 0.12 : currentLevel === 4 ? 0.15 : currentLevel === 5 ? 0.2 : 0.05)) { // 20% chance to spawn blitz powerup in level 5, 15% in level 4, 12% in level 1 and level 3, 5% in other levels spawnBlitz = true; } // Loop through and create fruits for (var i = 0; i < count; i++) { var fruit; // Create life fruit if it's time if (i === 0 && spawnLifeFruit) { fruit = new LifeFruit(); } else if (i === 0 && spawnBulletTime) { fruit = new BulletTimePowerup(); } else if (i === 0 && spawnBlitz) { fruit = new BlitzPowerup(); } else { var isBomb = Math.random() < BOMB_PROBABILITY; var type = isBomb ? 'bomb' : availableFruits[Math.floor(Math.random() * availableFruits.length)]; fruit = new Fruit(type); } // Determine flight pattern var pattern = Math.random(); if (pattern < 0.5) { // Horizontal flight from left to right var x = -fruit.width; var y = Math.random() * (GAME_HEIGHT / 3) + 300; // Higher position range fruit.x = x; fruit.y = y; fruit.vx = fruit.baseSpeed + Math.random() * 2.5; // Slightly faster fruit.vy = -Math.random() * 2; // Add slight upward movement } else { // Vertical drop var x = Math.random() * (GAME_WIDTH - 200) + 100; var direction = Math.random(); // Random direction between 0 and 1 fruit.init(x, GAME_HEIGHT, direction); } game.addChild(fruit); fruits.push(fruit); } // Schedule next spawn with appropriate delay based on level // Level 1, 2, & 3: 30% shorter delay // Level 4+: normal delay var delayMultiplier = 1; if (currentLevel === 1) { delayMultiplier = 0.6; // 40% shorter delay for level 1 (decreased from 0.8 to 0.6) } else if (currentLevel === 2 || currentLevel === 3) { delayMultiplier = 0.7; // 30% shorter delay for level 2 and 3 } else if (currentLevel === 4) { delayMultiplier = 0.55; // 45% shorter delay for level 4 } else if (currentLevel === 5) { delayMultiplier = 0.5; // 50% shorter delay for level 5 } nextSpawnTime = Date.now() + delayMultiplier * (Math.random() * (SPAWN_INTERVAL_MAX - SPAWN_INTERVAL_MIN) + SPAWN_INTERVAL_MIN); } function updateCombo() { if (comboCount > 1) { comboTxt.setText('COMBO x' + comboCount + '!'); comboTxt.alpha = 1; // Add combo bonus points LK.setScore(LK.getScore() + comboCount * 2); // Play combo sound LK.getSound('combo').play(); // Animate combo text tween(comboTxt, { alpha: 0 }, { duration: 3000, easing: tween.easeOut }); } comboCount = 0; } function handleBladeCollisions() { // Return early if the blade isn't active or doesn't have enough points to form a line if (!blade.active || blade.points.length < 2) { return; } // Process multiple segments for more accurate detection // Increase segments to check for more responsive slashing, especially for level 1 boss var segmentsToCheck = Math.min(15, blade.points.length - 1); // Check multiple segments of the blade for better sensitivity var hitFruits = {}; var hitBoss = false; // First check for boss collision across all blade segments if (boss && boss.active) { for (var j = 0; j < segmentsToCheck; j++) { var startPoint = blade.points[j]; var endPoint = blade.points[j + 1]; // Special case for level 1 boss - add velocity check for quicker response if (boss.level === 1) { // Calculate slice velocity for level 1 boss (faster slices are more effective) var sliceVelocity = Math.sqrt(Math.pow(endPoint.x - startPoint.x, 2) + Math.pow(endPoint.y - startPoint.y, 2)); // For level 1 boss, fast slices are more effective var adjustedWidth = sliceVelocity > 10 ? boss.width / 1.8 : boss.width / 2.2; if (lineIntersectsCircle(startPoint.x, startPoint.y, endPoint.x, endPoint.y, boss.x, boss.y, adjustedWidth)) { // Hit the boss var defeated = boss.hit(); // Play slice and squash sounds LK.getSound('slice').play(); LK.getSound('squash').play(); // Update combo comboCount++; comboTimer = Date.now() + comboTimeout; hitBoss = true; break; } } // Check for other boss levels else if (lineIntersectsCircle(startPoint.x, startPoint.y, endPoint.x, endPoint.y, boss.x, boss.y, boss.width / 2.5)) { // Hit the boss var defeated = boss.hit(); // Play slice and squash sounds LK.getSound('slice').play(); LK.getSound('squash').play(); // Update combo comboCount++; comboTimer = Date.now() + comboTimeout; hitBoss = true; break; } } } // Then check for fruit collisions for (var j = 0; j < segmentsToCheck; j++) { var startPoint = blade.points[j]; var endPoint = blade.points[j + 1]; // Check collisions with fruits for (var i = 0; i < fruits.length; i++) { var fruit = fruits[i]; if (!fruit.sliced && !hitFruits[i] && lineIntersectsCircle(startPoint.x, startPoint.y, endPoint.x, endPoint.y, fruit.x, fruit.y, fruit.width / 2)) { hitFruits[i] = true; if (fruit.type === 'bomb') { // Lose a life when hitting a bomb LK.getSound('explosion').play(); LK.effects.flashScreen(0xFF0000, 500); playerLives--; // Reduce lives by 1 updateLivesDisplay(); // Update hearts display // If no lives left, game over if (playerLives <= 0) { // Update high score if current score is higher if (LK.getScore() > highScore) { highScore = LK.getScore(); storage.highScore = highScore; } // Add score to global leaderboard addScoreToLeaderboard(LK.getScore()); // Pass the current score and high score to game over display LK.showGameOver({ score: LK.getScore(), highScore: highScore }); } break; // Exit the inner loop since we hit a bomb } } // Handle life fruit else if (fruit.type === 'lifefruit' && !hitFruits[i] && lineIntersectsCircle(startPoint.x, startPoint.y, endPoint.x, endPoint.y, fruit.x, fruit.y, fruit.width / 2)) { hitFruits[i] = true; // Slice the life fruit and gain an extra life fruit.slice(); // Play a special sound LK.getSound('combo').play(); // Flash green for the extra life LK.effects.flashScreen(0x00FF00, 300); // Update combo comboCount++; comboTimer = Date.now() + comboTimeout; } else if (!fruit.sliced && !hitFruits[i] && lineIntersectsCircle(startPoint.x, startPoint.y, endPoint.x, endPoint.y, fruit.x, fruit.y, fruit.width / 2)) { hitFruits[i] = true; // Slice regular fruit var points = fruit.slice(); LK.setScore(LK.getScore() + points); scoreTxt.setText(LK.getScore()); // Increment sliced fruits counter for current level slicedFruitsInLevel++; // Play slice sound LK.getSound('slice').play(); // Update combo comboCount++; comboTimer = Date.now() + comboTimeout; } } } } function lineIntersectsCircle(x1, y1, x2, y2, cx, cy, r) { // Check if we're dealing with the boss var isBoss = boss && cx === boss.x && cy === boss.y; var isLevel1Boss = isBoss && boss.level === 1; // Increase detection radius for bosses, especially level 1 boss // Make all bosses more sensitive to slashes when checking through fruits r = isLevel1Boss ? r * 7.0 : isBoss ? r * 5.0 : r * 1.5; // Find the closest point on the line segment to the circle center var dx = x2 - x1; var dy = y2 - y1; var len = Math.sqrt(dx * dx + dy * dy); // Normalize direction vector dx /= len; dy /= len; // Vector from line start to circle center var vx = cx - x1; var vy = cy - y1; // Project this vector onto the line direction var projection = vx * dx + vy * dy; // Clamp projection to line segment - bosses have much more tolerance var clampMax = isBoss ? len * 4.0 : len; projection = Math.max(0, Math.min(clampMax, projection)); // Find the closest point on the line segment var closestX = x1 + projection * dx; var closestY = y1 + projection * dy; // Check if this point is within the circle var distanceSquared = (cx - closestX) * (cx - closestX) + (cy - closestY) * (cy - closestY); // For bosses, further increase hit detection by being more lenient with distance check if (isBoss) { // For level 1 boss, be even more lenient to make it more sensitive through fruits if (isLevel1Boss) { return distanceSquared <= r * r * 4.5; } // For other bosses, also increase sensitivity but not as much return distanceSquared <= r * r * 3.5; } // Normal fruit collision detection return distanceSquared <= r * r; } // Show the leaderboard UI function showLeaderboard() { if (leaderboard) { leaderboard.show(leaderboardScores); } } // Update the hearts display based on current lives function updateLivesDisplay() { // Clear existing hearts while (heartsDisplay.children.length > 0) { heartsDisplay.removeChildAt(0); } // Add new hearts based on current lives for (var i = 0; i < playerLives; i++) { var heart = new Heart(); heart.x = -i * 80; // Space hearts horizontally from right to left heartsDisplay.addChild(heart); } // Update the missed counter updateMissedCounter(); } // Dedicated function to update missed fruits counter and its color function updateMissedCounter() { // Determine color based on missed count var missedColor; if (missedFruits <= 3) { missedColor = 0x00FF00; // Green for 0-3 misses } else if (missedFruits <= 6) { missedColor = 0xFFA500; // Orange for 4-6 misses } else { missedColor = 0xFF0000; // Red for 7-10 misses } // Remove old text from parent if it exists if (missedText && missedText.parent) { missedText.parent.removeChild(missedText); } // Create new Text2 with the appropriate color missedText = new Text2('Missed: ' + missedFruits + '/10', { size: 100, fill: missedColor, // Apply the color based on missed count weight: 'bold' // Make text bold }); missedText.anchor.set(1, 0); missedText.x = GAME_WIDTH - 50; missedText.y = 10; game.addChild(missedText); } // Activate bullet time effect function activateBulletTime() { bulletTimeActive = true; bulletTimeEndTime = Date.now() + bulletTimeDuration; bulletTimeBar.visible = true; // Flash screen blue to indicate bullet time activation LK.effects.flashScreen(0x00BFFF, 500); // Play a sound LK.getSound('combo').play(); } // Update bullet time progress bar function updateBulletTime() { if (!bulletTimeActive) { return; } var currentTime = Date.now(); var remaining = bulletTimeEndTime - currentTime; if (remaining <= 0) { // Bullet time has ended bulletTimeActive = false; bulletTimeBar.visible = false; return; } // Update progress bar var progress = remaining / bulletTimeDuration; bulletTimeBar.barFill.width = 1000 * progress; } // Create a fun victory dance with fruit bouncing animation function startVictoryDance() { // Create victory message var victoryText = new Text2('VICTORY!', { size: 200, fill: 0xFFD700, // Gold color weight: 'bold' }); victoryText.anchor.set(0.5, 0.5); victoryText.x = GAME_WIDTH / 2; victoryText.y = GAME_HEIGHT / 3; victoryText.alpha = 0; game.addChild(victoryText); // Create celebration fruits var celebrationFruits = []; var fruitTypes = ['strawberry', 'kiwi', 'orange', 'apple', 'watermelon', 'passion_fruit']; // Spawn celebration fruits for (var i = 0; i < 20; i++) { var fruitType = fruitTypes[Math.floor(Math.random() * fruitTypes.length)]; var celebrationFruit = new Fruit(fruitType); celebrationFruit.x = Math.random() * GAME_WIDTH; celebrationFruit.y = GAME_HEIGHT + 100 + Math.random() * 500; celebrationFruit.vx = (Math.random() - 0.5) * 10; celebrationFruit.vy = -10 - Math.random() * 5; celebrationFruit.rotationSpeed = (Math.random() - 0.5) * 0.1; game.addChild(celebrationFruit); celebrationFruits.push(celebrationFruit); } // Animate victory text tween(victoryText, { alpha: 1, scaleX: 1.5, scaleY: 1.5 }, { duration: 1000, easing: tween.elasticOut }); // Start celebration animation sequence var danceTimer = 0; var danceInterval = LK.setInterval(function () { danceTimer += 100; // Update fruits with bouncy motion for (var i = 0; i < celebrationFruits.length; i++) { var fruit = celebrationFruits[i]; // Apply gravity fruit.vy += 0.2; // Update position fruit.x += fruit.vx; fruit.y += fruit.vy; fruit.rotation += fruit.rotationSpeed; // Bounce off bottom if (fruit.y > GAME_HEIGHT - 150 && fruit.vy > 0) { fruit.vy = -fruit.vy * 0.8; // Play squash sound at random times if (Math.random() < 0.2) { LK.getSound('squash').play(); } } // Bounce off sides if (fruit.x < 100 && fruit.vx < 0 || fruit.x > GAME_WIDTH - 100 && fruit.vx > 0) { fruit.vx = -fruit.vx * 0.8; } // Wobble effect if (danceTimer % 500 < 250) { tween(fruit, { scaleX: 1.2, scaleY: 0.8 }, { duration: 250, easing: tween.easeOut }); } else { tween(fruit, { scaleX: 0.8, scaleY: 1.2 }, { duration: 250, easing: tween.easeOut }); } } // Make victory text pulse var scale = 1 + 0.1 * Math.sin(danceTimer / 200); victoryText.scale.set(scale); // End dance sequence after 10 seconds if (danceTimer >= 10000) { LK.clearInterval(danceInterval); // Clean up celebration fruits for (var i = 0; i < celebrationFruits.length; i++) { tween(celebrationFruits[i], { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 500, easing: tween.easeOut }); } // Fade out victory text tween(victoryText, { alpha: 0, scaleX: 3, scaleY: 3 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { // Show final game over screen if (LK.getScore() > highScore) { highScore = LK.getScore(); storage.highScore = highScore; } // Add score to global leaderboard addScoreToLeaderboard(LK.getScore()); // Show you win screen LK.showYouWin({ score: LK.getScore(), highScore: highScore }); } }); } }, 16); // Run at approximately 60fps } // Add a score to the leaderboard function addScoreToLeaderboard(score) { // Only add score if it's greater than 0 if (score <= 0) { return; } // Add new score to leaderboard with Upit username or 'Anon' var username = LK.getUptUsername ? LK.getUptUsername() : 'Anon'; leaderboardScores.push({ score: score, username: username }); // Sort leaderboard by score (highest first) leaderboardScores.sort(function (a, b) { return b.score - a.score; }); // Limit to top 10 scores if (leaderboardScores.length > 10) { leaderboardScores = leaderboardScores.slice(0, 10); } // Save to storage - convert to simple string format to avoid undefined JSON error var scoresString = ''; for (var i = 0; i < leaderboardScores.length; i++) { scoresString += leaderboardScores[i].score + "," + (leaderboardScores[i].username || "Player"); if (i < leaderboardScores.length - 1) { scoresString += ';'; } } storage.leaderboardScores = scoresString; } ; // Game update function game.update = function () { // Don't update game state if game is paused (leaderboard is shown) if (!gameActive) { return; } var currentTime = Date.now(); // Handle level text animation if (showingLevelText && currentTime > levelTextTimer && levelText.alpha > 0) { tween(levelText, { alpha: 0 }, { duration: 500, easing: tween.easeIn, onFinish: function onFinish() { showingLevelText = false; } }); } // Check if we should flash the boss at 80% into the level // For level 1: Flash at 16 fruits (80% of 20) // For level 2: Flash at 24 fruits (80% of 30) // For level 3: Flash at 32 fruits (80% of 40) // For level 4: Flash at 45 fruits (90% of 50) // For level 5: Flash at 45 fruits (90% of 50) if (currentLevel >= 1 && currentLevel <= 5 && (currentLevel === 1 && slicedFruitsInLevel >= 16 || currentLevel === 2 && slicedFruitsInLevel >= 24 || currentLevel === 3 && slicedFruitsInLevel >= 32 || currentLevel === 4 && slicedFruitsInLevel >= 45 || currentLevel === 5 && slicedFruitsInLevel >= 45) && !bossFlashed && !bossActivated) { var _doMultipleFlashes = function doMultipleFlashes(flashCount) { if (flashCount <= 0) { tempBoss.destroy(); return; } // Flash in tween(tempBoss, { alpha: 1 }, { duration: 250, // Half second total per flash (250ms in, 250ms out) easing: tween.easeOut, onFinish: function onFinish() { // Flash out tween(tempBoss, { alpha: 0 }, { duration: 250, // Half second total per flash (250ms in, 250ms out) easing: tween.easeIn, onFinish: function onFinish() { // Continue with next flash _doMultipleFlashes(flashCount - 1); } }); } }); }; // Start the sequence with 3 flashes bossFlashed = true; // Create a temporary boss image for flashing var tempBoss = new Boss(currentLevel); tempBoss.x = GAME_WIDTH / 2; tempBoss.y = GAME_HEIGHT / 2; tempBoss.alpha = 0; game.addChild(tempBoss); // Function to perform multiple boss flashes _doMultipleFlashes(3); } // Check if we should activate the boss fight based on fruit count // Level 1: After 20 sliced fruits // Level 2: After 30 sliced fruits // Level 3: After 40 sliced fruits // Other levels: After 5 sliced fruits consistently var shouldActivateBoss = false; if (currentLevel === 1) { shouldActivateBoss = slicedFruitsInLevel >= 20 && !bossActivated && !nextLevelNotified; } else if (currentLevel === 2) { shouldActivateBoss = slicedFruitsInLevel >= 30 && !bossActivated && !nextLevelNotified; } else if (currentLevel === 3) { shouldActivateBoss = slicedFruitsInLevel >= 40 && !bossActivated && !nextLevelNotified; } else if (currentLevel === 4) { shouldActivateBoss = slicedFruitsInLevel >= 50 && !bossActivated && !nextLevelNotified; } else if (currentLevel === 5) { shouldActivateBoss = slicedFruitsInLevel >= 50 && !bossActivated && !nextLevelNotified; } else { // Fallback for any future levels beyond 5 shouldActivateBoss = LK.getScore() >= LEVEL_THRESHOLDS[currentLevel - 1] && !bossActivated && !nextLevelNotified; } if (currentLevel <= 5 && shouldActivateBoss) { bossActivated = true; // Create a new boss for the current level if (boss) { boss.destroy(); } boss = game.addChild(new Boss(currentLevel)); boss.activate(); // Clear existing fruits during boss transition for (var i = fruits.length - 1; i >= 0; i--) { fruits[i].destroy(); fruits.splice(i, 1); } } // Only spawn normal fruits if we're not in a boss fight if (currentTime >= nextSpawnTime && (!bossActivated || !boss.active)) { spawnFruits(); } // Update all fruits for (var i = fruits.length - 1; i >= 0; i--) { var fruit = fruits[i]; fruit.update(); // Remove fruits that are off-screen if (fruit.isOffScreen()) { // Only count missed fruits that aren't bombs or already sliced if (!fruit.sliced && fruit.type !== 'bomb') { missedFruits++; // Update the missed counter with new value and color updateMissedCounter(); // If 10 fruits are missed, lose a life and reset miss counter if (missedFruits >= 10) { LK.effects.flashScreen(0xFF0000, 500); playerLives--; // Reduce lives by 1 updateLivesDisplay(); // Update hearts display missedFruits = 0; // Reset missed fruits counter updateMissedCounter(); // Update missed counter display // Only show game over if no lives left if (playerLives <= 0) { // Update high score if current score is higher if (LK.getScore() > highScore) { highScore = LK.getScore(); storage.highScore = highScore; } // Add score to global leaderboard addScoreToLeaderboard(LK.getScore()); // Pass the current score and high score to game over display LK.showGameOver({ score: LK.getScore(), highScore: highScore }); } } } fruit.destroy(); fruits.splice(i, 1); } } // Check for blade collisions handleBladeCollisions(); // Update blade blade.update(); // Update boss if active if (boss && boss.active) { boss.update(1 / 60); // Pass approximate delta time } // If boss was defeated, move to next level if (bossActivated && boss && !boss.active) { bossActivated = false; nextLevelNotified = false; // Move to next level if we haven't reached level 5 yet if (currentLevel < 5) { currentLevel++; // Show level notification showLevelText(currentLevel); } else if (currentLevel === 5) { // Player has beaten all levels LK.effects.flashScreen(0x00FF00, 800); // Start the victory dance sequence startVictoryDance(); } } // Check combo timer if (comboCount > 0 && Date.now() > comboTimer) { updateCombo(); } // Update bullet time effect if (bulletTimeActive) { updateBulletTime(); } }; // Handle touch/mouse events game.down = function (x, y, obj) { blade.active = true; blade.reset(); blade.addPoint(x, y); }; game.move = function (x, y, obj) { if (blade.active) { blade.addPoint(x, y); handleBladeCollisions(); } }; game.up = function (x, y, obj) { blade.active = false; }; // Start the game setupGame();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Blade = Container.expand(function () {
var self = Container.call(this);
self.active = false;
self.points = [];
self.maxPoints = 40; // Increased from 30 to 40 to track more points for even smoother detection
self.lastUpdateTime = 0; // Track time between updates for velocity calculation
self.trail = [];
for (var i = 0; i < self.maxPoints; i++) {
var trailPart = self.attachAsset('blade', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
self.trail.push(trailPart);
}
self.update = function () {
// Update trail visuals
for (var i = 0; i < self.trail.length; i++) {
if (i < self.points.length) {
var point = self.points[i];
var trailPart = self.trail[i];
trailPart.x = point.x;
trailPart.y = point.y;
trailPart.alpha = 0; // Keep blade invisible
if (i > 0) {
var prevPoint = self.points[i - 1];
var angle = Math.atan2(point.y - prevPoint.y, point.x - prevPoint.x);
trailPart.rotation = angle;
}
} else {
self.trail[i].alpha = 0;
}
}
};
self.addPoint = function (x, y) {
var currentTime = Date.now();
var velocity = 0;
var angle = 0;
// Calculate velocity and angle if we have previous points
if (self.points.length > 0) {
var prevPoint = self.points[0];
var dx = x - prevPoint.x;
var dy = y - prevPoint.y;
var timeDiff = currentTime - self.lastUpdateTime;
// Prevent division by zero
if (timeDiff > 0) {
var distance = Math.sqrt(dx * dx + dy * dy);
velocity = distance / timeDiff * 1000; // pixels per second
}
angle = Math.atan2(dy, dx);
}
self.lastUpdateTime = currentTime;
self.points.unshift({
x: x,
y: y,
time: currentTime,
velocity: velocity,
angle: angle
});
if (self.points.length > self.maxPoints) {
self.points.pop();
}
};
self.reset = function () {
self.points = [];
self.active = false;
for (var i = 0; i < self.trail.length; i++) {
self.trail[i].alpha = 0;
}
};
return self;
});
var BlitzPowerup = Container.expand(function () {
var self = Container.call(this);
self.type = 'blitz';
self.sliced = false;
self.width = 300;
self.height = 300;
self.points = 0;
self.baseSpeed = 1.5;
// Create a red lightning bolt-shaped fruit for blitz
var blitzGraphics = self.attachAsset('blitz', {
anchorX: 0.5,
anchorY: 0.5
});
// No color tint needed as we're using the blitz image asset
// No shape needed as we're using the blitz image asset
self.vx = 0;
self.vy = 0;
self.gravity = 0.01;
self.rotationSpeed = (Math.random() - 0.5) * 0.015;
self.init = function (x, y, direction) {
self.x = x;
self.y = y;
var angle = direction * (Math.PI / 4) + Math.random() * Math.PI / 8 - Math.PI / 16;
var speed = self.baseSpeed + Math.random() * 0.8;
self.vx = Math.cos(angle) * speed * 1.15;
self.vy = -Math.sin(angle) * speed - 8;
};
self.slice = function () {
if (self.sliced) {
return;
}
self.sliced = true;
// Play Blitzkrieg sound
LK.getSound('Blitzkrieg').play();
// Display "Blitzkrieg!" text in the middle of screen with large text that blinks red and yellow
var blitzText = new Text2('BLITZKRIEG!', {
size: 180,
// Larger size
fill: 0xFF0000,
// Start with red
weight: 'bold'
});
blitzText.anchor.set(0.5, 0.5);
blitzText.x = GAME_WIDTH / 2;
blitzText.y = GAME_HEIGHT / 2;
game.addChild(blitzText);
// Make the text shake and blink red/yellow for 1 second
var shakeCount = 0;
var maxShakes = 20; // More shakes for more intensity
var shakeIntensity = 25; // Higher intensity shake
var originalX = blitzText.x;
var originalY = blitzText.y;
var colorToggle = false;
// Shake and blink for 1 second (50ms * 20 = 1000ms)
var shakeInterval = LK.setInterval(function () {
if (shakeCount >= maxShakes) {
LK.clearInterval(shakeInterval);
blitzText.x = originalX;
blitzText.y = originalY;
// After 1 second of shaking, play blitzsquash
LK.getSound('blitzsquash').play();
// Then continue with the rest of the effect
// Flash effect
tween(blitzGraphics, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// No need to reset tint as we're not using tint
}
});
// Fade out the text
tween(blitzText, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 500,
easing: tween.elasticOut,
onFinish: function onFinish() {
blitzText.destroy();
}
});
// Convert all fruits to explosion assets first, then after 1 second, destroy them
for (var i = fruits.length - 1; i >= 0; i--) {
var fruit = fruits[i];
if (fruit.type !== 'lifefruit' && fruit.type !== 'bullettime' && fruit.type !== 'blitz') {
// Skip if already converted
if (fruit.converted) {
continue;
}
fruit.converted = true;
// Get the current fruit graphic
var fruitGraphics = fruit.children[0];
// Replace with explosion asset
var explosionGraphic = LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5
});
explosionGraphic.x = fruitGraphics.x;
explosionGraphic.y = fruitGraphics.y;
explosionGraphic.scale.set(fruitGraphics.scale.x, fruitGraphics.scale.y);
fruit.removeChild(fruitGraphics);
fruit.addChild(explosionGraphic);
// Add glow effect
tween(explosionGraphic, {
scaleX: explosionGraphic.scale.x * 1.2,
scaleY: explosionGraphic.scale.y * 1.2
}, {
duration: 500,
easing: tween.easeOut
});
}
}
// Shake the screen effect
var gameOriginalX = game.x;
var gameOriginalY = game.y;
var gameShakeCount = 0;
var gameMaxShakes = 10;
var gameShakeIntensity = 20;
var gameShakeInterval = LK.setInterval(function () {
if (gameShakeCount >= gameMaxShakes) {
LK.clearInterval(gameShakeInterval);
game.x = gameOriginalX;
game.y = gameOriginalY;
// After 1 second and shake is done, destroy all fruits
LK.setTimeout(function () {
// Destroy all fruits on screen (including bombs)
for (var i = fruits.length - 1; i >= 0; i--) {
var fruit = fruits[i];
if (fruit.type !== 'lifefruit' && fruit.type !== 'bullettime' && fruit.type !== 'blitz') {
// Add points for each destroyed fruit
if (fruit.type !== 'bomb') {
LK.setScore(LK.getScore() + fruit.points);
}
// Animate explosion effect
var explosionGraphic = fruit.children[0];
tween(explosionGraphic, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeOut
});
// Remove fruit
fruit.destroy();
fruits.splice(i, 1);
}
}
// Update score display
scoreTxt.setText(LK.getScore());
}, 1000); // Wait 1 second before clearing
return;
}
// Apply random shake offset
game.x = gameOriginalX + (Math.random() - 0.5) * gameShakeIntensity;
game.y = gameOriginalY + (Math.random() - 0.5) * gameShakeIntensity;
gameShakeCount++;
}, 50);
return;
}
// Toggle text color between red and yellow for blinking effect
colorToggle = !colorToggle;
// Direct property access for Text2 objects instead of using style
blitzText.fill = colorToggle ? 0xFFFF00 : 0xFF0000;
// Apply random shake offset with higher intensity
blitzText.x = originalX + (Math.random() - 0.5) * shakeIntensity * 2;
blitzText.y = originalY + (Math.random() - 0.5) * shakeIntensity * 1.5;
shakeCount++;
}, 50); // Slower interval for more visible blinks
};
self.down = function (x, y, obj) {
if (!self.sliced) {
self.slice();
// Update combo
comboCount++;
comboTimer = Date.now() + comboTimeout;
}
};
self.update = function () {
// Don't update movement if gameActive is false (leaderboard is visible)
if (!gameActive) {
return;
}
if (!self.sliced) {
// Update whole fruit
self.vy += self.gravity;
self.x += self.vx;
self.y += self.vy;
self.rotation += self.rotationSpeed;
// Bounce off walls if not sliced
if (self.x < self.width / 2 && self.vx < 0) {
self.vx = -self.vx * 0.8;
self.x = self.width / 2;
}
if (self.x > GAME_WIDTH - self.width / 2 && self.vx > 0) {
self.vx = -self.vx * 0.8;
self.x = GAME_WIDTH - self.width / 2;
}
}
};
self.isOffScreen = function () {
return self.y > 2732 + self.height || self.x > GAME_WIDTH + self.width;
};
return self;
});
var Boss = Container.expand(function (level) {
var self = Container.call(this);
// Boss properties
self.level = level || 1;
self.health = 5 + self.level; // Health increases with level
self.maxHealth = self.health;
self.width = 800;
self.height = 800;
self.active = false;
self.attackTimer = 0;
self.attackInterval = self.level === 5 ? 950 : self.level === 4 ? 600 : self.level === 1 ? 5000 : self.level === 2 ? 2000 : 3000 - self.level * 250; // Level 5 boss spawn speed at 950ms (increased from 833ms), Level 4 boss at 600ms, Level 1 boss at 5000ms (increased from 3000ms), Level 2 boss at 2000ms
// Ensure level is within valid range (1-5)
if (self.level < 1) {
self.level = 1;
}
if (self.level > 5) {
self.level = 5;
}
// Determine boss type based on level
var bossType = BOSS_FRUIT_TYPES[self.level - 1] || 'strawberry';
// Create boss visual
var bossGraphic = self.attachAsset(bossType, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: self.level === 1 ? 1.95 : self.level === 2 ? 2.8 : self.level === 5 ? 3.5 : 4,
// Make level 1 boss smaller by 35%, level 2 boss smaller by 30%, level 5 boss 30% smaller (from 5 to 3.5)
scaleY: self.level === 1 ? 1.95 : self.level === 2 ? 2.8 : self.level === 5 ? 3.5 : 4 // Make level 1 boss smaller by 35%, level 2 boss smaller by 30%, level 5 boss 30% smaller (from 5 to 3.5)
});
// Health bar background
var healthBarBg = new Container();
healthBarBg.x = -200;
healthBarBg.y = -450;
var healthBarBack = LK.getAsset('blade', {
anchorX: 0,
anchorY: 0.5,
width: 400,
height: 30
});
healthBarBack.tint = 0x333333;
// Health bar fill
var healthBarFill = LK.getAsset('blade', {
anchorX: 0,
anchorY: 0.5,
width: 400,
height: 30
});
healthBarFill.tint = 0xFF0000;
healthBarBg.addChild(healthBarBack);
healthBarBg.addChild(healthBarFill);
self.addChild(healthBarBg);
self.healthBar = healthBarFill;
// Boss movement
self.vx = self.level === 5 ? 8 : 2 + self.level * 0.5; // Level 5 boss moves much faster
self.targetX = GAME_WIDTH / 2;
self.activate = function () {
self.active = true;
self.health = self.maxHealth;
self.x = GAME_WIDTH / 2;
// Position boss below the top boundary (320px) with enough space to not overlap
// Boss dimensions depend on the asset scale (4x) and boss type
// Account for boss height and health bar to ensure it appears fully below the boundary
// Position bosses at different heights based on level to avoid overlapping the top boundary
if (self.level === 2) {
self.y = 320 + 570; // Level 2 boss: 60px lower
} else if (self.level === 3) {
self.y = 320 + 690; // Level 3 boss: 170px lower (moved additional 30px lower)
} else if (self.level === 4) {
self.y = 320 + 750; // Level 4 boss: 200px lower
} else if (self.level === 5) {
// For level 5 boss, require 65 slashes to defeat
self.health -= self.maxHealth / 65;
self.y = 320 + 900; // Level 5 boss: 300px lower (moved from 600 to 900)
} else {
self.y = 320 + 570; // Default position for level 1 boss
}
self.updateHealthBar();
};
self.updateHealthBar = function () {
self.healthBar.width = self.health / self.maxHealth * 400;
};
self.hit = function () {
if (!self.active) {
return;
}
if (self.lastHitTime && Date.now() - self.lastHitTime < 100) {
return false;
}
self.lastHitTime = Date.now();
// For level 1 boss, require 15 slashes to defeat
if (self.level === 1) {
// Decrement by 1/21 of health for level 1 boss
self.health -= self.maxHealth / 21;
} else if (self.level === 2) {
// For level 2 boss, require 35 slashes to defeat
self.health -= self.maxHealth / 35;
} else if (self.level === 3) {
// For level 3 boss, require 45 slashes to defeat
self.health -= self.maxHealth / 45;
} else if (self.level === 4) {
// For level 4 boss, require 50 slashes to defeat
self.health -= self.maxHealth / 50;
} else if (self.level === 5) {
// For level 5 boss, require 65 slashes to defeat
self.health -= self.maxHealth / 65;
} else {
// Normal behavior for other bosses
self.health--;
}
self.updateHealthBar();
// Play owww sound when boss is hit
LK.getSound('owww').play();
// Flash the boss red
bossGraphic.tint = 0xFF0000;
LK.setTimeout(function () {
bossGraphic.tint = 0xFFFFFF;
}, 200);
// Check if boss is defeated
if (self.health <= 0) {
self.defeat();
return true;
}
return false;
};
self.defeat = function () {
self.active = false;
// Make the health bar transparent
healthBarBg.alpha = 0;
// Create explosion effect
bossGraphic.tint = 0xFFFFFF;
tween(bossGraphic, {
alpha: 0,
scaleX: 3,
scaleY: 3,
rotation: Math.PI * 2
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
// Add bonus points (increased for higher level bosses)
var bonus = 25 * self.level;
LK.setScore(LK.getScore() + bonus);
scoreTxt.setText(LK.getScore());
LK.effects.flashScreen(0x00FF00, 500);
}
});
// Play explosion sound
LK.getSound('explosion').play();
// Play nooo sound when boss is killed
LK.getSound('nooo').play();
};
self.throwFruit = function () {
if (!self.active) {
return;
}
// Launch multiple bombs at the player
var bombCount = self.level === 5 ? Math.floor(Math.random() * 3) + 2 :
// Level 5: 2-4 bombs (reduced from 3-6)
self.level === 4 ? Math.floor(Math.random() * 2) + 1 :
// Level 4: 1-2 bombs (reduced from 1-4)
Math.floor(Math.random() * self.level) + 1; // Other levels: 1-level bombs
// For level 5, also spawn random fruits alongside bombs
// Also spawn random fruits with level 1 and level 2 bosses
var fruitCount = self.level === 5 || self.level === 1 || self.level === 2 ? Math.floor(Math.random() * 2) : 0; // Reduced from 1-2 to 0-1
// Spawn bombs
for (var i = 0; i < bombCount; i++) {
var fruit = new Fruit('bomb');
// Generate random position on the boss perimeter
var spawnAngle = Math.random() * Math.PI * 2; // Full 360 degrees
var spawnRadius = self.width / 3; // Radius to spawn from (edge of boss)
// Position the bomb on the perimeter
fruit.x = self.x + Math.cos(spawnAngle) * spawnRadius;
fruit.y = self.y + Math.sin(spawnAngle) * spawnRadius;
// Aim outward from the spawn point with spread
var angle = spawnAngle + Math.PI + (Math.random() - 0.5); // Outward direction with randomness
var speed = self.level === 5 ? 5 + Math.random() * 3 :
// Level 5: Much faster bombs
3 + Math.random() * 2 + self.level * 0.5; // Other levels: Normal speed
fruit.vx = Math.cos(angle) * speed;
fruit.vy = Math.sin(angle) * speed;
game.addChild(fruit);
fruits.push(fruit);
}
// For level 5, level 1, level 2, or level 3, also spawn random fruits
if (self.level === 5 || self.level === 1 || self.level === 2 || self.level === 3) {
// Level 2 boss spawns fewer fruits (1-2 instead of 2-3)
var fruitCount = self.level === 2 ? Math.floor(Math.random() * 2) + 1 : self.level === 3 ? Math.floor(Math.random() * 3) + 2 :
// Level 3 boss: 2-4 fruits
self.level === 5 || self.level === 1 ? Math.floor(Math.random() * 2) : 0;
for (var j = 0; j < fruitCount; j++) {
// Select a random fruit type
// For level 1, only use level 1 fruits (strawberry)
// For level 2, use level 2 fruits (strawberry, kiwi)
// For level 3, use level 3 fruits (strawberry, kiwi, orange)
// For level 4, use level 4 fruits (strawberry, kiwi, orange, apple)
var availableFruits;
if (self.level === 1) {
availableFruits = ['strawberry']; // Level 1 fruits
} else if (self.level === 2) {
availableFruits = ['strawberry', 'kiwi']; // Level 2 fruits
} else if (self.level === 3) {
availableFruits = ['strawberry', 'kiwi', 'orange']; // Level 3 fruits
} else if (self.level === 4) {
availableFruits = ['strawberry', 'kiwi', 'orange', 'apple']; // Level 4 fruits
// Spawn more fruits for level 4 boss
fruitCount = Math.floor(Math.random() * 6) + 10; // Level 4 boss: 10-15 fruits (increased from 6-10)
// Ensure we spawn level 4 fruits
// Make the boss spawn each fruit type at least once, then random selections
var guaranteedFruits = ['strawberry', 'kiwi', 'orange', 'apple'];
for (var j = 0; j < fruitCount; j++) {
var fruitType;
if (j < guaranteedFruits.length) {
// Guarantee each fruit type appears at least once
fruitType = guaranteedFruits[j];
} else {
// Then pick random fruits
fruitType = availableFruits[Math.floor(Math.random() * availableFruits.length)];
}
var randomFruit = new Fruit(fruitType);
// Generate random position on the boss perimeter
var fruitAngle = Math.random() * Math.PI * 2;
var fruitRadius = self.width / 3;
// Position the fruit on the perimeter
randomFruit.x = self.x + Math.cos(fruitAngle) * fruitRadius;
randomFruit.y = self.y + Math.sin(fruitAngle) * fruitRadius;
// Aim outward with different trajectory
var fruitDirection = fruitAngle + Math.PI + (Math.random() - 0.5) * 0.5;
var fruitSpeed = 4 + Math.random() * 2;
randomFruit.vx = Math.cos(fruitDirection) * fruitSpeed;
randomFruit.vy = Math.sin(fruitDirection) * fruitSpeed;
game.addChild(randomFruit);
fruits.push(randomFruit);
}
return; // Skip the regular fruit spawning below
} else if (self.level === 5) {
availableFruits = ['strawberry', 'kiwi', 'orange', 'apple', 'watermelon']; // Level 5 fruits
// Spawn more fruits for level 5 boss
fruitCount = Math.floor(Math.random() * 3) + 4; // Level 5 boss: 4-6 fruits
// Ensure we spawn level 5 fruits
for (var j = 0; j < fruitCount; j++) {
var fruitType = availableFruits[Math.floor(Math.random() * availableFruits.length)];
var randomFruit = new Fruit(fruitType);
// Generate random position on the boss perimeter
var fruitAngle = Math.random() * Math.PI * 2;
var fruitRadius = self.width / 3;
// Position the fruit on the perimeter
randomFruit.x = self.x + Math.cos(fruitAngle) * fruitRadius;
randomFruit.y = self.y + Math.sin(fruitAngle) * fruitRadius;
// Aim outward with different trajectory
var fruitDirection = fruitAngle + Math.PI + (Math.random() - 0.5) * 0.5;
var fruitSpeed = 4 + Math.random() * 2;
randomFruit.vx = Math.cos(fruitDirection) * fruitSpeed;
randomFruit.vy = Math.sin(fruitDirection) * fruitSpeed;
game.addChild(randomFruit);
fruits.push(randomFruit);
}
return; // Skip the regular fruit spawning below
} else {
availableFruits = ['strawberry', 'kiwi', 'orange', 'apple', 'watermelon']; // Level 5 fruits
}
var fruitType = availableFruits[Math.floor(Math.random() * availableFruits.length)];
var randomFruit = new Fruit(fruitType);
// Generate random position on the boss perimeter
var fruitAngle = Math.random() * Math.PI * 2;
var fruitRadius = self.width / 3;
// Position the fruit on the perimeter
randomFruit.x = self.x + Math.cos(fruitAngle) * fruitRadius;
randomFruit.y = self.y + Math.sin(fruitAngle) * fruitRadius;
// Aim outward with different trajectory
var fruitDirection = fruitAngle + Math.PI + (Math.random() - 0.5) * 0.5;
var fruitSpeed = 4 + Math.random() * 2;
randomFruit.vx = Math.cos(fruitDirection) * fruitSpeed;
randomFruit.vy = Math.sin(fruitDirection) * fruitSpeed;
game.addChild(randomFruit);
fruits.push(randomFruit);
}
}
};
self.update = function (delta) {
if (!self.active) {
return;
}
// Apply bullet time effect if active (slow down by 75%)
var speedMultiplier = bulletTimeActive ? 0.25 : 1;
// Move boss back and forth
if (Math.abs(self.x - self.targetX) < 10) {
self.targetX = Math.random() * (GAME_WIDTH - 400) + 200;
}
var dirX = self.targetX - self.x;
self.x += dirX / Math.abs(dirX) * self.vx * speedMultiplier;
// Attack on timer
var currentTime = Date.now();
if (currentTime >= self.attackTimer) {
self.throwFruit();
// Extend attack interval during bullet time
// For level 1 boss, spawn a fruit every 1 second (reduced from 2 seconds)
var interval = self.level === 1 ? 1000 : self.attackInterval;
self.attackTimer = currentTime + interval * (bulletTimeActive ? 4 : 1);
}
};
self.down = function (x, y, obj) {
// Allow clicking on boss to damage it
if (self.active) {
self.hit();
}
};
return self;
});
var BulletTimePowerup = Container.expand(function () {
var self = Container.call(this);
self.type = 'bullettime';
self.sliced = false;
self.width = 300;
self.height = 300;
self.points = 0;
self.baseSpeed = 1.5;
// Create a blue hourglass-shaped fruit for bullet time
var bulletTimeGraphics = self.attachAsset('bullettime', {
anchorX: 0.5,
anchorY: 0.5
});
// No color tint needed as we're using the bullettime image asset
// No shapes needed as we're using the bullettime image asset
self.vx = 0;
self.vy = 0;
self.gravity = 0.01;
self.rotationSpeed = (Math.random() - 0.5) * 0.015;
self.init = function (x, y, direction) {
self.x = x;
self.y = y;
var angle = direction * (Math.PI / 4) + Math.random() * Math.PI / 8 - Math.PI / 16;
var speed = self.baseSpeed + Math.random() * 0.8;
self.vx = Math.cos(angle) * speed * 1.15;
self.vy = -Math.sin(angle) * speed - 8;
};
self.slice = function () {
if (self.sliced) {
return;
}
self.sliced = true;
// Activate bullet time effect
activateBulletTime();
// Create bullet time text display instead of Blitzkrieg text
var bulletTimeText = new Text2('BULLET TIME!', {
size: 120,
fill: 0x00BFFF,
// Blue color for bullet time
weight: 'bold'
});
bulletTimeText.anchor.set(0.5, 0.5);
bulletTimeText.x = GAME_WIDTH / 2;
bulletTimeText.y = GAME_HEIGHT / 2;
game.addChild(bulletTimeText);
// Play bullet time sound
LK.getSound('bullet').play();
// Flash effect
tween(bulletTimeGraphics, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// No need to reset tint as we're not using tint
}
});
// Animate text to fade out
tween(bulletTimeText, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 1000,
easing: tween.elasticOut,
onFinish: function onFinish() {
bulletTimeText.destroy();
}
});
};
self.down = function (x, y, obj) {
if (!self.sliced) {
self.slice();
// Update combo
comboCount++;
comboTimer = Date.now() + comboTimeout;
}
};
self.update = function () {
// Don't update movement if gameActive is false (leaderboard is visible)
if (!gameActive) {
return;
}
if (!self.sliced) {
// Update whole fruit
self.vy += self.gravity;
self.x += self.vx;
self.y += self.vy;
self.rotation += self.rotationSpeed;
// Bounce off walls if not sliced
if (self.x < self.width / 2 && self.vx < 0) {
self.vx = -self.vx * 0.8;
self.x = self.width / 2;
}
if (self.x > GAME_WIDTH - self.width / 2 && self.vx > 0) {
self.vx = -self.vx * 0.8;
self.x = GAME_WIDTH - self.width / 2;
}
}
};
self.isOffScreen = function () {
return self.y > 2732 + self.height || self.x > GAME_WIDTH + self.width;
};
return self;
});
var Fruit = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'apple';
self.sliced = false;
self.width = 0;
self.height = 0;
self.points = 0;
self.baseSpeed = 0;
var fruitGraphics;
switch (self.type) {
case 'watermelon':
self.width = 200;
self.height = 200;
self.points = 5;
self.baseSpeed = 1.6; // Faster base speed
break;
case 'apple':
self.width = 240;
self.height = 240;
self.points = 4;
self.baseSpeed = 1.7; // Faster base speed
break;
case 'orange':
self.width = 280;
self.height = 280;
self.points = 3;
self.baseSpeed = 1.8; // Faster base speed
break;
case 'kiwi':
self.width = 320;
self.height = 320;
self.points = 2;
self.baseSpeed = 2.0; // Faster base speed
break;
case 'passion_fruit':
self.width = 360;
self.height = 360;
self.points = 6;
self.baseSpeed = 2.3; // Even faster base speed
break;
case 'strawberry':
self.width = 480;
self.height = 480;
self.points = 1;
self.baseSpeed = 2.2; // Faster base speed
break;
case 'bomb':
self.width = 320;
self.height = 320;
self.points = -10;
self.baseSpeed = 1.7; // Faster base speed
break;
}
fruitGraphics = self.attachAsset(self.type, {
anchorX: 0.5,
anchorY: 0.5
});
self.vx = 0;
self.vy = 0;
self.gravity = 0.01; // Reduced gravity to make fruits go higher
self.rotationSpeed = (Math.random() - 0.5) * 0.018; // Slightly faster rotation
self.init = function (x, y, direction) {
self.x = x;
self.y = y;
var angle = direction * (Math.PI / 4) + Math.random() * Math.PI / 8 - Math.PI / 16;
var currentLevel = Math.floor(LK.getScore() / 50) + 1;
var levelMultiplier = 1 + (currentLevel - 1) * 0.1; // 10% increase per level
// Use constant base speed without random factor to keep speed consistent within level
var speed = self.baseSpeed * levelMultiplier;
// Apply 30% speed reduction for level 1
if (currentLevel === 1) {
speed *= 0.7; // 30% slower for level 1
}
self.vx = Math.cos(angle) * speed * 1.15; // Slightly faster horizontal movement
self.vy = -Math.sin(angle) * speed - 8; // Higher initial upward velocity
};
self.slice = function () {
if (self.sliced) {
return;
}
self.sliced = true;
// Create explosive flash effect
fruitGraphics.tint = 0xFFFFFF;
fruitGraphics.alpha = 1;
// Animate explosive flash effect with scaling
tween(fruitGraphics, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.easeOut,
onUpdate: function onUpdate(progress) {
// Create a pulsing effect with rotation during explosion
fruitGraphics.rotation += 0.1;
},
onFinish: function onFinish() {
// Clean up after animation
fruitGraphics.tint = 0xFFFFFF;
fruitGraphics.scaleX = 1;
fruitGraphics.scaleY = 1;
}
});
// Play slice and squash sounds
LK.getSound('slice').play();
LK.getSound('squash').play();
return self.points;
};
self.down = function (x, y, obj) {
if (!self.sliced) {
// Slice the fruit when clicked
var points = self.slice();
// Check if it's a bomb
if (self.type === 'bomb') {
// Lose a life when clicking a bomb
LK.getSound('explosion').play();
LK.effects.flashScreen(0xFF0000, 500);
playerLives--; // Reduce lives by 1
updateLivesDisplay(); // Update hearts display
// Make bomb disappear
self.sliced = true;
// Create explosive flash effect for bomb
fruitGraphics.tint = 0xFFFFFF;
// Animate bomb disappearing
tween(fruitGraphics, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Remove bomb from game
self.destroy();
// Find and remove from fruits array
var index = fruits.indexOf(self);
if (index > -1) {
fruits.splice(index, 1);
}
}
});
// If no lives left, game over
if (playerLives <= 0) {
// Update high score if current score is higher
if (LK.getScore() > highScore) {
highScore = LK.getScore();
storage.highScore = highScore;
}
// Add score to global leaderboard
addScoreToLeaderboard(LK.getScore());
// Pass the current score and high score to game over display
LK.showGameOver({
score: LK.getScore(),
highScore: highScore
});
}
return;
}
// Add points to score
LK.setScore(LK.getScore() + points);
scoreTxt.setText(LK.getScore());
// Increment sliced fruits counter for current level
slicedFruitsInLevel++;
// Update combo
comboCount++;
comboTimer = Date.now() + comboTimeout;
}
};
self.update = function () {
// Don't update movement if gameActive is false (leaderboard is visible)
if (!gameActive) {
return;
}
if (!self.sliced) {
// Update whole fruit
// Apply bullet time effect if active (slow down by 75%)
var speedMultiplier = bulletTimeActive ? 0.25 : 1;
self.vy += self.gravity * speedMultiplier;
self.x += self.vx * speedMultiplier;
self.y += self.vy * speedMultiplier;
self.rotation += self.rotationSpeed * speedMultiplier;
// Bounce off walls if not sliced
if (self.x < self.width / 2 && self.vx < 0) {
self.vx = -self.vx * 0.8; // Bounce with some energy loss
self.x = self.width / 2;
}
if (self.x > GAME_WIDTH - self.width / 2 && self.vx > 0) {
self.vx = -self.vx * 0.8; // Bounce with some energy loss
self.x = GAME_WIDTH - self.width / 2;
}
} else {
// No update for sliced fruit since we're just flashing and fading them
}
};
self.isOffScreen = function () {
return self.y > 2732 + self.height || self.x > GAME_WIDTH + self.width;
};
return self;
});
var Heart = Container.expand(function () {
var self = Container.call(this);
// Create a red heart shape (using blade asset tinted red)
var heartShape = self.attachAsset('blade', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 60
});
heartShape.tint = 0xFF0000;
return self;
});
var Leaderboard = Container.expand(function () {
var self = Container.call(this);
self.visible = false;
// Background panel
var panel = self.attachAsset('blade', {
anchorX: 0.5,
anchorY: 0.5,
width: 1500,
height: 1800
});
panel.tint = 0x111111; // Very dark color (almost black) for better contrast
panel.alpha = 0.9; // Slightly transparent for better visibility
// Title
var title = new Text2('GLOBAL LEADERBOARD', {
size: 100,
fill: 0xFFFFFF
});
title.anchor.set(0.5, 0);
title.y = -800;
self.addChild(title);
// Scores container
var scoresContainer = new Container();
scoresContainer.y = -650;
self.addChild(scoresContainer);
self.scoresContainer = scoresContainer;
// Close button
var closeBtn = self.attachAsset('blade', {
anchorX: 0.5,
anchorY: 0.5,
width: 120,
height: 120
});
closeBtn.tint = 0xFF0000;
closeBtn.x = 700;
closeBtn.y = -800;
// X symbol on close button
var closeX1 = self.attachAsset('blade', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 12
});
closeX1.tint = 0xFFFFFF;
closeX1.rotation = Math.PI / 4;
closeX1.x = 700;
closeX1.y = -800;
var closeX2 = self.attachAsset('blade', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 12
});
closeX2.tint = 0xFFFFFF;
closeX2.rotation = -Math.PI / 4;
closeX2.x = 700;
closeX2.y = -800;
// Close button interaction
closeBtn.interactive = true;
closeBtn.down = function (x, y, obj) {
self.hide();
};
// Show leaderboard
self.show = function (scores) {
self.visible = true;
self.updateScores(scores);
// Pause the game when leaderboard is visible
gameActive = false;
// Make game area 50% transparent
game.alpha = 0.5;
// Make leaderboard panel fully opaque (0% transparent)
panel.alpha = 1.0;
// Bring leaderboard to front layer
if (self.parent) {
var parent = self.parent;
parent.removeChild(self);
parent.addChild(self);
}
};
// Hide leaderboard
self.hide = function () {
self.visible = false;
// Resume the game when leaderboard is hidden
gameActive = true;
// Restore game area opacity
game.alpha = 1.0;
};
// Update scores display
self.updateScores = function (scores) {
// Clear existing score entries
while (scoresContainer.children.length > 0) {
scoresContainer.removeChildAt(0);
}
// Add score entries
for (var i = 0; i < scores.length; i++) {
var entry = scores[i];
var yPos = i * 120;
// Rank
var rank = new Text2(i + 1 + '.', {
size: 80,
fill: 0xFFFFFF
});
rank.anchor.set(1, 0);
rank.x = -600;
rank.y = yPos;
scoresContainer.addChild(rank);
// Username
var username = entry.username || "Player";
var usernameText = new Text2(username, {
size: 60,
fill: 0xFFFFFF
});
usernameText.anchor.set(0, 0);
usernameText.x = -500;
usernameText.y = yPos;
scoresContainer.addChild(usernameText);
// Score value
var scoreText = new Text2(entry.score.toString(), {
size: 80,
fill: 0xFFF200 // Brighter yellow color for better visibility
});
scoreText.anchor.set(0, 0);
scoreText.x = 0;
scoreText.y = yPos;
scoresContainer.addChild(scoreText);
}
// Add message if no scores
if (scores.length === 0) {
var noScores = new Text2("No scores yet!", {
size: 80,
fill: 0x00FFFF //{3Y} // Bright cyan color for better visibility
});
noScores.anchor.set(0.5, 0);
noScores.y = 100;
scoresContainer.addChild(noScores);
}
};
return self;
});
var LeaderboardButton = Container.expand(function () {
var self = Container.call(this);
// Create button background
var buttonBg = self.attachAsset('blade', {
anchorX: 0.5,
anchorY: 0.5,
width: 400,
height: 120
});
buttonBg.tint = 0x3498db;
// Create button text that says LEADERBOARD
var buttonText = new Text2('LEADERBOARD', {
size: 60,
fill: 0xFFFFFF
});
buttonText.anchor.set(0.5, 0.5);
self.addChild(buttonText);
// Button interaction
self.down = function (x, y, obj) {
// Scale down effect on press
tween(self, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100
});
// Toggle leaderboard visibility
if (leaderboard && leaderboard.visible) {
leaderboard.hide();
} else {
showLeaderboard();
}
};
self.up = function (x, y, obj) {
// Scale back to normal on release
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
};
return self;
});
var LifeFruit = Container.expand(function () {
var self = Container.call(this);
self.type = 'lifefruit';
self.sliced = false;
self.width = 320;
self.height = 320;
self.points = 0;
self.baseSpeed = 1.5;
// Create a white fruit with heart shape for extra life
var lifeGraphics = self.attachAsset('lifefruit_img', {
anchorX: 0.5,
anchorY: 0.5
});
// No color tint needed as we're using the lifefruit_img asset
// No heart shape needed as we're using the lifefruit_img asset
self.vx = 0;
self.vy = 0;
self.gravity = 0.01;
self.rotationSpeed = (Math.random() - 0.5) * 0.015;
self.init = function (x, y, direction) {
self.x = x;
self.y = y;
var angle = direction * (Math.PI / 4) + Math.random() * Math.PI / 8 - Math.PI / 16;
var speed = self.baseSpeed + Math.random() * 0.8;
self.vx = Math.cos(angle) * speed * 1.15;
self.vy = -Math.sin(angle) * speed - 8;
};
self.slice = function () {
if (self.sliced) {
return;
}
self.sliced = true;
// Add an extra life but cap at 12 maximum lives
playerLives = Math.min(12, playerLives + 1); // Cap lives at 12
updateLivesDisplay();
// Flash effect
tween(lifeGraphics, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// No need to reset tint as we're not using tint
}
});
// Play fruit squash sound before lifesquash sound
LK.getSound('squash').play();
LK.getSound('lifesquash').play();
};
self.down = function (x, y, obj) {
if (!self.sliced) {
self.slice();
// Update combo
comboCount++;
comboTimer = Date.now() + comboTimeout;
}
};
self.update = function () {
// Don't update movement if gameActive is false (leaderboard is visible)
if (!gameActive) {
return;
}
if (!self.sliced) {
// Update whole fruit
// Apply bullet time effect if active (slow down by 75%)
var speedMultiplier = bulletTimeActive ? 0.25 : 1;
self.vy += self.gravity * speedMultiplier;
self.x += self.vx * speedMultiplier;
self.y += self.vy * speedMultiplier;
self.rotation += self.rotationSpeed * speedMultiplier;
// Bounce off walls if not sliced
if (self.x < self.width / 2 && self.vx < 0) {
self.vx = -self.vx * 0.8;
self.x = self.width / 2;
}
if (self.x > GAME_WIDTH - self.width / 2 && self.vx > 0) {
self.vx = -self.vx * 0.8;
self.x = GAME_WIDTH - self.width / 2;
}
}
};
self.isOffScreen = function () {
return self.y > 2732 + self.height || self.x > GAME_WIDTH + self.width;
};
return self;
});
var PlayAreaBoundary = Container.expand(function (isTop) {
var self = Container.call(this);
var line = self.attachAsset('blade', {
anchorX: 0,
anchorY: 0.5,
width: GAME_WIDTH,
height: 10
});
line.tint = 0xFFFFFF;
line.alpha = 0.7;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x3498DB
});
/****
* Game Code
****/
// Game constants
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var SPAWN_INTERVAL_MIN = 1000; // Decreased spawn interval minimum for more frequent spawning
var SPAWN_INTERVAL_MAX = 2000; // Decreased spawn interval maximum for more frequent spawning
var SPAWN_COUNT_MIN = 1;
var SPAWN_COUNT_MAX = 2; // Increased to spawn more fruits at once
var FRUIT_TYPES = ['watermelon', 'apple', 'orange', 'kiwi', 'strawberry', 'passion_fruit'];
var BOSS_FRUIT_TYPES = ['strawberry', 'kiwi', 'orange', 'apple', 'passion_fruit']; // Boss type matches newest fruit type introduced in each level
var LEVEL_THRESHOLDS = [5, 50, 150, 300, 450]; // Score thresholds for each level boss
var LEVEL_FRUITS = [['strawberry'],
// Level 1 fruits
['strawberry', 'kiwi'],
// Level 2 fruits
['strawberry', 'kiwi', 'orange'],
// Level 3 fruits
['strawberry', 'kiwi', 'orange', 'apple'],
// Level 4 fruits
['strawberry', 'kiwi', 'orange', 'apple', 'watermelon'] // Level 5 fruits
];
var BOMB_PROBABILITY = 0.2; // Increased bomb probability
// Game variables
var fruits = [];
var blade = null;
// Boss-related variables
var boss = null;
var bossActivated = false;
var slicedFruitsInLevel = 0; // Track how many fruits sliced in current level for boss activation
var bossFlashed = false; // Track if boss has been flashed in current level
// Bullet time variables
var bulletTimeActive = false;
var bulletTimeEndTime = 0;
var bulletTimeBar; // Progress bar for bullet time
var bulletTimeDuration = 10000; // 10 seconds
// Level tracking
var currentLevel = 1;
var nextLevelNotified = false;
var showingLevelText = false;
var levelTextTimer = 0;
var levelText; // Level notification text
var levelDisplay; // Text to display current level
// Spawn timers
var lastSpawnTime = 0;
var nextSpawnTime = 0;
// Combo system
var comboCount = 0;
var comboTimer = 0;
var comboTimeout = 1000; // ms to reset combo
var comboTxt; // Text to display combo
// Player state
var gameActive = true;
var playerLives = 3; // Player starts with 3 lives
var heartsDisplay; // Container for heart icons
var missedFruits = 0; // Counter for missed fruits
var missedText; // Text to display missed count
var lifeFruitSpawned = 0; // Track how many life fruits we've spawned
// Score-related
var scoreTxt; // Text to display score
var highScore = storage.highScore || 0; // Get high score from storage or default to 0
var highScoreText; // Text to display high score
// Leaderboard
var leaderboardButton; // Button to open leaderboard
var leaderboard; // Leaderboard UI component
// Parse leaderboard scores from storage or use empty array if none exists
var leaderboardScores = [];
if (storage.leaderboardScores) {
var scoresArray = storage.leaderboardScores.split(';');
for (var i = 0; i < scoresArray.length; i++) {
if (scoresArray[i]) {
var parts = scoresArray[i].split(',');
var scoreObj = {
score: parseInt(parts[0])
};
if (parts.length > 1) {
scoreObj.username = parts[1];
}
leaderboardScores.push(scoreObj);
}
}
} // Global leaderboard data
var playerName = storage.playerName || "Player"; // Get saved player name or use default
// UI elements
var scoreTxt;
var comboTxt;
function setupGame() {
// Prompt for player name if not already stored
if (!storage.playerName) {
var defaultName = "Player";
// Skip popup for now and just use default name
// LK.showPopup is not available in the current version
storage.playerName = defaultName;
playerName = defaultName;
}
// Create blade
blade = game.addChild(new Blade());
// Create boss reference but don't create the actual boss instance yet
// We'll create it only when needed for the boss fight
boss = null;
// Set up score display
scoreTxt = new Text2('0', {
size: 100,
fill: 0xFFFFFF,
weight: 'bold' // Make text bold
});
scoreTxt.anchor.set(0.5, 0);
scoreTxt.y = 30;
LK.gui.top.addChild(scoreTxt);
// Set up combo display
comboTxt = new Text2('', {
size: 60,
fill: 0xFFFF00
});
comboTxt.anchor.set(0.5, 0);
comboTxt.y = 140;
comboTxt.alpha = 0;
LK.gui.top.addChild(comboTxt);
// Set up level text display
levelText = new Text2('Level 1', {
size: 120,
fill: 0xFFFFFF
});
levelText.anchor.set(0.5, 0.5);
levelText.x = GAME_WIDTH / 2;
levelText.y = GAME_HEIGHT / 2;
levelText.alpha = 0;
game.addChild(levelText);
// Add level display to top right
levelDisplay = new Text2('Level: 1', {
size: 100,
fill: 0xFFFFFF,
weight: 'bold' // Make text bold
});
levelDisplay.anchor.set(1, 0);
levelDisplay.x = GAME_WIDTH - 50; // Right justified 50px from right wall
levelDisplay.y = 100;
game.addChild(levelDisplay);
// Initialize missed counter
missedFruits = 0;
// Initialize the missed counter display through the dedicated function
updateMissedCounter();
// Set up high score display
highScoreText = new Text2('Your High Score: ' + highScore, {
size: 60,
fill: 0xFFFFFF
});
highScoreText.anchor.set(0, 1);
highScoreText.x = 50;
highScoreText.y = -30; // Position at the bottom, moved 20 pixels below
LK.gui.bottomLeft.addChild(highScoreText);
// Create bullet time bar (initially hidden)
bulletTimeBar = new Container();
bulletTimeBar.x = GAME_WIDTH / 2;
bulletTimeBar.y = 280; // Moved 80px down from the top
bulletTimeBar.visible = false;
game.addChild(bulletTimeBar);
// Bullet time bar background
var bulletTimeBarBg = LK.getAsset('blade', {
anchorX: 0.5,
anchorY: 0.5,
width: 1000,
height: 30
});
bulletTimeBarBg.tint = 0x333333;
// Bullet time bar fill
var bulletTimeBarFill = LK.getAsset('blade', {
anchorX: 0,
anchorY: 0.5,
width: 1000,
height: 30
});
bulletTimeBarFill.tint = 0x00BFFF; // Match bullet time powerup color
bulletTimeBarFill.x = -500; // Center the bar
// Bullet time text
var bulletTimeText = new Text2('BULLET TIME', {
size: 60,
fill: 0xFFFFFF
});
bulletTimeText.anchor.set(0.5, 0.5);
bulletTimeText.y = -60;
// Add elements to bullet time bar
bulletTimeBar.addChild(bulletTimeBarBg);
bulletTimeBar.addChild(bulletTimeBarFill);
bulletTimeBar.addChild(bulletTimeText);
bulletTimeBar.barFill = bulletTimeBarFill; // Store reference to the fill for updates
// Create hearts display for lives
heartsDisplay = new Container();
heartsDisplay.x = GAME_WIDTH - 100; // Right justified 100px from right wall
heartsDisplay.y = 250; // 250px from top
game.addChild(heartsDisplay);
// Add initial hearts (3 lives)
updateLivesDisplay();
// Leaderboard button is hidden
// Create and position leaderboard UI
leaderboard = new Leaderboard();
leaderboard.x = GAME_WIDTH / 2;
leaderboard.y = GAME_HEIGHT / 2;
leaderboard.visible = false;
game.addChild(leaderboard);
// Set initial spawn time
nextSpawnTime = Date.now() + Math.random() * (SPAWN_INTERVAL_MAX - SPAWN_INTERVAL_MIN) + SPAWN_INTERVAL_MIN;
// Special level 1 powerup spawning removed
// Play background music
LK.playMusic('gameMusic');
// Reset all game state
currentLevel = 1;
bossActivated = false;
nextLevelNotified = false;
LK.setScore(0);
missedFruits = 0;
playerLives = 3; // Reset lives to 3
lifeFruitSpawned = 0; // Reset life fruit spawn counter
slicedFruitsInLevel = 0; // Reset sliced fruits counter
comboCount = 0; // Reset combo counter
comboTimer = 0; // Reset combo timer
// Add play area boundary lines
var topBoundary = new PlayAreaBoundary(true);
topBoundary.y = 320; // Position 320px from the top
game.addChild(topBoundary);
var bottomBoundary = new PlayAreaBoundary(false);
bottomBoundary.y = GAME_HEIGHT - 200; // Position 200px from the bottom
game.addChild(bottomBoundary);
// Show level 1 text at start
showLevelText(1);
}
// Function to display level text and reset level-specific state
function showLevelText(level) {
// Set level text content
levelText.setText('Level ' + level);
levelText.alpha = 0;
showingLevelText = true;
// Reset level-specific counters
missedFruits = 0;
updateMissedCounter();
slicedFruitsInLevel = 0;
// Reset boss state for new level
bossActivated = false;
bossFlashed = false; // Reset boss flash state for new level
// Reset bullet time
bulletTimeActive = false;
bulletTimeBar.visible = false;
// Update level display in top right
levelDisplay.setText('Level: ' + level);
// Clear all remaining fruits on screen
for (var i = fruits.length - 1; i >= 0; i--) {
fruits[i].destroy();
fruits.splice(i, 1);
}
// Animate the level text
tween(levelText, {
alpha: 1
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Hold the text visible for a moment
levelTextTimer = Date.now() + 2000;
}
});
}
function spawnFruits() {
var count = currentLevel === 1 ? Math.ceil((Math.floor(Math.random() * (SPAWN_COUNT_MAX - SPAWN_COUNT_MIN + 1)) + SPAWN_COUNT_MIN) / 2) :
// 50% reduction for level 1
Math.floor(Math.random() * (SPAWN_COUNT_MAX - SPAWN_COUNT_MIN + 1)) + SPAWN_COUNT_MIN;
// Get appropriate fruit types for current level
var availableFruits = LEVEL_FRUITS[currentLevel - 1] || ['strawberry'];
// Check if we should spawn a life fruit based on the current level
var spawnLifeFruit = false;
var spawnBulletTime = false;
var spawnBlitz = false;
var maxLifeFruits = currentLevel;
if (currentLevel === 4) {
maxLifeFruits = 21; // Level 4 gets 21 life fruits (increased from 17)
} else if (currentLevel === 5) {
maxLifeFruits = 20; // Level 5 gets 20 life fruits (increased from 14)
} // Level 4 gets 21 life fruits
if (currentLevel === 1) {
maxLifeFruits = 5; // Level 1 gets 5 life fruits (unchanged)
} else if (currentLevel === 2) {
maxLifeFruits = 12; // Level 2 gets 12 life fruits (increased from 9)
} else if (currentLevel === 3) {
maxLifeFruits = 10; // Level 3 gets 10 life fruits (reduced from 17)
}
// Increase spawn chances for all levels
if (lifeFruitSpawned < maxLifeFruits && Math.random() < (currentLevel === 1 ? 0.2 : currentLevel === 2 ? 0.25 : currentLevel === 3 ? 0.3 : currentLevel === 4 ? 0.25 : currentLevel === 5 ? 0.3 : 0.1)) {
// 30% chance to spawn a life fruit in level 5 and level 3, 25% chance in level 2 and level 4, 20% chance in level 1
spawnLifeFruit = true;
lifeFruitSpawned++;
} else if (!bulletTimeActive && Math.random() < (currentLevel === 3 ? 0.12 : currentLevel === 4 ? 0.15 : currentLevel === 5 ? 0.2 : 0.05)) {
// 20% chance to spawn bullet time powerup in level 5, 15% in level 4, 12% in level 3, 5% in other levels
spawnBulletTime = true;
} else if (Math.random() < (currentLevel === 1 ? 0.12 : currentLevel === 3 ? 0.12 : currentLevel === 4 ? 0.15 : currentLevel === 5 ? 0.2 : 0.05)) {
// 20% chance to spawn blitz powerup in level 5, 15% in level 4, 12% in level 1 and level 3, 5% in other levels
spawnBlitz = true;
}
// Loop through and create fruits
for (var i = 0; i < count; i++) {
var fruit;
// Create life fruit if it's time
if (i === 0 && spawnLifeFruit) {
fruit = new LifeFruit();
} else if (i === 0 && spawnBulletTime) {
fruit = new BulletTimePowerup();
} else if (i === 0 && spawnBlitz) {
fruit = new BlitzPowerup();
} else {
var isBomb = Math.random() < BOMB_PROBABILITY;
var type = isBomb ? 'bomb' : availableFruits[Math.floor(Math.random() * availableFruits.length)];
fruit = new Fruit(type);
}
// Determine flight pattern
var pattern = Math.random();
if (pattern < 0.5) {
// Horizontal flight from left to right
var x = -fruit.width;
var y = Math.random() * (GAME_HEIGHT / 3) + 300; // Higher position range
fruit.x = x;
fruit.y = y;
fruit.vx = fruit.baseSpeed + Math.random() * 2.5; // Slightly faster
fruit.vy = -Math.random() * 2; // Add slight upward movement
} else {
// Vertical drop
var x = Math.random() * (GAME_WIDTH - 200) + 100;
var direction = Math.random(); // Random direction between 0 and 1
fruit.init(x, GAME_HEIGHT, direction);
}
game.addChild(fruit);
fruits.push(fruit);
}
// Schedule next spawn with appropriate delay based on level
// Level 1, 2, & 3: 30% shorter delay
// Level 4+: normal delay
var delayMultiplier = 1;
if (currentLevel === 1) {
delayMultiplier = 0.6; // 40% shorter delay for level 1 (decreased from 0.8 to 0.6)
} else if (currentLevel === 2 || currentLevel === 3) {
delayMultiplier = 0.7; // 30% shorter delay for level 2 and 3
} else if (currentLevel === 4) {
delayMultiplier = 0.55; // 45% shorter delay for level 4
} else if (currentLevel === 5) {
delayMultiplier = 0.5; // 50% shorter delay for level 5
}
nextSpawnTime = Date.now() + delayMultiplier * (Math.random() * (SPAWN_INTERVAL_MAX - SPAWN_INTERVAL_MIN) + SPAWN_INTERVAL_MIN);
}
function updateCombo() {
if (comboCount > 1) {
comboTxt.setText('COMBO x' + comboCount + '!');
comboTxt.alpha = 1;
// Add combo bonus points
LK.setScore(LK.getScore() + comboCount * 2);
// Play combo sound
LK.getSound('combo').play();
// Animate combo text
tween(comboTxt, {
alpha: 0
}, {
duration: 3000,
easing: tween.easeOut
});
}
comboCount = 0;
}
function handleBladeCollisions() {
// Return early if the blade isn't active or doesn't have enough points to form a line
if (!blade.active || blade.points.length < 2) {
return;
}
// Process multiple segments for more accurate detection
// Increase segments to check for more responsive slashing, especially for level 1 boss
var segmentsToCheck = Math.min(15, blade.points.length - 1);
// Check multiple segments of the blade for better sensitivity
var hitFruits = {};
var hitBoss = false;
// First check for boss collision across all blade segments
if (boss && boss.active) {
for (var j = 0; j < segmentsToCheck; j++) {
var startPoint = blade.points[j];
var endPoint = blade.points[j + 1];
// Special case for level 1 boss - add velocity check for quicker response
if (boss.level === 1) {
// Calculate slice velocity for level 1 boss (faster slices are more effective)
var sliceVelocity = Math.sqrt(Math.pow(endPoint.x - startPoint.x, 2) + Math.pow(endPoint.y - startPoint.y, 2));
// For level 1 boss, fast slices are more effective
var adjustedWidth = sliceVelocity > 10 ? boss.width / 1.8 : boss.width / 2.2;
if (lineIntersectsCircle(startPoint.x, startPoint.y, endPoint.x, endPoint.y, boss.x, boss.y, adjustedWidth)) {
// Hit the boss
var defeated = boss.hit();
// Play slice and squash sounds
LK.getSound('slice').play();
LK.getSound('squash').play();
// Update combo
comboCount++;
comboTimer = Date.now() + comboTimeout;
hitBoss = true;
break;
}
}
// Check for other boss levels
else if (lineIntersectsCircle(startPoint.x, startPoint.y, endPoint.x, endPoint.y, boss.x, boss.y, boss.width / 2.5)) {
// Hit the boss
var defeated = boss.hit();
// Play slice and squash sounds
LK.getSound('slice').play();
LK.getSound('squash').play();
// Update combo
comboCount++;
comboTimer = Date.now() + comboTimeout;
hitBoss = true;
break;
}
}
}
// Then check for fruit collisions
for (var j = 0; j < segmentsToCheck; j++) {
var startPoint = blade.points[j];
var endPoint = blade.points[j + 1];
// Check collisions with fruits
for (var i = 0; i < fruits.length; i++) {
var fruit = fruits[i];
if (!fruit.sliced && !hitFruits[i] && lineIntersectsCircle(startPoint.x, startPoint.y, endPoint.x, endPoint.y, fruit.x, fruit.y, fruit.width / 2)) {
hitFruits[i] = true;
if (fruit.type === 'bomb') {
// Lose a life when hitting a bomb
LK.getSound('explosion').play();
LK.effects.flashScreen(0xFF0000, 500);
playerLives--; // Reduce lives by 1
updateLivesDisplay(); // Update hearts display
// If no lives left, game over
if (playerLives <= 0) {
// Update high score if current score is higher
if (LK.getScore() > highScore) {
highScore = LK.getScore();
storage.highScore = highScore;
}
// Add score to global leaderboard
addScoreToLeaderboard(LK.getScore());
// Pass the current score and high score to game over display
LK.showGameOver({
score: LK.getScore(),
highScore: highScore
});
}
break; // Exit the inner loop since we hit a bomb
}
}
// Handle life fruit
else if (fruit.type === 'lifefruit' && !hitFruits[i] && lineIntersectsCircle(startPoint.x, startPoint.y, endPoint.x, endPoint.y, fruit.x, fruit.y, fruit.width / 2)) {
hitFruits[i] = true;
// Slice the life fruit and gain an extra life
fruit.slice();
// Play a special sound
LK.getSound('combo').play();
// Flash green for the extra life
LK.effects.flashScreen(0x00FF00, 300);
// Update combo
comboCount++;
comboTimer = Date.now() + comboTimeout;
} else if (!fruit.sliced && !hitFruits[i] && lineIntersectsCircle(startPoint.x, startPoint.y, endPoint.x, endPoint.y, fruit.x, fruit.y, fruit.width / 2)) {
hitFruits[i] = true;
// Slice regular fruit
var points = fruit.slice();
LK.setScore(LK.getScore() + points);
scoreTxt.setText(LK.getScore());
// Increment sliced fruits counter for current level
slicedFruitsInLevel++;
// Play slice sound
LK.getSound('slice').play();
// Update combo
comboCount++;
comboTimer = Date.now() + comboTimeout;
}
}
}
}
function lineIntersectsCircle(x1, y1, x2, y2, cx, cy, r) {
// Check if we're dealing with the boss
var isBoss = boss && cx === boss.x && cy === boss.y;
var isLevel1Boss = isBoss && boss.level === 1;
// Increase detection radius for bosses, especially level 1 boss
// Make all bosses more sensitive to slashes when checking through fruits
r = isLevel1Boss ? r * 7.0 : isBoss ? r * 5.0 : r * 1.5;
// Find the closest point on the line segment to the circle center
var dx = x2 - x1;
var dy = y2 - y1;
var len = Math.sqrt(dx * dx + dy * dy);
// Normalize direction vector
dx /= len;
dy /= len;
// Vector from line start to circle center
var vx = cx - x1;
var vy = cy - y1;
// Project this vector onto the line direction
var projection = vx * dx + vy * dy;
// Clamp projection to line segment - bosses have much more tolerance
var clampMax = isBoss ? len * 4.0 : len;
projection = Math.max(0, Math.min(clampMax, projection));
// Find the closest point on the line segment
var closestX = x1 + projection * dx;
var closestY = y1 + projection * dy;
// Check if this point is within the circle
var distanceSquared = (cx - closestX) * (cx - closestX) + (cy - closestY) * (cy - closestY);
// For bosses, further increase hit detection by being more lenient with distance check
if (isBoss) {
// For level 1 boss, be even more lenient to make it more sensitive through fruits
if (isLevel1Boss) {
return distanceSquared <= r * r * 4.5;
}
// For other bosses, also increase sensitivity but not as much
return distanceSquared <= r * r * 3.5;
}
// Normal fruit collision detection
return distanceSquared <= r * r;
}
// Show the leaderboard UI
function showLeaderboard() {
if (leaderboard) {
leaderboard.show(leaderboardScores);
}
}
// Update the hearts display based on current lives
function updateLivesDisplay() {
// Clear existing hearts
while (heartsDisplay.children.length > 0) {
heartsDisplay.removeChildAt(0);
}
// Add new hearts based on current lives
for (var i = 0; i < playerLives; i++) {
var heart = new Heart();
heart.x = -i * 80; // Space hearts horizontally from right to left
heartsDisplay.addChild(heart);
}
// Update the missed counter
updateMissedCounter();
}
// Dedicated function to update missed fruits counter and its color
function updateMissedCounter() {
// Determine color based on missed count
var missedColor;
if (missedFruits <= 3) {
missedColor = 0x00FF00; // Green for 0-3 misses
} else if (missedFruits <= 6) {
missedColor = 0xFFA500; // Orange for 4-6 misses
} else {
missedColor = 0xFF0000; // Red for 7-10 misses
}
// Remove old text from parent if it exists
if (missedText && missedText.parent) {
missedText.parent.removeChild(missedText);
}
// Create new Text2 with the appropriate color
missedText = new Text2('Missed: ' + missedFruits + '/10', {
size: 100,
fill: missedColor,
// Apply the color based on missed count
weight: 'bold' // Make text bold
});
missedText.anchor.set(1, 0);
missedText.x = GAME_WIDTH - 50;
missedText.y = 10;
game.addChild(missedText);
}
// Activate bullet time effect
function activateBulletTime() {
bulletTimeActive = true;
bulletTimeEndTime = Date.now() + bulletTimeDuration;
bulletTimeBar.visible = true;
// Flash screen blue to indicate bullet time activation
LK.effects.flashScreen(0x00BFFF, 500);
// Play a sound
LK.getSound('combo').play();
}
// Update bullet time progress bar
function updateBulletTime() {
if (!bulletTimeActive) {
return;
}
var currentTime = Date.now();
var remaining = bulletTimeEndTime - currentTime;
if (remaining <= 0) {
// Bullet time has ended
bulletTimeActive = false;
bulletTimeBar.visible = false;
return;
}
// Update progress bar
var progress = remaining / bulletTimeDuration;
bulletTimeBar.barFill.width = 1000 * progress;
}
// Create a fun victory dance with fruit bouncing animation
function startVictoryDance() {
// Create victory message
var victoryText = new Text2('VICTORY!', {
size: 200,
fill: 0xFFD700,
// Gold color
weight: 'bold'
});
victoryText.anchor.set(0.5, 0.5);
victoryText.x = GAME_WIDTH / 2;
victoryText.y = GAME_HEIGHT / 3;
victoryText.alpha = 0;
game.addChild(victoryText);
// Create celebration fruits
var celebrationFruits = [];
var fruitTypes = ['strawberry', 'kiwi', 'orange', 'apple', 'watermelon', 'passion_fruit'];
// Spawn celebration fruits
for (var i = 0; i < 20; i++) {
var fruitType = fruitTypes[Math.floor(Math.random() * fruitTypes.length)];
var celebrationFruit = new Fruit(fruitType);
celebrationFruit.x = Math.random() * GAME_WIDTH;
celebrationFruit.y = GAME_HEIGHT + 100 + Math.random() * 500;
celebrationFruit.vx = (Math.random() - 0.5) * 10;
celebrationFruit.vy = -10 - Math.random() * 5;
celebrationFruit.rotationSpeed = (Math.random() - 0.5) * 0.1;
game.addChild(celebrationFruit);
celebrationFruits.push(celebrationFruit);
}
// Animate victory text
tween(victoryText, {
alpha: 1,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 1000,
easing: tween.elasticOut
});
// Start celebration animation sequence
var danceTimer = 0;
var danceInterval = LK.setInterval(function () {
danceTimer += 100;
// Update fruits with bouncy motion
for (var i = 0; i < celebrationFruits.length; i++) {
var fruit = celebrationFruits[i];
// Apply gravity
fruit.vy += 0.2;
// Update position
fruit.x += fruit.vx;
fruit.y += fruit.vy;
fruit.rotation += fruit.rotationSpeed;
// Bounce off bottom
if (fruit.y > GAME_HEIGHT - 150 && fruit.vy > 0) {
fruit.vy = -fruit.vy * 0.8;
// Play squash sound at random times
if (Math.random() < 0.2) {
LK.getSound('squash').play();
}
}
// Bounce off sides
if (fruit.x < 100 && fruit.vx < 0 || fruit.x > GAME_WIDTH - 100 && fruit.vx > 0) {
fruit.vx = -fruit.vx * 0.8;
}
// Wobble effect
if (danceTimer % 500 < 250) {
tween(fruit, {
scaleX: 1.2,
scaleY: 0.8
}, {
duration: 250,
easing: tween.easeOut
});
} else {
tween(fruit, {
scaleX: 0.8,
scaleY: 1.2
}, {
duration: 250,
easing: tween.easeOut
});
}
}
// Make victory text pulse
var scale = 1 + 0.1 * Math.sin(danceTimer / 200);
victoryText.scale.set(scale);
// End dance sequence after 10 seconds
if (danceTimer >= 10000) {
LK.clearInterval(danceInterval);
// Clean up celebration fruits
for (var i = 0; i < celebrationFruits.length; i++) {
tween(celebrationFruits[i], {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 500,
easing: tween.easeOut
});
}
// Fade out victory text
tween(victoryText, {
alpha: 0,
scaleX: 3,
scaleY: 3
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
// Show final game over screen
if (LK.getScore() > highScore) {
highScore = LK.getScore();
storage.highScore = highScore;
}
// Add score to global leaderboard
addScoreToLeaderboard(LK.getScore());
// Show you win screen
LK.showYouWin({
score: LK.getScore(),
highScore: highScore
});
}
});
}
}, 16); // Run at approximately 60fps
}
// Add a score to the leaderboard
function addScoreToLeaderboard(score) {
// Only add score if it's greater than 0
if (score <= 0) {
return;
}
// Add new score to leaderboard with Upit username or 'Anon'
var username = LK.getUptUsername ? LK.getUptUsername() : 'Anon';
leaderboardScores.push({
score: score,
username: username
});
// Sort leaderboard by score (highest first)
leaderboardScores.sort(function (a, b) {
return b.score - a.score;
});
// Limit to top 10 scores
if (leaderboardScores.length > 10) {
leaderboardScores = leaderboardScores.slice(0, 10);
}
// Save to storage - convert to simple string format to avoid undefined JSON error
var scoresString = '';
for (var i = 0; i < leaderboardScores.length; i++) {
scoresString += leaderboardScores[i].score + "," + (leaderboardScores[i].username || "Player");
if (i < leaderboardScores.length - 1) {
scoresString += ';';
}
}
storage.leaderboardScores = scoresString;
}
;
// Game update function
game.update = function () {
// Don't update game state if game is paused (leaderboard is shown)
if (!gameActive) {
return;
}
var currentTime = Date.now();
// Handle level text animation
if (showingLevelText && currentTime > levelTextTimer && levelText.alpha > 0) {
tween(levelText, {
alpha: 0
}, {
duration: 500,
easing: tween.easeIn,
onFinish: function onFinish() {
showingLevelText = false;
}
});
}
// Check if we should flash the boss at 80% into the level
// For level 1: Flash at 16 fruits (80% of 20)
// For level 2: Flash at 24 fruits (80% of 30)
// For level 3: Flash at 32 fruits (80% of 40)
// For level 4: Flash at 45 fruits (90% of 50)
// For level 5: Flash at 45 fruits (90% of 50)
if (currentLevel >= 1 && currentLevel <= 5 && (currentLevel === 1 && slicedFruitsInLevel >= 16 || currentLevel === 2 && slicedFruitsInLevel >= 24 || currentLevel === 3 && slicedFruitsInLevel >= 32 || currentLevel === 4 && slicedFruitsInLevel >= 45 || currentLevel === 5 && slicedFruitsInLevel >= 45) && !bossFlashed && !bossActivated) {
var _doMultipleFlashes = function doMultipleFlashes(flashCount) {
if (flashCount <= 0) {
tempBoss.destroy();
return;
}
// Flash in
tween(tempBoss, {
alpha: 1
}, {
duration: 250,
// Half second total per flash (250ms in, 250ms out)
easing: tween.easeOut,
onFinish: function onFinish() {
// Flash out
tween(tempBoss, {
alpha: 0
}, {
duration: 250,
// Half second total per flash (250ms in, 250ms out)
easing: tween.easeIn,
onFinish: function onFinish() {
// Continue with next flash
_doMultipleFlashes(flashCount - 1);
}
});
}
});
}; // Start the sequence with 3 flashes
bossFlashed = true;
// Create a temporary boss image for flashing
var tempBoss = new Boss(currentLevel);
tempBoss.x = GAME_WIDTH / 2;
tempBoss.y = GAME_HEIGHT / 2;
tempBoss.alpha = 0;
game.addChild(tempBoss);
// Function to perform multiple boss flashes
_doMultipleFlashes(3);
}
// Check if we should activate the boss fight based on fruit count
// Level 1: After 20 sliced fruits
// Level 2: After 30 sliced fruits
// Level 3: After 40 sliced fruits
// Other levels: After 5 sliced fruits consistently
var shouldActivateBoss = false;
if (currentLevel === 1) {
shouldActivateBoss = slicedFruitsInLevel >= 20 && !bossActivated && !nextLevelNotified;
} else if (currentLevel === 2) {
shouldActivateBoss = slicedFruitsInLevel >= 30 && !bossActivated && !nextLevelNotified;
} else if (currentLevel === 3) {
shouldActivateBoss = slicedFruitsInLevel >= 40 && !bossActivated && !nextLevelNotified;
} else if (currentLevel === 4) {
shouldActivateBoss = slicedFruitsInLevel >= 50 && !bossActivated && !nextLevelNotified;
} else if (currentLevel === 5) {
shouldActivateBoss = slicedFruitsInLevel >= 50 && !bossActivated && !nextLevelNotified;
} else {
// Fallback for any future levels beyond 5
shouldActivateBoss = LK.getScore() >= LEVEL_THRESHOLDS[currentLevel - 1] && !bossActivated && !nextLevelNotified;
}
if (currentLevel <= 5 && shouldActivateBoss) {
bossActivated = true;
// Create a new boss for the current level
if (boss) {
boss.destroy();
}
boss = game.addChild(new Boss(currentLevel));
boss.activate();
// Clear existing fruits during boss transition
for (var i = fruits.length - 1; i >= 0; i--) {
fruits[i].destroy();
fruits.splice(i, 1);
}
}
// Only spawn normal fruits if we're not in a boss fight
if (currentTime >= nextSpawnTime && (!bossActivated || !boss.active)) {
spawnFruits();
}
// Update all fruits
for (var i = fruits.length - 1; i >= 0; i--) {
var fruit = fruits[i];
fruit.update();
// Remove fruits that are off-screen
if (fruit.isOffScreen()) {
// Only count missed fruits that aren't bombs or already sliced
if (!fruit.sliced && fruit.type !== 'bomb') {
missedFruits++;
// Update the missed counter with new value and color
updateMissedCounter();
// If 10 fruits are missed, lose a life and reset miss counter
if (missedFruits >= 10) {
LK.effects.flashScreen(0xFF0000, 500);
playerLives--; // Reduce lives by 1
updateLivesDisplay(); // Update hearts display
missedFruits = 0; // Reset missed fruits counter
updateMissedCounter(); // Update missed counter display
// Only show game over if no lives left
if (playerLives <= 0) {
// Update high score if current score is higher
if (LK.getScore() > highScore) {
highScore = LK.getScore();
storage.highScore = highScore;
}
// Add score to global leaderboard
addScoreToLeaderboard(LK.getScore());
// Pass the current score and high score to game over display
LK.showGameOver({
score: LK.getScore(),
highScore: highScore
});
}
}
}
fruit.destroy();
fruits.splice(i, 1);
}
}
// Check for blade collisions
handleBladeCollisions();
// Update blade
blade.update();
// Update boss if active
if (boss && boss.active) {
boss.update(1 / 60); // Pass approximate delta time
}
// If boss was defeated, move to next level
if (bossActivated && boss && !boss.active) {
bossActivated = false;
nextLevelNotified = false;
// Move to next level if we haven't reached level 5 yet
if (currentLevel < 5) {
currentLevel++;
// Show level notification
showLevelText(currentLevel);
} else if (currentLevel === 5) {
// Player has beaten all levels
LK.effects.flashScreen(0x00FF00, 800);
// Start the victory dance sequence
startVictoryDance();
}
}
// Check combo timer
if (comboCount > 0 && Date.now() > comboTimer) {
updateCombo();
}
// Update bullet time effect
if (bulletTimeActive) {
updateBulletTime();
}
};
// Handle touch/mouse events
game.down = function (x, y, obj) {
blade.active = true;
blade.reset();
blade.addPoint(x, y);
};
game.move = function (x, y, obj) {
if (blade.active) {
blade.addPoint(x, y);
handleBladeCollisions();
}
};
game.up = function (x, y, obj) {
blade.active = false;
};
// Start the game
setupGame();
red bomb. In-Game asset. 2d. High contrast. No shadows
Head of pepe meme. each face shaped as a marble shaped face made in blender 3D. In-Game asset. 2d. High contrast. No shadows
Head of doge meme. face shaped as a marble shaped face made in blender 3D. In-Game asset. 2d. High contrast. No shadows
Head of troll face meme. face shaped as a marble shaped face made in blender 3D. In-Game asset. 2d. High contrast. No shadows
Head of think smart guy meme. face shaped as a marble shaped face made in blender 3D. In-Game asset. 2d. High contrast. No shadows
Head of white y u no meme. face shaped as a marble shaped face made in blender 3D. In-Game asset. 2d. High contrast. No shadows
Explosion. In-Game asset. 2d. High contrast. No shadows
Clock. In-Game asset. 3d. High contrast. No shadows
Red Heart. In-Game asset. 3d. High contrast. No shadows
gattling gun. In-Game asset. 2d. High contrast. No shadows