/****
* 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
LK.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
LK.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);
}
}
}; /****
* 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
LK.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
LK.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);
}
}
};