/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScore: 0, level: 1 }); /**** * Classes ****/ var Bubble = Container.expand(function (color, size) { var self = Container.call(this); self.bubbleColor = color || getRandomColor(); self.bubbleSize = size || 80; self.isMatched = false; self.isPowerUp = false; self.powerUpType = null; self.row = 0; self.col = 0; self.falling = false; var assetId = 'bubble_' + self.bubbleColor; self.graphics = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5, scaleX: self.bubbleSize / 80, scaleY: self.bubbleSize / 80 }); self.convertToPowerUp = function (type) { self.isPowerUp = true; self.powerUpType = type; // Replace the graphics with powerup graphics self.removeChild(self.graphics); var powerupAssetId = 'powerup_' + type; self.graphics = self.attachAsset(powerupAssetId, { anchorX: 0.5, anchorY: 0.5, scaleX: self.bubbleSize / 80, scaleY: self.bubbleSize / 80 }); }; self.match = function () { if (self.isMatched) return; self.isMatched = true; // Play pop animation tween(self.graphics, { scaleX: 1.3, scaleY: 1.3, alpha: 0 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { self.destroy(); } }); }; self.move = function (x, y) { self.falling = true; tween(self, { x: x, y: y }, { duration: 300, easing: tween.easeInOut, onFinish: function onFinish() { self.falling = false; } }); }; self.update = function () { // Bubble specific update logic if (self.falling === false && self.y > bottomLineY) { // Bubble has crossed the bottom line gameOver(); } }; self.down = function (x, y, obj) { // Handle bubble click/tap if (game && !self.falling) { game.checkMatches(self); } }; return self; }); var BubbleBullet = Container.expand(function (color) { var self = Container.call(this); // Bullet properties self.bubbleColor = color || getRandomColor(); self.bubbleSize = BUBBLE_SIZE; self.isPowerUp = false; self.powerUpType = null; self.velocityX = 0; self.velocityY = -15; self.lastX = 0; self.lastY = 0; // Create graphics var assetId = 'bubble_' + self.bubbleColor; self.graphics = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); self.convertToPowerUp = function (type) { self.isPowerUp = true; self.powerUpType = type; // Replace the graphics with powerup graphics self.removeChild(self.graphics); var powerupAssetId = 'powerup_' + type; self.graphics = self.attachAsset(powerupAssetId, { anchorX: 0.5, anchorY: 0.5 }); }; // Update bullet position and check collisions self.update = function () { // Save last position self.lastX = self.x; self.lastY = self.y; // Update position self.x += self.velocityX; self.y += self.velocityY; // Handle wall collisions if (self.x < GRID_LEFT_MARGIN + BUBBLE_SIZE / 2) { self.x = GRID_LEFT_MARGIN + BUBBLE_SIZE / 2; self.velocityX = -self.velocityX; } else if (self.x > GRID_LEFT_MARGIN + COLS * (BUBBLE_SIZE + BUBBLE_SPACING) - BUBBLE_SIZE / 2) { self.x = GRID_LEFT_MARGIN + COLS * (BUBBLE_SIZE + BUBBLE_SPACING) - BUBBLE_SIZE / 2; self.velocityX = -self.velocityX; } // Check for collision with existing bubbles if (self.y < GRID_TOP_MARGIN + ROWS * (BUBBLE_SIZE + BUBBLE_SPACING)) { var collision = checkBulletCollision(self); if (collision) { // Attach to grid attachBulletToGrid(self, collision); return; } } // Check if reached top if (self.y < GRID_TOP_MARGIN) { attachBulletToGrid(self, { row: 0, col: Math.floor((self.x - GRID_LEFT_MARGIN) / (BUBBLE_SIZE + BUBBLE_SPACING)) }); return; } }; return self; }); var Shooter = Container.expand(function () { var self = Container.call(this); // Create shooter base self.base = LK.getAsset('bubble_blue', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); self.addChild(self.base); // Create loaded bubble self.loadedBubble = new Bubble(); self.loadedBubble.y = -BUBBLE_SIZE - 10; self.addChild(self.loadedBubble); // Track next bubble to load self.nextBubble = new Bubble(); self.nextBubble.y = -BUBBLE_SIZE - 80; self.nextBubble.x = BUBBLE_SIZE + 10; self.addChild(self.nextBubble); // Shooter properties self.angle = 0; self.minAngle = -85; self.maxAngle = 85; self.canShoot = true; // Aim the shooter self.aim = function (targetX, targetY) { // Calculate angle var dx = targetX - self.x; var dy = targetY - self.y; var angle = Math.atan2(dx, -dy) * 180 / Math.PI; // Limit angle angle = Math.max(self.minAngle, Math.min(self.maxAngle, angle)); self.angle = angle; // Rotate shooter self.base.rotation = angle * Math.PI / 180; }; // Shoot the loaded bubble self.shoot = function () { if (!self.canShoot || !gameActive) return; // Create bullet var bullet = new BubbleBullet(self.loadedBubble.bubbleColor); if (self.loadedBubble.isPowerUp) { bullet.convertToPowerUp(self.loadedBubble.powerUpType); } // Set initial position bullet.x = self.x; bullet.y = self.y - BUBBLE_SIZE; // Set velocity based on angle var angleRad = self.angle * Math.PI / 180; bullet.velocityX = Math.sin(angleRad) * 20; bullet.velocityY = -Math.cos(angleRad) * 20; // Add to game game.addChild(bullet); game.bullets.push(bullet); // Play shooting sound LK.getSound('pop').play(); // Load next bubble self.loadedBubble.bubbleColor = self.nextBubble.bubbleColor; self.loadedBubble.isPowerUp = self.nextBubble.isPowerUp; self.loadedBubble.powerUpType = self.nextBubble.powerUpType; if (self.loadedBubble.isPowerUp) { self.loadedBubble.convertToPowerUp(self.loadedBubble.powerUpType); } else { // Reset graphics to show correct color self.loadedBubble.removeChild(self.loadedBubble.graphics); var assetId = 'bubble_' + self.loadedBubble.bubbleColor; self.loadedBubble.graphics = self.loadedBubble.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5, scaleX: self.loadedBubble.bubbleSize / 80, scaleY: self.loadedBubble.bubbleSize / 80 }); } // Create new next bubble self.nextBubble.bubbleColor = getRandomColor(); self.nextBubble.isPowerUp = false; if (Math.random() < POWERUP_CHANCE) { var powerupType = POWERUP_TYPES[Math.floor(Math.random() * POWERUP_TYPES.length)]; self.nextBubble.convertToPowerUp(powerupType); } else { // Reset graphics to show correct color self.nextBubble.removeChild(self.nextBubble.graphics); var assetId = 'bubble_' + self.nextBubble.bubbleColor; self.nextBubble.graphics = self.nextBubble.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5, scaleX: self.nextBubble.bubbleSize / 80, scaleY: self.nextBubble.bubbleSize / 80 }); } // Disable shooting briefly self.canShoot = false; LK.setTimeout(function () { self.canShoot = true; }, 500); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Game Constants var COLS = 8; var ROWS = 12; var BUBBLE_SIZE = 80; var BUBBLE_SPACING = 10; var GRID_TOP_MARGIN = 150; var GRID_LEFT_MARGIN = (2048 - COLS * (BUBBLE_SIZE + BUBBLE_SPACING)) / 2; var SPAWN_INTERVAL = 2000; var COLORS = ['red', 'blue', 'green', 'yellow', 'purple']; var POWERUP_CHANCE = 0.05; var POWERUP_TYPES = ['bomb', 'line']; var bottomLineY = 2732 - 200; // Game Variables var bubbleGrid = []; var gridPositions = []; var score = 0; var level = storage.level || 1; var highScore = storage.highScore || 0; var spawnTimer = null; var levelUpThreshold = 1000; var gameActive = true; var comboMultiplier = 1; var shooter = null; game.bullets = []; // Make bullets a property of the game instance // Game Elements var background = LK.getAsset('game_bg', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); game.addChild(background); // Bottom line (game over line) var bottomLine = LK.getAsset('bottom_line', { anchorX: 0, anchorY: 0, x: 0, y: bottomLineY }); game.addChild(bottomLine); // Setup UI var scoreTxt = new Text2('Score: 0', { size: 80, fill: 0xFFFFFF }); scoreTxt.anchor.set(0, 0); scoreTxt.x = 50; scoreTxt.y = 50; LK.gui.addChild(scoreTxt); var levelTxt = new Text2('Level: ' + level, { size: 80, fill: 0xFFFFFF }); levelTxt.anchor.set(1, 0); levelTxt.x = 2048 - 50; levelTxt.y = 50; LK.gui.addChild(levelTxt); var highScoreTxt = new Text2('High Score: ' + highScore, { size: 60, fill: 0xFFDD00 }); highScoreTxt.anchor.set(0.5, 0); highScoreTxt.x = 2048 / 2; highScoreTxt.y = 50; LK.gui.addChild(highScoreTxt); // Helper Functions function getRandomColor() { return COLORS[Math.floor(Math.random() * COLORS.length)]; } function initialSetup() { // Calculate grid positions calculateGridPositions(); // Initialize bubble grid initializeGrid(); // Initialize shooter initializeShooter(); // Play background music LK.playMusic('bgmusic'); } function initializeShooter() { // Create shooter shooter = new Shooter(); shooter.x = 2048 / 2; shooter.y = 2732 - 200; game.addChild(shooter); // The bullets array is now initialized as game.bullets } function calculateGridPositions() { gridPositions = []; for (var row = 0; row < ROWS; row++) { gridPositions[row] = []; for (var col = 0; col < COLS; col++) { var x = GRID_LEFT_MARGIN + col * (BUBBLE_SIZE + BUBBLE_SPACING) + BUBBLE_SIZE / 2; var y = GRID_TOP_MARGIN + row * (BUBBLE_SIZE + BUBBLE_SPACING) + BUBBLE_SIZE / 2; gridPositions[row][col] = { x: x, y: y }; } } } function initializeGrid() { // Initialize empty grid for (var row = 0; row < ROWS; row++) { bubbleGrid[row] = []; for (var col = 0; col < COLS; col++) { bubbleGrid[row][col] = null; } } // Add initial bubbles (first few rows) for (var row = 0; row < 3; row++) { for (var col = 0; col < COLS; col++) { if (Math.random() < 0.4 + row * 0.1) { // Chance increases for lower rows spawnBubbleAt(row, col); } } } } function spawnBubbleAt(row, col) { if (bubbleGrid[row][col] !== null) return; var bubble = new Bubble(); bubble.row = row; bubble.col = col; // Randomly convert to powerup if (Math.random() < POWERUP_CHANCE) { var powerupType = POWERUP_TYPES[Math.floor(Math.random() * POWERUP_TYPES.length)]; bubble.convertToPowerUp(powerupType); } bubble.x = gridPositions[row][col].x; bubble.y = gridPositions[row][col].y - 300; // Start above the grid game.addChild(bubble); // Animate to correct position bubble.move(gridPositions[row][col].x, gridPositions[row][col].y); bubbleGrid[row][col] = bubble; return bubble; } function startSpawning() { // Clear any existing timer if (spawnTimer) { LK.clearInterval(spawnTimer); } // Adjust spawn interval based on level var adjustedInterval = Math.max(300, SPAWN_INTERVAL - level * 100); spawnTimer = LK.setInterval(function () { if (!gameActive) return; // Shift all bubbles down by one row shiftBubblesDown(); // Spawn new bubbles in top row for (var col = 0; col < COLS; col++) { if (Math.random() < 0.3 + level * 0.03) { // Chance increases with level spawnBubbleAt(0, col); } } }, adjustedInterval); } function shiftBubblesDown() { // Start from bottom row and move up for (var row = ROWS - 1; row > 0; row--) { for (var col = 0; col < COLS; col++) { // Move bubble from row-1 to row bubbleGrid[row][col] = bubbleGrid[row - 1][col]; if (bubbleGrid[row][col]) { bubbleGrid[row][col].row = row; bubbleGrid[row][col].move(gridPositions[row][col].x, gridPositions[row][col].y); } } } // Clear top row for (var col = 0; col < COLS; col++) { bubbleGrid[0][col] = null; } } function checkForMatches(bubble) { if (!bubble || bubble.isMatched || bubble.falling) return []; var visited = {}; var matches = []; var color = bubble.bubbleColor; function visitBubble(r, c) { if (r < 0 || c < 0 || r >= ROWS || c >= COLS) return; if (visited[r + "," + c]) return; if (!bubbleGrid[r][c]) return; if (bubbleGrid[r][c].isMatched) return; if (bubbleGrid[r][c].falling) return; if (!bubble.isPowerUp && bubbleGrid[r][c].bubbleColor !== color) return; visited[r + "," + c] = true; matches.push(bubbleGrid[r][c]); // Check neighbors visitBubble(r - 1, c); // up visitBubble(r + 1, c); // down visitBubble(r, c - 1); // left visitBubble(r, c + 1); // right } visitBubble(bubble.row, bubble.col); return matches; } function popBubbles(bubbles) { if (!bubbles || bubbles.length === 0) return; // Apply powerups for (var i = 0; i < bubbles.length; i++) { var bubble = bubbles[i]; if (bubble.isPowerUp) { if (bubble.powerUpType === 'bomb') { // Bomb: pop all adjacent bubbles for (var r = Math.max(0, bubble.row - 1); r <= Math.min(ROWS - 1, bubble.row + 1); r++) { for (var c = Math.max(0, bubble.col - 1); c <= Math.min(COLS - 1, bubble.col + 1); c++) { if (bubbleGrid[r][c] && !bubbleGrid[r][c].isMatched) { bubbles.push(bubbleGrid[r][c]); } } } LK.getSound('powerup').play(); } else if (bubble.powerUpType === 'line') { // Line: pop entire row for (var c = 0; c < COLS; c++) { if (bubbleGrid[bubble.row][c] && !bubbleGrid[bubble.row][c].isMatched) { bubbles.push(bubbleGrid[bubble.row][c]); } } LK.getSound('powerup').play(); } } } // Remove duplicates from the array var uniqueBubbles = []; var bubbleIds = {}; for (var i = 0; i < bubbles.length; i++) { var bubbleId = bubbles[i].row + "," + bubbles[i].col; if (!bubbleIds[bubbleId]) { bubbleIds[bubbleId] = true; uniqueBubbles.push(bubbles[i]); } } // Calculate points var points = uniqueBubbles.length * 10 * comboMultiplier; // Play sound if (uniqueBubbles.length >= 5) { LK.getSound('combo').play(); // Increase combo multiplier comboMultiplier++; } else { LK.getSound('pop').play(); } // Pop each bubble for (var i = 0; i < uniqueBubbles.length; i++) { var bubble = uniqueBubbles[i]; // Clear from grid bubbleGrid[bubble.row][bubble.col] = null; // Animate and destroy bubble.match(); } // Update score updateScore(points); // Reset combo after a short delay LK.setTimeout(function () { comboMultiplier = 1; }, 1500); } function updateScore(points) { score += points; scoreTxt.setText('Score: ' + score); // Check for level up if (score >= level * levelUpThreshold) { levelUp(); } // Update high score if needed if (score > highScore) { highScore = score; storage.highScore = highScore; highScoreTxt.setText('High Score: ' + highScore); } } function levelUp() { level++; storage.level = level; levelTxt.setText('Level: ' + level); // Flash level text tween(levelTxt, { scaleX: 1.5, scaleY: 1.5 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(levelTxt, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeIn }); } }); // Restart spawning with adjusted timing startSpawning(); } function checkBulletCollision(bullet) { // Check collision with existing bubbles for (var row = 0; row < ROWS; row++) { for (var col = 0; col < COLS; col++) { if (bubbleGrid[row][col]) { var dx = bullet.x - gridPositions[row][col].x; var dy = bullet.y - gridPositions[row][col].y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < BUBBLE_SIZE) { // Find grid position to attach to return findAttachPosition(bullet, row, col); } } } } return null; } function findAttachPosition(bullet, hitRow, hitCol) { // Potential positions var positions = [{ row: hitRow - 1, col: hitCol }, // top { row: hitRow + 1, col: hitCol }, // bottom { row: hitRow, col: hitCol - 1 }, // left { row: hitRow, col: hitCol + 1 }, // right { row: hitRow - 1, col: hitCol - 1 }, // top-left { row: hitRow - 1, col: hitCol + 1 }, // top-right { row: hitRow + 1, col: hitCol - 1 }, // bottom-left { row: hitRow + 1, col: hitCol + 1 } // bottom-right ]; // Find closest empty position var closestPos = null; var closestDist = Infinity; for (var i = 0; i < positions.length; i++) { var pos = positions[i]; if (pos.row >= 0 && pos.row < ROWS && pos.col >= 0 && pos.col < COLS) { if (!bubbleGrid[pos.row][pos.col]) { var dx = bullet.x - gridPositions[pos.row][pos.col].x; var dy = bullet.y - gridPositions[pos.row][pos.col].y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < closestDist) { closestDist = dist; closestPos = pos; } } } } return closestPos; } function attachBulletToGrid(bullet, position) { // Remove from bullets array var index = game.bullets.indexOf(bullet); if (index !== -1) { game.bullets.splice(index, 1); } // Create new grid bubble var newBubble = new Bubble(bullet.bubbleColor); if (bullet.isPowerUp) { newBubble.convertToPowerUp(bullet.powerUpType); } // Set position newBubble.row = position.row; newBubble.col = position.col; newBubble.x = gridPositions[position.row][position.col].x; newBubble.y = gridPositions[position.row][position.col].y; // Add to grid bubbleGrid[position.row][position.col] = newBubble; game.addChild(newBubble); // Play pop sound LK.getSound('pop').play(); // Check for matches var matches = checkForMatches(newBubble); if (matches.length >= 3 || matches.length > 0 && newBubble.isPowerUp) { popBubbles(matches); } // Remove bullet bullet.destroy(); } function gameOver() { gameActive = false; // Stop spawning if (spawnTimer) { LK.clearInterval(spawnTimer); spawnTimer = null; } // Play game over sound LK.getSound('gameover').play(); // Show game over screen LK.showGameOver(); } // Game Logic game.checkMatches = function (clickedBubble) { if (!gameActive) return; var matches = checkForMatches(clickedBubble); // Need at least 3 matching bubbles or a power-up if (matches.length >= 3 || matches.length > 0 && clickedBubble.isPowerUp) { popBubbles(matches); } }; // Game update function game.update = function () { if (!gameActive) return; // Update all bubbles for (var row = 0; row < ROWS; row++) { for (var col = 0; col < COLS; col++) { if (bubbleGrid[row][col]) { bubbleGrid[row][col].update(); } } } // Update all bullets for (var i = game.bullets.length - 1; i >= 0; i--) { game.bullets[i].update(); } // Check for game over condition if (bubbleGrid[ROWS - 1].some(function (bubble) { return bubble !== null; })) { gameOver(); } }; // Input handlers game.down = function (x, y, obj) { if (!gameActive || !shooter) return; shooter.aim(x, y); }; game.move = function (x, y, obj) { if (!gameActive || !shooter) return; shooter.aim(x, y); }; game.up = function (x, y, obj) { if (!gameActive || !shooter) return; shooter.shoot(); }; // Remove startSpawning from initialSetup // Start the game initialSetup(); ;
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0,
level: 1
});
/****
* Classes
****/
var Bubble = Container.expand(function (color, size) {
var self = Container.call(this);
self.bubbleColor = color || getRandomColor();
self.bubbleSize = size || 80;
self.isMatched = false;
self.isPowerUp = false;
self.powerUpType = null;
self.row = 0;
self.col = 0;
self.falling = false;
var assetId = 'bubble_' + self.bubbleColor;
self.graphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: self.bubbleSize / 80,
scaleY: self.bubbleSize / 80
});
self.convertToPowerUp = function (type) {
self.isPowerUp = true;
self.powerUpType = type;
// Replace the graphics with powerup graphics
self.removeChild(self.graphics);
var powerupAssetId = 'powerup_' + type;
self.graphics = self.attachAsset(powerupAssetId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: self.bubbleSize / 80,
scaleY: self.bubbleSize / 80
});
};
self.match = function () {
if (self.isMatched) return;
self.isMatched = true;
// Play pop animation
tween(self.graphics, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
}
});
};
self.move = function (x, y) {
self.falling = true;
tween(self, {
x: x,
y: y
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
self.falling = false;
}
});
};
self.update = function () {
// Bubble specific update logic
if (self.falling === false && self.y > bottomLineY) {
// Bubble has crossed the bottom line
gameOver();
}
};
self.down = function (x, y, obj) {
// Handle bubble click/tap
if (game && !self.falling) {
game.checkMatches(self);
}
};
return self;
});
var BubbleBullet = Container.expand(function (color) {
var self = Container.call(this);
// Bullet properties
self.bubbleColor = color || getRandomColor();
self.bubbleSize = BUBBLE_SIZE;
self.isPowerUp = false;
self.powerUpType = null;
self.velocityX = 0;
self.velocityY = -15;
self.lastX = 0;
self.lastY = 0;
// Create graphics
var assetId = 'bubble_' + self.bubbleColor;
self.graphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
self.convertToPowerUp = function (type) {
self.isPowerUp = true;
self.powerUpType = type;
// Replace the graphics with powerup graphics
self.removeChild(self.graphics);
var powerupAssetId = 'powerup_' + type;
self.graphics = self.attachAsset(powerupAssetId, {
anchorX: 0.5,
anchorY: 0.5
});
};
// Update bullet position and check collisions
self.update = function () {
// Save last position
self.lastX = self.x;
self.lastY = self.y;
// Update position
self.x += self.velocityX;
self.y += self.velocityY;
// Handle wall collisions
if (self.x < GRID_LEFT_MARGIN + BUBBLE_SIZE / 2) {
self.x = GRID_LEFT_MARGIN + BUBBLE_SIZE / 2;
self.velocityX = -self.velocityX;
} else if (self.x > GRID_LEFT_MARGIN + COLS * (BUBBLE_SIZE + BUBBLE_SPACING) - BUBBLE_SIZE / 2) {
self.x = GRID_LEFT_MARGIN + COLS * (BUBBLE_SIZE + BUBBLE_SPACING) - BUBBLE_SIZE / 2;
self.velocityX = -self.velocityX;
}
// Check for collision with existing bubbles
if (self.y < GRID_TOP_MARGIN + ROWS * (BUBBLE_SIZE + BUBBLE_SPACING)) {
var collision = checkBulletCollision(self);
if (collision) {
// Attach to grid
attachBulletToGrid(self, collision);
return;
}
}
// Check if reached top
if (self.y < GRID_TOP_MARGIN) {
attachBulletToGrid(self, {
row: 0,
col: Math.floor((self.x - GRID_LEFT_MARGIN) / (BUBBLE_SIZE + BUBBLE_SPACING))
});
return;
}
};
return self;
});
var Shooter = Container.expand(function () {
var self = Container.call(this);
// Create shooter base
self.base = LK.getAsset('bubble_blue', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
self.addChild(self.base);
// Create loaded bubble
self.loadedBubble = new Bubble();
self.loadedBubble.y = -BUBBLE_SIZE - 10;
self.addChild(self.loadedBubble);
// Track next bubble to load
self.nextBubble = new Bubble();
self.nextBubble.y = -BUBBLE_SIZE - 80;
self.nextBubble.x = BUBBLE_SIZE + 10;
self.addChild(self.nextBubble);
// Shooter properties
self.angle = 0;
self.minAngle = -85;
self.maxAngle = 85;
self.canShoot = true;
// Aim the shooter
self.aim = function (targetX, targetY) {
// Calculate angle
var dx = targetX - self.x;
var dy = targetY - self.y;
var angle = Math.atan2(dx, -dy) * 180 / Math.PI;
// Limit angle
angle = Math.max(self.minAngle, Math.min(self.maxAngle, angle));
self.angle = angle;
// Rotate shooter
self.base.rotation = angle * Math.PI / 180;
};
// Shoot the loaded bubble
self.shoot = function () {
if (!self.canShoot || !gameActive) return;
// Create bullet
var bullet = new BubbleBullet(self.loadedBubble.bubbleColor);
if (self.loadedBubble.isPowerUp) {
bullet.convertToPowerUp(self.loadedBubble.powerUpType);
}
// Set initial position
bullet.x = self.x;
bullet.y = self.y - BUBBLE_SIZE;
// Set velocity based on angle
var angleRad = self.angle * Math.PI / 180;
bullet.velocityX = Math.sin(angleRad) * 20;
bullet.velocityY = -Math.cos(angleRad) * 20;
// Add to game
game.addChild(bullet);
game.bullets.push(bullet);
// Play shooting sound
LK.getSound('pop').play();
// Load next bubble
self.loadedBubble.bubbleColor = self.nextBubble.bubbleColor;
self.loadedBubble.isPowerUp = self.nextBubble.isPowerUp;
self.loadedBubble.powerUpType = self.nextBubble.powerUpType;
if (self.loadedBubble.isPowerUp) {
self.loadedBubble.convertToPowerUp(self.loadedBubble.powerUpType);
} else {
// Reset graphics to show correct color
self.loadedBubble.removeChild(self.loadedBubble.graphics);
var assetId = 'bubble_' + self.loadedBubble.bubbleColor;
self.loadedBubble.graphics = self.loadedBubble.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: self.loadedBubble.bubbleSize / 80,
scaleY: self.loadedBubble.bubbleSize / 80
});
}
// Create new next bubble
self.nextBubble.bubbleColor = getRandomColor();
self.nextBubble.isPowerUp = false;
if (Math.random() < POWERUP_CHANCE) {
var powerupType = POWERUP_TYPES[Math.floor(Math.random() * POWERUP_TYPES.length)];
self.nextBubble.convertToPowerUp(powerupType);
} else {
// Reset graphics to show correct color
self.nextBubble.removeChild(self.nextBubble.graphics);
var assetId = 'bubble_' + self.nextBubble.bubbleColor;
self.nextBubble.graphics = self.nextBubble.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: self.nextBubble.bubbleSize / 80,
scaleY: self.nextBubble.bubbleSize / 80
});
}
// Disable shooting briefly
self.canShoot = false;
LK.setTimeout(function () {
self.canShoot = true;
}, 500);
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Game Constants
var COLS = 8;
var ROWS = 12;
var BUBBLE_SIZE = 80;
var BUBBLE_SPACING = 10;
var GRID_TOP_MARGIN = 150;
var GRID_LEFT_MARGIN = (2048 - COLS * (BUBBLE_SIZE + BUBBLE_SPACING)) / 2;
var SPAWN_INTERVAL = 2000;
var COLORS = ['red', 'blue', 'green', 'yellow', 'purple'];
var POWERUP_CHANCE = 0.05;
var POWERUP_TYPES = ['bomb', 'line'];
var bottomLineY = 2732 - 200;
// Game Variables
var bubbleGrid = [];
var gridPositions = [];
var score = 0;
var level = storage.level || 1;
var highScore = storage.highScore || 0;
var spawnTimer = null;
var levelUpThreshold = 1000;
var gameActive = true;
var comboMultiplier = 1;
var shooter = null;
game.bullets = []; // Make bullets a property of the game instance
// Game Elements
var background = LK.getAsset('game_bg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(background);
// Bottom line (game over line)
var bottomLine = LK.getAsset('bottom_line', {
anchorX: 0,
anchorY: 0,
x: 0,
y: bottomLineY
});
game.addChild(bottomLine);
// Setup UI
var scoreTxt = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0, 0);
scoreTxt.x = 50;
scoreTxt.y = 50;
LK.gui.addChild(scoreTxt);
var levelTxt = new Text2('Level: ' + level, {
size: 80,
fill: 0xFFFFFF
});
levelTxt.anchor.set(1, 0);
levelTxt.x = 2048 - 50;
levelTxt.y = 50;
LK.gui.addChild(levelTxt);
var highScoreTxt = new Text2('High Score: ' + highScore, {
size: 60,
fill: 0xFFDD00
});
highScoreTxt.anchor.set(0.5, 0);
highScoreTxt.x = 2048 / 2;
highScoreTxt.y = 50;
LK.gui.addChild(highScoreTxt);
// Helper Functions
function getRandomColor() {
return COLORS[Math.floor(Math.random() * COLORS.length)];
}
function initialSetup() {
// Calculate grid positions
calculateGridPositions();
// Initialize bubble grid
initializeGrid();
// Initialize shooter
initializeShooter();
// Play background music
LK.playMusic('bgmusic');
}
function initializeShooter() {
// Create shooter
shooter = new Shooter();
shooter.x = 2048 / 2;
shooter.y = 2732 - 200;
game.addChild(shooter);
// The bullets array is now initialized as game.bullets
}
function calculateGridPositions() {
gridPositions = [];
for (var row = 0; row < ROWS; row++) {
gridPositions[row] = [];
for (var col = 0; col < COLS; col++) {
var x = GRID_LEFT_MARGIN + col * (BUBBLE_SIZE + BUBBLE_SPACING) + BUBBLE_SIZE / 2;
var y = GRID_TOP_MARGIN + row * (BUBBLE_SIZE + BUBBLE_SPACING) + BUBBLE_SIZE / 2;
gridPositions[row][col] = {
x: x,
y: y
};
}
}
}
function initializeGrid() {
// Initialize empty grid
for (var row = 0; row < ROWS; row++) {
bubbleGrid[row] = [];
for (var col = 0; col < COLS; col++) {
bubbleGrid[row][col] = null;
}
}
// Add initial bubbles (first few rows)
for (var row = 0; row < 3; row++) {
for (var col = 0; col < COLS; col++) {
if (Math.random() < 0.4 + row * 0.1) {
// Chance increases for lower rows
spawnBubbleAt(row, col);
}
}
}
}
function spawnBubbleAt(row, col) {
if (bubbleGrid[row][col] !== null) return;
var bubble = new Bubble();
bubble.row = row;
bubble.col = col;
// Randomly convert to powerup
if (Math.random() < POWERUP_CHANCE) {
var powerupType = POWERUP_TYPES[Math.floor(Math.random() * POWERUP_TYPES.length)];
bubble.convertToPowerUp(powerupType);
}
bubble.x = gridPositions[row][col].x;
bubble.y = gridPositions[row][col].y - 300; // Start above the grid
game.addChild(bubble);
// Animate to correct position
bubble.move(gridPositions[row][col].x, gridPositions[row][col].y);
bubbleGrid[row][col] = bubble;
return bubble;
}
function startSpawning() {
// Clear any existing timer
if (spawnTimer) {
LK.clearInterval(spawnTimer);
}
// Adjust spawn interval based on level
var adjustedInterval = Math.max(300, SPAWN_INTERVAL - level * 100);
spawnTimer = LK.setInterval(function () {
if (!gameActive) return;
// Shift all bubbles down by one row
shiftBubblesDown();
// Spawn new bubbles in top row
for (var col = 0; col < COLS; col++) {
if (Math.random() < 0.3 + level * 0.03) {
// Chance increases with level
spawnBubbleAt(0, col);
}
}
}, adjustedInterval);
}
function shiftBubblesDown() {
// Start from bottom row and move up
for (var row = ROWS - 1; row > 0; row--) {
for (var col = 0; col < COLS; col++) {
// Move bubble from row-1 to row
bubbleGrid[row][col] = bubbleGrid[row - 1][col];
if (bubbleGrid[row][col]) {
bubbleGrid[row][col].row = row;
bubbleGrid[row][col].move(gridPositions[row][col].x, gridPositions[row][col].y);
}
}
}
// Clear top row
for (var col = 0; col < COLS; col++) {
bubbleGrid[0][col] = null;
}
}
function checkForMatches(bubble) {
if (!bubble || bubble.isMatched || bubble.falling) return [];
var visited = {};
var matches = [];
var color = bubble.bubbleColor;
function visitBubble(r, c) {
if (r < 0 || c < 0 || r >= ROWS || c >= COLS) return;
if (visited[r + "," + c]) return;
if (!bubbleGrid[r][c]) return;
if (bubbleGrid[r][c].isMatched) return;
if (bubbleGrid[r][c].falling) return;
if (!bubble.isPowerUp && bubbleGrid[r][c].bubbleColor !== color) return;
visited[r + "," + c] = true;
matches.push(bubbleGrid[r][c]);
// Check neighbors
visitBubble(r - 1, c); // up
visitBubble(r + 1, c); // down
visitBubble(r, c - 1); // left
visitBubble(r, c + 1); // right
}
visitBubble(bubble.row, bubble.col);
return matches;
}
function popBubbles(bubbles) {
if (!bubbles || bubbles.length === 0) return;
// Apply powerups
for (var i = 0; i < bubbles.length; i++) {
var bubble = bubbles[i];
if (bubble.isPowerUp) {
if (bubble.powerUpType === 'bomb') {
// Bomb: pop all adjacent bubbles
for (var r = Math.max(0, bubble.row - 1); r <= Math.min(ROWS - 1, bubble.row + 1); r++) {
for (var c = Math.max(0, bubble.col - 1); c <= Math.min(COLS - 1, bubble.col + 1); c++) {
if (bubbleGrid[r][c] && !bubbleGrid[r][c].isMatched) {
bubbles.push(bubbleGrid[r][c]);
}
}
}
LK.getSound('powerup').play();
} else if (bubble.powerUpType === 'line') {
// Line: pop entire row
for (var c = 0; c < COLS; c++) {
if (bubbleGrid[bubble.row][c] && !bubbleGrid[bubble.row][c].isMatched) {
bubbles.push(bubbleGrid[bubble.row][c]);
}
}
LK.getSound('powerup').play();
}
}
}
// Remove duplicates from the array
var uniqueBubbles = [];
var bubbleIds = {};
for (var i = 0; i < bubbles.length; i++) {
var bubbleId = bubbles[i].row + "," + bubbles[i].col;
if (!bubbleIds[bubbleId]) {
bubbleIds[bubbleId] = true;
uniqueBubbles.push(bubbles[i]);
}
}
// Calculate points
var points = uniqueBubbles.length * 10 * comboMultiplier;
// Play sound
if (uniqueBubbles.length >= 5) {
LK.getSound('combo').play();
// Increase combo multiplier
comboMultiplier++;
} else {
LK.getSound('pop').play();
}
// Pop each bubble
for (var i = 0; i < uniqueBubbles.length; i++) {
var bubble = uniqueBubbles[i];
// Clear from grid
bubbleGrid[bubble.row][bubble.col] = null;
// Animate and destroy
bubble.match();
}
// Update score
updateScore(points);
// Reset combo after a short delay
LK.setTimeout(function () {
comboMultiplier = 1;
}, 1500);
}
function updateScore(points) {
score += points;
scoreTxt.setText('Score: ' + score);
// Check for level up
if (score >= level * levelUpThreshold) {
levelUp();
}
// Update high score if needed
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('High Score: ' + highScore);
}
}
function levelUp() {
level++;
storage.level = level;
levelTxt.setText('Level: ' + level);
// Flash level text
tween(levelTxt, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(levelTxt, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
});
// Restart spawning with adjusted timing
startSpawning();
}
function checkBulletCollision(bullet) {
// Check collision with existing bubbles
for (var row = 0; row < ROWS; row++) {
for (var col = 0; col < COLS; col++) {
if (bubbleGrid[row][col]) {
var dx = bullet.x - gridPositions[row][col].x;
var dy = bullet.y - gridPositions[row][col].y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < BUBBLE_SIZE) {
// Find grid position to attach to
return findAttachPosition(bullet, row, col);
}
}
}
}
return null;
}
function findAttachPosition(bullet, hitRow, hitCol) {
// Potential positions
var positions = [{
row: hitRow - 1,
col: hitCol
},
// top
{
row: hitRow + 1,
col: hitCol
},
// bottom
{
row: hitRow,
col: hitCol - 1
},
// left
{
row: hitRow,
col: hitCol + 1
},
// right
{
row: hitRow - 1,
col: hitCol - 1
},
// top-left
{
row: hitRow - 1,
col: hitCol + 1
},
// top-right
{
row: hitRow + 1,
col: hitCol - 1
},
// bottom-left
{
row: hitRow + 1,
col: hitCol + 1
} // bottom-right
];
// Find closest empty position
var closestPos = null;
var closestDist = Infinity;
for (var i = 0; i < positions.length; i++) {
var pos = positions[i];
if (pos.row >= 0 && pos.row < ROWS && pos.col >= 0 && pos.col < COLS) {
if (!bubbleGrid[pos.row][pos.col]) {
var dx = bullet.x - gridPositions[pos.row][pos.col].x;
var dy = bullet.y - gridPositions[pos.row][pos.col].y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < closestDist) {
closestDist = dist;
closestPos = pos;
}
}
}
}
return closestPos;
}
function attachBulletToGrid(bullet, position) {
// Remove from bullets array
var index = game.bullets.indexOf(bullet);
if (index !== -1) {
game.bullets.splice(index, 1);
}
// Create new grid bubble
var newBubble = new Bubble(bullet.bubbleColor);
if (bullet.isPowerUp) {
newBubble.convertToPowerUp(bullet.powerUpType);
}
// Set position
newBubble.row = position.row;
newBubble.col = position.col;
newBubble.x = gridPositions[position.row][position.col].x;
newBubble.y = gridPositions[position.row][position.col].y;
// Add to grid
bubbleGrid[position.row][position.col] = newBubble;
game.addChild(newBubble);
// Play pop sound
LK.getSound('pop').play();
// Check for matches
var matches = checkForMatches(newBubble);
if (matches.length >= 3 || matches.length > 0 && newBubble.isPowerUp) {
popBubbles(matches);
}
// Remove bullet
bullet.destroy();
}
function gameOver() {
gameActive = false;
// Stop spawning
if (spawnTimer) {
LK.clearInterval(spawnTimer);
spawnTimer = null;
}
// Play game over sound
LK.getSound('gameover').play();
// Show game over screen
LK.showGameOver();
}
// Game Logic
game.checkMatches = function (clickedBubble) {
if (!gameActive) return;
var matches = checkForMatches(clickedBubble);
// Need at least 3 matching bubbles or a power-up
if (matches.length >= 3 || matches.length > 0 && clickedBubble.isPowerUp) {
popBubbles(matches);
}
};
// Game update function
game.update = function () {
if (!gameActive) return;
// Update all bubbles
for (var row = 0; row < ROWS; row++) {
for (var col = 0; col < COLS; col++) {
if (bubbleGrid[row][col]) {
bubbleGrid[row][col].update();
}
}
}
// Update all bullets
for (var i = game.bullets.length - 1; i >= 0; i--) {
game.bullets[i].update();
}
// Check for game over condition
if (bubbleGrid[ROWS - 1].some(function (bubble) {
return bubble !== null;
})) {
gameOver();
}
};
// Input handlers
game.down = function (x, y, obj) {
if (!gameActive || !shooter) return;
shooter.aim(x, y);
};
game.move = function (x, y, obj) {
if (!gameActive || !shooter) return;
shooter.aim(x, y);
};
game.up = function (x, y, obj) {
if (!gameActive || !shooter) return;
shooter.shoot();
};
// Remove startSpawning from initialSetup
// Start the game
initialSetup();
;