/****
* 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();