/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Barrier = Container.expand(function () {
var self = Container.call(this);
var barrierGraphics = self.attachAsset('barrier', {
anchorX: .5,
anchorY: .5
});
});
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('Bunny 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('Bunny 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 = 40;
self.setStreakCount = function (level) {
self.streakCount = Math.min(level, maxLevel);
var newBonus = Math.floor(self.streakCount / 5) + 1;
if (newBonus != self.bonusAmount) {
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
});
}
/*if (!isFireBall && self.type > 1) {
bubbleGraphics.tint = bubbleColors[self.type];
}*/
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 += 1.5;
if (self.x < bubbleSize / 2 && speedX < 0 || self.x > game.width - bubbleSize / 2 && speedX > 0) {
speedX = -speedX;
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);
speedX = Math.cos(angle) * newSpeed * .7;
speedY = Math.sin(angle) * newSpeed * .7;
// 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 / 5))].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 FireBallPowerupOverlay = Container.expand(function () {
var self = Container.call(this);
var bubbleGraphics = self.attachAsset('fireball', {
anchorX: 0.5,
anchorY: 0.5
});
self.y = game.height - 140;
self.x = 200;
var countBG = self.attachAsset('countbg', {
anchorX: 0.5,
anchorY: 0.5
});
countBG.x = 90;
countBG.y = 50;
self.fireballsLeft = 0; // Start with 0 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++;
label.setText(self.fireballsLeft);
labelShadow.setText(self.fireballsLeft);
self.alpha = 1;
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.down = function () {
if (self.fireballsLeft > 0 && !launcher.isFireBall()) {
self.fireballsLeft--;
label.setText(self.fireballsLeft);
labelShadow.setText(self.fireballsLeft);
launcher.triggerFireBall();
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 powerupCol = -1;
var POWERUP_ROW_INTERVAL = 20; // Every 20th row has a powerup (was 10)
// 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);
}
for (var a = 0; a < rowWidth; a++) {
var bubble;
if (shouldSpawnPowerup && a === powerupCol) {
bubble = new PowerupBubble();
} 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) {
// Powerup 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, 150 / 2), 2048 - 150 / 2);
LK.getSound('circleBounce').play();
}
var intersectedBubble = self.bubbleIntersectsGrid(nextX + self.x, nextY + self.y);
if (intersectedBubble) {
gameIsStarted = true;
if (bubble.isFireBall) {
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);
var disconnected = self.getDetachedBubbles();
self.removeBubbles(disconnected);
bonusUX.setStreakCount(bonusUX.streakCount + 1);
} 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;
}
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();
// Show regular game over
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.currentType = -1;
self.setType = function (type, isFireBall) {
if (self.currentType !== type || self.isFireBall !== isFireBall) {
self.currentType = type;
self.isFireBall = isFireBall;
if (bubble.parent) {
bubble.parent.removeChild(bubble);
}
if (isFireBall) {
bubble = self.attachAsset('fireball', {
anchorX: 0.5,
anchorY: 0.5
});
bubble.width = 100;
bubble.height = 100;
} else {
bubble = self.attachAsset('bubble' + type, {
anchorX: 0.5,
anchorY: 0.5
});
bubble.width = 100;
bubble.height = 100;
}
bubble.alpha = 0.6; // Make hint bubbles semi-transparent
}
};
self.setTint = function (tint) {
// Keep for backwards compatibility but not used anymore
};
self.getTint = function (tint) {
return 0xffffff; // Return default since we don't use tinting anymore
};
});
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[0] && nextType == lastTypes[1]);
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 () {
bubble.destroy();
bubble = self.addChild(new Bubble(getMaxTypes(), true));
bubble.isFreeBubble = true;
};
});
var LeaderboardPopup = Container.expand(function (leaderboard) {
var self = Container.call(this);
// Semi-transparent background overlay
var overlay = LK.getAsset('uxoverlay', {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0
});
overlay.tint = 0x000000;
overlay.alpha = 0.7;
self.addChild(overlay);
// Main popup container
var popup = new Container();
popup.x = game.width / 2;
popup.y = game.height / 2 - 100;
self.addChild(popup);
// Popup background
var popupBg = LK.getAsset('uxoverlay', {
width: 900,
height: 700,
anchorX: 0.5,
anchorY: 0.5
});
popupBg.tint = 0x2fbe2d;
popup.addChild(popupBg);
// Title text shadow
var titleShadow = new Text2('TOP 5 PLAYERS', {
size: 80,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
});
titleShadow.anchor.set(0.5, 0.5);
titleShadow.x = 4;
titleShadow.y = -280 + 4;
popup.addChild(titleShadow);
// Title text
var titleText = new Text2('TOP 5 PLAYERS', {
size: 80,
fill: 0xFFFFFF,
font: "Impact"
});
titleText.anchor.set(0.5, 0.5);
titleText.y = -280;
popup.addChild(titleText);
// Display leaderboard entries
for (var i = 0; i < Math.min(5, leaderboard.length); i++) {
var entry = leaderboard[i];
var yPos = -180 + i * 80;
// Rank and name shadow
var entryShadow = new Text2(i + 1 + '. ' + entry.name, {
size: 50,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
});
entryShadow.anchor.set(0, 0.5);
entryShadow.x = -350 + 4;
entryShadow.y = yPos + 4;
popup.addChild(entryShadow);
// Rank and name
var entryText = new Text2(i + 1 + '. ' + entry.name, {
size: 50,
fill: 0xFFFFFF,
font: "Impact"
});
entryText.anchor.set(0, 0.5);
entryText.x = -350;
entryText.y = yPos;
popup.addChild(entryText);
// Score shadow
var scoreShadow = new Text2(entry.score.toString(), {
size: 50,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
});
scoreShadow.anchor.set(1, 0.5);
scoreShadow.x = 350 + 4;
scoreShadow.y = yPos + 4;
popup.addChild(scoreShadow);
// Score
var scoreText = new Text2(entry.score.toString(), {
size: 50,
fill: 0xFFD700,
font: "Impact"
});
scoreText.anchor.set(1, 0.5);
scoreText.x = 350;
scoreText.y = yPos;
popup.addChild(scoreText);
}
// Continue button background
var buttonBg = LK.getAsset('uxoverlay', {
width: 250,
height: 70,
anchorX: 0.5,
anchorY: 0.5
});
buttonBg.tint = 0x43d11f;
buttonBg.y = 250;
popup.addChild(buttonBg);
// Continue button text shadow
var buttonShadow = new Text2('CONTINUE', {
size: 45,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
});
buttonShadow.anchor.set(0.5, 0.5);
buttonShadow.x = 2;
buttonShadow.y = 252;
popup.addChild(buttonShadow);
// Continue button text
var buttonText = new Text2('CONTINUE', {
size: 45,
fill: 0xFFFFFF,
font: "Impact"
});
buttonText.anchor.set(0.5, 0.5);
buttonText.y = 250;
popup.addChild(buttonText);
// Handle continue button click
buttonBg.down = function () {
self.destroy();
// The game will automatically restart after game over
};
return self;
});
var NameInputPopup = Container.expand(function (finalScore) {
var self = Container.call(this);
// Semi-transparent background overlay
var overlay = LK.getAsset('uxoverlay', {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0
});
overlay.tint = 0x000000;
overlay.alpha = 0.7;
self.addChild(overlay);
// Main popup container
var popup = new Container();
popup.x = game.width / 2;
popup.y = game.height / 2 - 200;
self.addChild(popup);
// Popup background
var popupBg = LK.getAsset('uxoverlay', {
width: 800,
height: 500,
anchorX: 0.5,
anchorY: 0.5
});
popupBg.tint = 0x2fbe2d;
popup.addChild(popupBg);
// Title text shadow
var titleShadow = new Text2('NEW HIGH SCORE!', {
size: 80,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
});
titleShadow.anchor.set(0.5, 0.5);
titleShadow.x = 4;
titleShadow.y = -150 + 4;
popup.addChild(titleShadow);
// Title text
var titleText = new Text2('NEW HIGH SCORE!', {
size: 80,
fill: 0xFFFFFF,
font: "Impact"
});
titleText.anchor.set(0.5, 0.5);
titleText.y = -150;
popup.addChild(titleText);
// Score display shadow
var scoreShadow = new Text2('Score: ' + finalScore, {
size: 60,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
});
scoreShadow.anchor.set(0.5, 0.5);
scoreShadow.x = 4;
scoreShadow.y = -80 + 4;
popup.addChild(scoreShadow);
// Score display
var scoreText = new Text2('Score: ' + finalScore, {
size: 60,
fill: 0xFFD700,
font: "Impact"
});
scoreText.anchor.set(0.5, 0.5);
scoreText.y = -80;
popup.addChild(scoreText);
// Input prompt shadow
var promptShadow = new Text2('Enter your name:', {
size: 50,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
});
promptShadow.anchor.set(0.5, 0.5);
promptShadow.x = 4;
promptShadow.y = -20 + 4;
popup.addChild(promptShadow);
// Input prompt
var promptText = new Text2('Enter your name:', {
size: 50,
fill: 0xFFFFFF,
font: "Impact"
});
promptText.anchor.set(0.5, 0.5);
promptText.y = -20;
popup.addChild(promptText);
// Name input background
var inputBg = LK.getAsset('uxoverlay', {
width: 400,
height: 80,
anchorX: 0.5,
anchorY: 0.5
});
inputBg.tint = 0xFFFFFF;
inputBg.y = 40;
popup.addChild(inputBg);
// Current name text
var nameText = new Text2('', {
size: 45,
fill: 0x000000,
font: "Impact"
});
nameText.anchor.set(0.5, 0.5);
nameText.y = 40;
popup.addChild(nameText);
// Submit button background
var buttonBg = LK.getAsset('uxoverlay', {
width: 200,
height: 60,
anchorX: 0.5,
anchorY: 0.5
});
buttonBg.tint = 0x43d11f;
buttonBg.y = 140;
popup.addChild(buttonBg);
// Submit button text shadow
var buttonShadow = new Text2('SUBMIT', {
size: 40,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
});
buttonShadow.anchor.set(0.5, 0.5);
buttonShadow.x = 2;
buttonShadow.y = 142;
popup.addChild(buttonShadow);
// Submit button text
var buttonText = new Text2('SUBMIT', {
size: 40,
fill: 0xFFFFFF,
font: "Impact"
});
buttonText.anchor.set(0.5, 0.5);
buttonText.y = 140;
popup.addChild(buttonText);
// Name input handling
self.playerName = '';
var cursor = '_';
var cursorVisible = true;
var cursorTimer = 0;
self.addLetter = function (letter) {
if (self.playerName.length < 12) {
self.playerName += letter;
self.updateDisplay();
}
};
self.removeLetter = function () {
if (self.playerName.length > 0) {
self.playerName = self.playerName.slice(0, -1);
self.updateDisplay();
}
};
self.updateDisplay = function () {
var displayText = self.playerName;
if (cursorVisible) displayText += cursor;
nameText.setText(displayText);
};
self.submitScore = function () {
if (self.playerName.length > 0) {
// Just show game over instead of leaderboard
LK.showGameOver();
// Remove this popup
self.destroy();
}
};
// Handle submit button click
buttonBg.down = function () {
self.submitScore();
};
// Update cursor blinking
self.update = function () {
cursorTimer++;
if (cursorTimer % 30 === 0) {
cursorVisible = !cursorVisible;
self.updateDisplay();
}
};
// Initial display
self.updateDisplay();
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 += 1.5;
if (self.x < bubbleSize / 2 && speedX < 0 || self.x > game.width - bubbleSize / 2 && speedX > 0) {
speedX = -speedX;
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);
speedX = Math.cos(angle) * newSpeed * .7;
speedY = Math.sin(angle) * newSpeed * .7;
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 scoreIndicator = game.addChild(new ScoreIndicatorLabel(baseValue * currentMultiplier, bubble.type));
scoreIndicator.x = self.x;
scoreIndicator.y = self.y;
var particle = particlesLayer.addChild(new BubbleRemoveParticle());
particle.x = bubble.x;
particle.y = self.y + 150;
};
self.setMultiplier = function (multiplier) {
currentMultiplier = multiplier;
scoreMultiplierLabel.setText(baseValue * currentMultiplier);
scoreMultiplierLabelShadow.setText(baseValue * currentMultiplier);
};
});
var SimpleKeyboard = Container.expand(function (namePopup) {
var self = Container.call(this);
// Position keyboard at bottom of screen
self.x = game.width / 2;
self.y = game.height - 300;
var letters = ['ABCDEFGHIJ', 'KLMNOPQRST', 'UVWXYZ'];
var keyWidth = 60;
var keyHeight = 60;
var keySpacing = 70;
var rowSpacing = 70;
// Create letter keys
for (var row = 0; row < letters.length; row++) {
var rowLetters = letters[row];
var startX = -(rowLetters.length * keySpacing) / 2 + keySpacing / 2;
for (var col = 0; col < rowLetters.length; col++) {
var letter = rowLetters[col];
var keyContainer = new Container();
keyContainer.x = startX + col * keySpacing;
keyContainer.y = row * rowSpacing;
self.addChild(keyContainer);
// Key background
var keyBg = LK.getAsset('uxoverlay', {
width: keyWidth,
height: keyHeight,
anchorX: 0.5,
anchorY: 0.5
});
keyBg.tint = 0x43d11f;
keyContainer.addChild(keyBg);
// Key text
var keyText = new Text2(letter, {
size: 35,
fill: 0xFFFFFF,
font: "Impact"
});
keyText.anchor.set(0.5, 0.5);
keyContainer.addChild(keyText);
// Make key clickable
keyBg.letter = letter;
keyBg.down = function () {
namePopup.addLetter(this.letter);
};
}
}
// Backspace key
var backspaceContainer = new Container();
backspaceContainer.x = keySpacing * 4;
backspaceContainer.y = rowSpacing * 3;
self.addChild(backspaceContainer);
var backspaceBg = LK.getAsset('uxoverlay', {
width: keyWidth * 2,
height: keyHeight,
anchorX: 0.5,
anchorY: 0.5
});
backspaceBg.tint = 0xff2853;
backspaceContainer.addChild(backspaceBg);
var backspaceText = new Text2('DELETE', {
size: 30,
fill: 0xFFFFFF,
font: "Impact"
});
backspaceText.anchor.set(0.5, 0.5);
backspaceContainer.addChild(backspaceText);
backspaceBg.down = function () {
namePopup.removeLetter();
};
return self;
});
var StartNameInputPopup = Container.expand(function () {
var self = Container.call(this);
// Semi-transparent background overlay
var overlay = LK.getAsset('uxoverlay', {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0
});
overlay.tint = 0x000000;
overlay.alpha = 0.8;
self.addChild(overlay);
// Main popup container
var popup = new Container();
popup.x = game.width / 2;
popup.y = game.height / 2 - 200;
self.addChild(popup);
// Popup background
var popupBg = LK.getAsset('uxoverlay', {
width: 800,
height: 500,
anchorX: 0.5,
anchorY: 0.5
});
popupBg.tint = 0x2fbe2d;
popup.addChild(popupBg);
// Title text shadow
var titleShadow = new Text2('ENTER YOUR NAME', {
size: 80,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
});
titleShadow.anchor.set(0.5, 0.5);
titleShadow.x = 4;
titleShadow.y = -150 + 4;
popup.addChild(titleShadow);
// Title text
var titleText = new Text2('ENTER YOUR NAME', {
size: 80,
fill: 0xFFFFFF,
font: "Impact"
});
titleText.anchor.set(0.5, 0.5);
titleText.y = -150;
popup.addChild(titleText);
// Input prompt shadow
var promptShadow = new Text2('Name required to play:', {
size: 50,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
});
promptShadow.anchor.set(0.5, 0.5);
promptShadow.x = 4;
promptShadow.y = -80 + 4;
popup.addChild(promptShadow);
// Input prompt
var promptText = new Text2('Name required to play:', {
size: 50,
fill: 0xFFFFFF,
font: "Impact"
});
promptText.anchor.set(0.5, 0.5);
promptText.y = -80;
popup.addChild(promptText);
// Name input background
var inputBg = LK.getAsset('uxoverlay', {
width: 400,
height: 80,
anchorX: 0.5,
anchorY: 0.5
});
inputBg.tint = 0xFFFFFF;
inputBg.y = 0;
popup.addChild(inputBg);
// Current name text
var nameText = new Text2('', {
size: 45,
fill: 0x000000,
font: "Impact"
});
nameText.anchor.set(0.5, 0.5);
nameText.y = 0;
popup.addChild(nameText);
// Start button background
var buttonBg = LK.getAsset('uxoverlay', {
width: 200,
height: 60,
anchorX: 0.5,
anchorY: 0.5
});
buttonBg.tint = 0x43d11f;
buttonBg.y = 100;
buttonBg.alpha = 0.5; // Start disabled
popup.addChild(buttonBg);
// Start button text shadow
var buttonShadow = new Text2('START GAME', {
size: 35,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
});
buttonShadow.anchor.set(0.5, 0.5);
buttonShadow.x = 2;
buttonShadow.y = 102;
popup.addChild(buttonShadow);
// Start button text
var buttonText = new Text2('START GAME', {
size: 35,
fill: 0xFFFFFF,
font: "Impact"
});
buttonText.anchor.set(0.5, 0.5);
buttonText.y = 100;
popup.addChild(buttonText);
// Name input handling
self.playerName = '';
var cursor = '_';
var cursorVisible = true;
var cursorTimer = 0;
self.addLetter = function (letter) {
if (self.playerName.length < 12) {
self.playerName += letter;
self.updateDisplay();
}
};
self.removeLetter = function () {
if (self.playerName.length > 0) {
self.playerName = self.playerName.slice(0, -1);
self.updateDisplay();
}
};
self.updateDisplay = function () {
var displayText = self.playerName;
if (cursorVisible) displayText += cursor;
nameText.setText(displayText);
// Enable/disable start button based on name length
if (self.playerName.length > 0) {
buttonBg.alpha = 1;
} else {
buttonBg.alpha = 0.5;
}
};
self.startGame = function () {
if (self.playerName.length > 0) {
playerName = self.playerName;
gameCanStart = true;
// Remove this popup
self.destroy();
}
};
// Handle start button click
buttonBg.down = function () {
self.startGame();
};
// Update cursor blinking
self.update = function () {
cursorTimer++;
if (cursorTimer % 30 === 0) {
cursorVisible = !cursorVisible;
self.updateDisplay();
}
};
// Initial display
self.updateDisplay();
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;
function increaseScore(amount) {
var currentScore = LK.getScore();
var newScore = currentScore + amount;
LK.setScore(newScore); // Update the game score using LK method
// Only animate if score actually increased and we're not already animating
if (newScore > lastScore && !isAnimatingScore) {
isAnimatingScore = true;
lastScore = newScore;
// Update all text elements
scoreLabel.setText(newScore.toString());
scoreLabelShadow.setText(newScore.toString());
scoreGlow.setText(newScore.toString());
// Stop any existing tweens
tween.stop(scoreContainer.scale);
tween.stop(scoreGlow);
// Scale up animation with bounce
tween(scoreContainer.scale, {
x: 1.2,
y: 1.2
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
// Scale back down with bounce
tween(scoreContainer.scale, {
x: 1.0,
y: 1.0
}, {
duration: 200,
easing: tween.bounceOut,
onFinish: function onFinish() {
isAnimatingScore = false;
}
});
}
});
// Glow effect - fade in and out
tween(scoreGlow, {
alpha: 0.8
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(scoreGlow, {
alpha: 0
}, {
duration: 300,
easing: tween.easeIn
});
}
});
// Subtle color pulse on main label
var originalTint = scoreLabel.tint;
scoreLabel.tint = 0xFFD700; // Gold color
tween(scoreLabel, {
tint: originalTint
}, {
duration: 400,
easing: tween.easeOut
});
} else {
// Just update text without animation
scoreLabel.setText(newScore.toString());
scoreLabelShadow.setText(newScore.toString());
scoreGlow.setText(newScore.toString());
lastScore = newScore;
}
}
//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 gameCanStart = true;
var playerName = '';
var bubbleColors = [0xff2853, 0x44d31f, 0x5252ff, 0xcb2bff, 0x28f2f0, 0xffc411];
var barriers = [];
var maxSelectableBubble = 3;
var warningLines = [];
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;
// Create top earth bar that overlaps bubbles
var topEarthBar = new Container();
game.addChild(topEarthBar);
// Main earth bar background
var earthBarBg = topEarthBar.attachAsset('uxoverlay', {
width: 2048,
height: 120
});
earthBarBg.y = 0;
// Add diagonal cut effect on bottom edge using multiple small rectangles
for (var i = 0; i < 25; i++) {
var cutPiece = LK.getAsset('uxoverlay', {
width: 82,
height: 40
});
topEarthBar.addChild(cutPiece);
cutPiece.x = i * 82;
cutPiece.y = 120 - (i % 2 === 0 ? 15 : 25); // Alternating heights for jagged edge
cutPiece.tint = 0x268f26; // Slightly different green for variation
}
// Add some darker accent lines for depth
for (var i = 0; i < 3; i++) {
var accentLine = LK.getAsset('uxoverlay', {
width: 2048,
height: 8
});
topEarthBar.addChild(accentLine);
accentLine.tint = 0x1a5c1a; // Darker green
accentLine.y = 30 + i * 25;
}
// Add rocky texture details
for (var i = 0; i < 15; i++) {
var rockDetail = LK.getAsset('uxoverlay', {
width: 40 + Math.random() * 30,
height: 6 + Math.random() * 8
});
topEarthBar.addChild(rockDetail);
rockDetail.x = Math.random() * 2008 + 20;
rockDetail.y = 15 + Math.random() * 80;
rockDetail.tint = 0x1f7d1f; // Medium green
rockDetail.alpha = 0.7;
}
// Position the earth bar to overlap with bubbles (higher z-index)
topEarthBar.y = 0;
var uxoverlay = game.attachAsset('uxoverlay', {});
uxoverlay.y = 2440;
var uxoverlay2 = game.attachAsset('uxoverlay2', {});
uxoverlay2.y = 2460;
for (var a = 0; a < 4; a++) {
for (var b = 0; b < 3; b++) {
var barrier = game.addChild(new Barrier());
barrier.y = 2732 - 450 + b * 70;
barrier.x = 2048 / 5 * a + 2048 / 5;
barriers.push(barrier);
}
var barrierBlock = game.attachAsset('barrierblock', {});
barrierBlock.x = 2048 / 5 * a + 2048 / 5;
barrierBlock.y = 2732 - 450;
barrierBlock.anchor.x = .5;
}
// Create animated score display container
var scoreContainer = new Container();
scoreContainer.x = 0;
scoreContainer.y = 0;
LK.gui.top.addChild(scoreContainer);
// 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;
scoreContainer.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;
scoreContainer.addChild(scoreLabel);
// Create glow effect for score (initially invisible)
var scoreGlow = new Text2('0', {
size: 130,
fill: 0xFFD700,
font: "Impact"
});
scoreGlow.anchor.set(0.5, 0);
scoreGlow.x = 0;
scoreGlow.y = -5;
scoreGlow.alpha = 0;
scoreContainer.addChild(scoreGlow);
// Variables to track score animation state
var lastScore = 0;
var isAnimatingScore = false;
var scoreMultipliers = [];
var baseScores = [100, 250, 500, 250, 100];
for (var a = 0; a < 5; a++) {
var sm = new ScoreMultipliers(baseScores[a]);
sm.x = 2048 / 5 * a + 2048 / 10;
sm.y = 2300;
scoreMultipliers.push(sm);
game.addChild(sm);
}
var bonusUX = game.addChild(new BonusUX());
var fireBallPowerupOverlay = game.addChild(new FireBallPowerupOverlay());
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 - 138;
// Pause functionality is handled automatically by the LK engine
var hintBubbleCache = [];
var hintBubbles = [];
var isValid = false;
var path = [];
var bubbleAlpha = 1;
var hintTargetX = game.width / 2;
var hintTargetY = 0;
game.move = function (x, y, obj) {
hintTargetX = x;
hintTargetY = y;
refreshHintLine();
// }
};
game.down = game.move;
function getMaxTypes() {
if (bulletsFired > 30 * 3 * 3) {
return 6;
} else if (bulletsFired > 30 * 3) {
return 5;
} else if (bulletsFired > 30) {
return 4;
}
return 3;
}
function refreshHintLine() {
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 = 100;
function renderHintBubbels() {
if (isValid) {
hintOffset = hintOffset % distanceBetweenHintbubbles;
var distanceSinceLastDot = -hintOffset + 100;
var hintBubbleOffset = 0;
var lastPoint = path[0];
var bubble = launcher.getBubble();
var tint = bubble.isFireBall ? 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;
// Set the correct bubble type based on the launcher bubble
if (hintBubbleOffset == 0) {
currentBubble.setType(bubble.type, bubble.isFireBall);
} else {
currentBubble.setType(bubble.type, bubble.isFireBall);
}
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();
var alphaList = grid.calculateWarningScoreList();
for (var a = 0; a < warningLines.length; a++) {
var value = alphaList[a] / 3;
warningLines[a].alpha += (Math.min(value, .6) - warningLines[a].alpha) / 100;
warningLines[a].scale.y += (value * 60 - warningLines[a].scale.y) / 100;
}
};
game.up = function () {
if (isValid) {
launcher.fire();
}
};
// Move top earth bar to front to overlay all bubbles
game.removeChild(topEarthBar);
game.addChild(topEarthBar);
// Game can start immediately
gameCanStart = true; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Barrier = Container.expand(function () {
var self = Container.call(this);
var barrierGraphics = self.attachAsset('barrier', {
anchorX: .5,
anchorY: .5
});
});
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('Bunny 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('Bunny 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 = 40;
self.setStreakCount = function (level) {
self.streakCount = Math.min(level, maxLevel);
var newBonus = Math.floor(self.streakCount / 5) + 1;
if (newBonus != self.bonusAmount) {
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
});
}
/*if (!isFireBall && self.type > 1) {
bubbleGraphics.tint = bubbleColors[self.type];
}*/
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 += 1.5;
if (self.x < bubbleSize / 2 && speedX < 0 || self.x > game.width - bubbleSize / 2 && speedX > 0) {
speedX = -speedX;
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);
speedX = Math.cos(angle) * newSpeed * .7;
speedY = Math.sin(angle) * newSpeed * .7;
// 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 / 5))].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 FireBallPowerupOverlay = Container.expand(function () {
var self = Container.call(this);
var bubbleGraphics = self.attachAsset('fireball', {
anchorX: 0.5,
anchorY: 0.5
});
self.y = game.height - 140;
self.x = 200;
var countBG = self.attachAsset('countbg', {
anchorX: 0.5,
anchorY: 0.5
});
countBG.x = 90;
countBG.y = 50;
self.fireballsLeft = 0; // Start with 0 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++;
label.setText(self.fireballsLeft);
labelShadow.setText(self.fireballsLeft);
self.alpha = 1;
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.down = function () {
if (self.fireballsLeft > 0 && !launcher.isFireBall()) {
self.fireballsLeft--;
label.setText(self.fireballsLeft);
labelShadow.setText(self.fireballsLeft);
launcher.triggerFireBall();
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 powerupCol = -1;
var POWERUP_ROW_INTERVAL = 20; // Every 20th row has a powerup (was 10)
// 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);
}
for (var a = 0; a < rowWidth; a++) {
var bubble;
if (shouldSpawnPowerup && a === powerupCol) {
bubble = new PowerupBubble();
} 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) {
// Powerup 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, 150 / 2), 2048 - 150 / 2);
LK.getSound('circleBounce').play();
}
var intersectedBubble = self.bubbleIntersectsGrid(nextX + self.x, nextY + self.y);
if (intersectedBubble) {
gameIsStarted = true;
if (bubble.isFireBall) {
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);
var disconnected = self.getDetachedBubbles();
self.removeBubbles(disconnected);
bonusUX.setStreakCount(bonusUX.streakCount + 1);
} 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;
}
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();
// Show regular game over
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.currentType = -1;
self.setType = function (type, isFireBall) {
if (self.currentType !== type || self.isFireBall !== isFireBall) {
self.currentType = type;
self.isFireBall = isFireBall;
if (bubble.parent) {
bubble.parent.removeChild(bubble);
}
if (isFireBall) {
bubble = self.attachAsset('fireball', {
anchorX: 0.5,
anchorY: 0.5
});
bubble.width = 100;
bubble.height = 100;
} else {
bubble = self.attachAsset('bubble' + type, {
anchorX: 0.5,
anchorY: 0.5
});
bubble.width = 100;
bubble.height = 100;
}
bubble.alpha = 0.6; // Make hint bubbles semi-transparent
}
};
self.setTint = function (tint) {
// Keep for backwards compatibility but not used anymore
};
self.getTint = function (tint) {
return 0xffffff; // Return default since we don't use tinting anymore
};
});
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[0] && nextType == lastTypes[1]);
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 () {
bubble.destroy();
bubble = self.addChild(new Bubble(getMaxTypes(), true));
bubble.isFreeBubble = true;
};
});
var LeaderboardPopup = Container.expand(function (leaderboard) {
var self = Container.call(this);
// Semi-transparent background overlay
var overlay = LK.getAsset('uxoverlay', {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0
});
overlay.tint = 0x000000;
overlay.alpha = 0.7;
self.addChild(overlay);
// Main popup container
var popup = new Container();
popup.x = game.width / 2;
popup.y = game.height / 2 - 100;
self.addChild(popup);
// Popup background
var popupBg = LK.getAsset('uxoverlay', {
width: 900,
height: 700,
anchorX: 0.5,
anchorY: 0.5
});
popupBg.tint = 0x2fbe2d;
popup.addChild(popupBg);
// Title text shadow
var titleShadow = new Text2('TOP 5 PLAYERS', {
size: 80,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
});
titleShadow.anchor.set(0.5, 0.5);
titleShadow.x = 4;
titleShadow.y = -280 + 4;
popup.addChild(titleShadow);
// Title text
var titleText = new Text2('TOP 5 PLAYERS', {
size: 80,
fill: 0xFFFFFF,
font: "Impact"
});
titleText.anchor.set(0.5, 0.5);
titleText.y = -280;
popup.addChild(titleText);
// Display leaderboard entries
for (var i = 0; i < Math.min(5, leaderboard.length); i++) {
var entry = leaderboard[i];
var yPos = -180 + i * 80;
// Rank and name shadow
var entryShadow = new Text2(i + 1 + '. ' + entry.name, {
size: 50,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
});
entryShadow.anchor.set(0, 0.5);
entryShadow.x = -350 + 4;
entryShadow.y = yPos + 4;
popup.addChild(entryShadow);
// Rank and name
var entryText = new Text2(i + 1 + '. ' + entry.name, {
size: 50,
fill: 0xFFFFFF,
font: "Impact"
});
entryText.anchor.set(0, 0.5);
entryText.x = -350;
entryText.y = yPos;
popup.addChild(entryText);
// Score shadow
var scoreShadow = new Text2(entry.score.toString(), {
size: 50,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
});
scoreShadow.anchor.set(1, 0.5);
scoreShadow.x = 350 + 4;
scoreShadow.y = yPos + 4;
popup.addChild(scoreShadow);
// Score
var scoreText = new Text2(entry.score.toString(), {
size: 50,
fill: 0xFFD700,
font: "Impact"
});
scoreText.anchor.set(1, 0.5);
scoreText.x = 350;
scoreText.y = yPos;
popup.addChild(scoreText);
}
// Continue button background
var buttonBg = LK.getAsset('uxoverlay', {
width: 250,
height: 70,
anchorX: 0.5,
anchorY: 0.5
});
buttonBg.tint = 0x43d11f;
buttonBg.y = 250;
popup.addChild(buttonBg);
// Continue button text shadow
var buttonShadow = new Text2('CONTINUE', {
size: 45,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
});
buttonShadow.anchor.set(0.5, 0.5);
buttonShadow.x = 2;
buttonShadow.y = 252;
popup.addChild(buttonShadow);
// Continue button text
var buttonText = new Text2('CONTINUE', {
size: 45,
fill: 0xFFFFFF,
font: "Impact"
});
buttonText.anchor.set(0.5, 0.5);
buttonText.y = 250;
popup.addChild(buttonText);
// Handle continue button click
buttonBg.down = function () {
self.destroy();
// The game will automatically restart after game over
};
return self;
});
var NameInputPopup = Container.expand(function (finalScore) {
var self = Container.call(this);
// Semi-transparent background overlay
var overlay = LK.getAsset('uxoverlay', {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0
});
overlay.tint = 0x000000;
overlay.alpha = 0.7;
self.addChild(overlay);
// Main popup container
var popup = new Container();
popup.x = game.width / 2;
popup.y = game.height / 2 - 200;
self.addChild(popup);
// Popup background
var popupBg = LK.getAsset('uxoverlay', {
width: 800,
height: 500,
anchorX: 0.5,
anchorY: 0.5
});
popupBg.tint = 0x2fbe2d;
popup.addChild(popupBg);
// Title text shadow
var titleShadow = new Text2('NEW HIGH SCORE!', {
size: 80,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
});
titleShadow.anchor.set(0.5, 0.5);
titleShadow.x = 4;
titleShadow.y = -150 + 4;
popup.addChild(titleShadow);
// Title text
var titleText = new Text2('NEW HIGH SCORE!', {
size: 80,
fill: 0xFFFFFF,
font: "Impact"
});
titleText.anchor.set(0.5, 0.5);
titleText.y = -150;
popup.addChild(titleText);
// Score display shadow
var scoreShadow = new Text2('Score: ' + finalScore, {
size: 60,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
});
scoreShadow.anchor.set(0.5, 0.5);
scoreShadow.x = 4;
scoreShadow.y = -80 + 4;
popup.addChild(scoreShadow);
// Score display
var scoreText = new Text2('Score: ' + finalScore, {
size: 60,
fill: 0xFFD700,
font: "Impact"
});
scoreText.anchor.set(0.5, 0.5);
scoreText.y = -80;
popup.addChild(scoreText);
// Input prompt shadow
var promptShadow = new Text2('Enter your name:', {
size: 50,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
});
promptShadow.anchor.set(0.5, 0.5);
promptShadow.x = 4;
promptShadow.y = -20 + 4;
popup.addChild(promptShadow);
// Input prompt
var promptText = new Text2('Enter your name:', {
size: 50,
fill: 0xFFFFFF,
font: "Impact"
});
promptText.anchor.set(0.5, 0.5);
promptText.y = -20;
popup.addChild(promptText);
// Name input background
var inputBg = LK.getAsset('uxoverlay', {
width: 400,
height: 80,
anchorX: 0.5,
anchorY: 0.5
});
inputBg.tint = 0xFFFFFF;
inputBg.y = 40;
popup.addChild(inputBg);
// Current name text
var nameText = new Text2('', {
size: 45,
fill: 0x000000,
font: "Impact"
});
nameText.anchor.set(0.5, 0.5);
nameText.y = 40;
popup.addChild(nameText);
// Submit button background
var buttonBg = LK.getAsset('uxoverlay', {
width: 200,
height: 60,
anchorX: 0.5,
anchorY: 0.5
});
buttonBg.tint = 0x43d11f;
buttonBg.y = 140;
popup.addChild(buttonBg);
// Submit button text shadow
var buttonShadow = new Text2('SUBMIT', {
size: 40,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
});
buttonShadow.anchor.set(0.5, 0.5);
buttonShadow.x = 2;
buttonShadow.y = 142;
popup.addChild(buttonShadow);
// Submit button text
var buttonText = new Text2('SUBMIT', {
size: 40,
fill: 0xFFFFFF,
font: "Impact"
});
buttonText.anchor.set(0.5, 0.5);
buttonText.y = 140;
popup.addChild(buttonText);
// Name input handling
self.playerName = '';
var cursor = '_';
var cursorVisible = true;
var cursorTimer = 0;
self.addLetter = function (letter) {
if (self.playerName.length < 12) {
self.playerName += letter;
self.updateDisplay();
}
};
self.removeLetter = function () {
if (self.playerName.length > 0) {
self.playerName = self.playerName.slice(0, -1);
self.updateDisplay();
}
};
self.updateDisplay = function () {
var displayText = self.playerName;
if (cursorVisible) displayText += cursor;
nameText.setText(displayText);
};
self.submitScore = function () {
if (self.playerName.length > 0) {
// Just show game over instead of leaderboard
LK.showGameOver();
// Remove this popup
self.destroy();
}
};
// Handle submit button click
buttonBg.down = function () {
self.submitScore();
};
// Update cursor blinking
self.update = function () {
cursorTimer++;
if (cursorTimer % 30 === 0) {
cursorVisible = !cursorVisible;
self.updateDisplay();
}
};
// Initial display
self.updateDisplay();
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 += 1.5;
if (self.x < bubbleSize / 2 && speedX < 0 || self.x > game.width - bubbleSize / 2 && speedX > 0) {
speedX = -speedX;
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);
speedX = Math.cos(angle) * newSpeed * .7;
speedY = Math.sin(angle) * newSpeed * .7;
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 scoreIndicator = game.addChild(new ScoreIndicatorLabel(baseValue * currentMultiplier, bubble.type));
scoreIndicator.x = self.x;
scoreIndicator.y = self.y;
var particle = particlesLayer.addChild(new BubbleRemoveParticle());
particle.x = bubble.x;
particle.y = self.y + 150;
};
self.setMultiplier = function (multiplier) {
currentMultiplier = multiplier;
scoreMultiplierLabel.setText(baseValue * currentMultiplier);
scoreMultiplierLabelShadow.setText(baseValue * currentMultiplier);
};
});
var SimpleKeyboard = Container.expand(function (namePopup) {
var self = Container.call(this);
// Position keyboard at bottom of screen
self.x = game.width / 2;
self.y = game.height - 300;
var letters = ['ABCDEFGHIJ', 'KLMNOPQRST', 'UVWXYZ'];
var keyWidth = 60;
var keyHeight = 60;
var keySpacing = 70;
var rowSpacing = 70;
// Create letter keys
for (var row = 0; row < letters.length; row++) {
var rowLetters = letters[row];
var startX = -(rowLetters.length * keySpacing) / 2 + keySpacing / 2;
for (var col = 0; col < rowLetters.length; col++) {
var letter = rowLetters[col];
var keyContainer = new Container();
keyContainer.x = startX + col * keySpacing;
keyContainer.y = row * rowSpacing;
self.addChild(keyContainer);
// Key background
var keyBg = LK.getAsset('uxoverlay', {
width: keyWidth,
height: keyHeight,
anchorX: 0.5,
anchorY: 0.5
});
keyBg.tint = 0x43d11f;
keyContainer.addChild(keyBg);
// Key text
var keyText = new Text2(letter, {
size: 35,
fill: 0xFFFFFF,
font: "Impact"
});
keyText.anchor.set(0.5, 0.5);
keyContainer.addChild(keyText);
// Make key clickable
keyBg.letter = letter;
keyBg.down = function () {
namePopup.addLetter(this.letter);
};
}
}
// Backspace key
var backspaceContainer = new Container();
backspaceContainer.x = keySpacing * 4;
backspaceContainer.y = rowSpacing * 3;
self.addChild(backspaceContainer);
var backspaceBg = LK.getAsset('uxoverlay', {
width: keyWidth * 2,
height: keyHeight,
anchorX: 0.5,
anchorY: 0.5
});
backspaceBg.tint = 0xff2853;
backspaceContainer.addChild(backspaceBg);
var backspaceText = new Text2('DELETE', {
size: 30,
fill: 0xFFFFFF,
font: "Impact"
});
backspaceText.anchor.set(0.5, 0.5);
backspaceContainer.addChild(backspaceText);
backspaceBg.down = function () {
namePopup.removeLetter();
};
return self;
});
var StartNameInputPopup = Container.expand(function () {
var self = Container.call(this);
// Semi-transparent background overlay
var overlay = LK.getAsset('uxoverlay', {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0
});
overlay.tint = 0x000000;
overlay.alpha = 0.8;
self.addChild(overlay);
// Main popup container
var popup = new Container();
popup.x = game.width / 2;
popup.y = game.height / 2 - 200;
self.addChild(popup);
// Popup background
var popupBg = LK.getAsset('uxoverlay', {
width: 800,
height: 500,
anchorX: 0.5,
anchorY: 0.5
});
popupBg.tint = 0x2fbe2d;
popup.addChild(popupBg);
// Title text shadow
var titleShadow = new Text2('ENTER YOUR NAME', {
size: 80,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
});
titleShadow.anchor.set(0.5, 0.5);
titleShadow.x = 4;
titleShadow.y = -150 + 4;
popup.addChild(titleShadow);
// Title text
var titleText = new Text2('ENTER YOUR NAME', {
size: 80,
fill: 0xFFFFFF,
font: "Impact"
});
titleText.anchor.set(0.5, 0.5);
titleText.y = -150;
popup.addChild(titleText);
// Input prompt shadow
var promptShadow = new Text2('Name required to play:', {
size: 50,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
});
promptShadow.anchor.set(0.5, 0.5);
promptShadow.x = 4;
promptShadow.y = -80 + 4;
popup.addChild(promptShadow);
// Input prompt
var promptText = new Text2('Name required to play:', {
size: 50,
fill: 0xFFFFFF,
font: "Impact"
});
promptText.anchor.set(0.5, 0.5);
promptText.y = -80;
popup.addChild(promptText);
// Name input background
var inputBg = LK.getAsset('uxoverlay', {
width: 400,
height: 80,
anchorX: 0.5,
anchorY: 0.5
});
inputBg.tint = 0xFFFFFF;
inputBg.y = 0;
popup.addChild(inputBg);
// Current name text
var nameText = new Text2('', {
size: 45,
fill: 0x000000,
font: "Impact"
});
nameText.anchor.set(0.5, 0.5);
nameText.y = 0;
popup.addChild(nameText);
// Start button background
var buttonBg = LK.getAsset('uxoverlay', {
width: 200,
height: 60,
anchorX: 0.5,
anchorY: 0.5
});
buttonBg.tint = 0x43d11f;
buttonBg.y = 100;
buttonBg.alpha = 0.5; // Start disabled
popup.addChild(buttonBg);
// Start button text shadow
var buttonShadow = new Text2('START GAME', {
size: 35,
fill: 0x000000,
alpha: 0.35,
font: "Impact"
});
buttonShadow.anchor.set(0.5, 0.5);
buttonShadow.x = 2;
buttonShadow.y = 102;
popup.addChild(buttonShadow);
// Start button text
var buttonText = new Text2('START GAME', {
size: 35,
fill: 0xFFFFFF,
font: "Impact"
});
buttonText.anchor.set(0.5, 0.5);
buttonText.y = 100;
popup.addChild(buttonText);
// Name input handling
self.playerName = '';
var cursor = '_';
var cursorVisible = true;
var cursorTimer = 0;
self.addLetter = function (letter) {
if (self.playerName.length < 12) {
self.playerName += letter;
self.updateDisplay();
}
};
self.removeLetter = function () {
if (self.playerName.length > 0) {
self.playerName = self.playerName.slice(0, -1);
self.updateDisplay();
}
};
self.updateDisplay = function () {
var displayText = self.playerName;
if (cursorVisible) displayText += cursor;
nameText.setText(displayText);
// Enable/disable start button based on name length
if (self.playerName.length > 0) {
buttonBg.alpha = 1;
} else {
buttonBg.alpha = 0.5;
}
};
self.startGame = function () {
if (self.playerName.length > 0) {
playerName = self.playerName;
gameCanStart = true;
// Remove this popup
self.destroy();
}
};
// Handle start button click
buttonBg.down = function () {
self.startGame();
};
// Update cursor blinking
self.update = function () {
cursorTimer++;
if (cursorTimer % 30 === 0) {
cursorVisible = !cursorVisible;
self.updateDisplay();
}
};
// Initial display
self.updateDisplay();
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;
function increaseScore(amount) {
var currentScore = LK.getScore();
var newScore = currentScore + amount;
LK.setScore(newScore); // Update the game score using LK method
// Only animate if score actually increased and we're not already animating
if (newScore > lastScore && !isAnimatingScore) {
isAnimatingScore = true;
lastScore = newScore;
// Update all text elements
scoreLabel.setText(newScore.toString());
scoreLabelShadow.setText(newScore.toString());
scoreGlow.setText(newScore.toString());
// Stop any existing tweens
tween.stop(scoreContainer.scale);
tween.stop(scoreGlow);
// Scale up animation with bounce
tween(scoreContainer.scale, {
x: 1.2,
y: 1.2
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
// Scale back down with bounce
tween(scoreContainer.scale, {
x: 1.0,
y: 1.0
}, {
duration: 200,
easing: tween.bounceOut,
onFinish: function onFinish() {
isAnimatingScore = false;
}
});
}
});
// Glow effect - fade in and out
tween(scoreGlow, {
alpha: 0.8
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(scoreGlow, {
alpha: 0
}, {
duration: 300,
easing: tween.easeIn
});
}
});
// Subtle color pulse on main label
var originalTint = scoreLabel.tint;
scoreLabel.tint = 0xFFD700; // Gold color
tween(scoreLabel, {
tint: originalTint
}, {
duration: 400,
easing: tween.easeOut
});
} else {
// Just update text without animation
scoreLabel.setText(newScore.toString());
scoreLabelShadow.setText(newScore.toString());
scoreGlow.setText(newScore.toString());
lastScore = newScore;
}
}
//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 gameCanStart = true;
var playerName = '';
var bubbleColors = [0xff2853, 0x44d31f, 0x5252ff, 0xcb2bff, 0x28f2f0, 0xffc411];
var barriers = [];
var maxSelectableBubble = 3;
var warningLines = [];
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;
// Create top earth bar that overlaps bubbles
var topEarthBar = new Container();
game.addChild(topEarthBar);
// Main earth bar background
var earthBarBg = topEarthBar.attachAsset('uxoverlay', {
width: 2048,
height: 120
});
earthBarBg.y = 0;
// Add diagonal cut effect on bottom edge using multiple small rectangles
for (var i = 0; i < 25; i++) {
var cutPiece = LK.getAsset('uxoverlay', {
width: 82,
height: 40
});
topEarthBar.addChild(cutPiece);
cutPiece.x = i * 82;
cutPiece.y = 120 - (i % 2 === 0 ? 15 : 25); // Alternating heights for jagged edge
cutPiece.tint = 0x268f26; // Slightly different green for variation
}
// Add some darker accent lines for depth
for (var i = 0; i < 3; i++) {
var accentLine = LK.getAsset('uxoverlay', {
width: 2048,
height: 8
});
topEarthBar.addChild(accentLine);
accentLine.tint = 0x1a5c1a; // Darker green
accentLine.y = 30 + i * 25;
}
// Add rocky texture details
for (var i = 0; i < 15; i++) {
var rockDetail = LK.getAsset('uxoverlay', {
width: 40 + Math.random() * 30,
height: 6 + Math.random() * 8
});
topEarthBar.addChild(rockDetail);
rockDetail.x = Math.random() * 2008 + 20;
rockDetail.y = 15 + Math.random() * 80;
rockDetail.tint = 0x1f7d1f; // Medium green
rockDetail.alpha = 0.7;
}
// Position the earth bar to overlap with bubbles (higher z-index)
topEarthBar.y = 0;
var uxoverlay = game.attachAsset('uxoverlay', {});
uxoverlay.y = 2440;
var uxoverlay2 = game.attachAsset('uxoverlay2', {});
uxoverlay2.y = 2460;
for (var a = 0; a < 4; a++) {
for (var b = 0; b < 3; b++) {
var barrier = game.addChild(new Barrier());
barrier.y = 2732 - 450 + b * 70;
barrier.x = 2048 / 5 * a + 2048 / 5;
barriers.push(barrier);
}
var barrierBlock = game.attachAsset('barrierblock', {});
barrierBlock.x = 2048 / 5 * a + 2048 / 5;
barrierBlock.y = 2732 - 450;
barrierBlock.anchor.x = .5;
}
// Create animated score display container
var scoreContainer = new Container();
scoreContainer.x = 0;
scoreContainer.y = 0;
LK.gui.top.addChild(scoreContainer);
// 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;
scoreContainer.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;
scoreContainer.addChild(scoreLabel);
// Create glow effect for score (initially invisible)
var scoreGlow = new Text2('0', {
size: 130,
fill: 0xFFD700,
font: "Impact"
});
scoreGlow.anchor.set(0.5, 0);
scoreGlow.x = 0;
scoreGlow.y = -5;
scoreGlow.alpha = 0;
scoreContainer.addChild(scoreGlow);
// Variables to track score animation state
var lastScore = 0;
var isAnimatingScore = false;
var scoreMultipliers = [];
var baseScores = [100, 250, 500, 250, 100];
for (var a = 0; a < 5; a++) {
var sm = new ScoreMultipliers(baseScores[a]);
sm.x = 2048 / 5 * a + 2048 / 10;
sm.y = 2300;
scoreMultipliers.push(sm);
game.addChild(sm);
}
var bonusUX = game.addChild(new BonusUX());
var fireBallPowerupOverlay = game.addChild(new FireBallPowerupOverlay());
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 - 138;
// Pause functionality is handled automatically by the LK engine
var hintBubbleCache = [];
var hintBubbles = [];
var isValid = false;
var path = [];
var bubbleAlpha = 1;
var hintTargetX = game.width / 2;
var hintTargetY = 0;
game.move = function (x, y, obj) {
hintTargetX = x;
hintTargetY = y;
refreshHintLine();
// }
};
game.down = game.move;
function getMaxTypes() {
if (bulletsFired > 30 * 3 * 3) {
return 6;
} else if (bulletsFired > 30 * 3) {
return 5;
} else if (bulletsFired > 30) {
return 4;
}
return 3;
}
function refreshHintLine() {
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 = 100;
function renderHintBubbels() {
if (isValid) {
hintOffset = hintOffset % distanceBetweenHintbubbles;
var distanceSinceLastDot = -hintOffset + 100;
var hintBubbleOffset = 0;
var lastPoint = path[0];
var bubble = launcher.getBubble();
var tint = bubble.isFireBall ? 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;
// Set the correct bubble type based on the launcher bubble
if (hintBubbleOffset == 0) {
currentBubble.setType(bubble.type, bubble.isFireBall);
} else {
currentBubble.setType(bubble.type, bubble.isFireBall);
}
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();
var alphaList = grid.calculateWarningScoreList();
for (var a = 0; a < warningLines.length; a++) {
var value = alphaList[a] / 3;
warningLines[a].alpha += (Math.min(value, .6) - warningLines[a].alpha) / 100;
warningLines[a].scale.y += (value * 60 - warningLines[a].scale.y) / 100;
}
};
game.up = function () {
if (isValid) {
launcher.fire();
}
};
// Move top earth bar to front to overlay all bubbles
game.removeChild(topEarthBar);
game.addChild(topEarthBar);
// Game can start immediately
gameCanStart = true;
Soft straight Long red paint on black background. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
green notification bubble. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
bubble with a bunny on it. In-Game asset. 2d. High contrast. No shadows
a Carrot in a bubble. In-Game asset. 2d. High contrast. No shadows
an easten egg in a bubble. In-Game asset. 2d. High contrast. No shadows
the coin inside the bubble have a cute rabbit
a bubble with a cute rabbit holding a diamond stone like a troffy. In-Game asset. 2d. High contrast. No shadows
a bubble with a golden king rabbit inside sitting in a throne. In-Game asset. 2d. High contrast. No shadows
vertical and realistic carrot. In-Game asset. 2d. High contrast. No shadows
add a green color
a fire bubble ball with a rabbit on flame. In-Game asset. 2d. High contrast. No shadows
change the color to #2fbe2d
crear un rectangulo de color azu profundo. In-Game asset. 2d. High contrast. No shadows