User prompt
Clear the screen atthe end of a level
User prompt
Increase the number of fruits spawned by level 4 boss
User prompt
Increase the number of fruits spawned by level 4 boss
User prompt
Make level 4 boss spawn fruit 1, 2, 3 and 4 at random
User prompt
Make level 5 boss spawn level 5 fruits
User prompt
Make level 3 boss spawn level 3 fruits
User prompt
Let level 4 boss spawn level 4 fruits
User prompt
Make fruits bounce agains each other and not overlap ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Increase number of level 4 fruit types spawned by level 4 boss
User prompt
Increase number of level 3 fruit types spawned by level 3 boss
User prompt
Let level 4 boss spawn level 4 fruit types
User prompt
Let level 3 boss spawn level 3 fruit types
User prompt
Let level 5 boss spawn level 5 fruit types
User prompt
Let level 4 boss spawn level 4 fruit types
User prompt
Increase blitz, life and bullet time powerups spawned in level 5
User prompt
Increase blitz, life and bullet time powerups spawned in level 4
User prompt
Increase lifefruit spawn chances in all levels
User prompt
Increase powerup spawn chances in level 4 and 5
User prompt
Increase blitz, life and bullet time powerups spawned in level 3
User prompt
Increase blitz, life and bullet time powerups spawned in level 4
User prompt
Increase blitz, life and bullet time powerups spawned in level 3
User prompt
Add more blitz, life and bullet time powerups to levels 3, 4 and 5
User prompt
Let level 3 boss spawn level 3 fruit types
User prompt
Reduce delay between fruit spawns in level 1 by 20%
User prompt
Reduce delay between fruit spawns in level 1, 2 and 3 by 30%
/**** * 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 = 20; // Increased from 10 to 20 to track more points for smoother detection 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) { self.points.unshift({ x: x, y: y }); 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('kiwi', { anchorX: 0.5, anchorY: 0.5 }); blitzGraphics.tint = 0xFF0000; // Red color for blitz power // Create lightning bolt symbol var boltTop = self.attachAsset('blade', { anchorX: 0.5, anchorY: 0.5, width: 20, height: 100 }); boltTop.tint = 0xFFFFFF; boltTop.rotation = Math.PI / 4; boltTop.y = -30; var boltBottom = self.attachAsset('blade', { anchorX: 0.5, anchorY: 0.5, width: 20, height: 100 }); boltBottom.tint = 0xFFFFFF; boltBottom.rotation = -Math.PI / 4; boltBottom.y = 30; 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; // Flash effect blitzGraphics.tint = 0xFFFFFF; boltTop.tint = 0xFF0000; boltBottom.tint = 0xFF0000; tween(blitzGraphics, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { blitzGraphics.tint = 0xFF0000; } }); // Display "Blitzkrieg!" text in the middle of screen var blitzText = new Text2('Blitzkrieg!', { size: 120, fill: 0xFF0000, weight: 'bold' }); blitzText.anchor.set(0.5, 0.5); blitzText.x = GAME_WIDTH / 2; blitzText.y = GAME_HEIGHT / 2; game.addChild(blitzText); // Animate the text tween(blitzText, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 1500, easing: tween.elasticOut, onFinish: function onFinish() { blitzText.destroy(); } }); // Shake the screen effect var originalX = game.x; var originalY = game.y; var shakeCount = 0; var maxShakes = 10; var shakeIntensity = 20; var shakeInterval = LK.setInterval(function () { if (shakeCount >= maxShakes) { LK.clearInterval(shakeInterval); game.x = originalX; game.y = originalY; // After shake is done, destroy all fruits LK.getSound('explosion').play(); // 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); } // Create explosive effect for each fruit var fruitGraphics = fruit.children[0]; fruitGraphics.tint = 0xFFFFFF; tween(fruitGraphics, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 200, easing: tween.easeOut }); // Play squash sound for each fruit LK.getSound('squash').play(); // Remove fruit fruit.destroy(); fruits.splice(i, 1); } } // Update score display scoreTxt.setText(LK.getScore()); return; } // Apply random shake offset game.x = originalX + (Math.random() - 0.5) * shakeIntensity; game.y = originalY + (Math.random() - 0.5) * shakeIntensity; shakeCount++; }, 50); // Play slice sound LK.getSound('slice').play(); LK.getSound('squash').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 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 ? 833 : 3000 - self.level * 250; // Level 5 boss spawn speed reduced by 25% (increased from 667ms to 833ms) // 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) { 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; } self.health--; self.updateHealthBar(); // 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(); }; 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) 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, or level 2, also spawn random fruits if (self.level === 5 || self.level === 1 || self.level === 2) { // 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 === 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) var availableFruits; if (self.level === 1) { availableFruits = ['strawberry']; // Level 1 fruits } else if (self.level === 2) { availableFruits = ['strawberry', 'kiwi']; // Level 2 fruits } else { availableFruits = FRUIT_TYPES; // All fruits for level 5 } 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 self.attackTimer = currentTime + self.attackInterval * (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('kiwi', { anchorX: 0.5, anchorY: 0.5 }); bulletTimeGraphics.tint = 0x00BFFF; // Deep sky blue color // Create hourglass symbol var hourglassTop = self.attachAsset('blade', { anchorX: 0.5, anchorY: 0.5, width: 140, height: 20 }); hourglassTop.tint = 0xFFFFFF; hourglassTop.y = -50; var hourglassBottom = self.attachAsset('blade', { anchorX: 0.5, anchorY: 0.5, width: 140, height: 20 }); hourglassBottom.tint = 0xFFFFFF; hourglassBottom.y = 50; var hourglassLeft = self.attachAsset('blade', { anchorX: 0.5, anchorY: 0.5, width: 20, height: 100 }); hourglassLeft.tint = 0xFFFFFF; hourglassLeft.x = -60; var hourglassRight = self.attachAsset('blade', { anchorX: 0.5, anchorY: 0.5, width: 20, height: 100 }); hourglassRight.tint = 0xFFFFFF; hourglassRight.x = 60; 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; // Flash effect bulletTimeGraphics.tint = 0xFFFFFF; hourglassTop.tint = 0x00BFFF; hourglassBottom.tint = 0x00BFFF; hourglassLeft.tint = 0x00BFFF; hourglassRight.tint = 0x00BFFF; tween(bulletTimeGraphics, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { bulletTimeGraphics.tint = 0x00BFFF; } }); // Activate bullet time effect activateBulletTime(); // Play slice and bullet sounds only (no squash) LK.getSound('slice').play(); LK.getSound('bullet').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 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: 80, height: 80 }); 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: 40, height: 8 }); 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: 40, height: 8 }); 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('kiwi', { anchorX: 0.5, anchorY: 0.5 }); lifeGraphics.tint = 0xFFFFFF; // White color for life fruit var heartShape = self.attachAsset('blade', { anchorX: 0.5, anchorY: 0.5, width: 120, height: 120 }); heartShape.tint = 0xFF0000; 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 playerLives = Math.min(playerLives + 1, 5); // Cap at maximum 5 lives updateLivesDisplay(); // Flash effect lifeGraphics.tint = 0xFFFFFF; heartShape.tint = 0xFFFFFF; tween(lifeGraphics, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { lifeGraphics.tint = 0xFFFFFF; heartShape.tint = 0xFF0000; } }); // Play slice and squash sounds LK.getSound('slice').play(); LK.getSound('squash').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(); // Set up leaderboard button leaderboardButton = new LeaderboardButton(); leaderboardButton.x = GAME_WIDTH - 250; // Moved 30px to the left (from 220 to 250) leaderboardButton.y = GAME_HEIGHT - 100; game.addChild(leaderboardButton); // 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); // 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 = 3; } // Level 4 gets 3 life fruits if (currentLevel === 1) { maxLifeFruits = 3; // Level 1 gets 3 life fruits } if (lifeFruitSpawned < maxLifeFruits && Math.random() < 0.1) { // 10% chance to spawn a life fruit spawnLifeFruit = true; lifeFruitSpawned++; } else if (!bulletTimeActive && Math.random() < 0.05) { // 5% chance to spawn bullet time powerup if not already active spawnBulletTime = true; } else if (Math.random() < (currentLevel === 1 ? 0.12 : 0.05)) { // 12% chance to spawn blitz powerup in level 1, 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.7; // 30% shorter delay for level 1 } else if (currentLevel === 2 || currentLevel === 3) { delayMultiplier = 0.7; // 30% shorter delay for level 2 and 3 } 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: 1000, 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 var segmentsToCheck = Math.min(5, blade.points.length - 1); // Check multiple segments of the blade for better sensitivity var hitFruits = {}; var hitBoss = false; for (var j = 0; j < segmentsToCheck; j++) { var startPoint = blade.points[j]; var endPoint = blade.points[j + 1]; // Check boss collision if active if (boss && boss.active && !hitBoss && lineIntersectsCircle(startPoint.x, startPoint.y, endPoint.x, endPoint.y, boss.x, boss.y, boss.width / 3)) { // 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; } // 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') { // 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 { // 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) { // Increase detection radius by 30% to make blade more sensitive r = r * 1.3; // 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 projection = Math.max(0, Math.min(len, 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); 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; } // 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 (4 fruits sliced out of 5) if (currentLevel >= 1 && currentLevel <= 5 && slicedFruitsInLevel >= 4 && !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 // All levels: After 5 sliced fruits consistently var shouldActivateBoss = false; if (currentLevel >= 1 && currentLevel <= 5) { shouldActivateBoss = slicedFruitsInLevel >= 5 && !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); // Show "You Win" screen after completing all levels 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 }); } } // 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 = 20; // Increased from 10 to 20 to track more points for smoother detection
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) {
self.points.unshift({
x: x,
y: y
});
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('kiwi', {
anchorX: 0.5,
anchorY: 0.5
});
blitzGraphics.tint = 0xFF0000; // Red color for blitz power
// Create lightning bolt symbol
var boltTop = self.attachAsset('blade', {
anchorX: 0.5,
anchorY: 0.5,
width: 20,
height: 100
});
boltTop.tint = 0xFFFFFF;
boltTop.rotation = Math.PI / 4;
boltTop.y = -30;
var boltBottom = self.attachAsset('blade', {
anchorX: 0.5,
anchorY: 0.5,
width: 20,
height: 100
});
boltBottom.tint = 0xFFFFFF;
boltBottom.rotation = -Math.PI / 4;
boltBottom.y = 30;
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;
// Flash effect
blitzGraphics.tint = 0xFFFFFF;
boltTop.tint = 0xFF0000;
boltBottom.tint = 0xFF0000;
tween(blitzGraphics, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
blitzGraphics.tint = 0xFF0000;
}
});
// Display "Blitzkrieg!" text in the middle of screen
var blitzText = new Text2('Blitzkrieg!', {
size: 120,
fill: 0xFF0000,
weight: 'bold'
});
blitzText.anchor.set(0.5, 0.5);
blitzText.x = GAME_WIDTH / 2;
blitzText.y = GAME_HEIGHT / 2;
game.addChild(blitzText);
// Animate the text
tween(blitzText, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 1500,
easing: tween.elasticOut,
onFinish: function onFinish() {
blitzText.destroy();
}
});
// Shake the screen effect
var originalX = game.x;
var originalY = game.y;
var shakeCount = 0;
var maxShakes = 10;
var shakeIntensity = 20;
var shakeInterval = LK.setInterval(function () {
if (shakeCount >= maxShakes) {
LK.clearInterval(shakeInterval);
game.x = originalX;
game.y = originalY;
// After shake is done, destroy all fruits
LK.getSound('explosion').play();
// 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);
}
// Create explosive effect for each fruit
var fruitGraphics = fruit.children[0];
fruitGraphics.tint = 0xFFFFFF;
tween(fruitGraphics, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeOut
});
// Play squash sound for each fruit
LK.getSound('squash').play();
// Remove fruit
fruit.destroy();
fruits.splice(i, 1);
}
}
// Update score display
scoreTxt.setText(LK.getScore());
return;
}
// Apply random shake offset
game.x = originalX + (Math.random() - 0.5) * shakeIntensity;
game.y = originalY + (Math.random() - 0.5) * shakeIntensity;
shakeCount++;
}, 50);
// Play slice sound
LK.getSound('slice').play();
LK.getSound('squash').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
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 ? 833 : 3000 - self.level * 250; // Level 5 boss spawn speed reduced by 25% (increased from 667ms to 833ms)
// 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) {
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;
}
self.health--;
self.updateHealthBar();
// 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();
};
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)
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, or level 2, also spawn random fruits
if (self.level === 5 || self.level === 1 || self.level === 2) {
// 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 === 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)
var availableFruits;
if (self.level === 1) {
availableFruits = ['strawberry']; // Level 1 fruits
} else if (self.level === 2) {
availableFruits = ['strawberry', 'kiwi']; // Level 2 fruits
} else {
availableFruits = FRUIT_TYPES; // All fruits for level 5
}
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
self.attackTimer = currentTime + self.attackInterval * (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('kiwi', {
anchorX: 0.5,
anchorY: 0.5
});
bulletTimeGraphics.tint = 0x00BFFF; // Deep sky blue color
// Create hourglass symbol
var hourglassTop = self.attachAsset('blade', {
anchorX: 0.5,
anchorY: 0.5,
width: 140,
height: 20
});
hourglassTop.tint = 0xFFFFFF;
hourglassTop.y = -50;
var hourglassBottom = self.attachAsset('blade', {
anchorX: 0.5,
anchorY: 0.5,
width: 140,
height: 20
});
hourglassBottom.tint = 0xFFFFFF;
hourglassBottom.y = 50;
var hourglassLeft = self.attachAsset('blade', {
anchorX: 0.5,
anchorY: 0.5,
width: 20,
height: 100
});
hourglassLeft.tint = 0xFFFFFF;
hourglassLeft.x = -60;
var hourglassRight = self.attachAsset('blade', {
anchorX: 0.5,
anchorY: 0.5,
width: 20,
height: 100
});
hourglassRight.tint = 0xFFFFFF;
hourglassRight.x = 60;
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;
// Flash effect
bulletTimeGraphics.tint = 0xFFFFFF;
hourglassTop.tint = 0x00BFFF;
hourglassBottom.tint = 0x00BFFF;
hourglassLeft.tint = 0x00BFFF;
hourglassRight.tint = 0x00BFFF;
tween(bulletTimeGraphics, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
bulletTimeGraphics.tint = 0x00BFFF;
}
});
// Activate bullet time effect
activateBulletTime();
// Play slice and bullet sounds only (no squash)
LK.getSound('slice').play();
LK.getSound('bullet').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
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: 80,
height: 80
});
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: 40,
height: 8
});
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: 40,
height: 8
});
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('kiwi', {
anchorX: 0.5,
anchorY: 0.5
});
lifeGraphics.tint = 0xFFFFFF; // White color for life fruit
var heartShape = self.attachAsset('blade', {
anchorX: 0.5,
anchorY: 0.5,
width: 120,
height: 120
});
heartShape.tint = 0xFF0000;
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
playerLives = Math.min(playerLives + 1, 5); // Cap at maximum 5 lives
updateLivesDisplay();
// Flash effect
lifeGraphics.tint = 0xFFFFFF;
heartShape.tint = 0xFFFFFF;
tween(lifeGraphics, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
lifeGraphics.tint = 0xFFFFFF;
heartShape.tint = 0xFF0000;
}
});
// Play slice and squash sounds
LK.getSound('slice').play();
LK.getSound('squash').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();
// Set up leaderboard button
leaderboardButton = new LeaderboardButton();
leaderboardButton.x = GAME_WIDTH - 250; // Moved 30px to the left (from 220 to 250)
leaderboardButton.y = GAME_HEIGHT - 100;
game.addChild(leaderboardButton);
// 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);
// 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 = 3;
} // Level 4 gets 3 life fruits
if (currentLevel === 1) {
maxLifeFruits = 3; // Level 1 gets 3 life fruits
}
if (lifeFruitSpawned < maxLifeFruits && Math.random() < 0.1) {
// 10% chance to spawn a life fruit
spawnLifeFruit = true;
lifeFruitSpawned++;
} else if (!bulletTimeActive && Math.random() < 0.05) {
// 5% chance to spawn bullet time powerup if not already active
spawnBulletTime = true;
} else if (Math.random() < (currentLevel === 1 ? 0.12 : 0.05)) {
// 12% chance to spawn blitz powerup in level 1, 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.7; // 30% shorter delay for level 1
} else if (currentLevel === 2 || currentLevel === 3) {
delayMultiplier = 0.7; // 30% shorter delay for level 2 and 3
}
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: 1000,
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
var segmentsToCheck = Math.min(5, blade.points.length - 1);
// Check multiple segments of the blade for better sensitivity
var hitFruits = {};
var hitBoss = false;
for (var j = 0; j < segmentsToCheck; j++) {
var startPoint = blade.points[j];
var endPoint = blade.points[j + 1];
// Check boss collision if active
if (boss && boss.active && !hitBoss && lineIntersectsCircle(startPoint.x, startPoint.y, endPoint.x, endPoint.y, boss.x, boss.y, boss.width / 3)) {
// 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;
}
// 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') {
// 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 {
// 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) {
// Increase detection radius by 30% to make blade more sensitive
r = r * 1.3;
// 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
projection = Math.max(0, Math.min(len, 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);
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;
}
// 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 (4 fruits sliced out of 5)
if (currentLevel >= 1 && currentLevel <= 5 && slicedFruitsInLevel >= 4 && !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
// All levels: After 5 sliced fruits consistently
var shouldActivateBoss = false;
if (currentLevel >= 1 && currentLevel <= 5) {
shouldActivateBoss = slicedFruitsInLevel >= 5 && !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);
// Show "You Win" screen after completing all levels
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
});
}
}
// 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