/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var BeatRing = Container.expand(function () { var self = Container.call(this); var ringGraphics = self.attachAsset('beatRing', { anchorX: 0.5, anchorY: 0.5 }); ringGraphics.alpha = 0.3; ringGraphics.scaleX = 0.1; ringGraphics.scaleY = 0.1; self.animate = function () { tween(ringGraphics, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { self.destroy(); } }); }; return self; }); var Bubble = Container.expand(function (color, isGolden, specialType) { var self = Container.call(this); var bubbleAsset = 'bubble'; if (isGolden) { bubbleAsset = 'goldenBubble'; } else if (specialType === 'explosive') { bubbleAsset = 'explosiveBubble'; } else if (specialType === 'multiplier') { bubbleAsset = 'multiplierBubble'; } else if (specialType === 'time') { bubbleAsset = 'timeBubble'; } var bubbleGraphics = self.attachAsset(bubbleAsset, { anchorX: 0.5, anchorY: 0.5 }); if (!isGolden && !specialType) { bubbleGraphics.tint = color; } self.isGolden = isGolden || false; self.specialType = specialType || null; self.color = color; self.noteIndex = Math.floor(Math.random() * 4); self.spawned = false; self.pulseTween = null; self.lastPulseTime = 0; self.floatTween = null; // Add floating motion self.startFloat = function () { if (self.floatTween) { tween.stop(self, { y: true }); } var floatAmount = 20 + Math.random() * 30; var floatDuration = 2000 + Math.random() * 1000; self.floatTween = tween(self, { y: self.y - floatAmount }, { duration: floatDuration, easing: tween.easeInOut, onFinish: function onFinish() { tween(self, { y: self.y + floatAmount }, { duration: floatDuration, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.destroyed) { self.startFloat(); } } }); } }); }; // Enhanced pulsing effect with color changes self.startPulse = function () { self.lastPulseTime = LK.ticks; if (self.pulseTween) { tween.stop(bubbleGraphics, { scaleX: true, scaleY: true }); } // Add glow effect on pulse var originalTint = bubbleGraphics.tint; tween(bubbleGraphics, { tint: 0xFFFFFF }, { duration: 150, onFinish: function onFinish() { tween(bubbleGraphics, { tint: originalTint }, { duration: 150 }); } }); self.pulseTween = tween(bubbleGraphics, { scaleX: 1.3, scaleY: 1.3 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(bubbleGraphics, { scaleX: 1.0, scaleY: 1.0 }, { duration: 400, easing: tween.bounceOut }); } }); }; self.pop = function () { var popSounds = ['pop1', 'pop2', 'pop3', 'pop4']; var soundToPlay = 'miss'; // Default sound if (self.isGolden) { soundToPlay = 'miss'; // Use available sound } else if (self.specialType === 'explosive') { soundToPlay = 'explosion'; } else if (self.specialType === 'multiplier') { soundToPlay = 'multiplier'; } else if (self.specialType === 'time') { soundToPlay = 'powerup'; } else { soundToPlay = 'miss'; } LK.getSound(soundToPlay).play(); // Special effects based on bubble type if (self.specialType === 'explosive') { // Create massive explosion effect createExplosiveEffect(self.x, self.y); } else if (self.specialType === 'multiplier') { // Create sparkle multiplier effect createMultiplierEffect(self.x, self.y); } else if (self.specialType === 'time') { // Create time freeze effect createTimeEffect(self.x, self.y); } // Create particle explosion createParticleExplosion(self.x, self.y, self.color, self.isGolden); // Enhanced pop animation tween(bubbleGraphics, { scaleX: 2.0, scaleY: 2.0, alpha: 0, rotation: Math.PI * 2 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { self.destroy(); } }); }; self.down = function (x, y, obj) { var currentTime = LK.ticks; var beatInterval = 60; var beatPosition = currentTime % beatInterval; var timingWindow = 12; // Slightly larger timing window var isPerfectTiming = beatPosition <= timingWindow || beatPosition >= beatInterval - timingWindow; // Visual feedback on touch tween(bubbleGraphics, { scaleX: 0.8, scaleY: 0.8 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(bubbleGraphics, { scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.easeOut }); } }); // Handle special bubble effects if (self.specialType === 'explosive') { // Explosive bubbles destroy nearby bubbles handleExplosiveBubble(self.x, self.y); } else if (self.specialType === 'multiplier') { // Multiplier bubbles double next few scores activateScoreMultiplier(); } else if (self.specialType === 'time') { // Time bubbles slow down game temporarily activateTimeSlowdown(); } if (isPerfectTiming) { onBubblePopped(self, true); } else { onBubblePopped(self, false); } }; return self; }); var Particle = Container.expand(function (color, size) { var self = Container.call(this); var particleGraphics = self.attachAsset('particle', { anchorX: 0.5, anchorY: 0.5 }); particleGraphics.tint = color || 0xFFFFFF; particleGraphics.scaleX = size || 1; particleGraphics.scaleY = size || 1; self.velocity = { x: 0, y: 0 }; self.gravity = 0.2; self.life = 60; // 1 second at 60fps self.update = function () { self.x += self.velocity.x; self.y += self.velocity.y; self.velocity.y += self.gravity; self.life--; // Fade out over time particleGraphics.alpha = self.life / 60; if (self.life <= 0) { self.destroy(); } }; return self; }); var PerfectIndicator = Container.expand(function () { var self = Container.call(this); var indicator = self.attachAsset('perfectIndicator', { anchorX: 0.5, anchorY: 0.5, alpha: 0 }); self.show = function (x, y) { self.x = x; self.y = y; indicator.alpha = 1; indicator.scaleX = 0.5; indicator.scaleY = 0.5; tween(indicator, { scaleX: 1, scaleY: 1, alpha: 0 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { self.destroy(); } }); }; return self; }); var Scorpion = Container.expand(function () { var self = Container.call(this); // Create scorpion body var scorpionBody = self.attachAsset('scorpionBody', { anchorX: 0.5, anchorY: 0.5 }); // Create left claw var leftClaw = self.attachAsset('scorpionClaw', { anchorX: 1.0, anchorY: 0.5 }); leftClaw.x = -25; leftClaw.y = -10; leftClaw.rotation = -0.3; // Create right claw var rightClaw = self.attachAsset('scorpionClaw', { anchorX: 1.0, anchorY: 0.5 }); rightClaw.x = -25; rightClaw.y = 10; rightClaw.rotation = 0.3; // Create tail segments var tailSegments = []; for (var i = 0; i < 4; i++) { var segment = self.attachAsset('scorpionTail', { anchorX: 0.0, anchorY: 0.5 }); segment.x = 40 + i * 12; segment.y = 0; segment.rotation = i * 0.1; segment.scaleX = 0.8 - i * 0.1; tailSegments.push(segment); } // Create stinger at the end var stinger = self.attachAsset('scorpionStinger', { anchorX: 0.5, anchorY: 0.5 }); stinger.x = 88; stinger.y = -5; // Store references for animations self.body = scorpionBody; self.leftClaw = leftClaw; self.rightClaw = rightClaw; self.tailSegments = tailSegments; self.stinger = stinger; self.animationTimer = 0; self.speed = 1.5; // Moderate speed movement self.targetBubble = null; self.lastTargetTime = 0; // Find nearest bubble to target self.findTarget = function () { var nearestBubble = null; var nearestDistance = Infinity; for (var i = 0; i < bubbles.length; i++) { var bubble = bubbles[i]; var distance = Math.sqrt(Math.pow(self.x - bubble.x, 2) + Math.pow(self.y - bubble.y, 2)); if (distance < nearestDistance) { nearestDistance = distance; nearestBubble = bubble; } } return nearestBubble; }; self.update = function () { self.animationTimer++; // Animate tail swishing for (var i = 0; i < self.tailSegments.length; i++) { var segment = self.tailSegments[i]; segment.rotation = Math.sin(self.animationTimer * 0.1 + i * 0.5) * 0.2 + i * 0.1; } // Animate stinger bobbing with pulsing glow self.stinger.y = -5 + Math.sin(self.animationTimer * 0.15) * 3; // Add dangerous pulsing glow to stinger var stingerPulse = Math.sin(self.animationTimer * 0.2) * 0.5 + 0.5; var stingerGlow = 0x440000 + Math.floor(stingerPulse * 0xbb0000); self.stinger.tint = stingerGlow; self.stinger.scaleX = 1 + stingerPulse * 0.3; self.stinger.scaleY = 1 + stingerPulse * 0.3; // Animate claws opening and closing with shimmer effect var clawAnimation = Math.sin(self.animationTimer * 0.08) * 0.2; self.leftClaw.rotation = -0.3 + clawAnimation; self.rightClaw.rotation = 0.3 - clawAnimation; // Add shimmer effect to claws var shimmer = Math.sin(self.animationTimer * 0.15) * 0.3 + 0.7; self.leftClaw.alpha = shimmer; self.rightClaw.alpha = shimmer; var clawGlow = 0x444444 + Math.floor(shimmer * 0x888888); self.leftClaw.tint = clawGlow; self.rightClaw.tint = clawGlow; // Body bobbing while walking self.body.y = Math.sin(self.animationTimer * 0.12) * 2; // Beautiful rainbow color shifting effect var hue = self.animationTimer * 2 % 360; var rainbowColor = 0x8b4513; // Keep brown as base if (hue < 60) { rainbowColor = 0xff4500 + Math.floor(hue / 60 * 0x4000); } else if (hue < 120) { rainbowColor = 0xff8500 + Math.floor((hue - 60) / 60 * 0x4000); } else if (hue < 180) { rainbowColor = 0xffc500 + Math.floor((hue - 120) / 60 * 0x2000); } else if (hue < 240) { rainbowColor = 0xffe500 - Math.floor((hue - 180) / 60 * 0x6000); } else if (hue < 300) { rainbowColor = 0x9fe500 - Math.floor((hue - 240) / 60 * 0x4000); } else { rainbowColor = 0x5fe500 + Math.floor((hue - 300) / 60 * 0x6000); } self.body.tint = rainbowColor; // Find new target every 2 seconds or if current target is destroyed if (LK.ticks - self.lastTargetTime > 120 || !self.targetBubble || self.targetBubble.destroyed) { self.targetBubble = self.findTarget(); self.lastTargetTime = LK.ticks; } // Move towards target bubble if (self.targetBubble && !self.targetBubble.destroyed) { var dx = self.targetBubble.x - self.x; var dy = self.targetBubble.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { // Move towards bubble self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; // Rotate entire scorpion towards target self.rotation = Math.atan2(dy, dx); // Faster tail animation when moving for (var j = 0; j < self.tailSegments.length; j++) { var seg = self.tailSegments[j]; seg.rotation = Math.sin(self.animationTimer * 0.2 + j * 0.5) * 0.3 + j * 0.15; } } else { // Close enough to pop the bubble if (self.targetBubble && !self.targetBubble.destroyed) { // Attack animation - claws snap self.leftClaw.rotation = -0.8; self.rightClaw.rotation = 0.8; // Scorpion pops the bubble (counts as missed for player) for (var i = 0; i < bubbles.length; i++) { if (bubbles[i] === self.targetBubble) { bubbles.splice(i, 1); break; } } self.targetBubble.pop(); missedBeat(); // Player loses health self.targetBubble = null; } } } // Keep scorpion within screen bounds if (self.x < 50) self.x = 50; if (self.x > 1998) self.x = 1998; if (self.y < 50) self.y = 50; if (self.y > 2682) self.y = 2682; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x1a1a2e }); /**** * Game Code ****/ var gameState = 'start'; // 'start' or 'playing' var bubbles = []; var particles = []; var beatRings = []; var score = 0; var combo = 0; var health = 5; var gameSpeed = 1; var spawnTimer = 0; var beatTimer = 0; var perfectIndicators = []; var bubbleColors = [0x4A90E2, 0xFF6B6B, 0x4ECDC4, 0xFFE66D, 0x95E1D3, 0xFF69B4, 0x32CD32, 0xFFA500]; var bestScore = storage.bestScore || 0; var scorpion = null; var heartClickCount = 0; // Track clicks on heart bars for easter egg // New power-up variables var scoreMultiplier = 1; var multiplierTimer = 0; var timeSlowActive = false; var timeSlowTimer = 0; var specialEffects = []; // GTA 2 style leaderboard data - flattened for storage compatibility var leaderboardNames = storage.leaderboardNames || []; var leaderboardScores = storage.leaderboardScores || []; var playerName = storage.playerName || 'Player'; var showLeaderboard = false; // Build leaderboard from flattened arrays var leaderboard = []; for (var idx = 0; idx < leaderboardNames.length; idx++) { leaderboard.push({ name: leaderboardNames[idx], score: leaderboardScores[idx] }); } // Particle explosion function function createParticleExplosion(x, y, color, isGolden) { var particleCount = isGolden ? 15 : 8; var particleColors = isGolden ? [0xFFD700, 0xFFA500, 0xFFFF00] : [color, 0xFFFFFF]; for (var i = 0; i < particleCount; i++) { var particle = new Particle(particleColors[Math.floor(Math.random() * particleColors.length)], 0.5 + Math.random() * 0.5); particle.x = x + (Math.random() - 0.5) * 40; particle.y = y + (Math.random() - 0.5) * 40; particle.velocity.x = (Math.random() - 0.5) * 8; particle.velocity.y = (Math.random() - 0.5) * 8 - 2; particles.push(particle); game.addChild(particle); } } // Create explosive effect for explosive bubbles function createExplosiveEffect(x, y) { // Create shockwave var shockwave = LK.getAsset('beatRing', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8, scaleX: 0.1, scaleY: 0.1 }); shockwave.x = x; shockwave.y = y; shockwave.tint = 0xFF4444; game.addChild(shockwave); tween(shockwave, { scaleX: 4, scaleY: 4, alpha: 0 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { shockwave.destroy(); } }); // Create extra particles for (var i = 0; i < 25; i++) { var particle = new Particle(0xFF4444, 1 + Math.random()); particle.x = x + (Math.random() - 0.5) * 100; particle.y = y + (Math.random() - 0.5) * 100; particle.velocity.x = (Math.random() - 0.5) * 15; particle.velocity.y = (Math.random() - 0.5) * 15 - 5; particles.push(particle); game.addChild(particle); } } // Create multiplier effect function createMultiplierEffect(x, y) { var multiplierText = new Text2('x2 SCORE!', { size: 80, fill: 0x9932CC }); multiplierText.anchor.set(0.5, 0.5); multiplierText.x = x; multiplierText.y = y; multiplierText.alpha = 0; game.addChild(multiplierText); tween(multiplierText, { alpha: 1, y: y - 100, scaleX: 1.5, scaleY: 1.5 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { tween(multiplierText, { alpha: 0 }, { duration: 400, onFinish: function onFinish() { multiplierText.destroy(); } }); } }); } // Create time effect function createTimeEffect(x, y) { var timeText = new Text2('TIME SLOW!', { size: 70, fill: 0x00FF7F }); timeText.anchor.set(0.5, 0.5); timeText.x = x; timeText.y = y; timeText.alpha = 0; game.addChild(timeText); tween(timeText, { alpha: 1, y: y - 80, scaleX: 1.3, scaleY: 1.3 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { tween(timeText, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { timeText.destroy(); } }); } }); } // Handle explosive bubble chain reaction function handleExplosiveBubble(x, y) { var explosionRadius = 200; for (var i = bubbles.length - 1; i >= 0; i--) { var bubble = bubbles[i]; var distance = Math.sqrt(Math.pow(x - bubble.x, 2) + Math.pow(y - bubble.y, 2)); if (distance <= explosionRadius) { onBubblePopped(bubble, true); // Count as perfect hits } } } // Activate score multiplier power-up function activateScoreMultiplier() { scoreMultiplier = 2; multiplierTimer = 300; // 5 seconds at 60fps LK.effects.flashScreen(0x9932CC, 300); } // Activate time slowdown power-up function activateTimeSlowdown() { timeSlowActive = true; timeSlowTimer = 480; // 8 seconds at 60fps LK.effects.flashScreen(0x00FF7F, 400); } // Create beat ring effect function createBeatRing() { var ring = new BeatRing(); ring.x = 2048 / 2; ring.y = 2732 / 2; beatRings.push(ring); game.addChild(ring); ring.animate(); } // GTA 2 style city background function createCityBackground() { // Create grid pattern like GTA 2 city streets for (var x = 0; x < 2048; x += 256) { for (var y = 0; y < 2732; y += 256) { // Street lines var streetH = LK.getAsset('particle', { width: 256, height: 4, anchorX: 0, anchorY: 0, alpha: 0.2 }); streetH.x = x; streetH.y = y; streetH.tint = 0x666666; game.addChild(streetH); var streetV = LK.getAsset('particle', { width: 4, height: 256, anchorX: 0, anchorY: 0, alpha: 0.2 }); streetV.x = x; streetV.y = y; streetV.tint = 0x666666; game.addChild(streetV); } } // Add some building-like rectangles for (var i = 0; i < 20; i++) { var building = LK.getAsset('particle', { width: 60 + Math.random() * 100, height: 60 + Math.random() * 100, anchorX: 0, anchorY: 0, alpha: 0.1 }); building.x = Math.random() * 1900; building.y = Math.random() * 2600; building.tint = 0x444444; game.addChild(building); } } // Enhanced background effects function updateBackgroundEffects() { // Add subtle city ambiance effects if (LK.ticks % 180 === 0) { // Create occasional street lamp glow var glow = LK.getAsset('particle', { width: 20, height: 20, anchorX: 0.5, anchorY: 0.5, alpha: 0.5 }); glow.x = Math.random() * 2048; glow.y = Math.random() * 2732; glow.tint = 0xFFFF88; game.addChild(glow); tween(glow, { alpha: 0, scaleX: 3, scaleY: 3 }, { duration: 3000, easing: tween.easeOut, onFinish: function onFinish() { glow.destroy(); } }); } } // GTA 2 style start screen UI var titleText = new Text2('BUBBLE CITY', { size: 160, fill: 0xFFFF00 }); titleText.anchor.set(0.5, 0.5); titleText.y = -300; LK.gui.center.addChild(titleText); var subtitleText = new Text2('CRIME BEATS EDITION', { size: 60, fill: 0x00FF00 }); subtitleText.anchor.set(0.5, 0.5); subtitleText.y = -200; LK.gui.center.addChild(subtitleText); var instructionsText = new Text2('Pop bubbles in rhythm with the beats!\nTap to start your criminal career', { size: 50, fill: 0xFFFFFF }); instructionsText.anchor.set(0.5, 0.5); instructionsText.y = 100; LK.gui.center.addChild(instructionsText); // GTA 2 style leaderboard display var leaderboardTitle = new Text2('TOP CRIMINALS', { size: 80, fill: 0xFF4444 }); leaderboardTitle.anchor.set(0.5, 0.5); leaderboardTitle.y = 200; LK.gui.center.addChild(leaderboardTitle); var leaderboardText = new Text2('', { size: 50, fill: 0x00FFFF }); leaderboardText.anchor.set(0.5, 0); leaderboardText.y = 280; LK.gui.center.addChild(leaderboardText); var startBestScoreText = new Text2('Your Best: ' + bestScore, { size: 60, fill: 0x4ECDC4 }); startBestScoreText.anchor.set(0.5, 0.5); startBestScoreText.y = 550; LK.gui.center.addChild(startBestScoreText); // Function to update leaderboard display function updateLeaderboardDisplay() { var displayText = ''; for (var i = 0; i < Math.min(5, leaderboard.length); i++) { var entry = leaderboard[i]; displayText += i + 1 + '. ' + entry.name + ' - ' + entry.score + '\n'; } if (leaderboard.length === 0) { displayText = 'No scores yet!\nBe the first criminal!'; } leaderboardText.setText(displayText); } // Game UI (initially hidden) var scoreText = new Text2('Score: 0', { size: 80, fill: 0xFFFFFF }); scoreText.anchor.set(0.5, 0); scoreText.visible = false; LK.gui.top.addChild(scoreText); var comboText = new Text2('Combo: 0', { size: 60, fill: 0xFFD700 }); comboText.anchor.set(0, 0); comboText.x = 50; comboText.y = 120; comboText.visible = false; LK.gui.top.addChild(comboText); var healthText = new Text2('♥♥♥♥♥', { size: 70, fill: 0xFF4444 }); healthText.anchor.set(1, 0); healthText.visible = false; // Add click handler for easter egg healthText.down = function (x, y, obj) { if (gameState === 'playing') { heartClickCount++; // Visual feedback for click tween(healthText, { scaleX: 1.2, scaleY: 1.2 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(healthText, { scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.easeOut }); } }); // Easter egg trigger at 10 clicks if (heartClickCount >= 10) { heartClickCount = 0; // Reset counter score += 31000; // Award 31,000 points // Create spectacular visual effect LK.effects.flashScreen(0xFFD700, 1000); // Create multiple particle explosions for (var i = 0; i < 5; i++) { var explosionX = Math.random() * 2048; var explosionY = Math.random() * 2732; createParticleExplosion(explosionX, explosionY, 0xFFD700, true); } // Show special text indicator var easterEggText = new Text2('EASTER EGG!\n+31,000 POINTS!', { size: 120, fill: 0xFFD700 }); easterEggText.anchor.set(0.5, 0.5); easterEggText.x = 1024; easterEggText.y = 1366; easterEggText.alpha = 0; easterEggText.scaleX = 0; easterEggText.scaleY = 0; game.addChild(easterEggText); // Animate the easter egg text tween(easterEggText, { alpha: 1, scaleX: 1.5, scaleY: 1.5 }, { duration: 500, easing: tween.bounceOut, onFinish: function onFinish() { tween(easterEggText, { alpha: 0, scaleX: 0.5, scaleY: 0.5, y: easterEggText.y - 200 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { easterEggText.destroy(); } }); } }); updateUI(); } } }; LK.gui.topRight.addChild(healthText); var bestScoreText = new Text2('Best: ' + bestScore, { size: 50, fill: 0xFFFFFF }); bestScoreText.anchor.set(0, 0); bestScoreText.x = 50; bestScoreText.y = 200; bestScoreText.visible = false; LK.gui.top.addChild(bestScoreText); // Add score to leaderboard function addToLeaderboard(playerScore) { var newEntry = { name: playerName, score: playerScore }; leaderboard.push(newEntry); // Sort by score descending leaderboard.sort(function (a, b) { return b.score - a.score; }); // Keep only top 10 leaderboard = leaderboard.slice(0, 10); // Save to storage using flattened arrays leaderboardNames = []; leaderboardScores = []; for (var i = 0; i < leaderboard.length; i++) { leaderboardNames.push(leaderboard[i].name); leaderboardScores.push(leaderboard[i].score); } storage.leaderboardNames = leaderboardNames; storage.leaderboardScores = leaderboardScores; } function startGame() { gameState = 'playing'; // Create GTA 2 style city background createCityBackground(); // Hide start screen UI titleText.visible = false; subtitleText.visible = false; instructionsText.visible = false; startBestScoreText.visible = false; leaderboardTitle.visible = false; leaderboardText.visible = false; // Show game UI scoreText.visible = true; comboText.visible = true; healthText.visible = true; bestScoreText.visible = true; // Spawn scorpion scorpion = new Scorpion(); scorpion.x = Math.random() * 1800 + 124; scorpion.y = Math.random() * 2400 + 166; game.addChild(scorpion); } // Touch handler for starting game game.down = function (x, y, obj) { if (gameState === 'start') { startGame(); } }; function updateUI() { var displayScore = 'Score: ' + score; if (scoreMultiplier > 1) { displayScore += ' (x' + scoreMultiplier + ')'; } scoreText.setText(displayScore); var displayCombo = 'Combo: ' + combo; if (timeSlowActive) { displayCombo += ' [TIME SLOW]'; } comboText.setText(displayCombo); var hearts = ''; for (var i = 0; i < health; i++) { hearts += '♥'; } healthText.setText(hearts); // Update best score if current score is higher if (score > bestScore) { bestScore = score; storage.bestScore = bestScore; bestScoreText.setText('Best: ' + bestScore); } } function spawnBubble() { var isGolden = Math.random() < 0.15; // 15% chance for golden bubble var specialType = null; // 10% chance for special bubbles (only if not golden) if (!isGolden && Math.random() < 0.1) { var specialTypes = ['explosive', 'multiplier', 'time']; specialType = specialTypes[Math.floor(Math.random() * specialTypes.length)]; } var color = bubbleColors[Math.floor(Math.random() * bubbleColors.length)]; var bubble = new Bubble(color, isGolden, specialType); // Better spawn positioning with grid-like distribution var attempts = 0; var maxAttempts = 20; do { bubble.x = Math.random() * (2048 - 300) + 150; bubble.y = Math.random() * (2732 - 600) + 300; attempts++; } while (isTooCloseToOthers(bubble) && attempts < maxAttempts); if (attempts < maxAttempts) { bubbles.push(bubble); game.addChild(bubble); bubble.spawned = true; bubble.lastPulseTime = LK.ticks; // Enhanced spawn animation with rotation and bounce bubble.alpha = 0; bubble.scaleX = 0; bubble.scaleY = 0; bubble.rotation = Math.PI * 2; bubble.y -= 100; // Start above final position tween(bubble, { alpha: 1, scaleX: 1.2, scaleY: 1.2, rotation: 0, y: bubble.y + 100 }, { duration: 500, easing: tween.bounceOut, onFinish: function onFinish() { tween(bubble, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeOut }); bubble.startFloat(); // Start floating animation } }); // Add sparkle effect for golden bubbles if (isGolden) { createSparkleEffect(bubble); } } else { bubble.destroy(); } } function isTooCloseToOthers(bubble) { for (var i = 0; i < bubbles.length; i++) { var distance = Math.sqrt(Math.pow(bubble.x - bubbles[i].x, 2) + Math.pow(bubble.y - bubbles[i].y, 2)); if (distance < 200) { return true; } } return false; } function createSparkleEffect(bubble) { var sparkleTimer = LK.setInterval(function () { if (bubble.destroyed) { LK.clearInterval(sparkleTimer); return; } var sparkle = new Particle(0xFFD700, 0.3); sparkle.x = bubble.x + (Math.random() - 0.5) * 100; sparkle.y = bubble.y + (Math.random() - 0.5) * 100; sparkle.velocity.x = (Math.random() - 0.5) * 1; sparkle.velocity.y = (Math.random() - 0.5) * 1; sparkle.gravity = -0.05; // Float upward more slowly particles.push(sparkle); game.addChild(sparkle); }, 400); } function onBubblePopped(bubble, isPerfect) { var basePoints = bubble.isGolden ? 100 : 10; // Special bubble bonuses if (bubble.specialType === 'explosive') { basePoints = 50; } else if (bubble.specialType === 'multiplier') { basePoints = 25; } else if (bubble.specialType === 'time') { basePoints = 30; } var points = isPerfect ? basePoints * 2 : basePoints; if (isPerfect) { combo++; points *= 1 + combo * 0.1; // Show perfect indicator var indicator = new PerfectIndicator(); perfectIndicators.push(indicator); game.addChild(indicator); indicator.show(bubble.x, bubble.y); if (bubble.isGolden) { LK.effects.flashScreen(0xFFD700, 300); } } else { combo = 0; } // Apply score multiplier if active points *= scoreMultiplier; score += Math.floor(points); // Remove bubble from array for (var i = 0; i < bubbles.length; i++) { if (bubbles[i] === bubble) { bubbles.splice(i, 1); break; } } bubble.pop(); updateUI(); } function missedBeat() { combo = 0; health--; LK.effects.flashScreen(0xFF0000, 200); LK.getSound('miss').play(); if (health <= 0) { // Add score to leaderboard before game over addToLeaderboard(score); LK.showGameOver(); } updateUI(); } // Start background music LK.playMusic('bgMusic'); game.update = function () { if (gameState === 'start') { // Update best score display on start screen startBestScoreText.setText('Your Best: ' + bestScore); // Update leaderboard display updateLeaderboardDisplay(); // GTA 2 style animations titleText.rotation = Math.sin(LK.ticks * 0.01) * 0.1; subtitleText.alpha = 0.7 + Math.sin(LK.ticks * 0.05) * 0.3; leaderboardTitle.scaleX = 1 + Math.sin(LK.ticks * 0.03) * 0.1; leaderboardTitle.scaleY = 1 + Math.sin(LK.ticks * 0.03) * 0.1; return; } beatTimer++; spawnTimer++; updateBackgroundEffects(); // Update power-up timers if (multiplierTimer > 0) { multiplierTimer--; if (multiplierTimer <= 0) { scoreMultiplier = 1; } } if (timeSlowTimer > 0) { timeSlowTimer--; if (timeSlowTimer <= 0) { timeSlowActive = false; } } // Apply time slow effect to spawn rate var timeMultiplier = timeSlowActive ? 1.5 : 1; // Enhanced beat visualization if (beatTimer % 60 === 0) { // Every second - create beat ring and pulse bubbles createBeatRing(); for (var i = 0; i < bubbles.length; i++) { bubbles[i].startPulse(); } } // Update particles for (var p = particles.length - 1; p >= 0; p--) { var particle = particles[p]; if (particle.destroyed) { particles.splice(p, 1); } } // Clean up beat rings for (var r = beatRings.length - 1; r >= 0; r--) { if (beatRings[r].destroyed) { beatRings.splice(r, 1); } } // Check if 6 bubbles are on screen - game over condition (increased from 5) if (bubbles.length >= 6) { // Update best score before game over if (score > bestScore) { bestScore = score; storage.bestScore = bestScore; } // Add score to leaderboard addToLeaderboard(score); // Screen shake effect before game over LK.effects.flashScreen(0xFF0000, 500); LK.showGameOver(); } // Dynamic spawn rate based on score and combo var baseSpawnRate = Math.max(150 - Math.floor(score / 50) * 5, 80); var comboBonus = Math.min(combo * 5, 30); var spawnRate = (baseSpawnRate - comboBonus) * timeMultiplier; if (spawnTimer >= spawnRate && bubbles.length < 10) { spawnBubble(); spawnTimer = 0; } // Remove bubbles that have been on screen too long for (var i = bubbles.length - 1; i >= 0; i--) { var bubble = bubbles[i]; // Slightly longer lifetime for better gameplay if (bubble.spawned && LK.ticks - bubble.lastPulseTime > 1500) { // Warning effect before removal tween(bubble, { alpha: 0.3, scaleX: 0.8, scaleY: 0.8 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { if (!bubble.destroyed) { bubble.destroy(); bubbles.splice(i, 1); missedBeat(); } } }); } } // Clean up perfect indicators for (var j = perfectIndicators.length - 1; j >= 0; j--) { if (perfectIndicators[j].destroyed) { perfectIndicators.splice(j, 1); } } // Update scorpion if (scorpion && !scorpion.destroyed) { // Scorpion updates automatically via its update method } // Increase difficulty gameSpeed = 1 + score / 1000; // Win condition if (score >= 3000) { // Add score to leaderboard before showing win addToLeaderboard(score); LK.showYouWin(); } }; // Initialize UI updateUI();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var BeatRing = Container.expand(function () {
var self = Container.call(this);
var ringGraphics = self.attachAsset('beatRing', {
anchorX: 0.5,
anchorY: 0.5
});
ringGraphics.alpha = 0.3;
ringGraphics.scaleX = 0.1;
ringGraphics.scaleY = 0.1;
self.animate = function () {
tween(ringGraphics, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
var Bubble = Container.expand(function (color, isGolden, specialType) {
var self = Container.call(this);
var bubbleAsset = 'bubble';
if (isGolden) {
bubbleAsset = 'goldenBubble';
} else if (specialType === 'explosive') {
bubbleAsset = 'explosiveBubble';
} else if (specialType === 'multiplier') {
bubbleAsset = 'multiplierBubble';
} else if (specialType === 'time') {
bubbleAsset = 'timeBubble';
}
var bubbleGraphics = self.attachAsset(bubbleAsset, {
anchorX: 0.5,
anchorY: 0.5
});
if (!isGolden && !specialType) {
bubbleGraphics.tint = color;
}
self.isGolden = isGolden || false;
self.specialType = specialType || null;
self.color = color;
self.noteIndex = Math.floor(Math.random() * 4);
self.spawned = false;
self.pulseTween = null;
self.lastPulseTime = 0;
self.floatTween = null;
// Add floating motion
self.startFloat = function () {
if (self.floatTween) {
tween.stop(self, {
y: true
});
}
var floatAmount = 20 + Math.random() * 30;
var floatDuration = 2000 + Math.random() * 1000;
self.floatTween = tween(self, {
y: self.y - floatAmount
}, {
duration: floatDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self, {
y: self.y + floatAmount
}, {
duration: floatDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!self.destroyed) {
self.startFloat();
}
}
});
}
});
};
// Enhanced pulsing effect with color changes
self.startPulse = function () {
self.lastPulseTime = LK.ticks;
if (self.pulseTween) {
tween.stop(bubbleGraphics, {
scaleX: true,
scaleY: true
});
}
// Add glow effect on pulse
var originalTint = bubbleGraphics.tint;
tween(bubbleGraphics, {
tint: 0xFFFFFF
}, {
duration: 150,
onFinish: function onFinish() {
tween(bubbleGraphics, {
tint: originalTint
}, {
duration: 150
});
}
});
self.pulseTween = tween(bubbleGraphics, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(bubbleGraphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 400,
easing: tween.bounceOut
});
}
});
};
self.pop = function () {
var popSounds = ['pop1', 'pop2', 'pop3', 'pop4'];
var soundToPlay = 'miss'; // Default sound
if (self.isGolden) {
soundToPlay = 'miss'; // Use available sound
} else if (self.specialType === 'explosive') {
soundToPlay = 'explosion';
} else if (self.specialType === 'multiplier') {
soundToPlay = 'multiplier';
} else if (self.specialType === 'time') {
soundToPlay = 'powerup';
} else {
soundToPlay = 'miss';
}
LK.getSound(soundToPlay).play();
// Special effects based on bubble type
if (self.specialType === 'explosive') {
// Create massive explosion effect
createExplosiveEffect(self.x, self.y);
} else if (self.specialType === 'multiplier') {
// Create sparkle multiplier effect
createMultiplierEffect(self.x, self.y);
} else if (self.specialType === 'time') {
// Create time freeze effect
createTimeEffect(self.x, self.y);
}
// Create particle explosion
createParticleExplosion(self.x, self.y, self.color, self.isGolden);
// Enhanced pop animation
tween(bubbleGraphics, {
scaleX: 2.0,
scaleY: 2.0,
alpha: 0,
rotation: Math.PI * 2
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
}
});
};
self.down = function (x, y, obj) {
var currentTime = LK.ticks;
var beatInterval = 60;
var beatPosition = currentTime % beatInterval;
var timingWindow = 12; // Slightly larger timing window
var isPerfectTiming = beatPosition <= timingWindow || beatPosition >= beatInterval - timingWindow;
// Visual feedback on touch
tween(bubbleGraphics, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(bubbleGraphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut
});
}
});
// Handle special bubble effects
if (self.specialType === 'explosive') {
// Explosive bubbles destroy nearby bubbles
handleExplosiveBubble(self.x, self.y);
} else if (self.specialType === 'multiplier') {
// Multiplier bubbles double next few scores
activateScoreMultiplier();
} else if (self.specialType === 'time') {
// Time bubbles slow down game temporarily
activateTimeSlowdown();
}
if (isPerfectTiming) {
onBubblePopped(self, true);
} else {
onBubblePopped(self, false);
}
};
return self;
});
var Particle = Container.expand(function (color, size) {
var self = Container.call(this);
var particleGraphics = self.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5
});
particleGraphics.tint = color || 0xFFFFFF;
particleGraphics.scaleX = size || 1;
particleGraphics.scaleY = size || 1;
self.velocity = {
x: 0,
y: 0
};
self.gravity = 0.2;
self.life = 60; // 1 second at 60fps
self.update = function () {
self.x += self.velocity.x;
self.y += self.velocity.y;
self.velocity.y += self.gravity;
self.life--;
// Fade out over time
particleGraphics.alpha = self.life / 60;
if (self.life <= 0) {
self.destroy();
}
};
return self;
});
var PerfectIndicator = Container.expand(function () {
var self = Container.call(this);
var indicator = self.attachAsset('perfectIndicator', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
self.show = function (x, y) {
self.x = x;
self.y = y;
indicator.alpha = 1;
indicator.scaleX = 0.5;
indicator.scaleY = 0.5;
tween(indicator, {
scaleX: 1,
scaleY: 1,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
var Scorpion = Container.expand(function () {
var self = Container.call(this);
// Create scorpion body
var scorpionBody = self.attachAsset('scorpionBody', {
anchorX: 0.5,
anchorY: 0.5
});
// Create left claw
var leftClaw = self.attachAsset('scorpionClaw', {
anchorX: 1.0,
anchorY: 0.5
});
leftClaw.x = -25;
leftClaw.y = -10;
leftClaw.rotation = -0.3;
// Create right claw
var rightClaw = self.attachAsset('scorpionClaw', {
anchorX: 1.0,
anchorY: 0.5
});
rightClaw.x = -25;
rightClaw.y = 10;
rightClaw.rotation = 0.3;
// Create tail segments
var tailSegments = [];
for (var i = 0; i < 4; i++) {
var segment = self.attachAsset('scorpionTail', {
anchorX: 0.0,
anchorY: 0.5
});
segment.x = 40 + i * 12;
segment.y = 0;
segment.rotation = i * 0.1;
segment.scaleX = 0.8 - i * 0.1;
tailSegments.push(segment);
}
// Create stinger at the end
var stinger = self.attachAsset('scorpionStinger', {
anchorX: 0.5,
anchorY: 0.5
});
stinger.x = 88;
stinger.y = -5;
// Store references for animations
self.body = scorpionBody;
self.leftClaw = leftClaw;
self.rightClaw = rightClaw;
self.tailSegments = tailSegments;
self.stinger = stinger;
self.animationTimer = 0;
self.speed = 1.5; // Moderate speed movement
self.targetBubble = null;
self.lastTargetTime = 0;
// Find nearest bubble to target
self.findTarget = function () {
var nearestBubble = null;
var nearestDistance = Infinity;
for (var i = 0; i < bubbles.length; i++) {
var bubble = bubbles[i];
var distance = Math.sqrt(Math.pow(self.x - bubble.x, 2) + Math.pow(self.y - bubble.y, 2));
if (distance < nearestDistance) {
nearestDistance = distance;
nearestBubble = bubble;
}
}
return nearestBubble;
};
self.update = function () {
self.animationTimer++;
// Animate tail swishing
for (var i = 0; i < self.tailSegments.length; i++) {
var segment = self.tailSegments[i];
segment.rotation = Math.sin(self.animationTimer * 0.1 + i * 0.5) * 0.2 + i * 0.1;
}
// Animate stinger bobbing with pulsing glow
self.stinger.y = -5 + Math.sin(self.animationTimer * 0.15) * 3;
// Add dangerous pulsing glow to stinger
var stingerPulse = Math.sin(self.animationTimer * 0.2) * 0.5 + 0.5;
var stingerGlow = 0x440000 + Math.floor(stingerPulse * 0xbb0000);
self.stinger.tint = stingerGlow;
self.stinger.scaleX = 1 + stingerPulse * 0.3;
self.stinger.scaleY = 1 + stingerPulse * 0.3;
// Animate claws opening and closing with shimmer effect
var clawAnimation = Math.sin(self.animationTimer * 0.08) * 0.2;
self.leftClaw.rotation = -0.3 + clawAnimation;
self.rightClaw.rotation = 0.3 - clawAnimation;
// Add shimmer effect to claws
var shimmer = Math.sin(self.animationTimer * 0.15) * 0.3 + 0.7;
self.leftClaw.alpha = shimmer;
self.rightClaw.alpha = shimmer;
var clawGlow = 0x444444 + Math.floor(shimmer * 0x888888);
self.leftClaw.tint = clawGlow;
self.rightClaw.tint = clawGlow;
// Body bobbing while walking
self.body.y = Math.sin(self.animationTimer * 0.12) * 2;
// Beautiful rainbow color shifting effect
var hue = self.animationTimer * 2 % 360;
var rainbowColor = 0x8b4513; // Keep brown as base
if (hue < 60) {
rainbowColor = 0xff4500 + Math.floor(hue / 60 * 0x4000);
} else if (hue < 120) {
rainbowColor = 0xff8500 + Math.floor((hue - 60) / 60 * 0x4000);
} else if (hue < 180) {
rainbowColor = 0xffc500 + Math.floor((hue - 120) / 60 * 0x2000);
} else if (hue < 240) {
rainbowColor = 0xffe500 - Math.floor((hue - 180) / 60 * 0x6000);
} else if (hue < 300) {
rainbowColor = 0x9fe500 - Math.floor((hue - 240) / 60 * 0x4000);
} else {
rainbowColor = 0x5fe500 + Math.floor((hue - 300) / 60 * 0x6000);
}
self.body.tint = rainbowColor;
// Find new target every 2 seconds or if current target is destroyed
if (LK.ticks - self.lastTargetTime > 120 || !self.targetBubble || self.targetBubble.destroyed) {
self.targetBubble = self.findTarget();
self.lastTargetTime = LK.ticks;
}
// Move towards target bubble
if (self.targetBubble && !self.targetBubble.destroyed) {
var dx = self.targetBubble.x - self.x;
var dy = self.targetBubble.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
// Move towards bubble
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
// Rotate entire scorpion towards target
self.rotation = Math.atan2(dy, dx);
// Faster tail animation when moving
for (var j = 0; j < self.tailSegments.length; j++) {
var seg = self.tailSegments[j];
seg.rotation = Math.sin(self.animationTimer * 0.2 + j * 0.5) * 0.3 + j * 0.15;
}
} else {
// Close enough to pop the bubble
if (self.targetBubble && !self.targetBubble.destroyed) {
// Attack animation - claws snap
self.leftClaw.rotation = -0.8;
self.rightClaw.rotation = 0.8;
// Scorpion pops the bubble (counts as missed for player)
for (var i = 0; i < bubbles.length; i++) {
if (bubbles[i] === self.targetBubble) {
bubbles.splice(i, 1);
break;
}
}
self.targetBubble.pop();
missedBeat(); // Player loses health
self.targetBubble = null;
}
}
}
// Keep scorpion within screen bounds
if (self.x < 50) self.x = 50;
if (self.x > 1998) self.x = 1998;
if (self.y < 50) self.y = 50;
if (self.y > 2682) self.y = 2682;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
var gameState = 'start'; // 'start' or 'playing'
var bubbles = [];
var particles = [];
var beatRings = [];
var score = 0;
var combo = 0;
var health = 5;
var gameSpeed = 1;
var spawnTimer = 0;
var beatTimer = 0;
var perfectIndicators = [];
var bubbleColors = [0x4A90E2, 0xFF6B6B, 0x4ECDC4, 0xFFE66D, 0x95E1D3, 0xFF69B4, 0x32CD32, 0xFFA500];
var bestScore = storage.bestScore || 0;
var scorpion = null;
var heartClickCount = 0; // Track clicks on heart bars for easter egg
// New power-up variables
var scoreMultiplier = 1;
var multiplierTimer = 0;
var timeSlowActive = false;
var timeSlowTimer = 0;
var specialEffects = [];
// GTA 2 style leaderboard data - flattened for storage compatibility
var leaderboardNames = storage.leaderboardNames || [];
var leaderboardScores = storage.leaderboardScores || [];
var playerName = storage.playerName || 'Player';
var showLeaderboard = false;
// Build leaderboard from flattened arrays
var leaderboard = [];
for (var idx = 0; idx < leaderboardNames.length; idx++) {
leaderboard.push({
name: leaderboardNames[idx],
score: leaderboardScores[idx]
});
}
// Particle explosion function
function createParticleExplosion(x, y, color, isGolden) {
var particleCount = isGolden ? 15 : 8;
var particleColors = isGolden ? [0xFFD700, 0xFFA500, 0xFFFF00] : [color, 0xFFFFFF];
for (var i = 0; i < particleCount; i++) {
var particle = new Particle(particleColors[Math.floor(Math.random() * particleColors.length)], 0.5 + Math.random() * 0.5);
particle.x = x + (Math.random() - 0.5) * 40;
particle.y = y + (Math.random() - 0.5) * 40;
particle.velocity.x = (Math.random() - 0.5) * 8;
particle.velocity.y = (Math.random() - 0.5) * 8 - 2;
particles.push(particle);
game.addChild(particle);
}
}
// Create explosive effect for explosive bubbles
function createExplosiveEffect(x, y) {
// Create shockwave
var shockwave = LK.getAsset('beatRing', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8,
scaleX: 0.1,
scaleY: 0.1
});
shockwave.x = x;
shockwave.y = y;
shockwave.tint = 0xFF4444;
game.addChild(shockwave);
tween(shockwave, {
scaleX: 4,
scaleY: 4,
alpha: 0
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
shockwave.destroy();
}
});
// Create extra particles
for (var i = 0; i < 25; i++) {
var particle = new Particle(0xFF4444, 1 + Math.random());
particle.x = x + (Math.random() - 0.5) * 100;
particle.y = y + (Math.random() - 0.5) * 100;
particle.velocity.x = (Math.random() - 0.5) * 15;
particle.velocity.y = (Math.random() - 0.5) * 15 - 5;
particles.push(particle);
game.addChild(particle);
}
}
// Create multiplier effect
function createMultiplierEffect(x, y) {
var multiplierText = new Text2('x2 SCORE!', {
size: 80,
fill: 0x9932CC
});
multiplierText.anchor.set(0.5, 0.5);
multiplierText.x = x;
multiplierText.y = y;
multiplierText.alpha = 0;
game.addChild(multiplierText);
tween(multiplierText, {
alpha: 1,
y: y - 100,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(multiplierText, {
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
multiplierText.destroy();
}
});
}
});
}
// Create time effect
function createTimeEffect(x, y) {
var timeText = new Text2('TIME SLOW!', {
size: 70,
fill: 0x00FF7F
});
timeText.anchor.set(0.5, 0.5);
timeText.x = x;
timeText.y = y;
timeText.alpha = 0;
game.addChild(timeText);
tween(timeText, {
alpha: 1,
y: y - 80,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(timeText, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
timeText.destroy();
}
});
}
});
}
// Handle explosive bubble chain reaction
function handleExplosiveBubble(x, y) {
var explosionRadius = 200;
for (var i = bubbles.length - 1; i >= 0; i--) {
var bubble = bubbles[i];
var distance = Math.sqrt(Math.pow(x - bubble.x, 2) + Math.pow(y - bubble.y, 2));
if (distance <= explosionRadius) {
onBubblePopped(bubble, true); // Count as perfect hits
}
}
}
// Activate score multiplier power-up
function activateScoreMultiplier() {
scoreMultiplier = 2;
multiplierTimer = 300; // 5 seconds at 60fps
LK.effects.flashScreen(0x9932CC, 300);
}
// Activate time slowdown power-up
function activateTimeSlowdown() {
timeSlowActive = true;
timeSlowTimer = 480; // 8 seconds at 60fps
LK.effects.flashScreen(0x00FF7F, 400);
}
// Create beat ring effect
function createBeatRing() {
var ring = new BeatRing();
ring.x = 2048 / 2;
ring.y = 2732 / 2;
beatRings.push(ring);
game.addChild(ring);
ring.animate();
}
// GTA 2 style city background
function createCityBackground() {
// Create grid pattern like GTA 2 city streets
for (var x = 0; x < 2048; x += 256) {
for (var y = 0; y < 2732; y += 256) {
// Street lines
var streetH = LK.getAsset('particle', {
width: 256,
height: 4,
anchorX: 0,
anchorY: 0,
alpha: 0.2
});
streetH.x = x;
streetH.y = y;
streetH.tint = 0x666666;
game.addChild(streetH);
var streetV = LK.getAsset('particle', {
width: 4,
height: 256,
anchorX: 0,
anchorY: 0,
alpha: 0.2
});
streetV.x = x;
streetV.y = y;
streetV.tint = 0x666666;
game.addChild(streetV);
}
}
// Add some building-like rectangles
for (var i = 0; i < 20; i++) {
var building = LK.getAsset('particle', {
width: 60 + Math.random() * 100,
height: 60 + Math.random() * 100,
anchorX: 0,
anchorY: 0,
alpha: 0.1
});
building.x = Math.random() * 1900;
building.y = Math.random() * 2600;
building.tint = 0x444444;
game.addChild(building);
}
}
// Enhanced background effects
function updateBackgroundEffects() {
// Add subtle city ambiance effects
if (LK.ticks % 180 === 0) {
// Create occasional street lamp glow
var glow = LK.getAsset('particle', {
width: 20,
height: 20,
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.5
});
glow.x = Math.random() * 2048;
glow.y = Math.random() * 2732;
glow.tint = 0xFFFF88;
game.addChild(glow);
tween(glow, {
alpha: 0,
scaleX: 3,
scaleY: 3
}, {
duration: 3000,
easing: tween.easeOut,
onFinish: function onFinish() {
glow.destroy();
}
});
}
}
// GTA 2 style start screen UI
var titleText = new Text2('BUBBLE CITY', {
size: 160,
fill: 0xFFFF00
});
titleText.anchor.set(0.5, 0.5);
titleText.y = -300;
LK.gui.center.addChild(titleText);
var subtitleText = new Text2('CRIME BEATS EDITION', {
size: 60,
fill: 0x00FF00
});
subtitleText.anchor.set(0.5, 0.5);
subtitleText.y = -200;
LK.gui.center.addChild(subtitleText);
var instructionsText = new Text2('Pop bubbles in rhythm with the beats!\nTap to start your criminal career', {
size: 50,
fill: 0xFFFFFF
});
instructionsText.anchor.set(0.5, 0.5);
instructionsText.y = 100;
LK.gui.center.addChild(instructionsText);
// GTA 2 style leaderboard display
var leaderboardTitle = new Text2('TOP CRIMINALS', {
size: 80,
fill: 0xFF4444
});
leaderboardTitle.anchor.set(0.5, 0.5);
leaderboardTitle.y = 200;
LK.gui.center.addChild(leaderboardTitle);
var leaderboardText = new Text2('', {
size: 50,
fill: 0x00FFFF
});
leaderboardText.anchor.set(0.5, 0);
leaderboardText.y = 280;
LK.gui.center.addChild(leaderboardText);
var startBestScoreText = new Text2('Your Best: ' + bestScore, {
size: 60,
fill: 0x4ECDC4
});
startBestScoreText.anchor.set(0.5, 0.5);
startBestScoreText.y = 550;
LK.gui.center.addChild(startBestScoreText);
// Function to update leaderboard display
function updateLeaderboardDisplay() {
var displayText = '';
for (var i = 0; i < Math.min(5, leaderboard.length); i++) {
var entry = leaderboard[i];
displayText += i + 1 + '. ' + entry.name + ' - ' + entry.score + '\n';
}
if (leaderboard.length === 0) {
displayText = 'No scores yet!\nBe the first criminal!';
}
leaderboardText.setText(displayText);
}
// Game UI (initially hidden)
var scoreText = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
scoreText.visible = false;
LK.gui.top.addChild(scoreText);
var comboText = new Text2('Combo: 0', {
size: 60,
fill: 0xFFD700
});
comboText.anchor.set(0, 0);
comboText.x = 50;
comboText.y = 120;
comboText.visible = false;
LK.gui.top.addChild(comboText);
var healthText = new Text2('♥♥♥♥♥', {
size: 70,
fill: 0xFF4444
});
healthText.anchor.set(1, 0);
healthText.visible = false;
// Add click handler for easter egg
healthText.down = function (x, y, obj) {
if (gameState === 'playing') {
heartClickCount++;
// Visual feedback for click
tween(healthText, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(healthText, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut
});
}
});
// Easter egg trigger at 10 clicks
if (heartClickCount >= 10) {
heartClickCount = 0; // Reset counter
score += 31000; // Award 31,000 points
// Create spectacular visual effect
LK.effects.flashScreen(0xFFD700, 1000);
// Create multiple particle explosions
for (var i = 0; i < 5; i++) {
var explosionX = Math.random() * 2048;
var explosionY = Math.random() * 2732;
createParticleExplosion(explosionX, explosionY, 0xFFD700, true);
}
// Show special text indicator
var easterEggText = new Text2('EASTER EGG!\n+31,000 POINTS!', {
size: 120,
fill: 0xFFD700
});
easterEggText.anchor.set(0.5, 0.5);
easterEggText.x = 1024;
easterEggText.y = 1366;
easterEggText.alpha = 0;
easterEggText.scaleX = 0;
easterEggText.scaleY = 0;
game.addChild(easterEggText);
// Animate the easter egg text
tween(easterEggText, {
alpha: 1,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 500,
easing: tween.bounceOut,
onFinish: function onFinish() {
tween(easterEggText, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5,
y: easterEggText.y - 200
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
easterEggText.destroy();
}
});
}
});
updateUI();
}
}
};
LK.gui.topRight.addChild(healthText);
var bestScoreText = new Text2('Best: ' + bestScore, {
size: 50,
fill: 0xFFFFFF
});
bestScoreText.anchor.set(0, 0);
bestScoreText.x = 50;
bestScoreText.y = 200;
bestScoreText.visible = false;
LK.gui.top.addChild(bestScoreText);
// Add score to leaderboard
function addToLeaderboard(playerScore) {
var newEntry = {
name: playerName,
score: playerScore
};
leaderboard.push(newEntry);
// Sort by score descending
leaderboard.sort(function (a, b) {
return b.score - a.score;
});
// Keep only top 10
leaderboard = leaderboard.slice(0, 10);
// Save to storage using flattened arrays
leaderboardNames = [];
leaderboardScores = [];
for (var i = 0; i < leaderboard.length; i++) {
leaderboardNames.push(leaderboard[i].name);
leaderboardScores.push(leaderboard[i].score);
}
storage.leaderboardNames = leaderboardNames;
storage.leaderboardScores = leaderboardScores;
}
function startGame() {
gameState = 'playing';
// Create GTA 2 style city background
createCityBackground();
// Hide start screen UI
titleText.visible = false;
subtitleText.visible = false;
instructionsText.visible = false;
startBestScoreText.visible = false;
leaderboardTitle.visible = false;
leaderboardText.visible = false;
// Show game UI
scoreText.visible = true;
comboText.visible = true;
healthText.visible = true;
bestScoreText.visible = true;
// Spawn scorpion
scorpion = new Scorpion();
scorpion.x = Math.random() * 1800 + 124;
scorpion.y = Math.random() * 2400 + 166;
game.addChild(scorpion);
}
// Touch handler for starting game
game.down = function (x, y, obj) {
if (gameState === 'start') {
startGame();
}
};
function updateUI() {
var displayScore = 'Score: ' + score;
if (scoreMultiplier > 1) {
displayScore += ' (x' + scoreMultiplier + ')';
}
scoreText.setText(displayScore);
var displayCombo = 'Combo: ' + combo;
if (timeSlowActive) {
displayCombo += ' [TIME SLOW]';
}
comboText.setText(displayCombo);
var hearts = '';
for (var i = 0; i < health; i++) {
hearts += '♥';
}
healthText.setText(hearts);
// Update best score if current score is higher
if (score > bestScore) {
bestScore = score;
storage.bestScore = bestScore;
bestScoreText.setText('Best: ' + bestScore);
}
}
function spawnBubble() {
var isGolden = Math.random() < 0.15; // 15% chance for golden bubble
var specialType = null;
// 10% chance for special bubbles (only if not golden)
if (!isGolden && Math.random() < 0.1) {
var specialTypes = ['explosive', 'multiplier', 'time'];
specialType = specialTypes[Math.floor(Math.random() * specialTypes.length)];
}
var color = bubbleColors[Math.floor(Math.random() * bubbleColors.length)];
var bubble = new Bubble(color, isGolden, specialType);
// Better spawn positioning with grid-like distribution
var attempts = 0;
var maxAttempts = 20;
do {
bubble.x = Math.random() * (2048 - 300) + 150;
bubble.y = Math.random() * (2732 - 600) + 300;
attempts++;
} while (isTooCloseToOthers(bubble) && attempts < maxAttempts);
if (attempts < maxAttempts) {
bubbles.push(bubble);
game.addChild(bubble);
bubble.spawned = true;
bubble.lastPulseTime = LK.ticks;
// Enhanced spawn animation with rotation and bounce
bubble.alpha = 0;
bubble.scaleX = 0;
bubble.scaleY = 0;
bubble.rotation = Math.PI * 2;
bubble.y -= 100; // Start above final position
tween(bubble, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2,
rotation: 0,
y: bubble.y + 100
}, {
duration: 500,
easing: tween.bounceOut,
onFinish: function onFinish() {
tween(bubble, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeOut
});
bubble.startFloat(); // Start floating animation
}
});
// Add sparkle effect for golden bubbles
if (isGolden) {
createSparkleEffect(bubble);
}
} else {
bubble.destroy();
}
}
function isTooCloseToOthers(bubble) {
for (var i = 0; i < bubbles.length; i++) {
var distance = Math.sqrt(Math.pow(bubble.x - bubbles[i].x, 2) + Math.pow(bubble.y - bubbles[i].y, 2));
if (distance < 200) {
return true;
}
}
return false;
}
function createSparkleEffect(bubble) {
var sparkleTimer = LK.setInterval(function () {
if (bubble.destroyed) {
LK.clearInterval(sparkleTimer);
return;
}
var sparkle = new Particle(0xFFD700, 0.3);
sparkle.x = bubble.x + (Math.random() - 0.5) * 100;
sparkle.y = bubble.y + (Math.random() - 0.5) * 100;
sparkle.velocity.x = (Math.random() - 0.5) * 1;
sparkle.velocity.y = (Math.random() - 0.5) * 1;
sparkle.gravity = -0.05; // Float upward more slowly
particles.push(sparkle);
game.addChild(sparkle);
}, 400);
}
function onBubblePopped(bubble, isPerfect) {
var basePoints = bubble.isGolden ? 100 : 10;
// Special bubble bonuses
if (bubble.specialType === 'explosive') {
basePoints = 50;
} else if (bubble.specialType === 'multiplier') {
basePoints = 25;
} else if (bubble.specialType === 'time') {
basePoints = 30;
}
var points = isPerfect ? basePoints * 2 : basePoints;
if (isPerfect) {
combo++;
points *= 1 + combo * 0.1;
// Show perfect indicator
var indicator = new PerfectIndicator();
perfectIndicators.push(indicator);
game.addChild(indicator);
indicator.show(bubble.x, bubble.y);
if (bubble.isGolden) {
LK.effects.flashScreen(0xFFD700, 300);
}
} else {
combo = 0;
}
// Apply score multiplier if active
points *= scoreMultiplier;
score += Math.floor(points);
// Remove bubble from array
for (var i = 0; i < bubbles.length; i++) {
if (bubbles[i] === bubble) {
bubbles.splice(i, 1);
break;
}
}
bubble.pop();
updateUI();
}
function missedBeat() {
combo = 0;
health--;
LK.effects.flashScreen(0xFF0000, 200);
LK.getSound('miss').play();
if (health <= 0) {
// Add score to leaderboard before game over
addToLeaderboard(score);
LK.showGameOver();
}
updateUI();
}
// Start background music
LK.playMusic('bgMusic');
game.update = function () {
if (gameState === 'start') {
// Update best score display on start screen
startBestScoreText.setText('Your Best: ' + bestScore);
// Update leaderboard display
updateLeaderboardDisplay();
// GTA 2 style animations
titleText.rotation = Math.sin(LK.ticks * 0.01) * 0.1;
subtitleText.alpha = 0.7 + Math.sin(LK.ticks * 0.05) * 0.3;
leaderboardTitle.scaleX = 1 + Math.sin(LK.ticks * 0.03) * 0.1;
leaderboardTitle.scaleY = 1 + Math.sin(LK.ticks * 0.03) * 0.1;
return;
}
beatTimer++;
spawnTimer++;
updateBackgroundEffects();
// Update power-up timers
if (multiplierTimer > 0) {
multiplierTimer--;
if (multiplierTimer <= 0) {
scoreMultiplier = 1;
}
}
if (timeSlowTimer > 0) {
timeSlowTimer--;
if (timeSlowTimer <= 0) {
timeSlowActive = false;
}
}
// Apply time slow effect to spawn rate
var timeMultiplier = timeSlowActive ? 1.5 : 1;
// Enhanced beat visualization
if (beatTimer % 60 === 0) {
// Every second - create beat ring and pulse bubbles
createBeatRing();
for (var i = 0; i < bubbles.length; i++) {
bubbles[i].startPulse();
}
}
// Update particles
for (var p = particles.length - 1; p >= 0; p--) {
var particle = particles[p];
if (particle.destroyed) {
particles.splice(p, 1);
}
}
// Clean up beat rings
for (var r = beatRings.length - 1; r >= 0; r--) {
if (beatRings[r].destroyed) {
beatRings.splice(r, 1);
}
}
// Check if 6 bubbles are on screen - game over condition (increased from 5)
if (bubbles.length >= 6) {
// Update best score before game over
if (score > bestScore) {
bestScore = score;
storage.bestScore = bestScore;
}
// Add score to leaderboard
addToLeaderboard(score);
// Screen shake effect before game over
LK.effects.flashScreen(0xFF0000, 500);
LK.showGameOver();
}
// Dynamic spawn rate based on score and combo
var baseSpawnRate = Math.max(150 - Math.floor(score / 50) * 5, 80);
var comboBonus = Math.min(combo * 5, 30);
var spawnRate = (baseSpawnRate - comboBonus) * timeMultiplier;
if (spawnTimer >= spawnRate && bubbles.length < 10) {
spawnBubble();
spawnTimer = 0;
}
// Remove bubbles that have been on screen too long
for (var i = bubbles.length - 1; i >= 0; i--) {
var bubble = bubbles[i];
// Slightly longer lifetime for better gameplay
if (bubble.spawned && LK.ticks - bubble.lastPulseTime > 1500) {
// Warning effect before removal
tween(bubble, {
alpha: 0.3,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
if (!bubble.destroyed) {
bubble.destroy();
bubbles.splice(i, 1);
missedBeat();
}
}
});
}
}
// Clean up perfect indicators
for (var j = perfectIndicators.length - 1; j >= 0; j--) {
if (perfectIndicators[j].destroyed) {
perfectIndicators.splice(j, 1);
}
}
// Update scorpion
if (scorpion && !scorpion.destroyed) {
// Scorpion updates automatically via its update method
}
// Increase difficulty
gameSpeed = 1 + score / 1000;
// Win condition
if (score >= 3000) {
// Add score to leaderboard before showing win
addToLeaderboard(score);
LK.showYouWin();
}
};
// Initialize UI
updateUI();