/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Ball = Container.expand(function (colorIndex, isBomb) { var self = Container.call(this); self.colorIndex = colorIndex; self.isBomb = isBomb || false; self.gridX = 0; self.gridY = 0; self.isAnimating = false; var ballColors = ['redBall', 'blueBall', 'greenBall', 'yellowBall', 'purpleBall']; var assetName = self.isBomb ? 'bombBall' : ballColors[colorIndex]; var ballGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); self.setGridPosition = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; var targetX = gridOffsetX + gridX * cellSize + cellSize * 0.5; var targetY = gridOffsetY + gridY * cellSize + cellSize * 0.5; self.x = targetX; self.y = targetY; }; self.animateToGridPosition = function (gridX, gridY, onComplete) { self.gridX = gridX; self.gridY = gridY; var targetX = gridOffsetX + gridX * cellSize + cellSize * 0.5; var targetY = gridOffsetY + gridY * cellSize + cellSize * 0.5; self.isAnimating = true; tween(self, { x: targetX, y: targetY }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { self.isAnimating = false; if (onComplete) onComplete(); } }); }; return self; }); var Wall = Container.expand(function () { var self = Container.call(this); self.isWall = true; self.gridX = 0; self.gridY = 0; var wallGraphics = self.attachAsset('wall', { anchorX: 0.5, anchorY: 0.5 }); self.setGridPosition = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; var targetX = gridOffsetX + gridX * cellSize + cellSize * 0.5; var targetY = gridOffsetY + gridY * cellSize + cellSize * 0.5; self.x = targetX; self.y = targetY; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222222 }); /**** * Game Code ****/ var gridSize = 8; var cellSize = 200; var gridOffsetX = (2048 - gridSize * cellSize) * 0.5; var gridOffsetY = (2732 - gridSize * cellSize) * 0.5 - 200; var grid = []; var selectedBall = null; var isProcessing = false; var movesLeft = 30; var targetScore = 5000; var animatingBalls = 0; // Create grid background var background = game.attachAsset('gridBackground', { anchorX: 0.5, anchorY: 0.5, x: 2048 * 0.5, y: gridOffsetY + 800 }); // Initialize UI var scoreText = new Text2('Score: 0', { size: 80, fill: 0xFFFFFF }); scoreText.anchor.set(0.5, 0); LK.gui.top.addChild(scoreText); var movesText = new Text2('Moves: ' + movesLeft, { size: 60, fill: 0xFFFFFF }); movesText.anchor.set(1, 0); movesText.x = -50; movesText.y = 100; LK.gui.topRight.addChild(movesText); var targetText = new Text2('Target: ' + targetScore, { size: 60, fill: 0xFFFF00 }); targetText.anchor.set(0, 0); targetText.x = 50; targetText.y = 100; LK.gui.topLeft.addChild(targetText); // Initialize grid function initializeGrid() { grid = []; for (var y = 0; y < gridSize; y++) { grid[y] = []; for (var x = 0; x < gridSize; x++) { // Check if this position should be a wall var isWallPosition = false; if ((y === 3 || y === 4) && (x === 0 || x === 1 || x === 6 || x === 7)) { isWallPosition = true; } if (isWallPosition) { var wall = new Wall(); wall.setGridPosition(x, y); grid[y][x] = wall; game.addChild(wall); } else { var isBomb = Math.random() < 0.05; // 5% chance for bomb var colorIndex = Math.floor(Math.random() * 5); var ball = new Ball(colorIndex, isBomb); ball.setGridPosition(x, y); grid[y][x] = ball; game.addChild(ball); } } } // Remove initial matches var hasMatches = true; while (hasMatches) { var matches = findMatches(); if (matches.length > 0) { for (var i = 0; i < matches.length; i++) { var match = matches[i]; var isBomb = Math.random() < 0.05; // 5% chance for bomb var newColorIndex = Math.floor(Math.random() * 5); grid[match.y][match.x].colorIndex = newColorIndex; grid[match.y][match.x].isBomb = isBomb; // Update ball graphics var ball = grid[match.y][match.x]; ball.removeChildren(); var ballColors = ['redBall', 'blueBall', 'greenBall', 'yellowBall', 'purpleBall']; var assetName = isBomb ? 'bombBall' : ballColors[newColorIndex]; ball.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); } } else { hasMatches = false; } } } function getBallAt(x, y) { if (x < 0 || x >= gridSize || y < 0 || y >= gridSize) return null; var cell = grid[y][x]; if (cell && cell.isWall) return null; // Treat walls as empty for ball operations return cell; } function isAdjacent(ball1, ball2) { var dx = Math.abs(ball1.gridX - ball2.gridX); var dy = Math.abs(ball1.gridY - ball2.gridY); return dx === 1 && dy === 0 || dx === 0 && dy === 1; } function swapBalls(ball1, ball2) { var tempX = ball1.gridX; var tempY = ball1.gridY; // Update grid positions grid[ball1.gridY][ball1.gridX] = ball2; grid[ball2.gridY][ball2.gridX] = ball1; // Animate to new positions animatingBalls += 2; ball1.animateToGridPosition(ball2.gridX, ball2.gridY, function () { animatingBalls--; // Check if this ball is a bomb and explode it if (ball1.isBomb) { explodeBomb(ball1.gridX, ball1.gridY); LK.setTimeout(function () { dropBalls(); }, 300); } }); ball2.animateToGridPosition(tempX, tempY, function () { animatingBalls--; // Check if this ball is a bomb and explode it if (ball2.isBomb) { explodeBomb(ball2.gridX, ball2.gridY); LK.setTimeout(function () { dropBalls(); }, 300); } }); } function findMatches() { var matches = []; var checked = []; // Initialize checked array for (var y = 0; y < gridSize; y++) { checked[y] = []; for (var x = 0; x < gridSize; x++) { checked[y][x] = false; } } // Check horizontal matches for (var y = 0; y < gridSize; y++) { var count = 1; var currentColor = grid[y][0] ? grid[y][0].colorIndex : -1; var currentIsBomb = grid[y][0] ? grid[y][0].isBomb : false; for (var x = 1; x < gridSize; x++) { if (grid[y][x] && grid[y][x].colorIndex === currentColor && !grid[y][x].isBomb && !currentIsBomb) { count++; } else { if (count >= 3) { for (var i = x - count; i < x; i++) { if (!checked[y][i]) { matches.push({ x: i, y: y }); checked[y][i] = true; } } } count = 1; currentColor = grid[y][x] ? grid[y][x].colorIndex : -1; currentIsBomb = grid[y][x] ? grid[y][x].isBomb : false; } } if (count >= 3) { for (var i = gridSize - count; i < gridSize; i++) { if (!checked[y][i]) { matches.push({ x: i, y: y }); checked[y][i] = true; } } } } // Check vertical matches for (var x = 0; x < gridSize; x++) { var count = 1; var currentColor = grid[0][x] ? grid[0][x].colorIndex : -1; var currentIsBomb = grid[0][x] ? grid[0][x].isBomb : false; for (var y = 1; y < gridSize; y++) { if (grid[y][x] && grid[y][x].colorIndex === currentColor && !grid[y][x].isBomb && !currentIsBomb) { count++; } else { if (count >= 3) { for (var i = y - count; i < y; i++) { if (!checked[i][x]) { matches.push({ x: x, y: i }); checked[i][x] = true; } } } count = 1; currentColor = grid[y][x] ? grid[y][x].colorIndex : -1; } } if (count >= 3) { for (var i = gridSize - count; i < gridSize; i++) { if (!checked[i][x]) { matches.push({ x: x, y: i }); checked[i][x] = true; } } } } return matches; } function createExplosionEffect(x, y, colorIndex) { var ballColors = ['redBall', 'blueBall', 'greenBall', 'yellowBall', 'purpleBall']; var particleCount = 8; // Play explosion sound effect LK.getSound('explosion').play(); for (var i = 0; i < particleCount; i++) { // Create small particle var particle = game.attachAsset(ballColors[colorIndex], { anchorX: 0.5, anchorY: 0.5, x: x, y: y, scaleX: 0.3, scaleY: 0.3 }); // Calculate random direction and distance var angle = Math.PI * 2 / particleCount * i + (Math.random() - 0.5) * 0.5; var distance = 80 + Math.random() * 60; var targetX = x + Math.cos(angle) * distance; var targetY = y + Math.sin(angle) * distance; var rotationSpeed = (Math.random() - 0.5) * Math.PI * 4; // Animate particle explosion tween(particle, { x: targetX, y: targetY, alpha: 0, scaleX: 0.1, scaleY: 0.1, rotation: rotationSpeed }, { duration: 400 + Math.random() * 200, easing: tween.easeOut, onFinish: function onFinish() { particle.destroy(); } }); } } function explodeBomb(bombX, bombY) { var explodedBalls = []; var points = 0; // Play bomb explosion sound LK.getSound('bombExplosion').play(); // Create large explosion effect at bomb position var bombBall = grid[bombY][bombX]; if (bombBall) { createBombExplosionEffect(bombBall.x, bombBall.y); } // Explode 3x3 area around bomb for (var dy = -1; dy <= 1; dy++) { for (var dx = -1; dx <= 1; dx++) { var targetX = bombX + dx; var targetY = bombY + dy; if (targetX >= 0 && targetX < gridSize && targetY >= 0 && targetY < gridSize) { var targetBall = grid[targetY][targetX]; if (targetBall) { explodedBalls.push({ x: targetX, y: targetY }); points += 50; } } } } // Remove exploded balls for (var i = 0; i < explodedBalls.length; i++) { var pos = explodedBalls[i]; var ball = grid[pos.y][pos.x]; if (ball) { // Create explosion effect for each ball createExplosionEffect(ball.x, ball.y, ball.isBomb ? 0 : ball.colorIndex); tween(ball, { alpha: 0, scaleX: 0, scaleY: 0 }, { duration: 200, easing: tween.easeIn }); ball.destroy(); grid[pos.y][pos.x] = null; } } LK.setScore(LK.getScore() + points); scoreText.setText('Score: ' + LK.getScore()); } function removeMatches(matches) { var points = 0; if (matches.length >= 5) { points += 400; } else if (matches.length === 4) { points += 200; } else if (matches.length >= 3) { points += 100; } for (var i = 0; i < matches.length; i++) { var match = matches[i]; var ball = grid[match.y][match.x]; if (ball) { // Create explosion effect createExplosionEffect(ball.x, ball.y, ball.colorIndex); tween(ball, { alpha: 0, scaleX: 0, scaleY: 0 }, { duration: 200, easing: tween.easeIn }); ball.destroy(); grid[match.y][match.x] = null; } } LK.setScore(LK.getScore() + points); scoreText.setText('Score: ' + LK.getScore()); if (matches.length > 0) { LK.getSound('match').play(); } } function dropBalls() { var dropsHappened = false; for (var x = 0; x < gridSize; x++) { var writeY = gridSize - 1; // Skip writeY positions that have walls while (writeY >= 0 && grid[writeY][x] && grid[writeY][x].isWall) { writeY--; } for (var y = gridSize - 1; y >= 0; y--) { // Skip walls completely - they don't move if (grid[y][x] && grid[y][x].isWall) { continue; } if (grid[y][x] !== null) { if (y !== writeY) { grid[writeY][x] = grid[y][x]; grid[y][x] = null; animatingBalls++; grid[writeY][x].animateToGridPosition(x, writeY, function () { animatingBalls--; }); dropsHappened = true; } writeY--; // Skip writeY positions that have walls while (writeY >= 0 && grid[writeY][x] && grid[writeY][x].isWall) { writeY--; } } } // Fill empty spaces at top (but not where walls should be) for (var y = writeY; y >= 0; y--) { // Check if this position should be a wall var isWallPosition = false; if ((y === 3 || y === 4) && (x === 0 || x === 1 || x === 6 || x === 7)) { isWallPosition = true; } if (!isWallPosition) { var isBomb = Math.random() < 0.05; // 5% chance for bomb var colorIndex = Math.floor(Math.random() * 5); var ball = new Ball(colorIndex, isBomb); ball.setGridPosition(x, y - gridSize); grid[y][x] = ball; game.addChild(ball); animatingBalls++; ball.animateToGridPosition(x, y, function () { animatingBalls--; }); dropsHappened = true; } } } return dropsHappened; } function processMatches() { if (isProcessing || animatingBalls > 0) return; var matches = findMatches(); if (matches.length > 0) { isProcessing = true; removeMatches(matches); LK.setTimeout(function () { dropBalls(); LK.setTimeout(function () { isProcessing = false; }, 400); }, 300); } } function checkGameEnd() { if (movesLeft <= 0) { if (LK.getScore() >= targetScore) { LK.showYouWin(); } else { LK.showGameOver(); } } } game.down = function (x, y, obj) { if (isProcessing || animatingBalls > 0) return; // Convert to grid coordinates var gridX = Math.floor((x - gridOffsetX) / cellSize); var gridY = Math.floor((y - gridOffsetY) / cellSize); var clickedCell = grid[gridY] && grid[gridY][gridX] ? grid[gridY][gridX] : null; if (!clickedCell || clickedCell.isWall) return; var clickedBall = clickedCell; if (selectedBall === null) { selectedBall = clickedBall; tween(selectedBall, { scaleX: 1.2, scaleY: 1.2 }, { duration: 200, easing: tween.easeOut }); } else if (selectedBall === clickedBall) { tween(selectedBall, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeOut }); selectedBall = null; } else if (isAdjacent(selectedBall, clickedBall)) { // Attempt swap var tempGrid = []; for (var ty = 0; ty < gridSize; ty++) { tempGrid[ty] = []; for (var tx = 0; tx < gridSize; tx++) { tempGrid[ty][tx] = grid[ty][tx]; } } // Temporarily swap in grid tempGrid[selectedBall.gridY][selectedBall.gridX] = clickedBall; tempGrid[clickedBall.gridY][clickedBall.gridX] = selectedBall; // Check if this creates matches var tempMatches = []; // Simplified match check for the swapped positions var ball1Color = selectedBall.colorIndex; var ball2Color = clickedBall.colorIndex; var hasValidMatch = false; // Check around first ball's new position var newX1 = clickedBall.gridX; var newY1 = clickedBall.gridY; // Check horizontal var count = 1; for (var i = newX1 - 1; i >= 0 && getBallAt(i, newY1) && getBallAt(i, newY1).colorIndex === ball1Color; i--) count++; for (var i = newX1 + 1; i < gridSize && getBallAt(i, newY1) && getBallAt(i, newY1).colorIndex === ball1Color; i++) count++; if (count >= 3) hasValidMatch = true; // Check vertical count = 1; for (var i = newY1 - 1; i >= 0 && getBallAt(newX1, i) && getBallAt(newX1, i).colorIndex === ball1Color; i--) count++; for (var i = newY1 + 1; i < gridSize && getBallAt(newX1, i) && getBallAt(newX1, i).colorIndex === ball1Color; i++) count++; if (count >= 3) hasValidMatch = true; // Check around second ball's new position var newX2 = selectedBall.gridX; var newY2 = selectedBall.gridY; // Check horizontal count = 1; for (var i = newX2 - 1; i >= 0 && getBallAt(i, newY2) && getBallAt(i, newY2).colorIndex === ball2Color; i--) count++; for (var i = newX2 + 1; i < gridSize && getBallAt(i, newY2) && getBallAt(i, newY2).colorIndex === ball2Color; i++) count++; if (count >= 3) hasValidMatch = true; // Check vertical count = 1; for (var i = newY2 - 1; i >= 0 && getBallAt(newX2, i) && getBallAt(newX2, i).colorIndex === ball2Color; i--) count++; for (var i = newY2 + 1; i < gridSize && getBallAt(newX2, i) && getBallAt(newX2, i).colorIndex === ball2Color; i++) count++; if (count >= 3) hasValidMatch = true; if (hasValidMatch) { // Valid move movesLeft--; movesText.setText('Moves: ' + movesLeft); LK.getSound('swap').play(); swapBalls(selectedBall, clickedBall); LK.setTimeout(function () { processMatches(); }, 400); } tween(selectedBall, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeOut }); selectedBall = null; } else { tween(selectedBall, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeOut }); selectedBall = clickedBall; tween(selectedBall, { scaleX: 1.2, scaleY: 1.2 }, { duration: 200, easing: tween.easeOut }); } }; game.update = function () { processMatches(); checkGameEnd(); }; // Initialize the game initializeGrid(); function createBombExplosionEffect(x, y) { // Create screen shake effect var originalX = game.x; var originalY = game.y; var shakeIntensity = 20; var shakeDuration = 500; // Shake animation for (var i = 0; i < 10; i++) { LK.setTimeout(function () { var shakeX = (Math.random() - 0.5) * shakeIntensity; var shakeY = (Math.random() - 0.5) * shakeIntensity; tween(game, { x: originalX + shakeX, y: originalY + shakeY }, { duration: 50, easing: tween.easeOut }); }, i * 50); } // Return to original position LK.setTimeout(function () { tween(game, { x: originalX, y: originalY }, { duration: 100, easing: tween.easeOut }); }, shakeDuration); // Create large explosion particles var particleCount = 16; var ballColors = ['redBall', 'blueBall', 'greenBall', 'yellowBall', 'purpleBall']; for (var i = 0; i < particleCount; i++) { // Create large particle var particle = game.attachAsset(ballColors[Math.floor(Math.random() * 5)], { anchorX: 0.5, anchorY: 0.5, x: x, y: y, scaleX: 0.8, scaleY: 0.8, alpha: 0.9 }); // Calculate explosion direction var angle = Math.PI * 2 / particleCount * i + (Math.random() - 0.5) * 0.8; var distance = 150 + Math.random() * 100; var targetX = x + Math.cos(angle) * distance; var targetY = y + Math.sin(angle) * distance; var rotationSpeed = (Math.random() - 0.5) * Math.PI * 6; // Animate large explosion tween(particle, { x: targetX, y: targetY, alpha: 0, scaleX: 0.2, scaleY: 0.2, rotation: rotationSpeed }, { duration: 600 + Math.random() * 300, easing: tween.easeOut, onFinish: function onFinish() { particle.destroy(); } }); } // Create flash effect var flashOverlay = game.attachAsset('gridBackground', { anchorX: 0.5, anchorY: 0.5, x: 2048 * 0.5, y: 2732 * 0.5, scaleX: 5, scaleY: 5, alpha: 0, tint: 0xFFFFFF }); // Flash animation tween(flashOverlay, { alpha: 0.7 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(flashOverlay, { alpha: 0 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { flashOverlay.destroy(); } }); } }); }
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Ball = Container.expand(function (colorIndex, isBomb) {
var self = Container.call(this);
self.colorIndex = colorIndex;
self.isBomb = isBomb || false;
self.gridX = 0;
self.gridY = 0;
self.isAnimating = false;
var ballColors = ['redBall', 'blueBall', 'greenBall', 'yellowBall', 'purpleBall'];
var assetName = self.isBomb ? 'bombBall' : ballColors[colorIndex];
var ballGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
var targetX = gridOffsetX + gridX * cellSize + cellSize * 0.5;
var targetY = gridOffsetY + gridY * cellSize + cellSize * 0.5;
self.x = targetX;
self.y = targetY;
};
self.animateToGridPosition = function (gridX, gridY, onComplete) {
self.gridX = gridX;
self.gridY = gridY;
var targetX = gridOffsetX + gridX * cellSize + cellSize * 0.5;
var targetY = gridOffsetY + gridY * cellSize + cellSize * 0.5;
self.isAnimating = true;
tween(self, {
x: targetX,
y: targetY
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
self.isAnimating = false;
if (onComplete) onComplete();
}
});
};
return self;
});
var Wall = Container.expand(function () {
var self = Container.call(this);
self.isWall = true;
self.gridX = 0;
self.gridY = 0;
var wallGraphics = self.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
});
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
var targetX = gridOffsetX + gridX * cellSize + cellSize * 0.5;
var targetY = gridOffsetY + gridY * cellSize + cellSize * 0.5;
self.x = targetX;
self.y = targetY;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
var gridSize = 8;
var cellSize = 200;
var gridOffsetX = (2048 - gridSize * cellSize) * 0.5;
var gridOffsetY = (2732 - gridSize * cellSize) * 0.5 - 200;
var grid = [];
var selectedBall = null;
var isProcessing = false;
var movesLeft = 30;
var targetScore = 5000;
var animatingBalls = 0;
// Create grid background
var background = game.attachAsset('gridBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 * 0.5,
y: gridOffsetY + 800
});
// Initialize UI
var scoreText = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
var movesText = new Text2('Moves: ' + movesLeft, {
size: 60,
fill: 0xFFFFFF
});
movesText.anchor.set(1, 0);
movesText.x = -50;
movesText.y = 100;
LK.gui.topRight.addChild(movesText);
var targetText = new Text2('Target: ' + targetScore, {
size: 60,
fill: 0xFFFF00
});
targetText.anchor.set(0, 0);
targetText.x = 50;
targetText.y = 100;
LK.gui.topLeft.addChild(targetText);
// Initialize grid
function initializeGrid() {
grid = [];
for (var y = 0; y < gridSize; y++) {
grid[y] = [];
for (var x = 0; x < gridSize; x++) {
// Check if this position should be a wall
var isWallPosition = false;
if ((y === 3 || y === 4) && (x === 0 || x === 1 || x === 6 || x === 7)) {
isWallPosition = true;
}
if (isWallPosition) {
var wall = new Wall();
wall.setGridPosition(x, y);
grid[y][x] = wall;
game.addChild(wall);
} else {
var isBomb = Math.random() < 0.05; // 5% chance for bomb
var colorIndex = Math.floor(Math.random() * 5);
var ball = new Ball(colorIndex, isBomb);
ball.setGridPosition(x, y);
grid[y][x] = ball;
game.addChild(ball);
}
}
}
// Remove initial matches
var hasMatches = true;
while (hasMatches) {
var matches = findMatches();
if (matches.length > 0) {
for (var i = 0; i < matches.length; i++) {
var match = matches[i];
var isBomb = Math.random() < 0.05; // 5% chance for bomb
var newColorIndex = Math.floor(Math.random() * 5);
grid[match.y][match.x].colorIndex = newColorIndex;
grid[match.y][match.x].isBomb = isBomb;
// Update ball graphics
var ball = grid[match.y][match.x];
ball.removeChildren();
var ballColors = ['redBall', 'blueBall', 'greenBall', 'yellowBall', 'purpleBall'];
var assetName = isBomb ? 'bombBall' : ballColors[newColorIndex];
ball.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
}
} else {
hasMatches = false;
}
}
}
function getBallAt(x, y) {
if (x < 0 || x >= gridSize || y < 0 || y >= gridSize) return null;
var cell = grid[y][x];
if (cell && cell.isWall) return null; // Treat walls as empty for ball operations
return cell;
}
function isAdjacent(ball1, ball2) {
var dx = Math.abs(ball1.gridX - ball2.gridX);
var dy = Math.abs(ball1.gridY - ball2.gridY);
return dx === 1 && dy === 0 || dx === 0 && dy === 1;
}
function swapBalls(ball1, ball2) {
var tempX = ball1.gridX;
var tempY = ball1.gridY;
// Update grid positions
grid[ball1.gridY][ball1.gridX] = ball2;
grid[ball2.gridY][ball2.gridX] = ball1;
// Animate to new positions
animatingBalls += 2;
ball1.animateToGridPosition(ball2.gridX, ball2.gridY, function () {
animatingBalls--;
// Check if this ball is a bomb and explode it
if (ball1.isBomb) {
explodeBomb(ball1.gridX, ball1.gridY);
LK.setTimeout(function () {
dropBalls();
}, 300);
}
});
ball2.animateToGridPosition(tempX, tempY, function () {
animatingBalls--;
// Check if this ball is a bomb and explode it
if (ball2.isBomb) {
explodeBomb(ball2.gridX, ball2.gridY);
LK.setTimeout(function () {
dropBalls();
}, 300);
}
});
}
function findMatches() {
var matches = [];
var checked = [];
// Initialize checked array
for (var y = 0; y < gridSize; y++) {
checked[y] = [];
for (var x = 0; x < gridSize; x++) {
checked[y][x] = false;
}
}
// Check horizontal matches
for (var y = 0; y < gridSize; y++) {
var count = 1;
var currentColor = grid[y][0] ? grid[y][0].colorIndex : -1;
var currentIsBomb = grid[y][0] ? grid[y][0].isBomb : false;
for (var x = 1; x < gridSize; x++) {
if (grid[y][x] && grid[y][x].colorIndex === currentColor && !grid[y][x].isBomb && !currentIsBomb) {
count++;
} else {
if (count >= 3) {
for (var i = x - count; i < x; i++) {
if (!checked[y][i]) {
matches.push({
x: i,
y: y
});
checked[y][i] = true;
}
}
}
count = 1;
currentColor = grid[y][x] ? grid[y][x].colorIndex : -1;
currentIsBomb = grid[y][x] ? grid[y][x].isBomb : false;
}
}
if (count >= 3) {
for (var i = gridSize - count; i < gridSize; i++) {
if (!checked[y][i]) {
matches.push({
x: i,
y: y
});
checked[y][i] = true;
}
}
}
}
// Check vertical matches
for (var x = 0; x < gridSize; x++) {
var count = 1;
var currentColor = grid[0][x] ? grid[0][x].colorIndex : -1;
var currentIsBomb = grid[0][x] ? grid[0][x].isBomb : false;
for (var y = 1; y < gridSize; y++) {
if (grid[y][x] && grid[y][x].colorIndex === currentColor && !grid[y][x].isBomb && !currentIsBomb) {
count++;
} else {
if (count >= 3) {
for (var i = y - count; i < y; i++) {
if (!checked[i][x]) {
matches.push({
x: x,
y: i
});
checked[i][x] = true;
}
}
}
count = 1;
currentColor = grid[y][x] ? grid[y][x].colorIndex : -1;
}
}
if (count >= 3) {
for (var i = gridSize - count; i < gridSize; i++) {
if (!checked[i][x]) {
matches.push({
x: x,
y: i
});
checked[i][x] = true;
}
}
}
}
return matches;
}
function createExplosionEffect(x, y, colorIndex) {
var ballColors = ['redBall', 'blueBall', 'greenBall', 'yellowBall', 'purpleBall'];
var particleCount = 8;
// Play explosion sound effect
LK.getSound('explosion').play();
for (var i = 0; i < particleCount; i++) {
// Create small particle
var particle = game.attachAsset(ballColors[colorIndex], {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
scaleX: 0.3,
scaleY: 0.3
});
// Calculate random direction and distance
var angle = Math.PI * 2 / particleCount * i + (Math.random() - 0.5) * 0.5;
var distance = 80 + Math.random() * 60;
var targetX = x + Math.cos(angle) * distance;
var targetY = y + Math.sin(angle) * distance;
var rotationSpeed = (Math.random() - 0.5) * Math.PI * 4;
// Animate particle explosion
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.1,
scaleY: 0.1,
rotation: rotationSpeed
}, {
duration: 400 + Math.random() * 200,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
}
function explodeBomb(bombX, bombY) {
var explodedBalls = [];
var points = 0;
// Play bomb explosion sound
LK.getSound('bombExplosion').play();
// Create large explosion effect at bomb position
var bombBall = grid[bombY][bombX];
if (bombBall) {
createBombExplosionEffect(bombBall.x, bombBall.y);
}
// Explode 3x3 area around bomb
for (var dy = -1; dy <= 1; dy++) {
for (var dx = -1; dx <= 1; dx++) {
var targetX = bombX + dx;
var targetY = bombY + dy;
if (targetX >= 0 && targetX < gridSize && targetY >= 0 && targetY < gridSize) {
var targetBall = grid[targetY][targetX];
if (targetBall) {
explodedBalls.push({
x: targetX,
y: targetY
});
points += 50;
}
}
}
}
// Remove exploded balls
for (var i = 0; i < explodedBalls.length; i++) {
var pos = explodedBalls[i];
var ball = grid[pos.y][pos.x];
if (ball) {
// Create explosion effect for each ball
createExplosionEffect(ball.x, ball.y, ball.isBomb ? 0 : ball.colorIndex);
tween(ball, {
alpha: 0,
scaleX: 0,
scaleY: 0
}, {
duration: 200,
easing: tween.easeIn
});
ball.destroy();
grid[pos.y][pos.x] = null;
}
}
LK.setScore(LK.getScore() + points);
scoreText.setText('Score: ' + LK.getScore());
}
function removeMatches(matches) {
var points = 0;
if (matches.length >= 5) {
points += 400;
} else if (matches.length === 4) {
points += 200;
} else if (matches.length >= 3) {
points += 100;
}
for (var i = 0; i < matches.length; i++) {
var match = matches[i];
var ball = grid[match.y][match.x];
if (ball) {
// Create explosion effect
createExplosionEffect(ball.x, ball.y, ball.colorIndex);
tween(ball, {
alpha: 0,
scaleX: 0,
scaleY: 0
}, {
duration: 200,
easing: tween.easeIn
});
ball.destroy();
grid[match.y][match.x] = null;
}
}
LK.setScore(LK.getScore() + points);
scoreText.setText('Score: ' + LK.getScore());
if (matches.length > 0) {
LK.getSound('match').play();
}
}
function dropBalls() {
var dropsHappened = false;
for (var x = 0; x < gridSize; x++) {
var writeY = gridSize - 1;
// Skip writeY positions that have walls
while (writeY >= 0 && grid[writeY][x] && grid[writeY][x].isWall) {
writeY--;
}
for (var y = gridSize - 1; y >= 0; y--) {
// Skip walls completely - they don't move
if (grid[y][x] && grid[y][x].isWall) {
continue;
}
if (grid[y][x] !== null) {
if (y !== writeY) {
grid[writeY][x] = grid[y][x];
grid[y][x] = null;
animatingBalls++;
grid[writeY][x].animateToGridPosition(x, writeY, function () {
animatingBalls--;
});
dropsHappened = true;
}
writeY--;
// Skip writeY positions that have walls
while (writeY >= 0 && grid[writeY][x] && grid[writeY][x].isWall) {
writeY--;
}
}
}
// Fill empty spaces at top (but not where walls should be)
for (var y = writeY; y >= 0; y--) {
// Check if this position should be a wall
var isWallPosition = false;
if ((y === 3 || y === 4) && (x === 0 || x === 1 || x === 6 || x === 7)) {
isWallPosition = true;
}
if (!isWallPosition) {
var isBomb = Math.random() < 0.05; // 5% chance for bomb
var colorIndex = Math.floor(Math.random() * 5);
var ball = new Ball(colorIndex, isBomb);
ball.setGridPosition(x, y - gridSize);
grid[y][x] = ball;
game.addChild(ball);
animatingBalls++;
ball.animateToGridPosition(x, y, function () {
animatingBalls--;
});
dropsHappened = true;
}
}
}
return dropsHappened;
}
function processMatches() {
if (isProcessing || animatingBalls > 0) return;
var matches = findMatches();
if (matches.length > 0) {
isProcessing = true;
removeMatches(matches);
LK.setTimeout(function () {
dropBalls();
LK.setTimeout(function () {
isProcessing = false;
}, 400);
}, 300);
}
}
function checkGameEnd() {
if (movesLeft <= 0) {
if (LK.getScore() >= targetScore) {
LK.showYouWin();
} else {
LK.showGameOver();
}
}
}
game.down = function (x, y, obj) {
if (isProcessing || animatingBalls > 0) return;
// Convert to grid coordinates
var gridX = Math.floor((x - gridOffsetX) / cellSize);
var gridY = Math.floor((y - gridOffsetY) / cellSize);
var clickedCell = grid[gridY] && grid[gridY][gridX] ? grid[gridY][gridX] : null;
if (!clickedCell || clickedCell.isWall) return;
var clickedBall = clickedCell;
if (selectedBall === null) {
selectedBall = clickedBall;
tween(selectedBall, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut
});
} else if (selectedBall === clickedBall) {
tween(selectedBall, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeOut
});
selectedBall = null;
} else if (isAdjacent(selectedBall, clickedBall)) {
// Attempt swap
var tempGrid = [];
for (var ty = 0; ty < gridSize; ty++) {
tempGrid[ty] = [];
for (var tx = 0; tx < gridSize; tx++) {
tempGrid[ty][tx] = grid[ty][tx];
}
}
// Temporarily swap in grid
tempGrid[selectedBall.gridY][selectedBall.gridX] = clickedBall;
tempGrid[clickedBall.gridY][clickedBall.gridX] = selectedBall;
// Check if this creates matches
var tempMatches = [];
// Simplified match check for the swapped positions
var ball1Color = selectedBall.colorIndex;
var ball2Color = clickedBall.colorIndex;
var hasValidMatch = false;
// Check around first ball's new position
var newX1 = clickedBall.gridX;
var newY1 = clickedBall.gridY;
// Check horizontal
var count = 1;
for (var i = newX1 - 1; i >= 0 && getBallAt(i, newY1) && getBallAt(i, newY1).colorIndex === ball1Color; i--) count++;
for (var i = newX1 + 1; i < gridSize && getBallAt(i, newY1) && getBallAt(i, newY1).colorIndex === ball1Color; i++) count++;
if (count >= 3) hasValidMatch = true;
// Check vertical
count = 1;
for (var i = newY1 - 1; i >= 0 && getBallAt(newX1, i) && getBallAt(newX1, i).colorIndex === ball1Color; i--) count++;
for (var i = newY1 + 1; i < gridSize && getBallAt(newX1, i) && getBallAt(newX1, i).colorIndex === ball1Color; i++) count++;
if (count >= 3) hasValidMatch = true;
// Check around second ball's new position
var newX2 = selectedBall.gridX;
var newY2 = selectedBall.gridY;
// Check horizontal
count = 1;
for (var i = newX2 - 1; i >= 0 && getBallAt(i, newY2) && getBallAt(i, newY2).colorIndex === ball2Color; i--) count++;
for (var i = newX2 + 1; i < gridSize && getBallAt(i, newY2) && getBallAt(i, newY2).colorIndex === ball2Color; i++) count++;
if (count >= 3) hasValidMatch = true;
// Check vertical
count = 1;
for (var i = newY2 - 1; i >= 0 && getBallAt(newX2, i) && getBallAt(newX2, i).colorIndex === ball2Color; i--) count++;
for (var i = newY2 + 1; i < gridSize && getBallAt(newX2, i) && getBallAt(newX2, i).colorIndex === ball2Color; i++) count++;
if (count >= 3) hasValidMatch = true;
if (hasValidMatch) {
// Valid move
movesLeft--;
movesText.setText('Moves: ' + movesLeft);
LK.getSound('swap').play();
swapBalls(selectedBall, clickedBall);
LK.setTimeout(function () {
processMatches();
}, 400);
}
tween(selectedBall, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeOut
});
selectedBall = null;
} else {
tween(selectedBall, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeOut
});
selectedBall = clickedBall;
tween(selectedBall, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut
});
}
};
game.update = function () {
processMatches();
checkGameEnd();
};
// Initialize the game
initializeGrid();
function createBombExplosionEffect(x, y) {
// Create screen shake effect
var originalX = game.x;
var originalY = game.y;
var shakeIntensity = 20;
var shakeDuration = 500;
// Shake animation
for (var i = 0; i < 10; i++) {
LK.setTimeout(function () {
var shakeX = (Math.random() - 0.5) * shakeIntensity;
var shakeY = (Math.random() - 0.5) * shakeIntensity;
tween(game, {
x: originalX + shakeX,
y: originalY + shakeY
}, {
duration: 50,
easing: tween.easeOut
});
}, i * 50);
}
// Return to original position
LK.setTimeout(function () {
tween(game, {
x: originalX,
y: originalY
}, {
duration: 100,
easing: tween.easeOut
});
}, shakeDuration);
// Create large explosion particles
var particleCount = 16;
var ballColors = ['redBall', 'blueBall', 'greenBall', 'yellowBall', 'purpleBall'];
for (var i = 0; i < particleCount; i++) {
// Create large particle
var particle = game.attachAsset(ballColors[Math.floor(Math.random() * 5)], {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
scaleX: 0.8,
scaleY: 0.8,
alpha: 0.9
});
// Calculate explosion direction
var angle = Math.PI * 2 / particleCount * i + (Math.random() - 0.5) * 0.8;
var distance = 150 + Math.random() * 100;
var targetX = x + Math.cos(angle) * distance;
var targetY = y + Math.sin(angle) * distance;
var rotationSpeed = (Math.random() - 0.5) * Math.PI * 6;
// Animate large explosion
tween(particle, {
x: targetX,
y: targetY,
alpha: 0,
scaleX: 0.2,
scaleY: 0.2,
rotation: rotationSpeed
}, {
duration: 600 + Math.random() * 300,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
// Create flash effect
var flashOverlay = game.attachAsset('gridBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 * 0.5,
y: 2732 * 0.5,
scaleX: 5,
scaleY: 5,
alpha: 0,
tint: 0xFFFFFF
});
// Flash animation
tween(flashOverlay, {
alpha: 0.7
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(flashOverlay, {
alpha: 0
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
flashOverlay.destroy();
}
});
}
});
}
dumb smiley blue face, perfect round. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
perfectly round, red angry face. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
perfectly round, yellow surprised face. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
perfectly round, pink clever poker face. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
perfectly round, green sad face. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a bomb, perfectly round. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
perfect square, concrete wall. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat