User prompt
şu sayaçları zamanı görev yazısını top sayısını iyice bi düzenle
User prompt
top sayısını 30dan başlat
User prompt
110 da fazla
User prompt
200 çok fazla azalt
User prompt
atılan top sayısını kısıtla seviye geçtikçe yenilensin
User prompt
Please fix the bug: 'gameLogo is not defined' in or related to this line: 'gameLogo.scale.set(logoScale, logoScale);' Line Number: 3550
User prompt
oyunun ana menüsüne oyuna giriş için bir logo ekle
User prompt
geri al
User prompt
toplar aşşağıya yaklaştıkça yüz ifadeleri değişsin
User prompt
Make the game fully optimized for Android mobile devices. Add touch controls and responsive UI.
User prompt
Add different power-ups for Bubble Clash, like bomb bubbles, color changer, slow time, and double coins. ↪💡 Consider importing and using the following plugins: @upit/tween.v1, @upit/storage.v1
User prompt
bölüm geçtikçe kaç coin kazandığını ekranda yaz ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
marketteki şeyleri alınabilir yap ve kaç coine alınacağını altına yaz ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
coins yazısını az daha aşşağıya al ve belirgin yap
User prompt
yazıları birbirinden ayır ve shop yazısını şeffaf yap play gibi aralarını aç
User prompt
başlangıç ekranına shop ekle ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
oyun dinamiklerini daha gerçekçi yap
User prompt
topların yukardan aşşağıya inme hızını yavaşlat
User prompt
oyuna bomba da ekle
User prompt
arka arkaya aynı toptan verme
User prompt
top boyutlarını eskisi gibi yap
User prompt
geriye al
User prompt
oyuna mağaza getir toplara kostüm için ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
pembe için de bir havuz ekle topları çok az küçült
User prompt
aynı renkte olan toplar aynı renkte oldukları havuza girince ekstra puan ver ve 2 tane farklı fireball ekle
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
swapsRemaining: 3,
currentLevel: 1,
totalBubblesPopped: 0,
coins: 0,
shopUpgrades: {
extraSwaps: 0,
powerBalls: 0,
timeBonus: 0
}
});
/****
* Classes
****/
var Barrier = Container.expand(function () {
var self = Container.call(this);
var barrierGraphics = self.attachAsset('barrier', {
anchorX: .5,
anchorY: .5
});
});
var BombBall = Container.expand(function (max_types, type) {
var self = Container.call(this);
self.isFireBall = true;
self.isBombBall = true;
var state = 0;
self.isAttached = true;
self.isFreeBubble = false;
var speedX = 0;
var speedY = 0;
self.targetX = 0;
self.targetY = 0;
self.setPos = function (x, y) {
self.x = self.targetX = x;
self.y = self.targetY = y;
};
if (type !== undefined) {
this.type = type;
} else {
max_types = max_types || 3;
if (max_types > 4) {
self.type = Math.floor(Math.random() * (.8 + Math.random() * .2) * max_types);
} else {
self.type = Math.floor(Math.random() * max_types);
}
}
var bubbleGraphics = self.attachAsset('fireball', {
anchorX: 0.5,
anchorY: 0.5
});
bubbleGraphics.width = 150;
bubbleGraphics.height = 150;
bubbleGraphics.tint = 0x333333; // Dark gray/black tint for bomb
self.detach = function () {
freeBubbleLayer.addChild(self);
LK.getSound('detachCircle').play();
self.y += grid.y;
self.isAttached = false;
speedX = Math.random() * 40 - 20;
speedY = -Math.random() * 30;
self.down = undefined;
};
var spawnMod = 0;
self.update = function () {
if (self.isFreeBubble) {
if (++spawnMod % 3 == 0 && self.parent) {
// Spawn bomb particles every 3 ticks
var angle = Math.random() * Math.PI * 2;
var bombParticle = self.parent.addChild(new BombParticle(angle));
bombParticle.x = self.x + Math.cos(angle) * self.width / 4;
bombParticle.y = self.y + Math.sin(angle) * self.width / 4;
}
return;
}
if (self.isAttached) {
if (self.x != self.targetX) {
self.x += (self.targetX - self.x) / 10;
}
if (self.y != self.targetY) {
self.y += (self.targetY - self.y) / 10;
}
} else {
self.x += speedX;
self.y += speedY;
speedY += 0.8; // More realistic gravity
speedX *= 0.995; // Add air resistance
if (self.x < bubbleSize / 2 && speedX < 0 || self.x > game.width - bubbleSize / 2 && speedX > 0) {
speedX = -speedX * 0.75; // Energy loss on wall bounce
LK.getSound('circleBounce').play();
}
// Check for collision with barriers
for (var i = 0; i < barriers.length; i++) {
var barrier = barriers[i];
var dx = self.x - barrier.x;
var dy = self.y - barrier.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var minDist = bubbleSize / 2 + barrier.width / 2;
if (distance < minDist) {
var angle = Math.atan2(dy, dx);
var newSpeed = Math.sqrt(speedX * speedX + speedY * speedY);
// More realistic collision response with proper momentum transfer
var restitution = 0.6; // Coefficient of restitution
speedX = Math.cos(angle) * newSpeed * restitution;
speedY = Math.sin(angle) * newSpeed * restitution;
var overlap = minDist - distance;
self.x += overlap * Math.cos(angle);
self.y += overlap * Math.sin(angle);
LK.getSound('circleBounce').play();
}
}
// Remove unattached bubbles that fall below 2732 - 500
if (self.y > 2732 - 400) {
self.destroy();
scoreMultipliers[Math.floor(self.x / (2048 / 6))].applyBubble(self);
LK.getSound('scoreCollected').play();
}
}
};
return self;
});
var BombParticle = Container.expand(function (angle) {
var self = Container.call(this);
var particleGraphics = self.attachAsset('fireparticle', {
anchorX: 0.5,
anchorY: 0.5
});
particleGraphics.blendMode = 1;
particleGraphics.tint = 0x333333; // Dark gray/black tint for bomb particles
var speedX = Math.cos(angle) * 1.2;
var speedY = Math.sin(angle) * 1.2;
var rotationSpeed = Math.random() * 0.08 - 0.04;
self.update = function () {
self.x += speedX * self.alpha;
self.y += speedY * self.alpha;
particleGraphics.rotation += rotationSpeed;
self.alpha -= 0.012;
if (self.alpha <= 0) {
self.destroy();
}
};
return self;
});
var BonusUX = Container.expand(function () {
var self = Container.call(this);
//Insert label here
var barHeight = 50;
// Dropshadow for bonusLabel
var bonusLabelShadow = self.addChild(new Text2('Streak Bonus', {
size: 90,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
}));
bonusLabelShadow.anchor.set(1, 1);
bonusLabelShadow.x = -10 + 4;
bonusLabelShadow.y = barHeight / 2 + 4;
// Main bonusLabel
var bonusLabel = self.addChild(new Text2('Streak Bonus', {
size: 90,
fill: 0xF4F5FF,
font: "Impact"
}));
bonusLabel.anchor.set(1, 1);
bonusLabel.y = barHeight / 2;
var rightMargin = -10;
bonusLabel.x = rightMargin;
// Dropshadow for bonusAmountLabel
var bonusAmountLabelShadow = self.addChild(new Text2('1x', {
size: 170,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
}));
bonusAmountLabelShadow.anchor.set(.5, .5);
bonusAmountLabelShadow.x = 100 + 4;
bonusAmountLabelShadow.y = 4;
// Main bonusAmountLabel
var bonusAmountLabel = self.addChild(new Text2('1x', {
size: 170,
fill: 0xF4F5FF,
font: "Impact"
}));
bonusAmountLabel.anchor.set(.5, .5);
bonusAmountLabel.x = 100;
var bonusBarWidth = bonusLabel.width;
var bonusBarStart = self.attachAsset('bonusend', {
y: 30,
x: -bonusBarWidth + rightMargin
});
var bonuseBarEnd = self.attachAsset('bonusend', {
y: 30,
x: -bonusBarWidth + rightMargin
});
var bonusBarMiddle = self.attachAsset('bonusbarmiddle', {
y: 30,
x: -bonusBarWidth + rightMargin + barHeight / 2,
width: 0
});
self.x = game.width - 270;
self.y = game.height - 145;
var bonusBarStepSize = (bonusBarWidth - barHeight) / 5;
var targetWidth = 0;
var currentWidth = 0;
var jumpToAtEnd = 0;
self.bonusAmount = 1;
self.streakCount = 0;
var maxLevel = 24;
self.setStreakCount = function (level) {
self.streakCount = Math.min(level, maxLevel);
var newBonus = Math.floor(self.streakCount / 8) + 1;
if (newBonus != self.bonusAmount) {
LK.getSound('comboBonus').play();
for (var a = 0; a < scoreMultipliers.length; a++) {
scoreMultipliers[a].setMultiplier(newBonus);
}
}
self.bonusAmount = newBonus;
bonusAmountLabel.setText(self.bonusAmount + 'x');
var newbarpos = level >= maxLevel ? 5 : level % 5;
targetWidth = newbarpos * bonusBarStepSize;
jumpToAtEnd = targetWidth;
if (newbarpos == 0 && level > 0) {
targetWidth = 5 * bonusBarStepSize;
jumpToAtEnd = 0;
}
};
self.update = function () {
var delta = targetWidth - currentWidth;
if (delta < 1) {
targetWidth = currentWidth = jumpToAtEnd;
} else {
currentWidth += delta / 8;
}
bonuseBarEnd.x = -bonusBarWidth + currentWidth + rightMargin;
bonusBarMiddle.width = currentWidth;
};
// bonuseBarEnd.x = -bonusLabel.width;
});
var Bubble = Container.expand(function (max_types, isFireBall, type) {
var self = Container.call(this);
self.isFireBall = isFireBall;
var state = 0;
self.isAttached = true;
self.isFreeBubble = false;
var speedX = 0;
var speedY = 0;
self.targetX = 0;
self.targetY = 0;
self.setPos = function (x, y) {
self.x = self.targetX = x;
self.y = self.targetY = y;
};
if (type !== undefined) {
this.type = type;
} else {
max_types = max_types || 3;
if (max_types > 4) {
self.type = Math.floor(Math.random() * (.8 + Math.random() * .2) * max_types);
} else {
self.type = Math.floor(Math.random() * max_types);
}
}
if (isFireBall) {
var bubbleGraphics = self.attachAsset('fireball', {
anchorX: 0.5,
anchorY: 0.5
});
bubbleGraphics.width = 150;
bubbleGraphics.height = 150;
} else {
var bubbleGraphics = self.attachAsset('bubble' + self.type, {
anchorX: 0.5,
anchorY: 0.5
});
}
self.detach = function () {
freeBubbleLayer.addChild(self);
LK.getSound('detachCircle').play();
self.y += grid.y;
self.isAttached = false;
speedX = Math.random() * 40 - 20;
speedY = -Math.random() * 30;
self.down = undefined;
};
var spawnMod = 0;
self.update = function () {
if (self.isFreeBubble) {
if (isFireBall) {
if (++spawnMod % 2 == 0 && self.parent) {
// Spawn fire particles every 5 ticks
var angle = Math.random() * Math.PI * 2;
var fireParticle = self.parent.addChild(new FireParticle(angle));
fireParticle.x = self.x + Math.cos(angle) * self.width / 4;
fireParticle.y = self.y + Math.sin(angle) * self.width / 4;
}
}
return;
}
if (self.isAttached) {
if (self.x != self.targetX) {
self.x += (self.targetX - self.x) / 10;
}
if (self.y != self.targetY) {
self.y += (self.targetY - self.y) / 10;
}
} else {
self.x += speedX;
self.y += speedY;
speedY += 0.8; // More realistic gravity
speedX *= 0.995; // Add air resistance
if (self.x < bubbleSize / 2 && speedX < 0 || self.x > game.width - bubbleSize / 2 && speedX > 0) {
speedX = -speedX * 0.75; // Energy loss on wall bounce
LK.getSound('circleBounce').play();
}
// Check for collision with barriers
for (var i = 0; i < barriers.length; i++) {
var barrier = barriers[i];
var dx = self.x - barrier.x;
var dy = self.y - barrier.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var minDist = bubbleSize / 2 + barrier.width / 2;
if (distance < minDist) {
// Calculate the angle of the collision
var angle = Math.atan2(dy, dx);
// Calculate the new speed based on the angle of collision, treating the barrier as a static billiard ball
var newSpeed = Math.sqrt(speedX * speedX + speedY * speedY);
// More realistic collision response with proper momentum transfer
var restitution = 0.6; // Coefficient of restitution
speedX = Math.cos(angle) * newSpeed * restitution;
speedY = Math.sin(angle) * newSpeed * restitution;
// Move the bubble back to the point where it just touches the barrier
var overlap = minDist - distance;
self.x += overlap * Math.cos(angle);
self.y += overlap * Math.sin(angle);
LK.getSound('circleBounce').play();
}
}
// Remove unattached bubbles that fall below 2732 - 500
if (self.y > 2732 - 400) {
self.destroy();
scoreMultipliers[Math.floor(self.x / (2048 / 6))].applyBubble(self);
LK.getSound('scoreCollected').play();
}
}
};
});
var BubbleRemoveParticle = Container.expand(function () {
var self = Container.call(this);
var particle = self.attachAsset('removebubbleeffect', {
anchorX: 0.5,
anchorY: 0.5
});
particle.blendMode = 1;
self.scale.set(.33, .33);
var cscale = .5;
self.update = function () {
cscale += .02;
self.scale.set(cscale, cscale);
self.alpha = 1 - (cscale - .5) * 1.5;
if (self.alpha < 0) {
self.destroy();
}
};
});
var ColorChangerBubble = Container.expand(function () {
var self = Container.call(this);
// Use a rainbow-tinted bubble to represent color changer
var bubbleGraphics = self.attachAsset('bubble0', {
anchorX: 0.5,
anchorY: 0.5
});
// Make it visually distinct with rainbow effect
bubbleGraphics.tint = 0xffffff;
self.type = -4; // Special type for color changer bubble
self.isColorChangerBubble = true;
self.isAttached = true;
self.isFreeBubble = false;
var speedX = 0;
var speedY = 0;
self.targetX = 0;
self.targetY = 0;
var colorCycleTime = 0;
self.setPos = function (x, y) {
self.x = self.targetX = x;
self.y = self.targetY = y;
};
self.detach = function () {
freeBubbleLayer.addChild(self);
LK.getSound('detachCircle').play();
self.y += grid.y;
self.isAttached = false;
speedX = Math.random() * 40 - 20;
speedY = -Math.random() * 30;
self.down = undefined;
};
self.update = function () {
if (self.isFreeBubble) {
return;
}
if (self.isAttached) {
// Rainbow color cycling effect
colorCycleTime += 0.1;
var red = Math.sin(colorCycleTime) * 127 + 128;
var green = Math.sin(colorCycleTime + 2) * 127 + 128;
var blue = Math.sin(colorCycleTime + 4) * 127 + 128;
bubbleGraphics.tint = Math.floor(red) << 16 | Math.floor(green) << 8 | Math.floor(blue);
if (self.x != self.targetX) {
self.x += (self.targetX - self.x) / 10;
}
if (self.y != self.targetY) {
self.y += (self.targetY - self.y) / 10;
}
} else {
self.x += speedX;
self.y += speedY;
speedY += 0.8; // More realistic gravity
speedX *= 0.995; // Add air resistance
if (self.x < bubbleSize / 2 && speedX < 0 || self.x > game.width - bubbleSize / 2 && speedX > 0) {
speedX = -speedX * 0.75; // Energy loss on wall bounce
LK.getSound('circleBounce').play();
}
// Check for collision with barriers
for (var i = 0; i < barriers.length; i++) {
var barrier = barriers[i];
var dx = self.x - barrier.x;
var dy = self.y - barrier.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var minDist = bubbleSize / 2 + barrier.width / 2;
if (distance < minDist) {
var angle = Math.atan2(dy, dx);
var newSpeed = Math.sqrt(speedX * speedX + speedY * speedY);
var restitution = 0.6;
speedX = Math.cos(angle) * newSpeed * restitution;
speedY = Math.sin(angle) * newSpeed * restitution;
var overlap = minDist - distance;
self.x += overlap * Math.cos(angle);
self.y += overlap * Math.sin(angle);
LK.getSound('circleBounce').play();
}
}
// When color changer reaches bottom, change all bubbles to same color
if (self.y > 2732 - 400) {
self.activateColorChanger();
self.destroy();
}
}
};
self.activateColorChanger = function () {
// Pick a random color for all bubbles to change to
var newColor = Math.floor(Math.random() * getMaxTypes());
var bubblesChanged = 0;
// Change all grid bubbles to the new color
for (var row = 0; row < grid.container.children.length; row++) {
var bubble = grid.container.children[row];
if (bubble && bubble.isAttached && bubble.type >= 0 && bubble.type < 6) {
bubble.type = newColor;
bubblesChanged++;
}
}
if (bubblesChanged > 0) {
LK.getSound('powerupSwoosh').play();
LK.getSound('bubbleSwap').play();
// Visual effect
LK.effects.flashScreen(0xffffff, 500);
// Show effect text
var effectText = game.addChild(new Text2('COLOR CHANGE!', {
size: 120,
fill: 0xffffff,
font: "Impact"
}));
effectText.anchor.set(0.5, 0.5);
effectText.x = game.width / 2;
effectText.y = game.height / 2;
effectText.alpha = 1;
tween(effectText, {
y: effectText.y - 200,
alpha: 0
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
effectText.destroy();
}
});
}
};
return self;
});
var DoubleCoinsBubble = Container.expand(function () {
var self = Container.call(this);
// Use a gold-tinted bubble to represent double coins
var bubbleGraphics = self.attachAsset('bubble5', {
anchorX: 0.5,
anchorY: 0.5
});
// Make it visually distinct with gold tint
bubbleGraphics.tint = 0xffd700;
self.type = -6; // Special type for double coins bubble
self.isDoubleCoinsBubble = true;
self.isAttached = true;
self.isFreeBubble = false;
var speedX = 0;
var speedY = 0;
self.targetX = 0;
self.targetY = 0;
var glowPulse = 0;
self.setPos = function (x, y) {
self.x = self.targetX = x;
self.y = self.targetY = y;
};
self.detach = function () {
freeBubbleLayer.addChild(self);
LK.getSound('detachCircle').play();
self.y += grid.y;
self.isAttached = false;
speedX = Math.random() * 40 - 20;
speedY = -Math.random() * 30;
self.down = undefined;
};
self.update = function () {
if (self.isFreeBubble) {
return;
}
if (self.isAttached) {
// Gold pulsing effect
glowPulse += 0.15;
var glowIntensity = Math.sin(glowPulse) * 0.3 + 0.7;
bubbleGraphics.alpha = glowIntensity;
if (self.x != self.targetX) {
self.x += (self.targetX - self.x) / 10;
}
if (self.y != self.targetY) {
self.y += (self.targetY - self.y) / 10;
}
} else {
self.x += speedX;
self.y += speedY;
speedY += 0.8;
speedX *= 0.995;
if (self.x < bubbleSize / 2 && speedX < 0 || self.x > game.width - bubbleSize / 2 && speedX > 0) {
speedX = -speedX * 0.75;
LK.getSound('circleBounce').play();
}
// Check for collision with barriers
for (var i = 0; i < barriers.length; i++) {
var barrier = barriers[i];
var dx = self.x - barrier.x;
var dy = self.y - barrier.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var minDist = bubbleSize / 2 + barrier.width / 2;
if (distance < minDist) {
var angle = Math.atan2(dy, dx);
var newSpeed = Math.sqrt(speedX * speedX + speedY * speedY);
var restitution = 0.6;
speedX = Math.cos(angle) * newSpeed * restitution;
speedY = Math.sin(angle) * newSpeed * restitution;
var overlap = minDist - distance;
self.x += overlap * Math.cos(angle);
self.y += overlap * Math.sin(angle);
LK.getSound('circleBounce').play();
}
}
// When double coins bubble reaches bottom, activate double coins
if (self.y > 2732 - 400) {
self.activateDoubleCoins();
self.destroy();
}
}
};
self.activateDoubleCoins = function () {
// Enable double coins for 15 seconds
doubleCoinsActive = true;
LK.getSound('powerupSwoosh').play();
LK.getSound('scoreCollected').play();
// Visual effect
LK.effects.flashScreen(0xffd700, 500);
// Show effect text
var effectText = game.addChild(new Text2('DOUBLE COINS!', {
size: 120,
fill: 0xffd700,
font: "Impact"
}));
effectText.anchor.set(0.5, 0.5);
effectText.x = game.width / 2;
effectText.y = game.height / 2;
effectText.alpha = 1;
tween(effectText, {
y: effectText.y - 200,
alpha: 0
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
effectText.destroy();
}
});
// Disable double coins after 15 seconds
LK.setTimeout(function () {
doubleCoinsActive = false;
}, 15000);
};
return self;
});
var ExplosiveBubble = Container.expand(function () {
var self = Container.call(this);
// Use a red-tinted bubble to represent explosive
var bubbleGraphics = self.attachAsset('bubble0', {
anchorX: 0.5,
anchorY: 0.5
});
// Make it visually distinct with red tint
bubbleGraphics.tint = 0xff4444;
self.type = -3; // Special type for explosive bubble
self.isExplosiveBubble = true;
self.isAttached = true;
self.isFreeBubble = false;
var speedX = 0;
var speedY = 0;
self.targetX = 0;
self.targetY = 0;
self.setPos = function (x, y) {
self.x = self.targetX = x;
self.y = self.targetY = y;
};
self.detach = function () {
freeBubbleLayer.addChild(self);
LK.getSound('detachCircle').play();
self.y += grid.y;
self.isAttached = false;
speedX = Math.random() * 40 - 20;
speedY = -Math.random() * 30;
self.down = undefined;
};
self.explode = function () {
// Create explosion effect
var explosion = game.addChild(new BubbleRemoveParticle());
explosion.x = self.x;
explosion.y = self.y;
explosion.scale.set(3, 3);
// Find all bubbles within explosion radius
var explosionRadius = 200;
var bubblesInRadius = [];
// Check all grid bubbles
for (var row = 0; row < grid.container.children.length; row++) {
var bubble = grid.container.children[row];
if (bubble && bubble.isAttached) {
var dx = bubble.x - (self.x - grid.x);
var dy = bubble.y - (self.y - grid.y);
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= explosionRadius) {
bubblesInRadius.push(bubble);
}
}
}
// Remove bubbles in explosion radius
if (bubblesInRadius.length > 0) {
grid.removeBubbles(bubblesInRadius);
var disconnected = grid.getDetachedBubbles();
grid.removeBubbles(disconnected);
// Track mission progress for exploded bubbles
if (missionActive) {
for (var i = 0; i < bubblesInRadius.length; i++) {
var bubble = bubblesInRadius[i];
if (bubble.type >= 0 && bubble.type < 3) {
missionCollected[bubble.type]++;
}
}
for (var i = 0; i < disconnected.length; i++) {
var bubble = disconnected[i];
if (bubble.type >= 0 && bubble.type < 3) {
missionCollected[bubble.type]++;
}
}
}
}
LK.getSound('explosiveBubble').play();
LK.getSound('fireBubble').play();
LK.getSound('powerupThump').play();
};
self.update = function () {
if (self.isFreeBubble) {
return;
}
if (self.isAttached) {
if (self.x != self.targetX) {
self.x += (self.targetX - self.x) / 10;
}
if (self.y != self.targetY) {
self.y += (self.targetY - self.y) / 10;
}
} else {
self.x += speedX;
self.y += speedY;
speedY += 0.8; // More realistic gravity
speedX *= 0.995; // Add air resistance
if (self.x < bubbleSize / 2 && speedX < 0 || self.x > game.width - bubbleSize / 2 && speedX > 0) {
speedX = -speedX * 0.75; // Energy loss on wall bounce
LK.getSound('circleBounce').play();
}
// Check for collision with barriers
for (var i = 0; i < barriers.length; i++) {
var barrier = barriers[i];
var dx = self.x - barrier.x;
var dy = self.y - barrier.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var minDist = bubbleSize / 2 + barrier.width / 2;
if (distance < minDist) {
var angle = Math.atan2(dy, dx);
var newSpeed = Math.sqrt(speedX * speedX + speedY * speedY);
// More realistic collision response with proper momentum transfer
var restitution = 0.6; // Coefficient of restitution
speedX = Math.cos(angle) * newSpeed * restitution;
speedY = Math.sin(angle) * newSpeed * restitution;
var overlap = minDist - distance;
self.x += overlap * Math.cos(angle);
self.y += overlap * Math.sin(angle);
LK.getSound('circleBounce').play();
}
}
// When explosive bubble reaches bottom, explode
if (self.y > 2732 - 400) {
self.explode();
self.destroy();
}
}
};
return self;
});
var FireBallPowerupOverlay = Container.expand(function () {
var self = Container.call(this);
var bubbleGraphics = self.attachAsset('fireball', {
anchorX: 0.5,
anchorY: 0.5
});
// Larger size for mobile touch targets
bubbleGraphics.width = 180;
bubbleGraphics.height = 180;
self.y = game.height - 180;
self.x = 250;
var countBG = self.attachAsset('countbg', {
anchorX: 0.5,
anchorY: 0.5
});
// Larger background for better touch target
countBG.width = 160;
countBG.height = 160;
countBG.x = 110;
countBG.y = 60;
self.fireballsLeft = 3; // Start with 3 fireballs
// Dropshadow for label
var labelShadow = self.addChild(new Text2(self.fireballsLeft, {
size: 70,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
}));
labelShadow.anchor.set(.5, .5);
labelShadow.x = 90 + 3;
labelShadow.y = 50 + 3;
// Main label
var label = self.addChild(new Text2(self.fireballsLeft, {
size: 70,
fill: 0xFFFFFF,
font: "Impact"
}));
label.anchor.set(.5, .5);
label.x = 90;
label.y = 50;
self.alpha = 0.5; // Start with greyed out overlay
self.increaseFireballCount = function () {
self.fireballsLeft++;
self.updateDisplay();
tween(self.scale, {
x: 1.3,
y: 1.3
}, {
duration: 120,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self.scale, {
x: 1,
y: 1
}, {
duration: 220,
easing: tween.bounceOut
});
}
});
// Spawn powerup particles
for (var i = 0; i < 12; i++) {
var angle = Math.random() * Math.PI * 2;
var speed = 8 + Math.random() * 6;
var particle = game.addChild(new PowerupParticle(angle, speed));
particle.x = self.x + 90; // center of indicator
particle.y = self.y + 50;
}
};
self.updateDisplay = function () {
label.setText(self.fireballsLeft);
labelShadow.setText(self.fireballsLeft);
self.alpha = self.fireballsLeft > 0 ? 1 : 0.5;
};
self.down = function () {
if (self.fireballsLeft > 0 && !launcher.isFireBall()) {
self.fireballsLeft--;
label.setText(self.fireballsLeft);
labelShadow.setText(self.fireballsLeft);
// Cycle through fireball types: normal -> ice -> laser -> bomb -> normal
var fireballType = (4 - self.fireballsLeft) % 4;
launcher.triggerFireBall(fireballType);
if (self.fireballsLeft == 0) {
self.alpha = .5;
}
}
};
// State for wiggle animation
self.isWiggling = false;
// Update method to handle wiggle animation
self.update = function () {
// Check if bubbles are getting close to bottom and we have powerups
if (self.fireballsLeft > 0 && !self.isWiggling) {
// Get warning scores from grid
var warningScores = grid.calculateWarningScoreList();
var maxWarning = 0;
for (var i = 0; i < warningScores.length; i++) {
if (warningScores[i] > maxWarning) {
maxWarning = warningScores[i];
}
}
// If any column has high warning score (bubbles close to bottom), trigger wiggle
if (maxWarning > 1.5) {
// Threshold for "getting close"
self.isWiggling = true;
// First wiggle to the right
tween(self, {
rotation: 0.15
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
// Then wiggle to the left
tween(self, {
rotation: -0.15
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Then wiggle to the right again
tween(self, {
rotation: 0.15
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Return to normal position
tween(self, {
rotation: 0
}, {
duration: 100,
easing: tween.easeIn,
onFinish: function onFinish() {
// Reset wiggle state after a cooldown
LK.setTimeout(function () {
self.isWiggling = false;
}, 2000); // 2 second cooldown before next wiggle
}
});
}
});
}
});
}
});
}
}
};
});
var FireParticle = Container.expand(function (angle) {
var self = Container.call(this);
var particleGraphics = self.attachAsset('fireparticle', {
anchorX: 0.5,
anchorY: 0.5
});
particleGraphics.blendMode = 1;
var speedX = Math.cos(angle) * 1;
var speedY = Math.sin(angle) * 1;
var rotationSpeed = Math.random() * 0.1 - 0.05;
self.update = function () {
self.x += speedX * self.alpha;
self.y += speedY * self.alpha;
particleGraphics.rotation += rotationSpeed;
self.alpha -= 0.01;
if (self.alpha <= 0) {
self.destroy();
}
};
});
var Grid = Container.expand(function () {
var self = Container.call(this);
var rows = [];
self.container = self.addChild(new Container());
var rowCount = 0;
function insertRow() {
var row = [];
var rowWidth = rowCount % 2 == 0 ? 13 : 12;
// Determine if this row should have a powerup
var shouldSpawnPowerup = false;
var shouldSpawnTimeBubble = false;
var shouldSpawnExplosiveBubble = false;
var powerupCol = -1;
var POWERUP_ROW_INTERVAL = 20; // Every 20th row has a powerup (was 10)
var TIME_BUBBLE_INTERVAL = 15; // Every 15th row might have a time bubble
var EXPLOSIVE_BUBBLE_INTERVAL = 25; // Every 25th row might have an explosive bubble
// Move first powerup spawn to row 6, and then every POWERUP_ROW_INTERVAL after that
if (rowCount === 6) {
shouldSpawnPowerup = true;
powerupCol = Math.floor(Math.random() * rowWidth);
} else if (rowCount - 6 > 0 && (rowCount - 6) % POWERUP_ROW_INTERVAL === 0) {
shouldSpawnPowerup = true;
powerupCol = Math.floor(Math.random() * rowWidth);
}
// Check for time bubble spawn (level 3+)
if (currentGameLevel >= 3 && rowCount > 10 && rowCount % TIME_BUBBLE_INTERVAL === 0 && Math.random() < 0.3) {
shouldSpawnTimeBubble = true;
powerupCol = Math.floor(Math.random() * rowWidth);
}
// Check for explosive bubble spawn (level 5+)
if (currentGameLevel >= 5 && rowCount > 15 && rowCount % EXPLOSIVE_BUBBLE_INTERVAL === 0 && Math.random() < 0.2) {
shouldSpawnExplosiveBubble = true;
powerupCol = Math.floor(Math.random() * rowWidth);
}
// Check for other power-up spawns
var shouldSpawnColorChanger = false;
var shouldSpawnSlowTime = false;
var shouldSpawnDoubleCoins = false;
// Color Changer (level 4+, every 30 rows, 15% chance)
if (currentGameLevel >= 4 && rowCount > 12 && rowCount % 30 === 0 && Math.random() < 0.15) {
shouldSpawnColorChanger = true;
powerupCol = Math.floor(Math.random() * rowWidth);
}
// Slow Time (level 6+, every 35 rows, 12% chance)
if (currentGameLevel >= 6 && rowCount > 18 && rowCount % 35 === 0 && Math.random() < 0.12) {
shouldSpawnSlowTime = true;
powerupCol = Math.floor(Math.random() * rowWidth);
}
// Double Coins (level 3+, every 25 rows, 20% chance)
if (currentGameLevel >= 3 && rowCount > 8 && rowCount % 25 === 0 && Math.random() < 0.20) {
shouldSpawnDoubleCoins = true;
powerupCol = Math.floor(Math.random() * rowWidth);
}
for (var a = 0; a < rowWidth; a++) {
var bubble;
if (shouldSpawnPowerup && a === powerupCol) {
bubble = new PowerupBubble();
} else if (shouldSpawnTimeBubble && a === powerupCol) {
bubble = new TimeBubble();
} else if (shouldSpawnExplosiveBubble && a === powerupCol) {
bubble = new ExplosiveBubble();
} else if (shouldSpawnColorChanger && a === powerupCol) {
bubble = new ColorChangerBubble();
} else if (shouldSpawnSlowTime && a === powerupCol) {
bubble = new SlowTimeBubble();
} else if (shouldSpawnDoubleCoins && a === powerupCol) {
bubble = new DoubleCoinsBubble();
} else {
bubble = new Bubble(getMaxTypes());
}
bubble.setPos((2048 - bubbleSize * rowWidth) / 2 + bubbleSize * a + bubbleSize / 2, -rowCount * (1.7320508076 * bubbleSize) / 2);
self.container.addChild(bubble);
row.push(bubble);
/*bubble.down = function () {
var bubbles = self.getConnectedBubbles(this);
self.removeBubbles(bubbles);
var disconnected = self.getDetachedBubbles();
self.removeBubbles(disconnected);
};*/
}
rows.push(row);
rowCount++;
}
//Method that removes an array of bubbles from the rows array.
self.removeBubbles = function (bubbles) {
for (var i = 0; i < bubbles.length; i++) {
var bubble = bubbles[i];
if (bubble) {
var bubbleIndex = this.findBubbleIndex(bubble);
if (bubbleIndex) {
rows[bubbleIndex.row][bubbleIndex.col] = null;
bubble.detach();
}
}
}
};
self.getConnectedBubbles = function (bubble, ignoreType) {
var connectedBubbles = [];
var queue = [bubble];
var visited = [];
while (queue.length > 0) {
var currentBubble = queue.shift();
if (visited.indexOf(currentBubble) === -1) {
visited.push(currentBubble);
connectedBubbles.push(currentBubble);
var neighbors = self.getNeighbors(currentBubble);
for (var i = 0; i < neighbors.length; i++) {
var neighbor = neighbors[i];
if (neighbor) {
if (neighbor.isPowerup || neighbor.isTimeBubble || neighbor.isExplosiveBubble || neighbor.isColorChangerBubble || neighbor.isSlowTimeBubble || neighbor.isDoubleCoinsBubble) {
// Special bubbles connect with everything
queue.push(neighbor);
} else if (neighbor.type === bubble.type || ignoreType) {
queue.push(neighbor);
}
}
}
}
}
return connectedBubbles;
};
//Get a list of bubbles that are not connected to the top row, or to a chain of bubbles connected to the top row.
self.getDetachedBubbles = function () {
var detachedBubbles = [];
var connectedToTop = [];
// Mark all bubbles connected to the bottom row
var lastRowIndex = rows.length - 1;
for (var i = 0; i < rows[lastRowIndex].length; i++) {
if (rows[lastRowIndex][i] !== null) {
var bottomConnected = self.getConnectedBubbles(rows[lastRowIndex][i], true);
connectedToTop = connectedToTop.concat(bottomConnected);
}
}
// Mark all bubbles as visited or not
var visited = connectedToTop.filter(function (bubble) {
return bubble != null;
});
// Find all bubbles that are not visited and not connected to the top
for (var row = 0; row < rows.length - 1; row++) {
for (var col = 0; col < rows[row].length; col++) {
var bubble = rows[row][col];
if (bubble !== null && visited.indexOf(bubble) == -1) {
detachedBubbles.push(bubble);
}
}
}
return detachedBubbles;
};
self.getNeighbors = function (bubble) {
var neighbors = [];
var bubbleIndex = this.findBubbleIndex(bubble);
if (!bubbleIndex) {
return [];
}
var directions = [[-1, 0], [1, 0],
// left and right
[0, -1], [0, 1],
// above and below
[-1, -1], [1, -1] // diagonals for even rows
];
if (bubbleIndex && rows[bubbleIndex.row] && rows[bubbleIndex.row].length == 12) {
// Adjust diagonals for odd rows
directions[4] = [-1, 1];
directions[5] = [1, 1];
}
for (var i = 0; i < directions.length; i++) {
var dir = directions[i];
if (bubbleIndex && rows[bubbleIndex.row]) {
var newRow = bubbleIndex.row + dir[0];
}
var newCol = bubbleIndex.col + dir[1];
if (newRow >= 0 && newRow < rows.length && newCol >= 0 && newCol < rows[newRow].length) {
neighbors.push(rows[newRow][newCol]);
}
}
return neighbors;
};
self.findBubbleIndex = function (bubble) {
for (var row = 0; row < rows.length; row++) {
var col = rows[row].indexOf(bubble);
if (col !== -1) {
return {
row: row,
col: col
};
}
}
return null;
};
self.printRowsToConsole = function () {
var gridString = '';
for (var i = rows.length - 1; i >= 0; i--) {
var rowString = ': ' + (rows[i].length == 13 ? '' : ' ');
for (var j = 0; j < rows[i].length; j++) {
var bubble = rows[i][j];
rowString += bubble ? '[' + bubble.type + ']' : '[_]';
}
gridString += rowString + '\n';
}
console.log(gridString);
};
// Method to calculate path of movement based on angle and starting point
//TODO: MAKE THIS MUCH FASTER!
self.bubbleIntersectsGrid = function (nextX, nextY) {
outer: for (var row = 0; row < rows.length; row++) {
for (var col = 0; col < rows[row].length; col++) {
var bubble = rows[row][col];
if (bubble) {
var dist = nextY - bubble.y - self.y;
//Quick exit if we are nowhere near the row
if (dist > 145 || dist < -145) {
continue outer;
}
var dx = nextX - bubble.x - self.x;
var dy = nextY - bubble.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < (bubbleSize - 70) / 2 + bubbleSize / 2) {
return bubble;
}
}
}
}
return false;
};
self.calculatePath = function (startPoint, angle) {
var path = [];
var currentPoint = {
x: startPoint.x,
y: startPoint.y
};
var radians = angle;
var stepSize = 4;
var hitBubble = false;
while (currentPoint.y > 0 && !hitBubble) {
// Calculate next point
var nextX = currentPoint.x + stepSize * Math.cos(radians);
var nextY = currentPoint.y + stepSize * Math.sin(radians);
// Check for wall collisions
if (nextX < 150 / 2 || nextX > 2048 - 150 / 2) {
radians = Math.PI - radians; // Reflect angle
nextX = currentPoint.x + stepSize * Math.cos(radians); // Recalculate nextX after reflection
}
hitBubble = self.bubbleIntersectsGrid(nextX, nextY);
// Add point to path and update currentPoint
path.push({
x: nextX,
y: nextY
});
currentPoint.x = nextX;
currentPoint.y = nextY;
}
if (hitBubble) {
//Only increase avilable bubble type when we have actually pointed as such a bubble
if (hitBubble.type >= 0 && hitBubble.type + 1 > maxSelectableBubble) {
maxSelectableBubble = hitBubble.type + 1;
}
;
}
return path;
};
var bubblesInFlight = [];
self.fireBubble = function (bubble, angle) {
self.addChild(bubble);
bubble.x = launcher.x;
bubble.y += launcher.y - self.y;
bubblesInFlight.push({
bubble: bubble,
angle: angle
});
};
self.calculateWarningScoreList = function () {
var warningScores = [];
for (var i = 0; i < 13; i++) {
warningScores.push(0); // Initialize all scores to 0
}
// Calculate the distance from the bottom for each bubble and increment the warning score based on proximity
for (var row = 0; row < rows.length; row++) {
for (var col = 0; col < rows[row].length; col++) {
var bubble = rows[row][col];
if (bubble) {
var distanceFromBottom = 2732 - (bubble.y + self.y);
if (distanceFromBottom < 2000) {
// If a bubble is within 500px from the bottom
var columnIndex = Math.floor(bubble.x / (2048 / 13));
warningScores[columnIndex] += (2000 - distanceFromBottom) / 2000; // Increment the warning score for the column
}
}
}
}
return warningScores;
};
self.update = function () {
outer: for (var a = 0; a < bubblesInFlight.length; a++) {
var current = bubblesInFlight[a];
var bubble = current.bubble;
var nextX = bubble.x;
var nextY = bubble.y + gridSpeed;
var prevX = bubble.x;
var prevY = bubble.y;
for (var rep = 0; rep < 25; rep++) {
prevX = nextX;
prevY = nextY;
nextX += Math.cos(current.angle) * 4;
nextY += Math.sin(current.angle) * 4;
if (nextX < 150 / 2 || nextX > 2048 - 150 / 2) {
current.angle = Math.PI - current.angle; // Reflect angle
nextX = Math.min(Math.max(nextX, 130 / 2), 2048 - 130 / 2);
LK.getSound('circleBounce').play();
}
var intersectedBubble = self.bubbleIntersectsGrid(nextX + self.x, nextY + self.y);
if (intersectedBubble) {
gameIsStarted = true;
if (bubble.isFireBall) {
if (bubble.isIceBall) {
// Ice ball freezes bubbles in a 3x3 area for 3 seconds
var freezeRadius = 300;
var frozenBubbles = [];
for (var row = 0; row < rows.length; row++) {
for (var col = 0; col < rows[row].length; col++) {
var gridBubble = rows[row][col];
if (gridBubble && gridBubble.isAttached) {
var dx = gridBubble.x - (bubble.x - self.x);
var dy = gridBubble.y - (bubble.y - self.y);
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= freezeRadius) {
frozenBubbles.push(gridBubble);
}
}
}
}
// Freeze effect - slow down grid movement temporarily
var originalSpeed = gridSpeed;
gridSpeed *= 0.1; // Slow down to 10% speed
LK.setTimeout(function () {
gridSpeed = originalSpeed;
}, 3000);
// Visual freeze effect
for (var i = 0; i < frozenBubbles.length; i++) {
var frozenBubble = frozenBubbles[i];
frozenBubble.alpha = 0.5;
frozenBubble.tint = 0x88ccff;
LK.setTimeout(function () {
frozenBubble.alpha = 1;
frozenBubble.tint = 0xffffff;
}, 3000);
}
self.removeBubbles([intersectedBubble]);
} else if (bubble.isLaserBall) {
// Laser ball creates a vertical line destruction
var laserColumn = Math.floor((bubble.x - self.x) / bubbleSize);
var destroyedBubbles = [];
for (var row = 0; row < rows.length; row++) {
for (var col = 0; col < rows[row].length; col++) {
var gridBubble = rows[row][col];
if (gridBubble && Math.abs(col - laserColumn) <= 1) {
destroyedBubbles.push(gridBubble);
}
}
}
self.removeBubbles(destroyedBubbles);
} else if (bubble.isBombBall) {
// Bomb ball explodes in a large radius destroying many bubbles
var bombRadius = 250;
var explodedBubbles = [];
for (var row = 0; row < rows.length; row++) {
for (var col = 0; col < rows[row].length; col++) {
var gridBubble = rows[row][col];
if (gridBubble && gridBubble.isAttached) {
var dx = gridBubble.x - (bubble.x - self.x);
var dy = gridBubble.y - (bubble.y - self.y);
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= bombRadius) {
explodedBubbles.push(gridBubble);
}
}
}
}
// Create explosion effect
var explosion = game.addChild(new BubbleRemoveParticle());
explosion.x = bubble.x;
explosion.y = bubble.y;
explosion.scale.set(4, 4);
explosion.alpha = 0.9;
LK.getSound('explosiveBubble').play();
LK.getSound('fireBubble').play();
self.removeBubbles(explodedBubbles);
} else {
// Regular fireball
self.removeBubbles([intersectedBubble]);
}
var disconnected = self.getDetachedBubbles();
self.removeBubbles(disconnected);
} else {
var intersectedBubblePos = self.findBubbleIndex(intersectedBubble);
var colOffset = rows[intersectedBubblePos.row].length == 13 ? 0 : 1;
var offsetPositions = [{
x: intersectedBubble.targetX - bubbleSize / 2,
y: intersectedBubble.targetY - 1.7320508076 * bubbleSize / 2,
ro: intersectedBubblePos.row + 1,
co: intersectedBubblePos.col - 1 + colOffset
}, {
x: intersectedBubble.targetX + bubbleSize / 2,
y: intersectedBubble.targetY - 1.7320508076 * bubbleSize / 2,
ro: intersectedBubblePos.row + 1,
co: intersectedBubblePos.col + colOffset
}, {
x: intersectedBubble.targetX + bubbleSize,
y: intersectedBubble.targetY,
ro: intersectedBubblePos.row,
co: intersectedBubblePos.col + 1
}, {
x: intersectedBubble.targetX + bubbleSize / 2,
y: intersectedBubble.targetY + 1.7320508076 * bubbleSize / 2,
ro: intersectedBubblePos.row - 1,
co: intersectedBubblePos.col + colOffset
}, {
x: intersectedBubble.targetX - bubbleSize / 2,
y: intersectedBubble.targetY + 1.7320508076 * bubbleSize / 2,
ro: intersectedBubblePos.row - 1,
co: intersectedBubblePos.col - 1 + colOffset
}, {
x: intersectedBubble.targetX - bubbleSize,
y: intersectedBubble.targetY,
ro: intersectedBubblePos.row,
co: intersectedBubblePos.col - 1
}];
var closestPosition = 0;
var closestDistance = Math.sqrt(Math.pow(offsetPositions[0].x - bubble.x, 2) + Math.pow(offsetPositions[0].y - bubble.y, 2));
for (var i = 1; i < offsetPositions.length; i++) {
var currentPosition = offsetPositions[i];
var currentDistance = Math.sqrt(Math.pow(currentPosition.x - bubble.x, 2) + Math.pow(currentPosition.y - bubble.y, 2));
if (currentDistance < closestDistance) {
var row = rows[currentPosition.ro];
if (currentPosition.co < 0) {
continue;
}
if (row) {
if (row[currentPosition.co]) {
continue;
}
if (currentPosition.co >= row.length) {
continue;
}
} else {
var newRowLength = rows[intersectedBubblePos.row].length == 13 ? 12 : 13;
if (currentPosition.co >= newRowLength) {
continue;
}
}
closestDistance = currentDistance;
closestPosition = i;
}
}
// Attach bubble to the closest position
var currentMatch = offsetPositions[closestPosition];
bubble.x = prevX;
bubble.y = prevY;
bubble.targetX = currentMatch.x;
bubble.targetY = currentMatch.y;
bubble.isFreeBubble = false;
var row = rows[offsetPositions[closestPosition].ro];
if (!row) {
if (rows[intersectedBubblePos.row].length == 13) {
row = [null, null, null, null, null, null, null, null, null, null, null, null];
} else {
row = [null, null, null, null, null, null, null, null, null, null, null, null, null];
}
rows.unshift(row);
}
row[offsetPositions[closestPosition].co] = bubble;
bubblesInFlight.splice(a--, 1);
refreshHintLine();
var bubbles = self.getConnectedBubbles(bubble);
if (bubbles.length > 2) {
self.removeBubbles(bubbles);
LK.getSound('bubblePop').play(); // Sound for popping bubbles
LK.getSound('powerupThump').play(); // Additional sound for popping bubbles
var disconnected = self.getDetachedBubbles();
self.removeBubbles(disconnected);
// Handle special bubble effects before removal
for (var specialBubbleIndex = 0; specialBubbleIndex < bubbles.length; specialBubbleIndex++) {
var specialBubble = bubbles[specialBubbleIndex];
if (specialBubble.isTimeBubble) {
// Add time when time bubble is removed
if (missionActive) {
missionTimeLeft += 10 * 60; // 10 seconds in ticks
}
} else if (specialBubble.isExplosiveBubble) {
// Trigger explosion effect
specialBubble.explode();
} else if (specialBubble.isColorChangerBubble) {
// Activate color changer effect
specialBubble.activateColorChanger();
} else if (specialBubble.isSlowTimeBubble) {
// Activate slow time effect
specialBubble.activateSlowTime();
} else if (specialBubble.isDoubleCoinsBubble) {
// Activate double coins effect
specialBubble.activateDoubleCoins();
}
}
// Track mission progress for removed bubbles
if (missionActive) {
for (var missionBubbleIndex = 0; missionBubbleIndex < bubbles.length; missionBubbleIndex++) {
var missionBubble = bubbles[missionBubbleIndex];
if (missionBubble.type >= 0 && missionBubble.type < 3) {
missionCollected[missionBubble.type]++;
}
}
for (var disconnectedBubbleIndex = 0; disconnectedBubbleIndex < disconnected.length; disconnectedBubbleIndex++) {
var disconnectedBubble = disconnected[disconnectedBubbleIndex];
if (disconnectedBubble.type >= 0 && disconnectedBubble.type < 3) {
missionCollected[disconnectedBubble.type]++;
}
}
// Check if mission is complete
if (checkMissionComplete()) {
missionActive = false;
LK.effects.flashScreen(0x00ff00, 1000); // Green flash for success
LK.getSound('missionComplete').play();
LK.getSound('powerupThump').play();
LK.getSound('scoreCollected').play(); // Additional sound for mission completion
// Calculate coins earned for completing level
var coinsEarned = 50 + currentGameLevel * 25;
if (doubleCoinsActive) {
coinsEarned *= 2; // Double coins when power-up is active
}
storage.coins += coinsEarned;
// Show coins earned display
var coinsEarnedText = game.addChild(new Text2('+' + coinsEarned + ' Coins!', {
size: 120,
fill: 0xFFD700,
font: "Impact"
}));
coinsEarnedText.anchor.set(0.5, 0.5);
coinsEarnedText.x = game.width / 2;
coinsEarnedText.y = game.height / 2 + 100;
coinsEarnedText.alpha = 0;
// Animate coins earned text
tween(coinsEarnedText, {
alpha: 1,
y: game.height / 2
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Hold display for 1.5 seconds then fade out
LK.setTimeout(function () {
tween(coinsEarnedText, {
alpha: 0,
y: game.height / 2 - 100
}, {
duration: 500,
easing: tween.easeIn,
onFinish: function onFinish() {
coinsEarnedText.destroy();
}
});
}, 1500);
}
});
// Advance to next level
currentGameLevel++;
if (currentGameLevel > maxGameLevel) {
// Player completed all levels - show victory
LK.showYouWin();
} else {
// Start next level mission after a delay
LK.setTimeout(function () {
initializeMission();
}, 2000);
}
// Advance to next level
currentGameLevel++;
LK.getSound('powerupSwoosh').play();
if (currentGameLevel > maxGameLevel) {
// Player completed all levels - show victory
LK.showYouWin();
} else {
// Start next level mission after a delay
LK.setTimeout(function () {
initializeMission();
}, 2000);
}
}
}
bonusUX.setStreakCount(bonusUX.streakCount + 1);
// Track bubbles popped and restore swaps
storage.totalBubblesPopped += bubbles.length + disconnected.length;
// Restore 1 swap for successful pop, up to maximum of 3
if (storage.swapsRemaining < 3) {
storage.swapsRemaining++;
}
// Level progression: every 50 bubbles popped = next level
var newLevel = Math.floor(storage.totalBubblesPopped / 50) + 1;
if (newLevel > storage.currentLevel) {
storage.currentLevel = newLevel;
levelDisplayOverlay.updateLevel();
LK.getSound('levelUp').play();
LK.getSound('powerupSwoosh').play();
// Update grid speed for new level
gridSpeed = .5 + (storage.currentLevel - 1) * 0.1;
// Update max selectable bubble types
maxSelectableBubble = getMaxTypes();
}
} else {
bonusUX.setStreakCount(0);
LK.getSound('attachCircle').play();
}
//Add a grid movement effect when you don't do a match
var neighbors = self.getNeighbors(bubble);
var touched = [];
var neighbors2 = [];
for (var i = 0; i < neighbors.length; i++) {
var neighbor = neighbors[i];
if (neighbor) {
touched.push(neighbor);
neighbors2 = neighbors2.concat(self.getNeighbors(neighbor));
var ox = neighbor.x - bubble.x;
var oy = neighbor.y - bubble.y;
var angle = Math.atan2(oy, ox);
neighbor.x += Math.cos(angle) * 20;
neighbor.y += Math.sin(angle) * 20;
}
}
//One more layer
for (var i = 0; i < neighbors2.length; i++) {
var neighbor = neighbors2[i];
if (neighbor && touched.indexOf(neighbor) == -1) {
touched.push(neighbor);
var ox = neighbor.x - bubble.x;
var oy = neighbor.y - bubble.y;
var angle = Math.atan2(oy, ox);
neighbor.x += Math.cos(angle) * 10;
neighbor.y += Math.sin(angle) * 10;
}
}
//self.printRowsToConsole();
continue outer;
}
}
}
bubble.x = nextX;
bubble.y = nextY;
if (bubble.y + self.y < -1000) {
//Destory bubbles that somehow manages to escape at the top
bubblesInFlight.splice(a--, 1);
bubble.destroy();
}
}
if (gameIsStarted) {
self.y += gridSpeed * 0.5;
}
var zeroRow = rows[rows.length - 1];
if (zeroRow) {
for (var a = 0; a < zeroRow.length; a++) {
var bubble = zeroRow[a];
if (bubble) {
if (bubble.y + self.y > 0) {
insertRow();
}
break;
}
}
} else {
insertRow();
}
for (var row = rows.length - 1; row >= 0; row--) {
if (rows[row].every(function (bubble) {
return !bubble;
})) {
rows.splice(row, 1);
}
}
var lastRow = rows[0];
/*if(LK.ticks % 10 == 0){
self.printRowsToConsole()
}*/
if (lastRow) {
for (var a = 0; a < zeroRow.length; a++) {
var bubble = lastRow[a];
if (bubble) {
if (bubble.y + self.y > 2200) {
LK.effects.flashScreen(0xff0000, 3000);
LK.getSound('gameOverJingle').play();
LK.showGameOver();
}
if (gameIsStarted) {
var targetSpeed = Math.pow(Math.pow((2200 - (bubble.y + self.y)) / 2200, 2), 2) * 4 + 0.5;
if (bubble.y + self.y > 2000) {
targetSpeed = .2;
}
gridSpeed += (targetSpeed - gridSpeed) / 20;
if (LK.ticks % 10 == 0) {
//console.log(gridSpeed)
}
}
break;
}
}
}
};
for (var a = 0; a < 8; a++) {
insertRow();
}
});
var HintBubble = Container.expand(function () {
var self = Container.call(this);
var bubble = self.attachAsset('hintbubble', {
anchorX: 0.5,
anchorY: 0.5
});
self.setTint = function (tint) {
bubble.tint = tint;
};
self.getTint = function (tint) {
return bubble.tint;
};
});
var IceBall = Container.expand(function (max_types, type) {
var self = Container.call(this);
self.isFireBall = true;
self.isIceBall = true;
var state = 0;
self.isAttached = true;
self.isFreeBubble = false;
var speedX = 0;
var speedY = 0;
self.targetX = 0;
self.targetY = 0;
self.setPos = function (x, y) {
self.x = self.targetX = x;
self.y = self.targetY = y;
};
if (type !== undefined) {
this.type = type;
} else {
max_types = max_types || 3;
if (max_types > 4) {
self.type = Math.floor(Math.random() * (.8 + Math.random() * .2) * max_types);
} else {
self.type = Math.floor(Math.random() * max_types);
}
}
var bubbleGraphics = self.attachAsset('fireball', {
anchorX: 0.5,
anchorY: 0.5
});
bubbleGraphics.width = 150;
bubbleGraphics.height = 150;
bubbleGraphics.tint = 0x88ccff; // Ice blue tint
self.detach = function () {
freeBubbleLayer.addChild(self);
LK.getSound('detachCircle').play();
self.y += grid.y;
self.isAttached = false;
speedX = Math.random() * 40 - 20;
speedY = -Math.random() * 30;
self.down = undefined;
};
var spawnMod = 0;
self.update = function () {
if (self.isFreeBubble) {
if (++spawnMod % 3 == 0 && self.parent) {
// Spawn ice particles every 3 ticks
var angle = Math.random() * Math.PI * 2;
var iceParticle = self.parent.addChild(new IceParticle(angle));
iceParticle.x = self.x + Math.cos(angle) * self.width / 4;
iceParticle.y = self.y + Math.sin(angle) * self.width / 4;
}
return;
}
if (self.isAttached) {
if (self.x != self.targetX) {
self.x += (self.targetX - self.x) / 10;
}
if (self.y != self.targetY) {
self.y += (self.targetY - self.y) / 10;
}
} else {
self.x += speedX;
self.y += speedY;
speedY += 0.8; // More realistic gravity
speedX *= 0.995; // Add air resistance
if (self.x < bubbleSize / 2 && speedX < 0 || self.x > game.width - bubbleSize / 2 && speedX > 0) {
speedX = -speedX * 0.75; // Energy loss on wall bounce
LK.getSound('circleBounce').play();
}
// Check for collision with barriers
for (var i = 0; i < barriers.length; i++) {
var barrier = barriers[i];
var dx = self.x - barrier.x;
var dy = self.y - barrier.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var minDist = bubbleSize / 2 + barrier.width / 2;
if (distance < minDist) {
var angle = Math.atan2(dy, dx);
var newSpeed = Math.sqrt(speedX * speedX + speedY * speedY);
// More realistic collision response with proper momentum transfer
var restitution = 0.6; // Coefficient of restitution
speedX = Math.cos(angle) * newSpeed * restitution;
speedY = Math.sin(angle) * newSpeed * restitution;
var overlap = minDist - distance;
self.x += overlap * Math.cos(angle);
self.y += overlap * Math.sin(angle);
LK.getSound('circleBounce').play();
}
}
// Remove unattached bubbles that fall below 2732 - 500
if (self.y > 2732 - 400) {
self.destroy();
scoreMultipliers[Math.floor(self.x / (2048 / 6))].applyBubble(self);
LK.getSound('scoreCollected').play();
}
}
};
return self;
});
var IceParticle = Container.expand(function (angle) {
var self = Container.call(this);
var particleGraphics = self.attachAsset('fireparticle', {
anchorX: 0.5,
anchorY: 0.5
});
particleGraphics.blendMode = 1;
particleGraphics.tint = 0x88ccff; // Ice blue tint
var speedX = Math.cos(angle) * 1.5;
var speedY = Math.sin(angle) * 1.5;
var rotationSpeed = Math.random() * 0.05 - 0.025;
self.update = function () {
self.x += speedX * self.alpha;
self.y += speedY * self.alpha;
particleGraphics.rotation += rotationSpeed;
self.alpha -= 0.008;
if (self.alpha <= 0) {
self.destroy();
}
};
return self;
});
var LaserBall = Container.expand(function (max_types, type) {
var self = Container.call(this);
self.isFireBall = true;
self.isLaserBall = true;
var state = 0;
self.isAttached = true;
self.isFreeBubble = false;
var speedX = 0;
var speedY = 0;
self.targetX = 0;
self.targetY = 0;
self.setPos = function (x, y) {
self.x = self.targetX = x;
self.y = self.targetY = y;
};
if (type !== undefined) {
this.type = type;
} else {
max_types = max_types || 3;
if (max_types > 4) {
self.type = Math.floor(Math.random() * (.8 + Math.random() * .2) * max_types);
} else {
self.type = Math.floor(Math.random() * max_types);
}
}
var bubbleGraphics = self.attachAsset('fireball', {
anchorX: 0.5,
anchorY: 0.5
});
bubbleGraphics.width = 150;
bubbleGraphics.height = 150;
bubbleGraphics.tint = 0xff00ff; // Magenta tint
self.detach = function () {
freeBubbleLayer.addChild(self);
LK.getSound('detachCircle').play();
self.y += grid.y;
self.isAttached = false;
speedX = Math.random() * 40 - 20;
speedY = -Math.random() * 30;
self.down = undefined;
};
var spawnMod = 0;
self.update = function () {
if (self.isFreeBubble) {
if (++spawnMod % 2 == 0 && self.parent) {
// Spawn laser particles every 2 ticks
var angle = Math.random() * Math.PI * 2;
var laserParticle = self.parent.addChild(new LaserParticle(angle));
laserParticle.x = self.x + Math.cos(angle) * self.width / 4;
laserParticle.y = self.y + Math.sin(angle) * self.width / 4;
}
return;
}
if (self.isAttached) {
if (self.x != self.targetX) {
self.x += (self.targetX - self.x) / 10;
}
if (self.y != self.targetY) {
self.y += (self.targetY - self.y) / 10;
}
} else {
self.x += speedX;
self.y += speedY;
speedY += 0.8; // More realistic gravity
speedX *= 0.995; // Add air resistance
if (self.x < bubbleSize / 2 && speedX < 0 || self.x > game.width - bubbleSize / 2 && speedX > 0) {
speedX = -speedX * 0.75; // Energy loss on wall bounce
LK.getSound('circleBounce').play();
}
// Check for collision with barriers
for (var i = 0; i < barriers.length; i++) {
var barrier = barriers[i];
var dx = self.x - barrier.x;
var dy = self.y - barrier.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var minDist = bubbleSize / 2 + barrier.width / 2;
if (distance < minDist) {
var angle = Math.atan2(dy, dx);
var newSpeed = Math.sqrt(speedX * speedX + speedY * speedY);
// More realistic collision response with proper momentum transfer
var restitution = 0.6; // Coefficient of restitution
speedX = Math.cos(angle) * newSpeed * restitution;
speedY = Math.sin(angle) * newSpeed * restitution;
var overlap = minDist - distance;
self.x += overlap * Math.cos(angle);
self.y += overlap * Math.sin(angle);
LK.getSound('circleBounce').play();
}
}
// Remove unattached bubbles that fall below 2732 - 500
if (self.y > 2732 - 400) {
self.destroy();
scoreMultipliers[Math.floor(self.x / (2048 / 6))].applyBubble(self);
LK.getSound('scoreCollected').play();
}
}
};
return self;
});
var LaserParticle = Container.expand(function (angle) {
var self = Container.call(this);
var particleGraphics = self.attachAsset('fireparticle', {
anchorX: 0.5,
anchorY: 0.5
});
particleGraphics.blendMode = 1;
particleGraphics.tint = 0xff00ff; // Magenta tint
var speedX = Math.cos(angle) * 2;
var speedY = Math.sin(angle) * 2;
var rotationSpeed = Math.random() * 0.15 - 0.075;
self.update = function () {
self.x += speedX * self.alpha;
self.y += speedY * self.alpha;
particleGraphics.rotation += rotationSpeed;
self.alpha -= 0.015;
if (self.alpha <= 0) {
self.destroy();
}
};
return self;
});
var Launcher = Container.expand(function () {
var self = Container.call(this);
var bubble = self.addChild(new Bubble(getMaxTypes(), false));
bubble.isFreeBubble = true;
var previewBubble;
var lastTypes = [undefined, bubble.type];
function createPreviewBubble() {
var nextType;
do {
nextType = Math.floor(Math.random() * maxSelectableBubble);
} while (nextType == lastTypes[lastTypes.length - 1]); // Prevent consecutive same types
lastTypes.shift();
lastTypes.push(nextType);
previewBubble = self.addChildAt(new Bubble(maxSelectableBubble, false, nextType), 0);
previewBubble.scale.set(.7, .7);
previewBubble.x = -90;
previewBubble.y = 20;
previewBubble.isFreeBubble = true;
}
createPreviewBubble();
self.fire = function () {
bulletsFired++;
LK.getSound('fireBubble').play(); // Play sound when the ball is fired
grid.fireBubble(bubble, self.angle);
bubble = previewBubble;
previewBubble.x = previewBubble.y = 0;
previewBubble.scale.set(1, 1);
createPreviewBubble();
};
self.angle = -Math.PI / 2;
self.getBubble = function () {
return bubble;
};
self.isFireBall = function () {
return bubble.isFireBall;
};
self.triggerFireBall = function (type) {
bubble.destroy();
type = type || 0;
if (type === 1) {
bubble = self.addChild(new IceBall(getMaxTypes()));
} else if (type === 2) {
bubble = self.addChild(new LaserBall(getMaxTypes()));
} else if (type === 3) {
bubble = self.addChild(new BombBall(getMaxTypes()));
} else {
bubble = self.addChild(new Bubble(getMaxTypes(), true));
}
bubble.isFreeBubble = true;
};
self.swapBubbles = function () {
if (storage.swapsRemaining > 0) {
storage.swapsRemaining--;
// Swap current bubble with preview bubble
var tempType = bubble.type;
var tempIsFireBall = bubble.isFireBall;
// Store preview bubble properties
var previewType = previewBubble.type;
var previewIsFireBall = previewBubble.isFireBall;
// Destroy current bubbles
bubble.destroy();
previewBubble.destroy();
// Create new current bubble with preview properties
bubble = self.addChild(new Bubble(getMaxTypes(), previewIsFireBall, previewType));
bubble.isFreeBubble = true;
// Create new preview bubble with old current properties
previewBubble = self.addChildAt(new Bubble(getMaxTypes(), tempIsFireBall, tempType), 0);
previewBubble.scale.set(.7, .7);
previewBubble.x = -90;
previewBubble.y = 20;
previewBubble.isFreeBubble = true;
refreshHintLine();
LK.getSound('bubbleSwap').play();
LK.getSound('powerupSwoosh').play();
return true;
}
return false;
};
self.down = function () {
// Handle bubble swap when clicking on launcher
self.swapBubbles();
};
});
var LevelDisplayOverlay = Container.expand(function () {
var self = Container.call(this);
// Position in top left area with more margin for mobile
self.y = 140;
self.x = 180;
// Background circle - larger for mobile
var levelBG = self.attachAsset('countbg', {
anchorX: 0.5,
anchorY: 0.5
});
levelBG.width = 160;
levelBG.height = 160;
levelBG.x = 0;
levelBG.y = 0;
// Dropshadow for label
var labelShadow = self.addChild(new Text2(storage.currentLevel, {
size: 70,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
}));
labelShadow.anchor.set(.5, .5);
labelShadow.x = 3;
labelShadow.y = 3;
// Main label
var label = self.addChild(new Text2(storage.currentLevel, {
size: 70,
fill: 0xFFFFFF,
font: "Impact"
}));
label.anchor.set(.5, .5);
label.x = 0;
label.y = 0;
// Level text
var levelText = self.addChild(new Text2('LV', {
size: 40,
fill: 0xFFFFFF,
font: "Impact"
}));
levelText.anchor.set(.5, .5);
levelText.x = -50;
levelText.y = 0;
levelText.alpha = 0.7;
self.updateLevel = function () {
label.setText(storage.currentLevel);
labelShadow.setText(storage.currentLevel);
};
// Initialize display
self.updateLevel();
return self;
});
var MissionOverlay = Container.expand(function () {
var self = Container.call(this);
// Position in top right corner in GUI space - adjusted for mobile
self.x = 0;
self.y = 0;
// Mission background - larger for mobile readability
var missionBG = self.attachAsset('countbg', {
anchorX: 1,
anchorY: 0
});
missionBG.width = 380;
missionBG.height = 170;
missionBG.alpha = 0.9;
missionBG.x = -20;
missionBG.y = 130;
// Mission title - larger for mobile
var titleText = self.addChild(new Text2('GÖREV', {
size: 36,
fill: 0xFFFFFF,
font: "Impact"
}));
titleText.anchor.set(0.5, 0);
titleText.x = -190;
titleText.y = 140;
// Level indicator
var levelText = self.addChild(new Text2('LV 1', {
size: 24,
fill: 0xFFFF00,
font: "Impact"
}));
levelText.anchor.set(0.5, 0);
levelText.x = -175;
levelText.y = 100;
// Mission targets display - properly spaced
var targetTexts = [];
var targetColors = [0xff2853, 0x44d31f, 0x5252ff]; // Red, Green, Blue
for (var i = 0; i < 3; i++) {
var targetText = self.addChild(new Text2('0/0', {
size: 24,
fill: targetColors[i],
font: "Impact"
}));
targetText.anchor.set(0.5, 0.5);
targetText.x = -260 + i * 75;
targetText.y = 165;
targetTexts.push(targetText);
}
// Timer display - properly positioned
var timerText = self.addChild(new Text2('60s', {
size: 32,
fill: 0xFFFFFF,
font: "Impact"
}));
timerText.anchor.set(0.5, 0.5);
timerText.x = -175;
timerText.y = 200;
self.updateMission = function (targets, collected, timeLeft) {
for (var i = 0; i < 3; i++) {
targetTexts[i].setText(collected[i] + '/' + targets[i]);
}
timerText.setText(Math.ceil(timeLeft / 60) + 's');
levelText.setText('LV ' + currentGameLevel);
// Flash timer red when low
if (timeLeft < 600) {
// 10 seconds
timerText.fill = timeLeft % 20 < 10 ? 0xff0000 : 0xFFFFFF;
}
};
return self;
});
var PowerupBubble = Container.expand(function () {
var self = Container.call(this);
var bubbleGraphics = self.attachAsset('fireball', {
anchorX: 0.5,
anchorY: 0.5
});
bubbleGraphics.width = 150;
bubbleGraphics.height = 150;
self.type = -1; // Special type for powerup
self.isPowerup = true;
self.isAttached = true;
self.isFreeBubble = false;
var speedX = 0;
var speedY = 0;
self.targetX = 0;
self.targetY = 0;
self.setPos = function (x, y) {
self.x = self.targetX = x;
self.y = self.targetY = y;
};
self.detach = function () {
freeBubbleLayer.addChild(self);
LK.getSound('detachCircle').play();
self.y += grid.y;
self.isAttached = false;
speedX = Math.random() * 40 - 20;
speedY = -Math.random() * 30;
self.down = undefined;
};
var spawnMod = 0;
self.update = function () {
if (self.isFreeBubble) {
return;
}
if (self.isAttached) {
if (self.x != self.targetX) {
self.x += (self.targetX - self.x) / 10;
}
if (self.y != self.targetY) {
self.y += (self.targetY - self.y) / 10;
}
} else {
self.x += speedX;
self.y += speedY;
speedY += 0.8; // More realistic gravity
speedX *= 0.995; // Add air resistance
if (self.x < bubbleSize / 2 && speedX < 0 || self.x > game.width - bubbleSize / 2 && speedX > 0) {
speedX = -speedX * 0.75; // Energy loss on wall bounce
LK.getSound('circleBounce').play();
}
// Check for collision with barriers
for (var i = 0; i < barriers.length; i++) {
var barrier = barriers[i];
var dx = self.x - barrier.x;
var dy = self.y - barrier.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var minDist = bubbleSize / 2 + barrier.width / 2;
if (distance < minDist) {
var angle = Math.atan2(dy, dx);
var newSpeed = Math.sqrt(speedX * speedX + speedY * speedY);
// More realistic collision response with proper momentum transfer
var restitution = 0.6; // Coefficient of restitution
speedX = Math.cos(angle) * newSpeed * restitution;
speedY = Math.sin(angle) * newSpeed * restitution;
var overlap = minDist - distance;
self.x += overlap * Math.cos(angle);
self.y += overlap * Math.sin(angle);
LK.getSound('circleBounce').play();
}
}
// When powerup reaches the bottom of the screen, trigger powerup earned animation
if (self.y > 2732 - 400) {
// Play sound
LK.getSound('scoreCollected').play();
// Create and start powerup earned animation
var animation = game.addChild(new PowerupEarnedAnimation(self.x, self.y));
animation.start();
// Destroy the original bubble
self.destroy();
}
}
};
});
var PowerupEarnedAnimation = Container.expand(function (startX, startY) {
var self = Container.call(this);
// Create a 2x size powerup graphic
var powerupGraphics = self.attachAsset('fireball', {
anchorX: 0.5,
anchorY: 0.5
});
// Create dropshadow for earnedText (not a child of self, but a global overlay)
var earnedTextShadow = new Text2('POWERUP EARNED!', {
size: 150,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
});
earnedTextShadow.anchor.set(0.5, 0.5);
earnedTextShadow.y = game.height / 2 - 250 + 20 + 100 + 8;
earnedTextShadow.x = game.width / 2 + 8;
earnedTextShadow.alpha = 0;
// Create main earnedText (no stroke)
var earnedText = new Text2('POWERUP EARNED!', {
size: 150,
fill: 0xFFFFFF,
font: "Impact"
});
earnedText.anchor.set(0.5, 0.5);
powerupGraphics.width = 150;
powerupGraphics.height = 150;
earnedText.y = game.height / 2 - 250 + 20 + 100;
earnedText.x = game.width / 2;
earnedText.alpha = 0;
// Set initial position
self.x = startX;
self.y = startY;
// Animation phases
var phase = 0;
self.start = function () {
// Add text and dropshadow to overlay (so it doesn't move with self)
LK.getSound('powerupSwoosh').play();
if (!earnedTextShadow.parent) {
game.addChild(earnedTextShadow);
}
if (!earnedText.parent) {
game.addChild(earnedText);
}
// Phase 1: Move to center of screen
tween(powerupGraphics.scale, {
x: 2,
y: 2
}, {
duration: 300,
easing: tween.easeIn
});
tween(self, {
x: game.width / 2,
y: game.height / 2
}, {
duration: 300,
easing: tween.easeIn
});
// Show text and dropshadow
tween(earnedTextShadow, {
alpha: 0.35,
y: game.height / 2 - 250 + 8
}, {
duration: 300,
delay: 100,
easing: tween.easeOut
});
tween(earnedText, {
alpha: 1,
y: game.height / 2 - 250
}, {
duration: 300,
delay: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
// Wait a moment before moving to powerup counter
LK.setTimeout(function () {
LK.getSound('powerupSwoosh').play();
// Get target position (powerup counter)
var targetX = fireBallPowerupOverlay.x;
var targetY = fireBallPowerupOverlay.y;
// Phase 2: Move to powerup counter
tween(self, {
x: targetX,
y: targetY
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
LK.getSound('powerupThump').play();
// Increment powerup counter
fireBallPowerupOverlay.increaseFireballCount();
// Destroy animation
if (earnedTextShadow.parent) {
earnedTextShadow.parent.removeChild(earnedTextShadow);
}
if (earnedText.parent) {
earnedText.parent.removeChild(earnedText);
}
self.destroy();
}
});
// Fade out text and dropshadow as we move
tween(earnedTextShadow, {
alpha: 0,
y: game.height / 2 - 250 - 20 + 8
}, {
duration: 300,
easing: tween.easeOut
});
tween(earnedText, {
alpha: 0,
y: game.height / 2 - 250 - 20
}, {
duration: 300,
easing: tween.easeOut
});
// Scale down as we move to counter
tween(powerupGraphics, {
width: 210,
height: 210
}, {
duration: 300,
easing: tween.easeIn
});
tween(powerupGraphics.scale, {
x: 1,
y: 1
}, {
duration: 300,
easing: tween.easeIn
});
}, 1000);
}
});
};
return self;
});
// Make active when we have fireballs
var PowerupParticle = Container.expand(function (angle, speed) {
var self = Container.call(this);
var particle = self.attachAsset('fireball', {
anchorX: 0.5,
anchorY: 0.5
});
particle.width = 60;
particle.height = 60;
self.scale.set(0.7 + Math.random() * 0.5, 0.7 + Math.random() * 0.5);
self.alpha = 1;
var vx = Math.cos(angle) * speed;
var vy = Math.sin(angle) * speed;
var gravity = 0.5 + Math.random() * 0.3;
var life = 24 + Math.floor(Math.random() * 10);
var tick = 0;
self.update = function () {
self.x += vx;
self.y += vy;
vy += gravity;
self.alpha -= 0.04 + Math.random() * 0.01;
tick++;
if (tick > life || self.alpha <= 0) {
self.destroy();
}
};
return self;
});
var ScoreIndicatorLabel = Container.expand(function (score, type) {
var self = Container.call(this);
var label = new Text2(score, {
size: 100,
fill: "#" + bubbleColors[type].toString(16).padStart(6, '0'),
font: "Impact"
});
label.anchor.set(0.5, 0);
self.addChild(label);
self.update = function () {
self.y -= 7;
self.alpha -= .05;
if (self.alpha <= 0) {
self.destroy();
increaseScore(score);
}
};
});
var ScoreMultipliers = Container.expand(function (baseValue) {
var self = Container.call(this);
// Dropshadow for scoreMultiplierLabel
var scoreMultiplierLabelShadow = new Text2(baseValue, {
size: 100,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
});
scoreMultiplierLabelShadow.anchor.set(0.5, 0);
self.addChild(scoreMultiplierLabelShadow);
// Create a score label text string for ScoreMultipliers
var scoreMultiplierLabel = new Text2(baseValue, {
size: 100,
fill: 0x3954FF,
font: "Impact"
});
scoreMultiplierLabel.anchor.set(0.5, 0);
self.addChild(scoreMultiplierLabel);
var currentMultiplier = 1;
self.applyBubble = function (bubble) {
var scoreValue = baseValue * currentMultiplier;
// Check if bubble color matches the pool color for bonus scoring
var poolIndex = Math.floor(self.x / (2048 / 6)); // Determine which pool this is (0-5)
var isColorMatch = false;
// Check if bubble type matches pool color (red=0, green=1, blue=2, purple=3, cyan=4, pink=5)
if (bubble.type >= 0 && bubble.type < 6 && bubble.type === poolIndex) {
isColorMatch = true;
scoreValue *= 3; // Triple score for matching color
// Play bonus sound
LK.getSound('comboBonus').play();
// Create special color match particle effect
var bonusParticle = particlesLayer.addChild(new BubbleRemoveParticle());
bonusParticle.x = bubble.x;
bonusParticle.y = self.y + 100;
bonusParticle.scale.set(2, 2);
bonusParticle.alpha = 0.8;
}
var scoreIndicator = game.addChild(new ScoreIndicatorLabel(scoreValue, bubble.type));
scoreIndicator.x = self.x;
scoreIndicator.y = self.y;
// Make score indicator bigger for color matches
if (isColorMatch) {
scoreIndicator.scale.set(1.5, 1.5);
}
// Award coins based on score
var coinsEarned = Math.floor(scoreValue / 50);
if (doubleCoinsActive) {
coinsEarned *= 2; // Double coins when power-up is active
}
if (coinsEarned > 0) {
storage.coins += coinsEarned;
}
var particle = particlesLayer.addChild(new BubbleRemoveParticle());
particle.x = bubble.x;
particle.y = self.y + 150;
// Track mission progress
if (missionActive && bubble.type >= 0 && bubble.type < 3) {
missionCollected[bubble.type]++;
// Check if mission is complete
if (checkMissionComplete()) {
missionActive = false;
// Mission completed - continue playing
LK.effects.flashScreen(0x00ff00, 1000); // Green flash for success
}
}
};
self.setMultiplier = function (multiplier) {
currentMultiplier = multiplier;
scoreMultiplierLabel.setText(baseValue * currentMultiplier);
scoreMultiplierLabelShadow.setText(baseValue * currentMultiplier);
};
});
var ShopOverlay = Container.expand(function () {
var self = Container.call(this);
// Full screen background
var background = self.attachAsset('uxoverlay', {
anchorX: 0,
anchorY: 0
});
background.width = 2048;
background.height = 2732;
background.alpha = 0.95;
// Shop title
var titleShadow = self.addChild(new Text2('SHOP', {
size: 150,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
}));
titleShadow.anchor.set(0.5, 0.5);
titleShadow.x = game.width / 2 + 4;
titleShadow.y = 300 + 4;
var titleText = self.addChild(new Text2('SHOP', {
size: 150,
fill: 0xFFFFFF,
font: "Impact"
}));
titleText.anchor.set(0.5, 0.5);
titleText.x = game.width / 2;
titleText.y = 300;
// Coins display
var coinsText = self.addChild(new Text2('Coins: ' + storage.coins, {
size: 80,
fill: 0xFFD700,
font: "Impact"
}));
coinsText.anchor.set(0.5, 0.5);
coinsText.x = game.width / 2;
coinsText.y = 400;
// Shop items
var shopItems = [{
name: 'Extra Swaps',
description: 'Start with +1 swap',
cost: 100,
maxLevel: 3,
upgrade: 'extraSwaps'
}, {
name: 'Power Balls',
description: 'Start with +1 fireball',
cost: 200,
maxLevel: 3,
upgrade: 'powerBalls'
}, {
name: 'Time Bonus',
description: '+10s mission time',
cost: 150,
maxLevel: 5,
upgrade: 'timeBonus'
}];
var itemElements = [];
for (var i = 0; i < shopItems.length; i++) {
var item = shopItems[i];
var yPos = 550 + i * 220;
var currentLevel = storage.shopUpgrades[item.upgrade];
var isMaxed = currentLevel >= item.maxLevel;
var actualCost = item.cost + currentLevel * Math.floor(item.cost * 0.5);
var canAfford = storage.coins >= actualCost;
// Item background
var itemBG = self.attachAsset('countbg', {
anchorX: 0.5,
anchorY: 0.5
});
itemBG.x = game.width / 2;
itemBG.y = yPos;
itemBG.width = 800;
itemBG.height = 180;
itemBG.alpha = 0.7;
// Item name
var itemName = self.addChild(new Text2(item.name, {
size: 60,
fill: 0xFFFFFF,
font: "Impact"
}));
itemName.anchor.set(0.5, 0.5);
itemName.x = game.width / 2;
itemName.y = yPos - 50;
// Item description
var itemDesc = self.addChild(new Text2(item.description, {
size: 40,
fill: 0xCCCCCC,
font: "Impact"
}));
itemDesc.anchor.set(0.5, 0.5);
itemDesc.x = game.width / 2;
itemDesc.y = yPos - 15;
// Level indicator
var levelText = self.addChild(new Text2('Level: ' + currentLevel + '/' + item.maxLevel, {
size: 35,
fill: 0xFFFF00,
font: "Impact"
}));
levelText.anchor.set(0.5, 0.5);
levelText.x = game.width / 2 - 200;
levelText.y = yPos + 25;
// Cost display underneath
var costDisplayText = self.addChild(new Text2(isMaxed ? 'MAXED OUT' : actualCost + ' coins', {
size: 50,
fill: isMaxed ? 0x888888 : 0xFFD700,
font: "Impact"
}));
costDisplayText.anchor.set(0.5, 0.5);
costDisplayText.x = game.width / 2;
costDisplayText.y = yPos + 55;
// Buy button - larger for mobile touch
var buyButtonBG = self.attachAsset('countbg', {
anchorX: 0.5,
anchorY: 0.5
});
buyButtonBG.x = game.width / 2 + 200;
buyButtonBG.y = yPos + 25;
buyButtonBG.width = 200; // Larger touch target
buyButtonBG.height = 80; // Larger touch target
buyButtonBG.alpha = isMaxed ? 0.3 : canAfford ? 0.8 : 0.5;
var buttonText = isMaxed ? 'MAXED' : canAfford ? 'BUY' : 'LOCKED';
var buttonColor = isMaxed ? 0x666666 : canAfford ? 0x00FF00 : 0xFF0000;
var buyText = self.addChild(new Text2(buttonText, {
size: 40,
fill: buttonColor,
font: "Impact"
}));
buyText.anchor.set(0.5, 0.5);
buyText.x = game.width / 2 + 200;
buyText.y = yPos + 25;
itemElements.push({
bg: itemBG,
buyBG: buyButtonBG,
item: item,
buyText: buyText,
levelText: levelText,
costText: costDisplayText,
canAfford: canAfford,
isMaxed: isMaxed,
actualCost: actualCost
});
}
// Back button
var backButtonBG = self.attachAsset('countbg', {
anchorX: 0.5,
anchorY: 0.5
});
backButtonBG.x = game.width / 2;
backButtonBG.y = game.height - 300;
backButtonBG.width = 300;
backButtonBG.height = 100;
var backButtonText = self.addChild(new Text2('BACK', {
size: 60,
fill: 0xFFFFFF,
font: "Impact"
}));
backButtonText.anchor.set(0.5, 0.5);
backButtonText.x = game.width / 2;
backButtonText.y = game.height - 300;
// Click handler
self.down = function (x, y, obj) {
// Check back button
var backLeft = backButtonBG.x - backButtonBG.width / 2;
var backRight = backButtonBG.x + backButtonBG.width / 2;
var backTop = backButtonBG.y - backButtonBG.height / 2;
var backBottom = backButtonBG.y + backButtonBG.height / 2;
if (x >= backLeft && x <= backRight && y >= backTop && y <= backBottom) {
self.closeShop();
return;
}
// Check shop items - specifically the buy button area
for (var i = 0; i < itemElements.length; i++) {
var element = itemElements[i];
var buyLeft = element.buyBG.x - element.buyBG.width / 2;
var buyRight = element.buyBG.x + element.buyBG.width / 2;
var buyTop = element.buyBG.y - element.buyBG.height / 2;
var buyBottom = element.buyBG.y + element.buyBG.height / 2;
if (x >= buyLeft && x <= buyRight && y >= buyTop && y <= buyBottom) {
self.buyItem(element.item, i);
break;
}
}
};
self.buyItem = function (item, index) {
var currentLevel = storage.shopUpgrades[item.upgrade];
if (currentLevel >= item.maxLevel) return;
var actualCost = item.cost + currentLevel * Math.floor(item.cost * 0.5);
if (storage.coins < actualCost) return;
storage.coins -= actualCost;
storage.shopUpgrades[item.upgrade]++;
LK.getSound('buttonPress').play();
LK.getSound('scoreCollected').play();
// Update display
self.updateDisplay();
};
self.updateDisplay = function () {
coinsText.setText('Coins: ' + storage.coins);
for (var i = 0; i < itemElements.length; i++) {
var element = itemElements[i];
var item = element.item;
var currentLevel = storage.shopUpgrades[item.upgrade];
var isMaxed = currentLevel >= item.maxLevel;
var actualCost = item.cost + currentLevel * Math.floor(item.cost * 0.5);
var canAfford = storage.coins >= actualCost;
element.levelText.setText('Level: ' + currentLevel + '/' + item.maxLevel);
element.costText.setText(isMaxed ? 'MAXED OUT' : actualCost + ' coins');
element.costText.fill = isMaxed ? 0x888888 : 0xFFD700;
var buttonText = isMaxed ? 'MAXED' : canAfford ? 'BUY' : 'LOCKED';
var buttonColor = isMaxed ? 0x666666 : canAfford ? 0x00FF00 : 0xFF0000;
element.buyText.setText(buttonText);
element.buyText.fill = buttonColor;
element.buyBG.alpha = isMaxed ? 0.3 : canAfford ? 0.8 : 0.5;
element.canAfford = canAfford;
element.isMaxed = isMaxed;
element.actualCost = actualCost;
}
};
self.closeShop = function () {
LK.getSound('buttonPress').play();
tween(self, {
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
var SlowTimeBubble = Container.expand(function () {
var self = Container.call(this);
// Use a blue-tinted bubble to represent slow time
var bubbleGraphics = self.attachAsset('bubble2', {
anchorX: 0.5,
anchorY: 0.5
});
// Make it visually distinct with blue tint
bubbleGraphics.tint = 0x4444ff;
self.type = -5; // Special type for slow time bubble
self.isSlowTimeBubble = true;
self.isAttached = true;
self.isFreeBubble = false;
var speedX = 0;
var speedY = 0;
self.targetX = 0;
self.targetY = 0;
self.setPos = function (x, y) {
self.x = self.targetX = x;
self.y = self.targetY = y;
};
self.detach = function () {
freeBubbleLayer.addChild(self);
LK.getSound('detachCircle').play();
self.y += grid.y;
self.isAttached = false;
speedX = Math.random() * 40 - 20;
speedY = -Math.random() * 30;
self.down = undefined;
};
self.update = function () {
if (self.isFreeBubble) {
return;
}
if (self.isAttached) {
if (self.x != self.targetX) {
self.x += (self.targetX - self.x) / 10;
}
if (self.y != self.targetY) {
self.y += (self.targetY - self.y) / 10;
}
} else {
self.x += speedX;
self.y += speedY;
speedY += 0.8;
speedX *= 0.995;
if (self.x < bubbleSize / 2 && speedX < 0 || self.x > game.width - bubbleSize / 2 && speedX > 0) {
speedX = -speedX * 0.75;
LK.getSound('circleBounce').play();
}
// Check for collision with barriers
for (var i = 0; i < barriers.length; i++) {
var barrier = barriers[i];
var dx = self.x - barrier.x;
var dy = self.y - barrier.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var minDist = bubbleSize / 2 + barrier.width / 2;
if (distance < minDist) {
var angle = Math.atan2(dy, dx);
var newSpeed = Math.sqrt(speedX * speedX + speedY * speedY);
var restitution = 0.6;
speedX = Math.cos(angle) * newSpeed * restitution;
speedY = Math.sin(angle) * newSpeed * restitution;
var overlap = minDist - distance;
self.x += overlap * Math.cos(angle);
self.y += overlap * Math.sin(angle);
LK.getSound('circleBounce').play();
}
}
// When slow time bubble reaches bottom, slow down grid movement
if (self.y > 2732 - 400) {
self.activateSlowTime();
self.destroy();
}
}
};
self.activateSlowTime = function () {
// Slow down grid movement by 70% for 10 seconds
var originalSpeed = gridSpeed;
gridSpeed *= 0.3; // Slow to 30% speed
LK.getSound('powerupSwoosh').play();
LK.getSound('timeBubbleCollect').play();
// Visual effect
LK.effects.flashScreen(0x4444ff, 500);
// Show effect text
var effectText = game.addChild(new Text2('SLOW TIME!', {
size: 120,
fill: 0x4444ff,
font: "Impact"
}));
effectText.anchor.set(0.5, 0.5);
effectText.x = game.width / 2;
effectText.y = game.height / 2;
effectText.alpha = 1;
tween(effectText, {
y: effectText.y - 200,
alpha: 0
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
effectText.destroy();
}
});
// Restore normal speed after 10 seconds
LK.setTimeout(function () {
gridSpeed = originalSpeed;
}, 10000);
};
return self;
});
var StartScreenOverlay = Container.expand(function () {
var self = Container.call(this);
// Full screen background
var background = self.attachAsset('uxoverlay', {
anchorX: 0,
anchorY: 0
});
background.width = 2048;
background.height = 2732;
background.alpha = 0.95;
// Game title
var titleShadow = self.addChild(new Text2('BUBBLE CLASH', {
size: 200,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
}));
titleShadow.anchor.set(0.5, 0.5);
titleShadow.x = game.width / 2 + 6;
titleShadow.y = game.height / 2 - 200 + 6;
var titleText = self.addChild(new Text2('BUBBLE CLASH', {
size: 200,
fill: 0xFFFFFF,
font: "Impact"
}));
titleText.anchor.set(0.5, 0.5);
titleText.x = game.width / 2;
titleText.y = game.height / 2 - 200;
// Play button background - larger for mobile
var playButtonBG = self.attachAsset('countbg', {
anchorX: 0.5,
anchorY: 0.5
});
playButtonBG.x = game.width / 2;
playButtonBG.y = game.height / 2 + 50;
playButtonBG.width = 500; // Larger touch area
playButtonBG.height = 150; // Larger touch area
playButtonBG.alpha = 0.1; // Slightly visible for better mobile UX
// Play button text shadow
var playButtonShadow = self.addChild(new Text2('PLAY', {
size: 80,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
}));
playButtonShadow.anchor.set(0.5, 0.5);
playButtonShadow.x = game.width / 2 + 4;
playButtonShadow.y = game.height / 2 + 50 + 4;
// Play button text
var playButtonText = self.addChild(new Text2('PLAY', {
size: 80,
fill: 0xFFFFFF,
font: "Impact"
}));
playButtonText.anchor.set(0.5, 0.5);
playButtonText.x = game.width / 2;
playButtonText.y = game.height / 2 + 50;
// Shop button background
var shopButtonBG = self.attachAsset('countbg', {
anchorX: 0.5,
anchorY: 0.5
});
shopButtonBG.x = game.width / 2;
shopButtonBG.y = game.height / 2 + 200;
shopButtonBG.width = 300;
shopButtonBG.height = 100;
shopButtonBG.alpha = 0; // Make shop button transparent like play button
// Shop button text shadow
var shopButtonShadow = self.addChild(new Text2('SHOP', {
size: 60,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
}));
shopButtonShadow.anchor.set(0.5, 0.5);
shopButtonShadow.x = game.width / 2 + 3;
shopButtonShadow.y = game.height / 2 + 200 + 3;
// Shop button text
var shopButtonText = self.addChild(new Text2('SHOP', {
size: 60,
fill: 0xFFD700,
font: "Impact"
}));
shopButtonText.anchor.set(0.5, 0.5);
shopButtonText.x = game.width / 2;
shopButtonText.y = game.height / 2 + 200;
// Instructions text
var instructionText = self.addChild(new Text2('Tap to aim and shoot bubbles!', {
size: 60,
fill: 0xFFFFFF,
font: "Impact"
}));
instructionText.anchor.set(0.5, 0.5);
instructionText.x = game.width / 2;
instructionText.y = game.height / 2 + 300;
instructionText.alpha = 0.8;
// Mission info text
var missionInfoText = self.addChild(new Text2('Complete color missions before time runs out!', {
size: 45,
fill: 0xFFFF00,
font: "Impact"
}));
missionInfoText.anchor.set(0.5, 0.5);
missionInfoText.x = game.width / 2;
missionInfoText.y = game.height / 2 + 370;
missionInfoText.alpha = 0.9;
// Coins display
var coinsDisplay = self.addChild(new Text2('Coins: ' + storage.coins, {
size: 60,
fill: 0xFFD700,
font: "Impact"
}));
coinsDisplay.anchor.set(0.5, 0.5);
coinsDisplay.x = game.width / 2;
coinsDisplay.y = game.height / 2 + 480;
coinsDisplay.alpha = 1.0;
// Play button click handler
self.down = function (x, y, obj) {
// Check if click is within play button bounds
var buttonLeft = playButtonBG.x - playButtonBG.width / 2;
var buttonRight = playButtonBG.x + playButtonBG.width / 2;
var buttonTop = playButtonBG.y - playButtonBG.height / 2;
var buttonBottom = playButtonBG.y + playButtonBG.height / 2;
if (x >= buttonLeft && x <= buttonRight && y >= buttonTop && y <= buttonBottom) {
// Start the game
self.startGame();
return;
}
// Check if click is within shop button bounds
var shopLeft = shopButtonBG.x - shopButtonBG.width / 2;
var shopRight = shopButtonBG.x + shopButtonBG.width / 2;
var shopTop = shopButtonBG.y - shopButtonBG.height / 2;
var shopBottom = shopButtonBG.y + shopButtonBG.height / 2;
if (x >= shopLeft && x <= shopRight && y >= shopTop && y <= shopBottom) {
// Open shop
self.openShop();
}
};
self.openShop = function () {
LK.getSound('buttonPress').play();
var shop = game.addChild(new ShopOverlay());
};
self.startGame = function () {
LK.getSound('buttonPress').play();
LK.getSound('powerupThump').play();
// Hide start screen with fade out animation
tween(self, {
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
gameStarted = true;
// Apply shop upgrades
applyShopUpgrades();
}
});
};
return self;
});
var SwapsCounterOverlay = Container.expand(function () {
var self = Container.call(this);
// Position in top right area with more margin for mobile
self.y = 140;
self.x = game.width - 250;
// Background circle - larger for mobile
var countBG = self.attachAsset('countbg', {
anchorX: 0.5,
anchorY: 0.5
});
countBG.width = 160;
countBG.height = 160;
countBG.x = 0;
countBG.y = 0;
// Dropshadow for label
var labelShadow = self.addChild(new Text2(storage.swapsRemaining, {
size: 70,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
}));
labelShadow.anchor.set(.5, .5);
labelShadow.x = 3;
labelShadow.y = 3;
// Main label
var label = self.addChild(new Text2(storage.swapsRemaining, {
size: 70,
fill: 0xFFFFFF,
font: "Impact"
}));
label.anchor.set(.5, .5);
label.x = 0;
label.y = 0;
// Swap icon (using bubble asset as placeholder)
var swapIcon = self.attachAsset('bubble0', {
anchorX: 0.5,
anchorY: 0.5
});
swapIcon.x = -80;
swapIcon.y = 0;
swapIcon.width = 80;
swapIcon.height = 80;
swapIcon.alpha = 0.7;
self.updateCount = function () {
label.setText(storage.swapsRemaining);
labelShadow.setText(storage.swapsRemaining);
// Fade out when no swaps remaining
self.alpha = storage.swapsRemaining > 0 ? 1 : 0.5;
};
// Initialize display
self.updateCount();
return self;
});
var TimeBubble = Container.expand(function () {
var self = Container.call(this);
// Use a green-tinted bubble to represent time bonus
var bubbleGraphics = self.attachAsset('bubble1', {
anchorX: 0.5,
anchorY: 0.5
});
// Make it visually distinct with green tint and glow effect
bubbleGraphics.tint = 0x00ff00;
self.type = -2; // Special type for time bubble
self.isTimeBubble = true;
self.isAttached = true;
self.isFreeBubble = false;
var speedX = 0;
var speedY = 0;
self.targetX = 0;
self.targetY = 0;
self.setPos = function (x, y) {
self.x = self.targetX = x;
self.y = self.targetY = y;
};
self.detach = function () {
freeBubbleLayer.addChild(self);
LK.getSound('detachCircle').play();
self.y += grid.y;
self.isAttached = false;
speedX = Math.random() * 40 - 20;
speedY = -Math.random() * 30;
self.down = undefined;
};
// Pulsing animation for visual appeal
var pulseScale = 1.0;
var pulseDirection = 1;
self.update = function () {
if (self.isFreeBubble) {
return;
}
if (self.isAttached) {
if (self.x != self.targetX) {
self.x += (self.targetX - self.x) / 10;
}
if (self.y != self.targetY) {
self.y += (self.targetY - self.y) / 10;
}
// Pulsing animation
pulseScale += pulseDirection * 0.01;
if (pulseScale > 1.1) {
pulseDirection = -1;
} else if (pulseScale < 0.9) {
pulseDirection = 1;
}
self.scale.set(pulseScale, pulseScale);
} else {
self.x += speedX;
self.y += speedY;
speedY += 0.8; // More realistic gravity
speedX *= 0.995; // Add air resistance
if (self.x < bubbleSize / 2 && speedX < 0 || self.x > game.width - bubbleSize / 2 && speedX > 0) {
speedX = -speedX * 0.75; // Energy loss on wall bounce
LK.getSound('circleBounce').play();
}
// Check for collision with barriers
for (var i = 0; i < barriers.length; i++) {
var barrier = barriers[i];
var dx = self.x - barrier.x;
var dy = self.y - barrier.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var minDist = bubbleSize / 2 + barrier.width / 2;
if (distance < minDist) {
var angle = Math.atan2(dy, dx);
var newSpeed = Math.sqrt(speedX * speedX + speedY * speedY);
// More realistic collision response with proper momentum transfer
var restitution = 0.6; // Coefficient of restitution
speedX = Math.cos(angle) * newSpeed * restitution;
speedY = Math.sin(angle) * newSpeed * restitution;
var overlap = minDist - distance;
self.x += overlap * Math.cos(angle);
self.y += overlap * Math.sin(angle);
LK.getSound('circleBounce').play();
}
}
// When time bubble reaches bottom, add time to mission
if (self.y > 2732 - 400) {
LK.getSound('timeBubbleCollect').play();
LK.getSound('scoreCollected').play();
LK.getSound('powerupSwoosh').play();
// Add 15 seconds to mission time
if (missionActive) {
missionTimeLeft += 15 * 60; // 15 seconds in ticks
// Show time bonus animation
var bonusText = game.addChild(new Text2('+15s', {
size: 120,
fill: 0x00ff00,
font: "Impact"
}));
bonusText.anchor.set(0.5, 0.5);
bonusText.x = self.x;
bonusText.y = self.y - 100;
bonusText.alpha = 1;
tween(bonusText, {
y: bonusText.y - 200,
alpha: 0
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
bonusText.destroy();
}
});
}
self.destroy();
}
}
};
return self;
});
var WarningLine = Container.expand(function () {
var self = Container.call(this);
var warning = self.attachAsset('warningstripe', {
anchorX: .5,
anchorY: .5
});
var warningOffset = Math.random() * 100;
var speed = Math.random() * 1 + 1;
self.update = function () {
warningOffset += speed;
warning.alpha = (Math.cos(warningOffset / 50) + 1) / 2 * 0.3 + .7;
};
warning.blendMode = 1;
warning.rotation = .79;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0c0d25
});
/****
* Game Code
****/
var gridSpeed = .5 + (storage.currentLevel - 1) * 0.1; // Increase speed by 0.1 per level
var gameStarted = false; // Flag to track if game has started
function increaseScore(amount) {
var currentScore = LK.getScore();
var newScore = currentScore + amount;
LK.setScore(newScore); // Update the game score using LK method
scoreLabel.setText(newScore.toString()); // Update the score label with the new score
scoreLabelShadow.setText(newScore.toString()); // Update the shadow as well
}
//Game size 2048x2732
/*
Todo:
[X] Make sure we GC nodes that drop of screen
[ ] Make preview line fade out at the end
*/
var bulletsFired = 0; //3*30+1
var bubbleSize = 150;
var gameIsStarted = false;
var bubbleColors = [0xff2853, 0x44d31f, 0x5252ff, 0xcb2bff, 0x28f2f0, 0xff69b4];
var barriers = [];
var maxSelectableBubble = getMaxTypes();
var warningLines = [];
// Mission system variables
var missionTargets = [0, 0, 0]; // Targets for red, green, blue bubbles
var missionCollected = [0, 0, 0]; // Collected count for each color
var missionTimeLeft = 3600; // 60 seconds * 60 ticks per second
var missionActive = false;
var missionOverlay;
var currentGameLevel = 1; // Current level (1-10)
var maxGameLevel = 10;
var doubleCoinsActive = false; // Track if double coins power-up is active
// Level-based mission configurations
var levelMissions = [{
level: 1,
targets: [8, 6, 4],
time: 45
},
// Level 1: 45 seconds
{
level: 2,
targets: [10, 8, 6],
time: 50
},
// Level 2: 50 seconds
{
level: 3,
targets: [12, 10, 8],
time: 55
},
// Level 3: 55 seconds
{
level: 4,
targets: [15, 12, 10],
time: 60
},
// Level 4: 60 seconds
{
level: 5,
targets: [18, 15, 12],
time: 65
},
// Level 5: 65 seconds
{
level: 6,
targets: [20, 18, 15],
time: 70
},
// Level 6: 70 seconds
{
level: 7,
targets: [25, 20, 18],
time: 75
},
// Level 7: 75 seconds
{
level: 8,
targets: [30, 25, 20],
time: 80
},
// Level 8: 80 seconds
{
level: 9,
targets: [35, 30, 25],
time: 85
},
// Level 9: 85 seconds
{
level: 10,
targets: [40, 35, 30],
time: 90
} // Level 10: 90 seconds
];
// Initialize mission based on current level
function initializeMission() {
var levelConfig = levelMissions[currentGameLevel - 1];
if (levelConfig) {
for (var i = 0; i < 3; i++) {
missionTargets[i] = levelConfig.targets[i];
missionCollected[i] = 0;
}
// Apply time bonus from shop upgrades
var bonusTime = storage.shopUpgrades.timeBonus * 10; // 10 seconds per upgrade level
missionTimeLeft = (levelConfig.time + bonusTime) * 60; // Convert to ticks
missionActive = true;
}
}
// Check if mission is completed
function checkMissionComplete() {
for (var i = 0; i < 3; i++) {
if (missionCollected[i] < missionTargets[i]) {
return false;
}
}
return true;
}
function applyShopUpgrades() {
// Apply extra swaps upgrade
storage.swapsRemaining += storage.shopUpgrades.extraSwaps;
if (swapsCounterOverlay) {
swapsCounterOverlay.updateCount();
}
// Apply power balls upgrade
if (storage.shopUpgrades.powerBalls > 0) {
fireBallPowerupOverlay.fireballsLeft += storage.shopUpgrades.powerBalls;
fireBallPowerupOverlay.updateDisplay();
}
// Apply time bonus upgrade (will be used in initializeMission)
}
// Handle mission failure
function handleMissionFailure() {
missionActive = false;
LK.effects.flashScreen(0xff0000, 3000);
LK.getSound('timeRunOut').play();
LK.getSound('gameOverJingle').play();
LK.getSound('powerupSwoosh').play(); // Additional sound for mission failure
LK.showGameOver();
}
for (var a = 0; a < 13; a++) {
var wl = game.addChild(new WarningLine());
wl.x = 2048 / 13 * (a + .5);
wl.y = 2200;
wl.alpha = 0;
warningLines.push(wl);
wl.scale.set(16, 40);
}
var warningOverlay = game.attachAsset('dangeroverlay', {});
warningOverlay.y = 2280;
var uxoverlay = game.attachAsset('uxoverlay', {});
uxoverlay.y = 2440;
var uxoverlay2 = game.attachAsset('uxoverlay2', {});
uxoverlay2.y = 2460;
for (var a = 0; a < 5; a++) {
for (var b = 0; b < 3; b++) {
var barrier = game.addChild(new Barrier());
barrier.y = 2732 - 450 + b * 70;
barrier.x = 2048 / 6 * a + 2048 / 6;
barriers.push(barrier);
}
var barrierBlock = game.attachAsset('barrierblock', {});
barrierBlock.x = 2048 / 6 * a + 2048 / 6;
barrierBlock.y = 2732 - 450;
barrierBlock.anchor.x = .5;
}
// Create a score label dropshadow (offset, semi-transparent)
var scoreLabelShadow = new Text2('0', {
size: 120,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
});
scoreLabelShadow.anchor.set(0.5, 0);
scoreLabelShadow.x = 4;
scoreLabelShadow.y = 6;
LK.gui.top.addChild(scoreLabelShadow);
// Create a score label (main)
var scoreLabel = new Text2('0', {
size: 120,
fill: 0xFFFFFF,
font: "Impact"
});
scoreLabel.anchor.set(0.5, 0);
scoreLabel.x = 0;
scoreLabel.y = 0;
LK.gui.top.addChild(scoreLabel);
var scoreMultipliers = [];
var baseScores = [10, 20, 50, 25, 15, 30];
var scoreIcons = ['bubble0', 'bubble1', 'bubble2', 'bubble3', 'bubble4', 'bubble5']; // Different visual icons for each multiplier
for (var a = 0; a < 6; a++) {
var sm = new ScoreMultipliers(baseScores[a]);
sm.x = 2048 / 6 * a + 2048 / 12;
sm.y = 2300;
// Add icon above the score
var scoreIcon = sm.attachAsset(scoreIcons[a], {
anchorX: 0.5,
anchorY: 0.5
});
scoreIcon.x = 0;
scoreIcon.y = -80;
scoreIcon.width = 80;
scoreIcon.height = 80;
scoreIcon.alpha = 0.8;
scoreMultipliers.push(sm);
game.addChild(sm);
}
var bonusUX = game.addChild(new BonusUX());
var fireBallPowerupOverlay = game.addChild(new FireBallPowerupOverlay());
var swapsCounterOverlay = game.addChild(new SwapsCounterOverlay());
var levelDisplayOverlay = game.addChild(new LevelDisplayOverlay());
missionOverlay = LK.gui.topRight.addChild(new MissionOverlay());
// Initialize the mission
initializeMission();
// Add start screen overlay
var startScreenOverlay = game.addChild(new StartScreenOverlay());
var particlesLayer = game.addChild(new Container());
var grid = game.addChild(new Grid());
grid.y = 1000;
var freeBubbleLayer = game.addChild(new Container());
var hintBubblePlayer = game.addChild(new Container());
var launcher = game.addChild(new Launcher());
launcher.x = game.width / 2;
launcher.y = game.height - 180; // More space from bottom for mobile
// Scale up launcher for better mobile visibility
launcher.scale.set(1.2, 1.2);
var hintBubbleCache = [];
var hintBubbles = [];
var isValid = false;
var path = [];
var bubbleAlpha = 1;
var hintTargetX = game.width / 2;
var hintTargetY = 0;
// Mobile-optimized touch controls with larger touch zones
game.move = function (x, y, obj) {
if (!gameStarted) return; // Don't process moves before game starts
hintTargetX = x;
hintTargetY = y;
refreshHintLine();
};
// Separate down handler for better mobile touch response
game.down = function (x, y, obj) {
if (!gameStarted) return;
// Call move to update hint line immediately
game.move(x, y, obj);
};
function getMaxTypes() {
// Level-based difficulty: more colors as level increases
var level = storage.currentLevel;
// Ensure minimum of 3 colors, with progressive increase
var maxTypes = Math.max(3, Math.min(6, Math.floor(level / 3) + 3));
// Additional progression based on bubbles popped
var bubblesPopped = storage.totalBubblesPopped || 0;
var bonusTypes = Math.floor(bubblesPopped / 100);
maxTypes = Math.min(6, maxTypes + bonusTypes);
return maxTypes;
}
function refreshHintLine() {
if (!gameStarted) return; // Don't update hint line before game starts
var ox = hintTargetX - launcher.x;
var oy = hintTargetY - launcher.y;
var angle = Math.atan2(oy, ox);
launcher.angle = angle;
isValid = angle < -.2 && angle > -Math.PI + .2;
if (isValid) {
path = grid.calculatePath(launcher, angle);
//This allows updated faster than 60fps, making everyting feel better.
}
renderHintBubbels();
}
var hintOffset = 0;
var distanceBetweenHintbubbles = 120; // Increase spacing for mobile performance
function renderHintBubbels() {
if (isValid) {
hintOffset = hintOffset % distanceBetweenHintbubbles;
var distanceSinceLastDot = -hintOffset + 120;
var hintBubbleOffset = 0;
var lastPoint = path[0];
var bubble = launcher.getBubble();
var tint = bubble.isFireBall ? bubble.isBombBall ? 0x333333 : 0xff9c00 : bubbleColors[bubble.type];
var updateTint = true;
for (var a = 1; a < path.length; a++) {
var p2 = path[a];
var ox = p2.x - lastPoint.x;
var oy = p2.y - lastPoint.y;
var dist = Math.sqrt(ox * ox + oy * oy);
distanceSinceLastDot += dist;
if (distanceSinceLastDot >= distanceBetweenHintbubbles) {
var amountOver = distanceSinceLastDot - distanceBetweenHintbubbles;
var angle = Math.atan2(oy, ox);
var currentBubble = hintBubbles[hintBubbleOffset];
if (!currentBubble) {
currentBubble = hintBubbles[hintBubbleOffset] = new HintBubble();
hintBubblePlayer.addChild(currentBubble);
}
currentBubble.alpha = bubbleAlpha;
currentBubble.visible = true;
var currentTint = currentBubble.getTint();
if (hintBubbleOffset == 0) {
currentBubble.setTint(tint);
} else if (updateTint && currentTint != tint || currentTint == 0xffffff) {
currentBubble.setTint(tint);
updateTint = false;
}
currentBubble.x = lastPoint.x - Math.cos(angle) * amountOver;
currentBubble.y = lastPoint.y - Math.sin(angle) * amountOver;
hintBubbleOffset++;
distanceSinceLastDot = 0;
lastPoint = currentBubble;
} else {
lastPoint = p2;
}
}
for (var a = hintBubbleOffset; a < hintBubbles.length; a++) {
hintBubbles[a].visible = false;
}
} else {
for (var a = 0; a < hintBubbles.length; a++) {
hintBubbles[a].alpha = bubbleAlpha;
}
}
}
game.update = function () {
hintOffset += 5;
if (isValid) {
bubbleAlpha = Math.min(bubbleAlpha + .05, 1);
} else {
bubbleAlpha = Math.max(bubbleAlpha - .05, 0);
}
refreshHintLine();
// Update swaps counter display
swapsCounterOverlay.updateCount();
var alphaList = grid.calculateWarningScoreList();
var hasHighWarning = false;
for (var a = 0; a < warningLines.length; a++) {
var value = alphaList[a] / 3;
if (value > 0.4) hasHighWarning = true;
warningLines[a].alpha += (Math.min(value, .6) - warningLines[a].alpha) / 100;
warningLines[a].scale.y += (value * 60 - warningLines[a].scale.y) / 100;
}
// Play warning sound when bubbles get dangerously close
if (hasHighWarning && LK.ticks % 180 === 0) {
LK.getSound('warningAlert').play();
}
// Update mission system - only if game has started
if (missionActive && gameStarted) {
missionTimeLeft--;
// Check for mission failure (time ran out)
if (missionTimeLeft <= 0) {
handleMissionFailure();
}
// Update mission display
missionOverlay.updateMission(missionTargets, missionCollected, missionTimeLeft);
} else if (missionActive) {
// Update mission display without decrementing time
missionOverlay.updateMission(missionTargets, missionCollected, missionTimeLeft);
}
};
game.up = function () {
if (!gameStarted) return; // Don't allow firing before game starts
if (isValid) {
launcher.fire();
// Start mission on first shot if not already active
if (!missionActive) {
initializeMission();
}
}
};
;
;
;
;
;
;
;
;
;
; ===================================================================
--- original.js
+++ change.js
@@ -277,13 +277,8 @@
anchorX: 0.5,
anchorY: 0.5
});
}
- // Add facial expressions to regular bubbles (not fireballs)
- var faceExpression = null;
- if (!isFireBall && self.type >= 0) {
- faceExpression = self.addChild(new FacialExpression(self));
- }
self.detach = function () {
freeBubbleLayer.addChild(self);
LK.getSound('detachCircle').play();
self.y += grid.y;
@@ -384,10 +379,8 @@
self.type = -4; // Special type for color changer bubble
self.isColorChangerBubble = true;
self.isAttached = true;
self.isFreeBubble = false;
- // Add facial expressions to color changer bubbles
- var faceExpression = self.addChild(new FacialExpression(self));
var speedX = 0;
var speedY = 0;
self.targetX = 0;
self.targetY = 0;
@@ -510,10 +503,8 @@
self.type = -6; // Special type for double coins bubble
self.isDoubleCoinsBubble = true;
self.isAttached = true;
self.isFreeBubble = false;
- // Add facial expressions to double coins bubbles
- var faceExpression = self.addChild(new FacialExpression(self));
var speedX = 0;
var speedY = 0;
self.targetX = 0;
self.targetY = 0;
@@ -627,10 +618,8 @@
self.type = -3; // Special type for explosive bubble
self.isExplosiveBubble = true;
self.isAttached = true;
self.isFreeBubble = false;
- // Add facial expressions to explosive bubbles - they look angry/determined
- var faceExpression = self.addChild(new FacialExpression(self));
var speedX = 0;
var speedY = 0;
self.targetX = 0;
self.targetY = 0;
@@ -741,113 +730,8 @@
}
};
return self;
});
-var FacialExpression = Container.expand(function (bubbleRef) {
- var self = Container.call(this);
- self.bubbleRef = bubbleRef;
- self.currentExpression = 'normal';
- self.lastExpression = 'normal';
- // Create face elements
- var leftEye = self.attachAsset('bubble0', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- leftEye.width = 15;
- leftEye.height = 15;
- leftEye.x = -20;
- leftEye.y = -10;
- leftEye.tint = 0x000000;
- var rightEye = self.attachAsset('bubble0', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- rightEye.width = 15;
- rightEye.height = 15;
- rightEye.x = 20;
- rightEye.y = -10;
- rightEye.tint = 0x000000;
- var mouth = self.attachAsset('bubble0', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- mouth.width = 25;
- mouth.height = 8;
- mouth.x = 0;
- mouth.y = 15;
- mouth.tint = 0x000000;
- self.updateExpression = function () {
- if (!self.bubbleRef || !self.bubbleRef.parent) return;
- // Calculate distance from bottom danger zone
- var bubbleY = self.bubbleRef.y + (grid ? grid.y : 0);
- var distanceFromDanger = 2200 - bubbleY;
- var normalizedDistance = Math.max(0, Math.min(1, distanceFromDanger / 800));
- var newExpression = 'normal';
- if (normalizedDistance < 0.2) {
- newExpression = 'terrified';
- } else if (normalizedDistance < 0.4) {
- newExpression = 'worried';
- } else if (normalizedDistance < 0.6) {
- newExpression = 'concerned';
- }
- if (newExpression !== self.lastExpression) {
- self.applyExpression(newExpression);
- self.lastExpression = newExpression;
- }
- };
- self.applyExpression = function (expression) {
- switch (expression) {
- case 'terrified':
- // Wide shocked eyes
- leftEye.width = rightEye.width = 20;
- leftEye.height = rightEye.height = 20;
- leftEye.y = rightEye.y = -12;
- // Open shocked mouth
- mouth.width = 30;
- mouth.height = 25;
- mouth.y = 20;
- break;
- case 'worried':
- // Slightly wider eyes
- leftEye.width = rightEye.width = 18;
- leftEye.height = rightEye.height = 18;
- leftEye.y = rightEye.y = -11;
- // Frown mouth
- mouth.width = 20;
- mouth.height = 6;
- mouth.y = 18;
- break;
- case 'concerned':
- // Normal but alert eyes
- leftEye.width = rightEye.width = 16;
- leftEye.height = rightEye.height = 16;
- leftEye.y = rightEye.y = -10;
- // Straight mouth
- mouth.width = 18;
- mouth.height = 4;
- mouth.y = 16;
- break;
- default:
- // normal
- // Standard eyes
- leftEye.width = rightEye.width = 15;
- leftEye.height = rightEye.height = 15;
- leftEye.y = rightEye.y = -10;
- // Small smile
- mouth.width = 25;
- mouth.height = 8;
- mouth.y = 15;
- break;
- }
- self.currentExpression = expression;
- };
- self.update = function () {
- self.updateExpression();
- };
- // Initialize with normal expression
- self.applyExpression('normal');
- return self;
-});
var FireBallPowerupOverlay = Container.expand(function () {
var self = Container.call(this);
var bubbleGraphics = self.attachAsset('fireball', {
anchorX: 0.5,
@@ -2192,10 +2076,8 @@
self.type = -1; // Special type for powerup
self.isPowerup = true;
self.isAttached = true;
self.isFreeBubble = false;
- // Add facial expressions to powerup bubbles
- var faceExpression = self.addChild(new FacialExpression(self));
var speedX = 0;
var speedY = 0;
self.targetX = 0;
self.targetY = 0;
@@ -2768,10 +2650,8 @@
self.type = -5; // Special type for slow time bubble
self.isSlowTimeBubble = true;
self.isAttached = true;
self.isFreeBubble = false;
- // Add facial expressions to slow time bubbles
- var faceExpression = self.addChild(new FacialExpression(self));
var speedX = 0;
var speedY = 0;
self.targetX = 0;
self.targetY = 0;
@@ -3095,10 +2975,8 @@
self.type = -2; // Special type for time bubble
self.isTimeBubble = true;
self.isAttached = true;
self.isFreeBubble = false;
- // Add facial expressions to time bubbles
- var faceExpression = self.addChild(new FacialExpression(self));
var speedX = 0;
var speedY = 0;
self.targetX = 0;
self.targetY = 0;