User prompt
When the game is over, the level should be reset and when it starts it should be zero. When you lose the game, it should say game over.
User prompt
RESET SCORE AFTER GAME END
User prompt
There should be no bombs in the bubbles we burst and add a bomb pattern to the bombs so that the health bar is heart-shaped. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Let there be 3 bombs
User prompt
Let there be 3 bombs Show your life in the form of a bar and throw the balls as a ball ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Give 3 lives
User prompt
Give her the right to throw 3 bombs at the beginning of the game.
User prompt
make the background moving ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Improve this game and make it better, get a leval or something ↪💡 Consider importing and using the following plugins: @upit/tween.v1, @upit/storage.v1
User prompt
Remove the score boxes and give 10 points for each exploding ball. The score will go to the top and be reflected on the scoreboard.
User prompt
Give 10 points to each ball instead of score boxes
Remix started
Copy Bubble Shooter
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.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('Streak Bonus', { size: 90, fill: 0x000000, alpha: 0.35, font: "Impact" })); bonusLabelShadow.anchor.set(1, 1); bonusLabelShadow.x = -10 + 4; bonusLabelShadow.y = barHeight / 2 + 4; // Main bonusLabel var bonusLabel = self.addChild(new Text2('Streak Bonus', { size: 90, fill: 0xF4F5FF, font: "Impact" })); bonusLabel.anchor.set(1, 1); bonusLabel.y = barHeight / 2; var rightMargin = -10; bonusLabel.x = rightMargin; // Dropshadow for bonusAmountLabel var bonusAmountLabelShadow = self.addChild(new Text2('1x', { size: 170, fill: 0x000000, alpha: 0.35, font: "Impact" })); bonusAmountLabelShadow.anchor.set(.5, .5); bonusAmountLabelShadow.x = 100 + 4; bonusAmountLabelShadow.y = 4; // Main bonusAmountLabel var bonusAmountLabel = self.addChild(new Text2('1x', { size: 170, fill: 0xF4F5FF, font: "Impact" })); bonusAmountLabel.anchor.set(.5, .5); bonusAmountLabel.x = 100; var bonusBarWidth = bonusLabel.width; var bonusBarStart = self.attachAsset('bonusend', { y: 30, x: -bonusBarWidth + rightMargin }); var bonuseBarEnd = self.attachAsset('bonusend', { y: 30, x: -bonusBarWidth + rightMargin }); var bonusBarMiddle = self.attachAsset('bonusbarmiddle', { y: 30, x: -bonusBarWidth + rightMargin + barHeight / 2, width: 0 }); self.x = game.width - 270; self.y = game.height - 145; var bonusBarStepSize = (bonusBarWidth - barHeight) / 5; var targetWidth = 0; var currentWidth = 0; var jumpToAtEnd = 0; self.bonusAmount = 1; self.streakCount = 0; var maxLevel = 40; self.setStreakCount = function (level) { self.streakCount = Math.min(level, maxLevel); var newBonus = Math.floor(self.streakCount / 5) + 1; if (newBonus != self.bonusAmount) {} 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) { // Add gentle floating animation when attached var floatOffset = Math.sin(LK.ticks * 0.02 + self.x * 0.01) * 2; if (self.x != self.targetX) { self.x += (self.targetX - self.x) / 10; } if (self.y != self.targetY) { self.y += (self.targetY - self.y) / 10 + floatOffset * 0.1; } // Add subtle rotation when attached bubbleGraphics.rotation += 0.005; } else { // Enhanced ball physics self.x += speedX; self.y += speedY; speedY += 1.5; // Add ball rotation based on movement bubbleGraphics.rotation += speedX * 0.02; // Add ball squash and stretch effect based on speed var speed = Math.sqrt(speedX * speedX + speedY * speedY); var squash = Math.min(speed * 0.005, 0.2); bubbleGraphics.scaleY = 1 - squash; bubbleGraphics.scaleX = 1 + squash * 0.5; // Wall bouncing with enhanced effect if (self.x < bubbleSize / 2 && speedX < 0 || self.x > game.width - bubbleSize / 2 && speedX > 0) { speedX = -speedX * 0.8; // Add some energy loss // Add bounce squash effect tween(bubbleGraphics.scale, { x: 1.2, y: 0.8 }, { duration: 100, easing: tween.bounceOut, onFinish: function onFinish() { tween(bubbleGraphics.scale, { x: 1, y: 1 }, { duration: 200, easing: tween.bounceOut }); } }); 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); // Add collision squash effect tween(bubbleGraphics.scale, { x: 0.8, y: 1.2 }, { duration: 80, easing: tween.easeOut, onFinish: function onFinish() { tween(bubbleGraphics.scale, { x: 1, y: 1 }, { duration: 150, easing: tween.bounceOut }); } }); LK.getSound('circleBounce').play(); } } // Remove unattached bubbles that fall below 2732 - 500 if (self.y > 2732 - 400) { self.destroy(); increaseScore(10); 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); self.y = game.height - 140; self.x = 200; self.fireballsLeft = 0; // Start with 0 fireballs var maxBombs = 3; var bombSpacing = 60; self.bombs = []; // Create 3 bomb indicators in heart shape using bubble graphics var heartPositions = [{ x: -30, y: -10 }, // Left curve of heart { x: 30, y: -10 }, // Right curve of heart { x: 0, y: 15 } // Bottom point of heart ]; for (var i = 0; i < maxBombs; i++) { var bomb = self.addChild(new Container()); var bombGraphics = bomb.attachAsset('bubble0', { anchorX: 0.5, anchorY: 0.5 }); bombGraphics.width = 45; bombGraphics.height = 45; bombGraphics.tint = 0xff2853; // Red color for heart bomb.x = heartPositions[i].x; bomb.y = heartPositions[i].y; bomb.alpha = 0.3; // Start inactive self.bombs.push(bomb); // Add heart-shaped glow effect when active var glowEffect = bomb.addChild(new Container()); var glow = glowEffect.attachAsset('bubble0', { anchorX: 0.5, anchorY: 0.5 }); glow.width = 55; glow.height = 55; glow.tint = 0xff6b85; glow.alpha = 0; bomb.glowEffect = glowEffect; } self.alpha = 0.5; // Start with greyed out overlay self.increaseFireballCount = function () { if (self.fireballsLeft < maxBombs) { self.fireballsLeft++; // Update bomb indicators for (var i = 0; i < self.bombs.length; i++) { if (i < self.fireballsLeft) { self.bombs[i].alpha = 1; // Active bomb // Add glow effect to active hearts if (self.bombs[i].glowEffect) { tween(self.bombs[i].glowEffect, { alpha: 0.4, scaleX: 1.2, scaleY: 1.2 }, { duration: 300, easing: tween.easeOut }); } // Add bounce animation to newly activated bomb tween(self.bombs[i].scale, { x: 1.3, y: 1.3 }, { duration: 120, easing: tween.cubicOut, onFinish: function (bombIndex) { return function () { tween(self.bombs[bombIndex].scale, { x: 1, y: 1 }, { duration: 220, easing: tween.bounceOut }); }; }(i) }); } else { self.bombs[i].alpha = 0.3; // Inactive bomb // Remove glow from inactive hearts if (self.bombs[i].glowEffect) { tween(self.bombs[i].glowEffect, { alpha: 0, scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeOut }); } } } self.alpha = self.fireballsLeft > 0 ? 1 : 0.5; // Spawn powerup particles around all bombs for (var j = 0; j < 8; j++) { var angle = Math.random() * Math.PI * 2; var speed = 6 + Math.random() * 4; var particle = game.addChild(new PowerupParticle(angle, speed)); particle.x = self.x + (maxBombs - 1) * bombSpacing / 2; // center of bar particle.y = self.y; } } }; self.down = function () { if (self.fireballsLeft > 0 && !launcher.isFireBall()) { self.fireballsLeft--; // Update bomb indicators for (var i = 0; i < self.bombs.length; i++) { if (i < self.fireballsLeft) { self.bombs[i].alpha = 1; // Active bomb // Keep glow for remaining active hearts if (self.bombs[i].glowEffect) { self.bombs[i].glowEffect.alpha = 0.4; } } else { self.bombs[i].alpha = 0.3; // Inactive bomb // Remove glow from used hearts if (self.bombs[i].glowEffect) { tween(self.bombs[i].glowEffect, { alpha: 0 }, { duration: 300, easing: tween.easeOut }); } } } launcher.triggerFireBall(); self.alpha = self.fireballsLeft > 0 ? 1 : 0.5; } }; // State for wiggle animation self.isWiggling = false; // Update method to handle wiggle animation self.update = function () { // Add gentle pulsing animation to active hearts for (var i = 0; i < self.bombs.length; i++) { if (i < self.fireballsLeft) { // Hearts beat with different phases for more organic feel var heartbeat = Math.sin(LK.ticks * 0.15 + i * 1.2) * 0.08 + 1; self.bombs[i].scale.set(heartbeat, heartbeat); // Add subtle glow pulsing if (self.bombs[i].glowEffect) { var glowPulse = Math.sin(LK.ticks * 0.1 + i * 0.8) * 0.1 + 0.4; self.bombs[i].glowEffect.alpha = glowPulse; } } } // 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; for (var a = 0; a < rowWidth; a++) { var 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(); increaseScore(10); } } } }; 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) { // Lose a life instead of immediate game over livesRemaining--; livesIndicator.updateLives(livesRemaining); if (livesRemaining <= 0) { // Game over when no lives left LK.effects.flashScreen(0xff0000, 3000); LK.getSound('gameOverJingle').play(); LK.showGameOver(); } else { // Flash red and reset grid position when losing a life LK.effects.flashScreen(0xff0000, 1000); self.y = Math.max(self.y - 400, 1000); // Move grid back up gridSpeed = Math.max(gridSpeed - 0.2, 0.5); // Slow down grid slightly } } if (gameIsStarted) { var targetSpeed = Math.pow(Math.pow((2200 - (bubble.y + self.y)) / 2200, 2), 2) * 4 + 0.5; if (bubble.y + self.y > 2000) { targetSpeed = .2; } gridSpeed += (targetSpeed - gridSpeed) / 20; if (LK.ticks % 10 == 0) { //console.log(gridSpeed) } } break; } } } }; for (var a = 0; a < 8; a++) { insertRow(); } }); var HintBubble = Container.expand(function () { var self = Container.call(this); var bubble = self.attachAsset('hintbubble', { anchorX: 0.5, anchorY: 0.5 }); self.setTint = function (tint) { bubble.tint = tint; }; self.getTint = function (tint) { return bubble.tint; }; }); var 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 LevelIndicator = Container.expand(function () { var self = Container.call(this); // Dropshadow for level label var levelLabelShadow = self.addChild(new Text2('Level 1', { size: 80, fill: 0x000000, alpha: 0.35, font: "Impact" })); levelLabelShadow.anchor.set(0, 0); levelLabelShadow.x = 4; levelLabelShadow.y = 6; // Main level label var levelLabel = self.addChild(new Text2('Level 1', { size: 80, fill: 0xFFFFFF, font: "Impact" })); levelLabel.anchor.set(0, 0); levelLabel.x = 0; levelLabel.y = 0; self.currentLevel = 1; self.updateLevel = function (level) { self.currentLevel = level; levelLabel.setText('Level ' + level); levelLabelShadow.setText('Level ' + level); }; return self; }); var LivesIndicator = Container.expand(function () { var self = Container.call(this); var maxLives = 3; var heartSpacing = 60; var hearts = []; // Create 3 heart indicators in a horizontal bar for (var i = 0; i < maxLives; i++) { var heart = self.addChild(new Container()); // Use bubble graphics as heart substitute (making them red) var heartGraphics = heart.attachAsset('bubble0', { anchorX: 0.5, anchorY: 0.5 }); heartGraphics.width = 50; heartGraphics.height = 50; heartGraphics.tint = 0xff2853; // Red color for hearts heart.x = i * heartSpacing; heart.y = 40; hearts.push(heart); } self.currentLives = 3; self.updateLives = function (lives) { self.currentLives = lives; // Update heart indicators with animation for (var i = 0; i < hearts.length; i++) { if (i < lives) { // Heart is active - full opacity and normal scale tween(hearts[i], { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.bounceOut }); } else { // Heart is lost - animate losing it tween(hearts[i], { alpha: 0.2, scaleX: 0.7, scaleY: 0.7 }, { duration: 300, easing: tween.easeOut }); // Add broken heart effect tween(hearts[i], { rotation: hearts[i].rotation + Math.PI * 0.1 }, { duration: 500, easing: tween.easeOut }); } } }; // Add gentle pulsing animation to active hearts self.update = function () { for (var i = 0; i < hearts.length; i++) { if (i < self.currentLives) { var pulse = Math.sin(LK.ticks * 0.08 + i * 0.5) * 0.05 + 1; hearts[i].scale.set(pulse, pulse); } } }; 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 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 ****/ // Create animated background layer var backgroundLayer = game.addChild(new Container()); // Create background bubbles for movement effect var backgroundBubbles = []; var _loop = function _loop() { bgBubble = backgroundLayer.addChild(new Container()); bubbleType = Math.floor(Math.random() * 6); bubbleGraphics = bgBubble.attachAsset('bubble' + bubbleType, { anchorX: 0.5, anchorY: 0.5 }); // Make background bubbles larger and more transparent bgBubble.scale.set(2 + Math.random() * 1.5, 2 + Math.random() * 1.5); bgBubble.alpha = 0.1 + Math.random() * 0.15; // Random starting positions bgBubble.x = Math.random() * game.width; bgBubble.y = Math.random() * game.height; backgroundBubbles.push(bgBubble); // Start continuous floating animation function animateBackgroundBubble(bubble) { var duration = 8000 + Math.random() * 4000; // 8-12 seconds var targetX = Math.random() * game.width; var targetY = Math.random() * game.height; tween(bubble, { x: targetX, y: targetY, rotation: bubble.rotation + (Math.random() - 0.5) * Math.PI }, { duration: duration, easing: tween.easeInOut, onFinish: function onFinish() { animateBackgroundBubble(bubble); // Loop the animation } }); } // Start animation with random delay (function (bubble) { LK.setTimeout(function () { animateBackgroundBubble(bubble); }, Math.random() * 2000); })(bgBubble); }, bgBubble, bubbleType, bubbleGraphics; for (var i = 0; i < 8; i++) { _loop(); } var gridSpeed = .5; function increaseScore(amount) { var currentScore = LK.getScore(); var newScore = currentScore + amount; LK.setScore(newScore); // Update the game score using LK method scoreLabel.setText(newScore.toString()); // Update the score label with the new score scoreLabelShadow.setText(newScore.toString()); // Update the shadow as well // Count bubbles popped for level progression bubblesPopped++; storage.bubblesPopped = bubblesPopped; // Check for level up if (newScore >= levelScoreThreshold) { currentLevel++; levelScoreThreshold = currentLevel * 500; // Each level needs 500 more points // Save progress storage.level = currentLevel; storage.levelScoreThreshold = levelScoreThreshold; // Update UI levelIndicator.updateLevel(currentLevel); // Level up effect LK.effects.flashScreen(0x44d31f, 1500); // Show level up animation var levelUpText = new Text2('LEVEL UP!', { size: 200, fill: 0x44d31f, font: "Impact" }); levelUpText.anchor.set(0.5, 0.5); levelUpText.x = game.width / 2; levelUpText.y = game.height / 2; levelUpText.alpha = 0; game.addChild(levelUpText); // Animate level up text tween(levelUpText, { alpha: 1, scaleX: 1.2, scaleY: 1.2 }, { duration: 500, easing: tween.bounceOut, onFinish: function onFinish() { tween(levelUpText, { alpha: 0, y: game.height / 2 - 100 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { levelUpText.destroy(); } }); } }); // Increase game difficulty based on level if (currentLevel > 1 && maxSelectableBubble < 6) { maxSelectableBubble = Math.min(3 + Math.floor(currentLevel / 3), 6); } // Increase grid speed slightly each level gridSpeed = Math.min(0.5 + (currentLevel - 1) * 0.1, 2.0); } } //Game size 2048x2732 /* Todo: [X] Make sure we GC nodes that drop of screen [ ] Make preview line fade out at the end */ var bulletsFired = 0; //3*30+1 var bubbleSize = 150; var gameIsStarted = false; var bubbleColors = [0xff2853, 0x44d31f, 0x5252ff, 0xcb2bff, 0x28f2f0, 0xffc411]; var barriers = []; var maxSelectableBubble = 3; var livesRemaining = 3; // Player starts with 3 lives 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; 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 a score label dropshadow (offset, semi-transparent) // Initialize level system with persistent storage var currentLevel = storage.level || 1; var bubblesPopped = storage.bubblesPopped || 0; var levelScoreThreshold = storage.levelScoreThreshold || 500; // Score needed to reach next level // Create a score label dropshadow (offset, semi-transparent) var scoreLabelShadow = new Text2('0', { size: 120, fill: 0x000000, alpha: 0.35, font: "Impact" }); scoreLabelShadow.anchor.set(0.5, 0); scoreLabelShadow.x = 4; scoreLabelShadow.y = 6; LK.gui.top.addChild(scoreLabelShadow); // Create a score label (main) var scoreLabel = new Text2('0', { size: 120, fill: 0xFFFFFF, font: "Impact" }); scoreLabel.anchor.set(0.5, 0); scoreLabel.x = 0; scoreLabel.y = 0; LK.gui.top.addChild(scoreLabel); // Create level indicator var levelIndicator = new LevelIndicator(); levelIndicator.updateLevel(currentLevel); LK.gui.topRight.addChild(levelIndicator); levelIndicator.x = -300; levelIndicator.y = 0; // Create lives indicator var livesIndicator = new LivesIndicator(); livesIndicator.updateLives(livesRemaining); LK.gui.topLeft.addChild(livesIndicator); livesIndicator.x = 120; // Offset from top-left to avoid platform menu livesIndicator.y = 0; var bonusUX = game.addChild(new BonusUX()); var fireBallPowerupOverlay = game.addChild(new FireBallPowerupOverlay()); // Give player 3 bombs at the start of the game fireBallPowerupOverlay.fireballsLeft = 3; // Update bomb indicators to show all 3 bombs as active for (var i = 0; i < fireBallPowerupOverlay.bombs.length; i++) { if (i < fireBallPowerupOverlay.fireballsLeft) { fireBallPowerupOverlay.bombs[i].alpha = 1; // Active bomb } else { fireBallPowerupOverlay.bombs[i].alpha = 0.3; // Inactive bomb } } fireBallPowerupOverlay.alpha = fireBallPowerupOverlay.fireballsLeft > 0 ? 1 : 0.5; 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; 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() { // Base level difficulty on current level instead of bullets fired var levelBasedTypes = Math.min(3 + Math.floor(currentLevel / 2), 6); if (bulletsFired > 30 * 3 * 3) { return Math.max(levelBasedTypes, 6); } else if (bulletsFired > 30 * 3) { return Math.max(levelBasedTypes, 5); } else if (bulletsFired > 30) { return Math.max(levelBasedTypes, 4); } return Math.max(levelBasedTypes, 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; var currentTint = currentBubble.getTint(); if (hintBubbleOffset == 0) { currentBubble.setTint(tint); } else if (updateTint && currentTint != tint || currentTint == 0xffffff) { currentBubble.setTint(tint); updateTint = false; } currentBubble.x = lastPoint.x - Math.cos(angle) * amountOver; currentBubble.y = lastPoint.y - Math.sin(angle) * amountOver; hintBubbleOffset++; distanceSinceLastDot = 0; lastPoint = currentBubble; } else { lastPoint = p2; } } for (var a = hintBubbleOffset; a < hintBubbles.length; a++) { hintBubbles[a].visible = false; } } else { for (var a = 0; a < hintBubbles.length; a++) { hintBubbles[a].alpha = bubbleAlpha; } } } game.update = function () { hintOffset += 5; if (isValid) { bubbleAlpha = Math.min(bubbleAlpha + .05, 1); } else { bubbleAlpha = Math.max(bubbleAlpha - .05, 0); } refreshHintLine(); 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; } // Animate background bubbles with gentle floating for (var i = 0; i < backgroundBubbles.length; i++) { var bubble = backgroundBubbles[i]; // Add subtle continuous rotation bubble.rotation += 0.002 + Math.sin(LK.ticks * 0.01 + i) * 0.001; // Add gentle vertical floating bubble.y += Math.sin(LK.ticks * 0.008 + i * 2) * 0.3; } // Subtle background color pulsing based on game state var colorIntensity = Math.sin(LK.ticks * 0.005) * 0.05 + 0.95; var baseColor = 0x0c0d25; var r = Math.floor((baseColor >> 16 & 0xFF) * colorIntensity); var g = Math.floor((baseColor >> 8 & 0xFF) * colorIntensity); var b = Math.floor((baseColor & 0xFF) * colorIntensity); game.setBackgroundColor(r << 16 | g << 8 | b); }; game.up = function () { if (isValid) { launcher.fire(); } }; ; ; ; ; ; ; ; ; ; ;
===================================================================
--- original.js
+++ change.js
@@ -285,21 +285,48 @@
self.fireballsLeft = 0; // Start with 0 fireballs
var maxBombs = 3;
var bombSpacing = 60;
self.bombs = [];
- // Create 3 bomb indicators in a horizontal bar
+ // Create 3 bomb indicators in heart shape using bubble graphics
+ var heartPositions = [{
+ x: -30,
+ y: -10
+ },
+ // Left curve of heart
+ {
+ x: 30,
+ y: -10
+ },
+ // Right curve of heart
+ {
+ x: 0,
+ y: 15
+ } // Bottom point of heart
+ ];
for (var i = 0; i < maxBombs; i++) {
var bomb = self.addChild(new Container());
- var bombGraphics = bomb.attachAsset('fireball', {
+ var bombGraphics = bomb.attachAsset('bubble0', {
anchorX: 0.5,
anchorY: 0.5
});
- bombGraphics.width = 50;
- bombGraphics.height = 50;
- bomb.x = i * bombSpacing;
- bomb.y = 0;
+ bombGraphics.width = 45;
+ bombGraphics.height = 45;
+ bombGraphics.tint = 0xff2853; // Red color for heart
+ bomb.x = heartPositions[i].x;
+ bomb.y = heartPositions[i].y;
bomb.alpha = 0.3; // Start inactive
self.bombs.push(bomb);
+ // Add heart-shaped glow effect when active
+ var glowEffect = bomb.addChild(new Container());
+ var glow = glowEffect.attachAsset('bubble0', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ glow.width = 55;
+ glow.height = 55;
+ glow.tint = 0xff6b85;
+ glow.alpha = 0;
+ bomb.glowEffect = glowEffect;
}
self.alpha = 0.5; // Start with greyed out overlay
self.increaseFireballCount = function () {
if (self.fireballsLeft < maxBombs) {
@@ -307,8 +334,19 @@
// Update bomb indicators
for (var i = 0; i < self.bombs.length; i++) {
if (i < self.fireballsLeft) {
self.bombs[i].alpha = 1; // Active bomb
+ // Add glow effect to active hearts
+ if (self.bombs[i].glowEffect) {
+ tween(self.bombs[i].glowEffect, {
+ alpha: 0.4,
+ scaleX: 1.2,
+ scaleY: 1.2
+ }, {
+ duration: 300,
+ easing: tween.easeOut
+ });
+ }
// Add bounce animation to newly activated bomb
tween(self.bombs[i].scale, {
x: 1.3,
y: 1.3
@@ -328,8 +366,19 @@
}(i)
});
} else {
self.bombs[i].alpha = 0.3; // Inactive bomb
+ // Remove glow from inactive hearts
+ if (self.bombs[i].glowEffect) {
+ tween(self.bombs[i].glowEffect, {
+ alpha: 0,
+ scaleX: 1,
+ scaleY: 1
+ }, {
+ duration: 200,
+ easing: tween.easeOut
+ });
+ }
}
}
self.alpha = self.fireballsLeft > 0 ? 1 : 0.5;
// Spawn powerup particles around all bombs
@@ -348,10 +397,23 @@
// Update bomb indicators
for (var i = 0; i < self.bombs.length; i++) {
if (i < self.fireballsLeft) {
self.bombs[i].alpha = 1; // Active bomb
+ // Keep glow for remaining active hearts
+ if (self.bombs[i].glowEffect) {
+ self.bombs[i].glowEffect.alpha = 0.4;
+ }
} else {
self.bombs[i].alpha = 0.3; // Inactive bomb
+ // Remove glow from used hearts
+ if (self.bombs[i].glowEffect) {
+ tween(self.bombs[i].glowEffect, {
+ alpha: 0
+ }, {
+ duration: 300,
+ easing: tween.easeOut
+ });
+ }
}
}
launcher.triggerFireBall();
self.alpha = self.fireballsLeft > 0 ? 1 : 0.5;
@@ -360,14 +422,19 @@
// State for wiggle animation
self.isWiggling = false;
// Update method to handle wiggle animation
self.update = function () {
- // Add gentle pulsing animation to active bombs
+ // Add gentle pulsing animation to active hearts
for (var i = 0; i < self.bombs.length; i++) {
if (i < self.fireballsLeft) {
- self.bombs[i].rotation += 0.02;
- var pulse = Math.sin(LK.ticks * 0.1 + i) * 0.05 + 1;
- self.bombs[i].scale.set(pulse, pulse);
+ // Hearts beat with different phases for more organic feel
+ var heartbeat = Math.sin(LK.ticks * 0.15 + i * 1.2) * 0.08 + 1;
+ self.bombs[i].scale.set(heartbeat, heartbeat);
+ // Add subtle glow pulsing
+ if (self.bombs[i].glowEffect) {
+ var glowPulse = Math.sin(LK.ticks * 0.1 + i * 0.8) * 0.1 + 0.4;
+ self.bombs[i].glowEffect.alpha = glowPulse;
+ }
}
}
// Check if bubbles are getting close to bottom and we have powerups
if (self.fireballsLeft > 0 && !self.isWiggling) {
@@ -454,27 +521,10 @@
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 = Math.max(15 - Math.floor(currentLevel / 2), 8); // Decrease interval with level, minimum 8
- // 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());
- }
+ var 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 () {
Circular white gradient circle on black background. Gradient from white on the center to black on the outer edge all around.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
Soft straight Long red paint on black background. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
Empty area
green notification bubble. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.