/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Bubble = Container.expand(function (colorType) {
var self = Container.call(this);
self.colorType = colorType || 0;
var colorAssets = ['bubble_red', 'bubble_blue', 'bubble_green', 'bubble_yellow', 'bubble_purple', 'bubble_orange'];
var bubbleGraphics = self.attachAsset(colorAssets[self.colorType], {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 40;
self.gridX = 0;
self.gridY = 0;
self.isConnected = false;
self.isMarkedForRemoval = false;
self.setGridPosition = function (gx, gy) {
self.gridX = gx;
self.gridY = gy;
var offsetX = gy % 2 * self.radius;
self.x = 150 + gx * (self.radius * 2) + offsetX;
self.y = 200 + gy * (self.radius * 1.8);
};
self.getNeighbors = function () {
var neighbors = [];
var isEvenRow = self.gridY % 2 === 0;
var offsets = isEvenRow ? [[-1, -1], [0, -1], [-1, 0], [1, 0], [-1, 1], [0, 1]] : [[0, -1], [1, -1], [-1, 0], [1, 0], [0, 1], [1, 1]];
for (var i = 0; i < offsets.length; i++) {
var nx = self.gridX + offsets[i][0];
var ny = self.gridY + offsets[i][1];
var neighbor = getBubbleAt(nx, ny);
if (neighbor) {
neighbors.push(neighbor);
}
}
return neighbors;
};
return self;
});
var ProjectileBubble = Container.expand(function (colorType) {
var self = Container.call(this);
self.colorType = colorType || 0;
var colorAssets = ['bubble_red', 'bubble_blue', 'bubble_green', 'bubble_yellow', 'bubble_purple', 'bubble_orange'];
var bubbleGraphics = self.attachAsset(colorAssets[self.colorType], {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 40;
self.velocityX = 0;
self.velocityY = 0;
self.speed = 15;
self.isMoving = false;
self.launch = function (angle) {
self.velocityX = Math.cos(angle) * self.speed;
self.velocityY = Math.sin(angle) * self.speed;
self.isMoving = true;
LK.getSound('launch').play();
};
self.update = function () {
if (!self.isMoving) return;
self.x += self.velocityX;
self.y += self.velocityY;
// Wall bounce
if (self.x - self.radius <= 0 || self.x + self.radius >= 2048) {
self.velocityX *= -1;
self.x = Math.max(self.radius, Math.min(2048 - self.radius, self.x));
LK.getSound('bounce').play();
}
// Check collision with bubbles
for (var i = 0; i < bubbleGrid.length; i++) {
if (bubbleGrid[i] && bubbleGrid[i].length > 0) {
for (var j = 0; j < bubbleGrid[i].length; j++) {
if (bubbleGrid[i][j]) {
var bubble = bubbleGrid[i][j];
var dx = self.x - bubble.x;
var dy = self.y - bubble.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < self.radius + bubble.radius) {
attachProjectileToBubbleGrid(self, bubble);
return;
}
}
}
}
}
// Remove if goes too high
if (self.y < -100) {
removeProjectile();
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2c3e50
});
/****
* Game Code
****/
var bubbleGrid = [];
var gridWidth = 18;
var gridHeight = 12;
var currentProjectile = null;
var nextBubbleType = 0;
var launcher = null;
var trajectoryDots = [];
var isAiming = false;
var gameStarted = false;
var bubbleColors = 6;
// Score display
var scoreTxt = new Text2('0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
scoreTxt.y = 50;
// Initialize bubble grid
function initializeBubbleGrid() {
for (var i = 0; i < gridHeight; i++) {
bubbleGrid[i] = [];
for (var j = 0; j < gridWidth; j++) {
bubbleGrid[i][j] = null;
}
}
// Fill top rows with bubbles
for (var row = 0; row < 8; row++) {
var bubblesInRow = row % 2 === 0 ? 12 : 11;
for (var col = 0; col < bubblesInRow; col++) {
var colorType = Math.floor(Math.random() * 4); // Use only first 4 colors initially
var bubble = new Bubble(colorType);
bubble.setGridPosition(col, row);
bubbleGrid[row][col] = bubble;
game.addChild(bubble);
}
}
}
function getBubbleAt(gridX, gridY) {
if (gridY < 0 || gridY >= gridHeight || gridX < 0 || gridX >= gridWidth) {
return null;
}
return bubbleGrid[gridY][gridX];
}
function createLauncher() {
launcher = game.addChild(LK.getAsset('launcher', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 2600
}));
}
function createProjectile() {
if (currentProjectile) return;
nextBubbleType = Math.floor(Math.random() * bubbleColors);
currentProjectile = new ProjectileBubble(nextBubbleType);
currentProjectile.x = launcher.x;
currentProjectile.y = launcher.y;
game.addChild(currentProjectile);
}
function createTrajectoryDots() {
for (var i = 0; i < 15; i++) {
var dot = LK.getAsset('trajectory_dot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
trajectoryDots.push(dot);
game.addChild(dot);
}
}
function updateTrajectoryPreview(angle) {
var startX = launcher.x;
var startY = launcher.y;
var stepX = Math.cos(angle) * 30;
var stepY = Math.sin(angle) * 30;
for (var i = 0; i < trajectoryDots.length; i++) {
var dot = trajectoryDots[i];
var x = startX + stepX * (i + 1);
var y = startY + stepY * (i + 1);
// Simple wall bounce simulation
if (x < 0 || x > 2048) {
stepX *= -1;
x = Math.max(0, Math.min(2048, x));
}
dot.x = x;
dot.y = y;
dot.alpha = Math.max(0, 1 - i * 0.1);
}
}
function hideTrajectoryPreview() {
for (var i = 0; i < trajectoryDots.length; i++) {
trajectoryDots[i].alpha = 0;
}
}
function findClosestGridPosition(worldX, worldY) {
var bestDistance = Infinity;
var bestGridX = 0;
var bestGridY = 0;
for (var row = 0; row < gridHeight; row++) {
var maxCols = row % 2 === 0 ? 12 : 11;
for (var col = 0; col < maxCols; col++) {
var offsetX = row % 2 * 40;
var gridWorldX = 150 + col * 80 + offsetX;
var gridWorldY = 200 + row * 72;
var dx = worldX - gridWorldX;
var dy = worldY - gridWorldY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < bestDistance && !getBubbleAt(col, row)) {
bestDistance = distance;
bestGridX = col;
bestGridY = row;
}
}
}
return {
x: bestGridX,
y: bestGridY
};
}
function attachProjectileToBubbleGrid(projectile, nearBubble) {
var gridPos = findClosestGridPosition(projectile.x, projectile.y);
// Create new bubble at grid position
var newBubble = new Bubble(projectile.colorType);
newBubble.setGridPosition(gridPos.x, gridPos.y);
bubbleGrid[gridPos.y][gridPos.x] = newBubble;
game.addChild(newBubble);
// Remove projectile
removeProjectile();
// Check for matches
checkMatches(newBubble);
// Create next projectile
setTimeout(function () {
createProjectile();
}, 200);
}
function removeProjectile() {
if (currentProjectile) {
currentProjectile.destroy();
currentProjectile = null;
}
}
function checkMatches(bubble) {
var matchedBubbles = [];
var visited = [];
// Initialize visited array
for (var i = 0; i < gridHeight; i++) {
visited[i] = [];
for (var j = 0; j < gridWidth; j++) {
visited[i][j] = false;
}
}
// Find connected bubbles of same color
function findConnected(bubble, colorType, matches) {
if (!bubble || visited[bubble.gridY][bubble.gridX] || bubble.colorType !== colorType) {
return;
}
visited[bubble.gridY][bubble.gridX] = true;
matches.push(bubble);
var neighbors = bubble.getNeighbors();
for (var i = 0; i < neighbors.length; i++) {
findConnected(neighbors[i], colorType, matches);
}
}
findConnected(bubble, bubble.colorType, matchedBubbles);
// Remove if 3 or more matches
if (matchedBubbles.length >= 3) {
for (var i = 0; i < matchedBubbles.length; i++) {
var matchBubble = matchedBubbles[i];
bubbleGrid[matchBubble.gridY][matchBubble.gridX] = null;
// Pop animation
tween(matchBubble, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
matchBubble.destroy();
}
});
}
LK.getSound('pop').play();
LK.setScore(LK.getScore() + matchedBubbles.length * 10);
scoreTxt.setText(LK.getScore());
// Check for floating bubbles
setTimeout(function () {
removeFloatingBubbles();
checkWinCondition();
}, 300);
}
}
function removeFloatingBubbles() {
var connected = [];
for (var i = 0; i < gridHeight; i++) {
connected[i] = [];
for (var j = 0; j < gridWidth; j++) {
connected[i][j] = false;
}
}
// Mark bubbles connected to top
function markConnected(gridX, gridY) {
if (gridY < 0 || gridY >= gridHeight || gridX < 0 || gridX >= gridWidth) return;
if (!bubbleGrid[gridY][gridX] || connected[gridY][gridX]) return;
connected[gridY][gridX] = true;
var bubble = bubbleGrid[gridY][gridX];
var neighbors = bubble.getNeighbors();
for (var i = 0; i < neighbors.length; i++) {
markConnected(neighbors[i].gridX, neighbors[i].gridY);
}
}
// Start from top row
for (var col = 0; col < gridWidth; col++) {
if (bubbleGrid[0] && bubbleGrid[0][col]) {
markConnected(col, 0);
}
}
// Remove unconnected bubbles
var floatingCount = 0;
for (var row = 1; row < gridHeight; row++) {
for (var col = 0; col < gridWidth; col++) {
if (bubbleGrid[row][col] && !connected[row][col]) {
var floatingBubble = bubbleGrid[row][col];
bubbleGrid[row][col] = null;
// Drop animation
tween(floatingBubble, {
y: floatingBubble.y + 500,
alpha: 0
}, {
duration: 800,
easing: tween.easeIn,
onFinish: function onFinish() {
floatingBubble.destroy();
}
});
floatingCount++;
}
}
}
if (floatingCount > 0) {
LK.setScore(LK.getScore() + floatingCount * 20);
scoreTxt.setText(LK.getScore());
}
}
function checkWinCondition() {
var bubblesRemaining = 0;
for (var row = 0; row < gridHeight; row++) {
for (var col = 0; col < gridWidth; col++) {
if (bubbleGrid[row][col]) {
bubblesRemaining++;
}
}
}
if (bubblesRemaining === 0) {
LK.showYouWin();
}
}
function checkLoseCondition() {
// Check if any bubble is too low
for (var row = 0; row < gridHeight; row++) {
for (var col = 0; col < gridWidth; col++) {
if (bubbleGrid[row][col] && bubbleGrid[row][col].y > 2400) {
LK.showGameOver();
return;
}
}
}
}
// Initialize game
initializeBubbleGrid();
createLauncher();
createProjectile();
createTrajectoryDots();
game.down = function (x, y, obj) {
if (!currentProjectile || currentProjectile.isMoving) return;
var dx = x - launcher.x;
var dy = y - launcher.y;
// Only allow upward shots
if (dy > 0) return;
isAiming = true;
var angle = Math.atan2(dy, dx);
updateTrajectoryPreview(angle);
};
game.move = function (x, y, obj) {
if (!isAiming || !currentProjectile || currentProjectile.isMoving) return;
var dx = x - launcher.x;
var dy = y - launcher.y;
// Only allow upward shots
if (dy > 0) return;
var angle = Math.atan2(dy, dx);
updateTrajectoryPreview(angle);
};
game.up = function (x, y, obj) {
if (!isAiming || !currentProjectile || currentProjectile.isMoving) return;
var dx = x - launcher.x;
var dy = y - launcher.y;
// Only allow upward shots
if (dy > 0) {
isAiming = false;
hideTrajectoryPreview();
return;
}
var angle = Math.atan2(dy, dx);
currentProjectile.launch(angle);
isAiming = false;
hideTrajectoryPreview();
};
game.update = function () {
if (currentProjectile && currentProjectile.isMoving) {
// Projectile updates itself
}
// Periodically check lose condition
if (LK.ticks % 60 === 0) {
checkLoseCondition();
}
// Add new row every 30 seconds in harder difficulty
if (LK.ticks % 1800 === 0 && LK.getScore() > 100) {
// Shift all bubbles down
for (var row = gridHeight - 2; row >= 0; row--) {
for (var col = 0; col < gridWidth; col++) {
if (bubbleGrid[row][col]) {
var bubble = bubbleGrid[row][col];
bubbleGrid[row + 1][col] = bubble;
bubbleGrid[row][col] = null;
bubble.setGridPosition(col, row + 1);
}
}
}
// Add new top row
var bubblesInRow = 12;
for (var col = 0; col < bubblesInRow; col++) {
var colorType = Math.floor(Math.random() * bubbleColors);
var bubble = new Bubble(colorType);
bubble.setGridPosition(col, 0);
bubbleGrid[0][col] = bubble;
game.addChild(bubble);
}
}
}; ===================================================================
--- original.js
+++ change.js
@@ -1,6 +1,447 @@
-/****
+/****
+* Plugins
+****/
+var tween = LK.import("@upit/tween.v1");
+
+/****
+* Classes
+****/
+var Bubble = Container.expand(function (colorType) {
+ var self = Container.call(this);
+ self.colorType = colorType || 0;
+ var colorAssets = ['bubble_red', 'bubble_blue', 'bubble_green', 'bubble_yellow', 'bubble_purple', 'bubble_orange'];
+ var bubbleGraphics = self.attachAsset(colorAssets[self.colorType], {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.radius = 40;
+ self.gridX = 0;
+ self.gridY = 0;
+ self.isConnected = false;
+ self.isMarkedForRemoval = false;
+ self.setGridPosition = function (gx, gy) {
+ self.gridX = gx;
+ self.gridY = gy;
+ var offsetX = gy % 2 * self.radius;
+ self.x = 150 + gx * (self.radius * 2) + offsetX;
+ self.y = 200 + gy * (self.radius * 1.8);
+ };
+ self.getNeighbors = function () {
+ var neighbors = [];
+ var isEvenRow = self.gridY % 2 === 0;
+ var offsets = isEvenRow ? [[-1, -1], [0, -1], [-1, 0], [1, 0], [-1, 1], [0, 1]] : [[0, -1], [1, -1], [-1, 0], [1, 0], [0, 1], [1, 1]];
+ for (var i = 0; i < offsets.length; i++) {
+ var nx = self.gridX + offsets[i][0];
+ var ny = self.gridY + offsets[i][1];
+ var neighbor = getBubbleAt(nx, ny);
+ if (neighbor) {
+ neighbors.push(neighbor);
+ }
+ }
+ return neighbors;
+ };
+ return self;
+});
+var ProjectileBubble = Container.expand(function (colorType) {
+ var self = Container.call(this);
+ self.colorType = colorType || 0;
+ var colorAssets = ['bubble_red', 'bubble_blue', 'bubble_green', 'bubble_yellow', 'bubble_purple', 'bubble_orange'];
+ var bubbleGraphics = self.attachAsset(colorAssets[self.colorType], {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.radius = 40;
+ self.velocityX = 0;
+ self.velocityY = 0;
+ self.speed = 15;
+ self.isMoving = false;
+ self.launch = function (angle) {
+ self.velocityX = Math.cos(angle) * self.speed;
+ self.velocityY = Math.sin(angle) * self.speed;
+ self.isMoving = true;
+ LK.getSound('launch').play();
+ };
+ self.update = function () {
+ if (!self.isMoving) return;
+ self.x += self.velocityX;
+ self.y += self.velocityY;
+ // Wall bounce
+ if (self.x - self.radius <= 0 || self.x + self.radius >= 2048) {
+ self.velocityX *= -1;
+ self.x = Math.max(self.radius, Math.min(2048 - self.radius, self.x));
+ LK.getSound('bounce').play();
+ }
+ // Check collision with bubbles
+ for (var i = 0; i < bubbleGrid.length; i++) {
+ if (bubbleGrid[i] && bubbleGrid[i].length > 0) {
+ for (var j = 0; j < bubbleGrid[i].length; j++) {
+ if (bubbleGrid[i][j]) {
+ var bubble = bubbleGrid[i][j];
+ var dx = self.x - bubble.x;
+ var dy = self.y - bubble.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ if (distance < self.radius + bubble.radius) {
+ attachProjectileToBubbleGrid(self, bubble);
+ return;
+ }
+ }
+ }
+ }
+ }
+ // Remove if goes too high
+ if (self.y < -100) {
+ removeProjectile();
+ }
+ };
+ return self;
+});
+
+/****
* Initialize Game
-****/
+****/
var game = new LK.Game({
- backgroundColor: 0x000000
-});
\ No newline at end of file
+ backgroundColor: 0x2c3e50
+});
+
+/****
+* Game Code
+****/
+var bubbleGrid = [];
+var gridWidth = 18;
+var gridHeight = 12;
+var currentProjectile = null;
+var nextBubbleType = 0;
+var launcher = null;
+var trajectoryDots = [];
+var isAiming = false;
+var gameStarted = false;
+var bubbleColors = 6;
+// Score display
+var scoreTxt = new Text2('0', {
+ size: 80,
+ fill: 0xFFFFFF
+});
+scoreTxt.anchor.set(0.5, 0);
+LK.gui.top.addChild(scoreTxt);
+scoreTxt.y = 50;
+// Initialize bubble grid
+function initializeBubbleGrid() {
+ for (var i = 0; i < gridHeight; i++) {
+ bubbleGrid[i] = [];
+ for (var j = 0; j < gridWidth; j++) {
+ bubbleGrid[i][j] = null;
+ }
+ }
+ // Fill top rows with bubbles
+ for (var row = 0; row < 8; row++) {
+ var bubblesInRow = row % 2 === 0 ? 12 : 11;
+ for (var col = 0; col < bubblesInRow; col++) {
+ var colorType = Math.floor(Math.random() * 4); // Use only first 4 colors initially
+ var bubble = new Bubble(colorType);
+ bubble.setGridPosition(col, row);
+ bubbleGrid[row][col] = bubble;
+ game.addChild(bubble);
+ }
+ }
+}
+function getBubbleAt(gridX, gridY) {
+ if (gridY < 0 || gridY >= gridHeight || gridX < 0 || gridX >= gridWidth) {
+ return null;
+ }
+ return bubbleGrid[gridY][gridX];
+}
+function createLauncher() {
+ launcher = game.addChild(LK.getAsset('launcher', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ x: 1024,
+ y: 2600
+ }));
+}
+function createProjectile() {
+ if (currentProjectile) return;
+ nextBubbleType = Math.floor(Math.random() * bubbleColors);
+ currentProjectile = new ProjectileBubble(nextBubbleType);
+ currentProjectile.x = launcher.x;
+ currentProjectile.y = launcher.y;
+ game.addChild(currentProjectile);
+}
+function createTrajectoryDots() {
+ for (var i = 0; i < 15; i++) {
+ var dot = LK.getAsset('trajectory_dot', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ alpha: 0
+ });
+ trajectoryDots.push(dot);
+ game.addChild(dot);
+ }
+}
+function updateTrajectoryPreview(angle) {
+ var startX = launcher.x;
+ var startY = launcher.y;
+ var stepX = Math.cos(angle) * 30;
+ var stepY = Math.sin(angle) * 30;
+ for (var i = 0; i < trajectoryDots.length; i++) {
+ var dot = trajectoryDots[i];
+ var x = startX + stepX * (i + 1);
+ var y = startY + stepY * (i + 1);
+ // Simple wall bounce simulation
+ if (x < 0 || x > 2048) {
+ stepX *= -1;
+ x = Math.max(0, Math.min(2048, x));
+ }
+ dot.x = x;
+ dot.y = y;
+ dot.alpha = Math.max(0, 1 - i * 0.1);
+ }
+}
+function hideTrajectoryPreview() {
+ for (var i = 0; i < trajectoryDots.length; i++) {
+ trajectoryDots[i].alpha = 0;
+ }
+}
+function findClosestGridPosition(worldX, worldY) {
+ var bestDistance = Infinity;
+ var bestGridX = 0;
+ var bestGridY = 0;
+ for (var row = 0; row < gridHeight; row++) {
+ var maxCols = row % 2 === 0 ? 12 : 11;
+ for (var col = 0; col < maxCols; col++) {
+ var offsetX = row % 2 * 40;
+ var gridWorldX = 150 + col * 80 + offsetX;
+ var gridWorldY = 200 + row * 72;
+ var dx = worldX - gridWorldX;
+ var dy = worldY - gridWorldY;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ if (distance < bestDistance && !getBubbleAt(col, row)) {
+ bestDistance = distance;
+ bestGridX = col;
+ bestGridY = row;
+ }
+ }
+ }
+ return {
+ x: bestGridX,
+ y: bestGridY
+ };
+}
+function attachProjectileToBubbleGrid(projectile, nearBubble) {
+ var gridPos = findClosestGridPosition(projectile.x, projectile.y);
+ // Create new bubble at grid position
+ var newBubble = new Bubble(projectile.colorType);
+ newBubble.setGridPosition(gridPos.x, gridPos.y);
+ bubbleGrid[gridPos.y][gridPos.x] = newBubble;
+ game.addChild(newBubble);
+ // Remove projectile
+ removeProjectile();
+ // Check for matches
+ checkMatches(newBubble);
+ // Create next projectile
+ setTimeout(function () {
+ createProjectile();
+ }, 200);
+}
+function removeProjectile() {
+ if (currentProjectile) {
+ currentProjectile.destroy();
+ currentProjectile = null;
+ }
+}
+function checkMatches(bubble) {
+ var matchedBubbles = [];
+ var visited = [];
+ // Initialize visited array
+ for (var i = 0; i < gridHeight; i++) {
+ visited[i] = [];
+ for (var j = 0; j < gridWidth; j++) {
+ visited[i][j] = false;
+ }
+ }
+ // Find connected bubbles of same color
+ function findConnected(bubble, colorType, matches) {
+ if (!bubble || visited[bubble.gridY][bubble.gridX] || bubble.colorType !== colorType) {
+ return;
+ }
+ visited[bubble.gridY][bubble.gridX] = true;
+ matches.push(bubble);
+ var neighbors = bubble.getNeighbors();
+ for (var i = 0; i < neighbors.length; i++) {
+ findConnected(neighbors[i], colorType, matches);
+ }
+ }
+ findConnected(bubble, bubble.colorType, matchedBubbles);
+ // Remove if 3 or more matches
+ if (matchedBubbles.length >= 3) {
+ for (var i = 0; i < matchedBubbles.length; i++) {
+ var matchBubble = matchedBubbles[i];
+ bubbleGrid[matchBubble.gridY][matchBubble.gridX] = null;
+ // Pop animation
+ tween(matchBubble, {
+ scaleX: 1.5,
+ scaleY: 1.5,
+ alpha: 0
+ }, {
+ duration: 200,
+ onFinish: function onFinish() {
+ matchBubble.destroy();
+ }
+ });
+ }
+ LK.getSound('pop').play();
+ LK.setScore(LK.getScore() + matchedBubbles.length * 10);
+ scoreTxt.setText(LK.getScore());
+ // Check for floating bubbles
+ setTimeout(function () {
+ removeFloatingBubbles();
+ checkWinCondition();
+ }, 300);
+ }
+}
+function removeFloatingBubbles() {
+ var connected = [];
+ for (var i = 0; i < gridHeight; i++) {
+ connected[i] = [];
+ for (var j = 0; j < gridWidth; j++) {
+ connected[i][j] = false;
+ }
+ }
+ // Mark bubbles connected to top
+ function markConnected(gridX, gridY) {
+ if (gridY < 0 || gridY >= gridHeight || gridX < 0 || gridX >= gridWidth) return;
+ if (!bubbleGrid[gridY][gridX] || connected[gridY][gridX]) return;
+ connected[gridY][gridX] = true;
+ var bubble = bubbleGrid[gridY][gridX];
+ var neighbors = bubble.getNeighbors();
+ for (var i = 0; i < neighbors.length; i++) {
+ markConnected(neighbors[i].gridX, neighbors[i].gridY);
+ }
+ }
+ // Start from top row
+ for (var col = 0; col < gridWidth; col++) {
+ if (bubbleGrid[0] && bubbleGrid[0][col]) {
+ markConnected(col, 0);
+ }
+ }
+ // Remove unconnected bubbles
+ var floatingCount = 0;
+ for (var row = 1; row < gridHeight; row++) {
+ for (var col = 0; col < gridWidth; col++) {
+ if (bubbleGrid[row][col] && !connected[row][col]) {
+ var floatingBubble = bubbleGrid[row][col];
+ bubbleGrid[row][col] = null;
+ // Drop animation
+ tween(floatingBubble, {
+ y: floatingBubble.y + 500,
+ alpha: 0
+ }, {
+ duration: 800,
+ easing: tween.easeIn,
+ onFinish: function onFinish() {
+ floatingBubble.destroy();
+ }
+ });
+ floatingCount++;
+ }
+ }
+ }
+ if (floatingCount > 0) {
+ LK.setScore(LK.getScore() + floatingCount * 20);
+ scoreTxt.setText(LK.getScore());
+ }
+}
+function checkWinCondition() {
+ var bubblesRemaining = 0;
+ for (var row = 0; row < gridHeight; row++) {
+ for (var col = 0; col < gridWidth; col++) {
+ if (bubbleGrid[row][col]) {
+ bubblesRemaining++;
+ }
+ }
+ }
+ if (bubblesRemaining === 0) {
+ LK.showYouWin();
+ }
+}
+function checkLoseCondition() {
+ // Check if any bubble is too low
+ for (var row = 0; row < gridHeight; row++) {
+ for (var col = 0; col < gridWidth; col++) {
+ if (bubbleGrid[row][col] && bubbleGrid[row][col].y > 2400) {
+ LK.showGameOver();
+ return;
+ }
+ }
+ }
+}
+// Initialize game
+initializeBubbleGrid();
+createLauncher();
+createProjectile();
+createTrajectoryDots();
+game.down = function (x, y, obj) {
+ if (!currentProjectile || currentProjectile.isMoving) return;
+ var dx = x - launcher.x;
+ var dy = y - launcher.y;
+ // Only allow upward shots
+ if (dy > 0) return;
+ isAiming = true;
+ var angle = Math.atan2(dy, dx);
+ updateTrajectoryPreview(angle);
+};
+game.move = function (x, y, obj) {
+ if (!isAiming || !currentProjectile || currentProjectile.isMoving) return;
+ var dx = x - launcher.x;
+ var dy = y - launcher.y;
+ // Only allow upward shots
+ if (dy > 0) return;
+ var angle = Math.atan2(dy, dx);
+ updateTrajectoryPreview(angle);
+};
+game.up = function (x, y, obj) {
+ if (!isAiming || !currentProjectile || currentProjectile.isMoving) return;
+ var dx = x - launcher.x;
+ var dy = y - launcher.y;
+ // Only allow upward shots
+ if (dy > 0) {
+ isAiming = false;
+ hideTrajectoryPreview();
+ return;
+ }
+ var angle = Math.atan2(dy, dx);
+ currentProjectile.launch(angle);
+ isAiming = false;
+ hideTrajectoryPreview();
+};
+game.update = function () {
+ if (currentProjectile && currentProjectile.isMoving) {
+ // Projectile updates itself
+ }
+ // Periodically check lose condition
+ if (LK.ticks % 60 === 0) {
+ checkLoseCondition();
+ }
+ // Add new row every 30 seconds in harder difficulty
+ if (LK.ticks % 1800 === 0 && LK.getScore() > 100) {
+ // Shift all bubbles down
+ for (var row = gridHeight - 2; row >= 0; row--) {
+ for (var col = 0; col < gridWidth; col++) {
+ if (bubbleGrid[row][col]) {
+ var bubble = bubbleGrid[row][col];
+ bubbleGrid[row + 1][col] = bubble;
+ bubbleGrid[row][col] = null;
+ bubble.setGridPosition(col, row + 1);
+ }
+ }
+ }
+ // Add new top row
+ var bubblesInRow = 12;
+ for (var col = 0; col < bubblesInRow; col++) {
+ var colorType = Math.floor(Math.random() * bubbleColors);
+ var bubble = new Bubble(colorType);
+ bubble.setGridPosition(col, 0);
+ bubbleGrid[0][col] = bubble;
+ game.addChild(bubble);
+ }
+ }
+};
\ No newline at end of file