User prompt
Please fix the bug: 'TypeError: Cannot use 'in' operator to search for 'scaleX' in null' in or related to this line: 'tween(targetBall, {' Line Number: 350 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Make the bomb effect 1 second and we will see the tip of the bomb burn. When this happens, the bomb effect will cover the entire screen, but do not forget that it is for 1 second. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
When the bomb touches any two of the same colored balls, that is, when there are 3 of them, the bomb's pin will explode with a burning effect in a 3x3 area, again with an effect. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
The game is very nice and looks nice but at the very beginning of the game it tries to score points by itself. Prevent this.
Code edit (1 edits merged)
Please save this source code
User prompt
Bubble Blast Crush
Initial prompt
Make me a Candy Crush-style game where you score points by matching 3 balls of the same color. Have a bar at the top and score points there. Also, make a bomb that will come every once in a while. When it lands next to any three, it will destroy a 3x3 area. The rest is up to you.
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Ball = Container.expand(function (colorType, isBomb) {
var self = Container.call(this);
self.colorType = colorType || 'red';
self.isBomb = isBomb || false;
self.gridX = 0;
self.gridY = 0;
self.isAnimating = false;
// Create ball graphics
var assetId = self.isBomb ? 'bombBall' : self.colorType + 'Ball';
var ballGraphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
if (self.isBomb) {
// Add visual indicator for bomb (make it slightly larger)
ballGraphics.scaleX = 1.1;
ballGraphics.scaleY = 1.1;
}
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = GRID_START_X + gridX * CELL_SIZE;
self.y = GRID_START_Y + gridY * CELL_SIZE;
};
self.animateToPosition = function (targetX, targetY, duration, onComplete) {
self.isAnimating = true;
tween(self, {
x: targetX,
y: targetY
}, {
duration: duration || 300,
easing: tween.easeOut,
onFinish: function onFinish() {
self.isAnimating = false;
if (onComplete) onComplete();
}
});
};
self.animateDestroy = function (onComplete) {
tween(self, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 200,
easing: tween.easeIn,
onFinish: onComplete
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
// Sound effects
// Grid background
// Special bomb ball
// Grid balls in different colors
// Game constants
var GRID_SIZE = 8;
var CELL_SIZE = 220;
var GRID_START_X = (2048 - GRID_SIZE * CELL_SIZE) / 2 + CELL_SIZE / 2;
var GRID_START_Y = 400;
var BALL_COLORS = ['red', 'blue', 'green', 'yellow', 'purple', 'orange'];
// Game variables
var grid = [];
var selectedBall = null;
var isProcessingMatches = false;
var score = 0;
// Initialize score display
var scoreTxt = new Text2('Score: 0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Create grid background
function createGridBackground() {
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
var cell = LK.getAsset('gridCell', {
anchorX: 0.5,
anchorY: 0.5
});
cell.x = GRID_START_X + x * CELL_SIZE;
cell.y = GRID_START_Y + y * CELL_SIZE;
cell.alpha = 0.3;
game.addChild(cell);
}
}
}
// Initialize grid with random balls
function initializeGrid() {
grid = [];
for (var x = 0; x < GRID_SIZE; x++) {
grid[x] = [];
for (var y = 0; y < GRID_SIZE; y++) {
var colorType = BALL_COLORS[Math.floor(Math.random() * BALL_COLORS.length)];
var isBomb = Math.random() < 0.05; // 5% chance for bomb
var ball = new Ball(colorType, isBomb);
ball.setGridPosition(x, y);
grid[x][y] = ball;
game.addChild(ball);
}
}
}
// Get ball at grid position
function getBallAt(gridX, gridY) {
if (gridX < 0 || gridX >= GRID_SIZE || gridY < 0 || gridY >= GRID_SIZE) {
return null;
}
return grid[gridX][gridY];
}
// Check if two grid positions are adjacent
function areAdjacent(x1, y1, x2, y2) {
var dx = Math.abs(x1 - x2);
var dy = Math.abs(y1 - y2);
return dx === 1 && dy === 0 || dx === 0 && dy === 1;
}
// Swap two balls in the grid
function swapBalls(ball1, ball2) {
var tempX = ball1.gridX;
var tempY = ball1.gridY;
// Update grid references
grid[ball1.gridX][ball1.gridY] = ball2;
grid[ball2.gridX][ball2.gridY] = ball1;
// Update ball positions
ball1.setGridPosition(ball2.gridX, ball2.gridY);
ball2.setGridPosition(tempX, tempY);
// Animate the swap
ball1.animateToPosition(ball1.x, ball1.y, 200);
ball2.animateToPosition(ball2.x, ball2.y, 200);
LK.getSound('swap').play();
}
// Find matches of 3 or more
function findMatches() {
var matches = [];
var checked = [];
// Initialize checked array
for (var x = 0; x < GRID_SIZE; x++) {
checked[x] = [];
for (var y = 0; y < GRID_SIZE; y++) {
checked[x][y] = false;
}
}
// Check horizontal matches
for (var y = 0; y < GRID_SIZE; y++) {
for (var x = 0; x < GRID_SIZE - 2; x++) {
var ball = getBallAt(x, y);
if (!ball || ball.isBomb) continue;
var matchLength = 1;
var matchBalls = [ball];
for (var i = x + 1; i < GRID_SIZE; i++) {
var nextBall = getBallAt(i, y);
if (nextBall && !nextBall.isBomb && nextBall.colorType === ball.colorType) {
matchLength++;
matchBalls.push(nextBall);
} else {
break;
}
}
if (matchLength >= 3) {
for (var j = 0; j < matchBalls.length; j++) {
var mb = matchBalls[j];
if (!checked[mb.gridX][mb.gridY]) {
matches.push(mb);
checked[mb.gridX][mb.gridY] = true;
}
}
}
}
}
// Check vertical matches
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE - 2; y++) {
var ball = getBallAt(x, y);
if (!ball || ball.isBomb) continue;
var matchLength = 1;
var matchBalls = [ball];
for (var i = y + 1; i < GRID_SIZE; i++) {
var nextBall = getBallAt(x, i);
if (nextBall && !nextBall.isBomb && nextBall.colorType === ball.colorType) {
matchLength++;
matchBalls.push(nextBall);
} else {
break;
}
}
if (matchLength >= 3) {
for (var j = 0; j < matchBalls.length; j++) {
var mb = matchBalls[j];
if (!checked[mb.gridX][mb.gridY]) {
matches.push(mb);
checked[mb.gridX][mb.gridY] = true;
}
}
}
}
}
return matches;
}
// Find bomb matches (bombs matched with 3+ regular balls)
function findBombMatches() {
var bombMatches = [];
// Check if any bombs are part of regular matches by checking if bomb + 2 adjacent balls form a match
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
var ball = getBallAt(x, y);
if (!ball || !ball.isBomb) continue;
// Check horizontal matches with bomb
if (x >= 2) {
var left1 = getBallAt(x - 1, y);
var left2 = getBallAt(x - 2, y);
if (left1 && left2 && !left1.isBomb && !left2.isBomb && left1.colorType === left2.colorType) {
bombMatches.push(ball);
continue;
}
}
if (x <= GRID_SIZE - 3) {
var right1 = getBallAt(x + 1, y);
var right2 = getBallAt(x + 2, y);
if (right1 && right2 && !right1.isBomb && !right2.isBomb && right1.colorType === right2.colorType) {
bombMatches.push(ball);
continue;
}
}
if (x >= 1 && x <= GRID_SIZE - 2) {
var left = getBallAt(x - 1, y);
var right = getBallAt(x + 1, y);
if (left && right && !left.isBomb && !right.isBomb && left.colorType === right.colorType) {
bombMatches.push(ball);
continue;
}
}
// Check vertical matches with bomb
if (y >= 2) {
var up1 = getBallAt(x, y - 1);
var up2 = getBallAt(x, y - 2);
if (up1 && up2 && !up1.isBomb && !up2.isBomb && up1.colorType === up2.colorType) {
bombMatches.push(ball);
continue;
}
}
if (y <= GRID_SIZE - 3) {
var down1 = getBallAt(x, y + 1);
var down2 = getBallAt(x, y + 2);
if (down1 && down2 && !down1.isBomb && !down2.isBomb && down1.colorType === down2.colorType) {
bombMatches.push(ball);
continue;
}
}
if (y >= 1 && y <= GRID_SIZE - 2) {
var up = getBallAt(x, y - 1);
var down = getBallAt(x, y + 1);
if (up && down && !up.isBomb && !down.isBomb && up.colorType === down.colorType) {
bombMatches.push(ball);
continue;
}
}
}
}
return bombMatches;
}
// Explode bomb in 3x3 area
function explodeBomb(bombBall) {
var toDestroy = [];
var centerX = bombBall.gridX;
var centerY = bombBall.gridY;
// Add bomb itself
toDestroy.push(bombBall);
// Create burning explosion effect first
var explosionCenter = {
x: GRID_START_X + centerX * CELL_SIZE,
y: GRID_START_Y + centerY * CELL_SIZE
};
// Show bomb tip burning effect first
tween(bombBall, {
tint: 0xFF0000,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Create screen-wide explosion effect for 1 second
LK.effects.flashScreen(0xFF6600, 1000);
}
});
// Add 3x3 area around bomb
for (var dx = -1; dx <= 1; dx++) {
for (var dy = -1; dy <= 1; dy++) {
var targetX = centerX + dx;
var targetY = centerY + dy;
var targetBall = getBallAt(targetX, targetY);
if (targetBall && targetBall !== bombBall) {
toDestroy.push(targetBall);
// Create burning effect for each affected ball
tween(targetBall, {
tint: 0xFF4400
}, {
duration: 500,
easing: tween.easeIn
});
tween(targetBall, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(targetBall, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 500,
easing: tween.easeIn
});
}
});
}
}
}
return toDestroy;
}
// Remove matched balls and update score
function removeMatches(matches) {
var pointsPerBall = 10;
var bombBonus = 50;
for (var i = 0; i < matches.length; i++) {
var ball = matches[i];
if (ball.isBomb) {
score += bombBonus;
} else {
score += pointsPerBall;
}
// Remove from grid
grid[ball.gridX][ball.gridY] = null;
// Animate destruction
ball.animateDestroy(function () {
// Ball will be destroyed after animation
});
}
// Update score display
scoreTxt.setText('Score: ' + score);
LK.setScore(score);
if (matches.length > 0) {
LK.getSound('match').play();
}
}
// Make balls fall down to fill empty spaces
function applyGravity() {
var ballsMoved = false;
for (var x = 0; x < GRID_SIZE; x++) {
// Move existing balls down
for (var y = GRID_SIZE - 1; y >= 0; y--) {
if (grid[x][y] === null) {
// Find the next ball above
for (var searchY = y - 1; searchY >= 0; searchY--) {
if (grid[x][searchY] !== null) {
var ball = grid[x][searchY];
grid[x][y] = ball;
grid[x][searchY] = null;
ball.setGridPosition(x, y);
ball.animateToPosition(ball.x, ball.y, 300);
ballsMoved = true;
break;
}
}
}
}
// Fill empty spaces at top with new balls
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[x][y] === null) {
var colorType = BALL_COLORS[Math.floor(Math.random() * BALL_COLORS.length)];
var isBomb = Math.random() < 0.03; // 3% chance for new bomb
var newBall = new Ball(colorType, isBomb);
// Start above the grid
newBall.x = GRID_START_X + x * CELL_SIZE;
newBall.y = GRID_START_Y - CELL_SIZE;
newBall.gridX = x;
newBall.gridY = y;
grid[x][y] = newBall;
game.addChild(newBall);
// Animate falling
newBall.animateToPosition(GRID_START_X + x * CELL_SIZE, GRID_START_Y + y * CELL_SIZE, 400);
ballsMoved = true;
}
}
}
return ballsMoved;
}
// Process all matches and cascades
function processMatches() {
if (isProcessingMatches) return;
isProcessingMatches = true;
function processStep() {
// Find regular matches
var matches = findMatches();
// Find bomb matches
var bombMatches = findBombMatches();
var bombExplosions = [];
// Process bomb explosions
for (var i = 0; i < bombMatches.length; i++) {
var explosionBalls = explodeBomb(bombMatches[i]);
for (var j = 0; j < explosionBalls.length; j++) {
bombExplosions.push(explosionBalls[j]);
}
}
// Combine all balls to remove
var allMatches = matches.concat(bombExplosions);
if (allMatches.length > 0) {
if (bombMatches.length > 0) {
LK.getSound('bomb').play();
}
removeMatches(allMatches);
// Wait for destruction animation, then apply gravity
LK.setTimeout(function () {
var ballsMoved = applyGravity();
if (ballsMoved) {
// Wait for gravity animation, then check for more matches
LK.setTimeout(function () {
processStep(); // Recursive call for cascading
}, 500);
} else {
isProcessingMatches = false;
}
}, 300);
} else {
isProcessingMatches = false;
}
}
processStep();
}
// Handle ball selection and swapping
function handleBallClick(clickedBall) {
if (isProcessingMatches || clickedBall.isAnimating) return;
if (selectedBall === null) {
// Select the ball
selectedBall = clickedBall;
selectedBall.alpha = 0.7; // Visual feedback
} else if (selectedBall === clickedBall) {
// Deselect the same ball
selectedBall.alpha = 1.0;
selectedBall = null;
} else if (areAdjacent(selectedBall.gridX, selectedBall.gridY, clickedBall.gridX, clickedBall.gridY)) {
// Swap adjacent balls
selectedBall.alpha = 1.0;
// Perform the swap
swapBalls(selectedBall, clickedBall);
// Check for matches after swap
LK.setTimeout(function () {
processMatches();
selectedBall = null;
}, 250);
} else {
// Select new ball
selectedBall.alpha = 1.0;
selectedBall = clickedBall;
selectedBall.alpha = 0.7;
}
}
// Touch/click handling
game.down = function (x, y, obj) {
// Find which ball was clicked
for (var gridX = 0; gridX < GRID_SIZE; gridX++) {
for (var gridY = 0; gridY < GRID_SIZE; gridY++) {
var ball = getBallAt(gridX, gridY);
if (ball && !ball.isAnimating) {
var ballBounds = {
left: ball.x - CELL_SIZE / 2,
right: ball.x + CELL_SIZE / 2,
top: ball.y - CELL_SIZE / 2,
bottom: ball.y + CELL_SIZE / 2
};
if (x >= ballBounds.left && x <= ballBounds.right && y >= ballBounds.top && y <= ballBounds.bottom) {
handleBallClick(ball);
return;
}
}
}
}
// If no ball clicked, deselect
if (selectedBall) {
selectedBall.alpha = 1.0;
selectedBall = null;
}
};
// Game update loop
game.update = function () {
// The game logic is event-driven, so minimal updates needed here
// Most of the game happens in response to user clicks and animations
};
// Initialize the game
createGridBackground();
initializeGrid(); ===================================================================
--- original.js
+++ change.js
@@ -288,8 +288,21 @@
var explosionCenter = {
x: GRID_START_X + centerX * CELL_SIZE,
y: GRID_START_Y + centerY * CELL_SIZE
};
+ // Show bomb tip burning effect first
+ tween(bombBall, {
+ tint: 0xFF0000,
+ scaleX: 1.2,
+ scaleY: 1.2
+ }, {
+ duration: 500,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ // Create screen-wide explosion effect for 1 second
+ LK.effects.flashScreen(0xFF6600, 1000);
+ }
+ });
// Add 3x3 area around bomb
for (var dx = -1; dx <= 1; dx++) {
for (var dy = -1; dy <= 1; dy++) {
var targetX = centerX + dx;
@@ -300,32 +313,30 @@
// Create burning effect for each affected ball
tween(targetBall, {
tint: 0xFF4400
}, {
- duration: 100,
+ duration: 500,
easing: tween.easeIn
});
tween(targetBall, {
scaleX: 1.3,
scaleY: 1.3
}, {
- duration: 150,
+ duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(targetBall, {
scaleX: 0.8,
scaleY: 0.8
}, {
- duration: 100,
+ duration: 500,
easing: tween.easeIn
});
}
});
}
}
}
- // Create explosion flash effect at bomb center
- LK.effects.flashScreen(0xFF6600, 300);
return toDestroy;
}
// Remove matched balls and update score
function removeMatches(matches) {