/****
* 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 (ballRef) {
return function () {
if (ballRef) {
tween(ballRef, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 500,
easing: tween.easeIn
});
}
};
}(targetBall)
});
}
}
}
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(); /****
* 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 (ballRef) {
return function () {
if (ballRef) {
tween(ballRef, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 500,
easing: tween.easeIn
});
}
};
}(targetBall)
});
}
}
}
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();