User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'push')' in or related to this line: 'game.bullets.push(bullet);' Line Number: 226
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'push')' in or related to this line: 'game.bullets.push(bullet);' Line Number: 226
User prompt
This game create to like bubble shooter game add to this
Code edit (1 edits merged)
Please save this source code
User prompt
Bubble Pop Frenzy
User prompt
Please continue polishing my design document.
Initial prompt
Robbery game to catching a police officer
/**** * 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();
;