/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var Bomb = Container.expand(function () { var self = Container.call(this); var bombGraphics = self.attachAsset('bomb', { anchorX: 0.5, anchorY: 0.5 }); self.gridX = 0; self.gridY = 0; self.setGridPosition = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; var targetX = gridStartX + gridX * cellSize + cellSize / 2; var targetY = gridStartY + gridY * cellSize + cellSize / 2; self.x = targetX; self.y = targetY; }; self.animateToGridPosition = function (gridX, gridY, duration, onComplete) { self.gridX = gridX; self.gridY = gridY; var targetX = gridStartX + gridX * cellSize + cellSize / 2; var targetY = gridStartY + gridY * cellSize + cellSize / 2; self.animateToPosition(targetX, targetY, duration, onComplete); }; self.animateToPosition = function (targetX, targetY, duration, onComplete) { tween(self, { x: targetX, y: targetY }, { duration: duration || 300, easing: tween.easeOut, onFinish: function onFinish() { if (onComplete) { onComplete(); } } }); }; self.down = function () { explodeBomb(self.gridX, self.gridY); self.destroy(); grid[self.gridX][self.gridY] = null; LK.setTimeout(function () { applyGravity(); LK.setTimeout(function () { fillEmptySpaces(); LK.setTimeout(function () { processMatches(); }, 500); }, 400); }, 200); }; return self; }); var Candy = Container.expand(function (type) { var self = Container.call(this); self.type = type; self.gridX = 0; self.gridY = 0; self.isAnimating = false; var candyGraphics = self.attachAsset('candy_' + type, { anchorX: 0.5, anchorY: 0.5 }); self.setGridPosition = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; var targetX = gridStartX + gridX * cellSize + cellSize / 2; var targetY = gridStartY + gridY * cellSize + cellSize / 2; self.x = targetX; self.y = targetY; }; 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.animateToGridPosition = function (gridX, gridY, duration, onComplete) { self.gridX = gridX; self.gridY = gridY; var targetX = gridStartX + gridX * cellSize + cellSize / 2; var targetY = gridStartY + gridY * cellSize + cellSize / 2; self.animateToPosition(targetX, targetY, duration, onComplete); }; return self; }); var DiscoBall = Container.expand(function () { var self = Container.call(this); var discoBallGraphics = self.attachAsset('disco_ball', { anchorX: 0.5, anchorY: 0.5 }); self.gridX = 0; self.gridY = 0; self.setGridPosition = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; var targetX = gridStartX + gridX * cellSize + cellSize / 2; var targetY = gridStartY + gridY * cellSize + cellSize / 2; self.x = targetX; self.y = targetY; }; self.animateToGridPosition = function (gridX, gridY, duration, onComplete) { self.gridX = gridX; self.gridY = gridY; var targetX = gridStartX + gridX * cellSize + cellSize / 2; var targetY = gridStartY + gridY * cellSize + cellSize / 2; self.animateToPosition(targetX, targetY, duration, onComplete); }; self.animateToPosition = function (targetX, targetY, duration, onComplete) { tween(self, { x: targetX, y: targetY }, { duration: duration || 300, easing: tween.easeOut, onFinish: function onFinish() { if (onComplete) { onComplete(); } } }); }; self.down = function () { // Play disco sound LK.getSound('Disco').play(); // Play music 'a' only once if (!self.musicAPlayed) { LK.playMusic('a'); self.musicAPlayed = true; } // Stop music 'a' after 15 seconds LK.setTimeout(function () { LK.stopMusic(); }, 15000); // Play for 15 seconds // Award 3500 points immediately LK.setScore(LK.getScore() + 3500); scoreText.setText('Score: ' + LK.getScore()); // Update high score if current score is higher if (LK.getScore() > (storage.highScore || 0)) { storage.highScore = LK.getScore(); highScoreText.setText('High Score: ' + LK.getScore()); } // Start disco ball effect for 15 seconds startDiscoBallEffect(); // Remove disco ball from grid self.destroy(); grid[self.gridX][self.gridY] = null; LK.setTimeout(function () { applyGravity(); LK.setTimeout(function () { fillEmptySpaces(); LK.setTimeout(function () { processMatches(); }, 500); }, 400); }, 200); }; return self; }); var Firework = Container.expand(function () { var self = Container.call(this); var fireworkGraphics = self.attachAsset('firework', { anchorX: 0.5, anchorY: 0.5 }); self.gridX = 0; self.gridY = 0; self.setGridPosition = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; var targetX = gridStartX + gridX * cellSize + cellSize / 2; var targetY = gridStartY + gridY * cellSize + cellSize / 2; self.x = targetX; self.y = targetY; }; self.animateToGridPosition = function (gridX, gridY, duration, onComplete) { self.gridX = gridX; self.gridY = gridY; var targetX = gridStartX + gridX * cellSize + cellSize / 2; var targetY = gridStartY + gridY * cellSize + cellSize / 2; self.animateToPosition(targetX, targetY, duration, onComplete); }; self.animateToPosition = function (targetX, targetY, duration, onComplete) { tween(self, { x: targetX, y: targetY }, { duration: duration || 300, easing: tween.easeOut, onFinish: function onFinish() { if (onComplete) { onComplete(); } } }); }; self.down = function () { explodeFirework(self.gridX, self.gridY); self.destroy(); grid[self.gridX][self.gridY] = null; LK.setTimeout(function () { applyGravity(); LK.setTimeout(function () { fillEmptySpaces(); LK.setTimeout(function () { processMatches(); }, 500); }, 400); }, 200); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2E8B57 }); /**** * Game Code ****/ function startDiscoBallEffect() { discoEffectActive = true; var effectDuration = 15000; // 15 seconds var destroyInterval = 150; // Destroy candies every 150ms var destroyTimer = LK.setInterval(function () { if (!discoEffectActive) return; // Find all regular candies (not bombs, fireworks, or disco balls) var regularCandies = []; for (var x = 0; x < GRID_SIZE; x++) { for (var y = 0; y < GRID_SIZE; y++) { if (grid[x][y] && grid[x][y].type && grid[x][y].type !== 'bomb' && grid[x][y].type !== 'firework' && grid[x][y].type !== 'disco_ball') { regularCandies.push({ x: x, y: y }); } } } // Destroy 1-3 random candies var numToDestroy = Math.min(Math.floor(Math.random() * 3) + 1, regularCandies.length); for (var i = 0; i < numToDestroy; i++) { if (regularCandies.length > 0) { var randomIndex = Math.floor(Math.random() * regularCandies.length); var pos = regularCandies[randomIndex]; if (grid[pos.x][pos.y]) { grid[pos.x][pos.y].destroy(); grid[pos.x][pos.y] = null; // Add visual effect LK.effects.flashObject(game, 0xFFFF00, 100); } regularCandies.splice(randomIndex, 1); } } // Apply gravity and fill after destroying candies LK.setTimeout(function () { applyGravity(); LK.setTimeout(function () { fillEmptySpaces(); }, 200); }, 50); }, destroyInterval); // Stop effect after 15 seconds LK.setTimeout(function () { discoEffectActive = false; LK.clearInterval(destroyTimer); }, effectDuration); } var GRID_SIZE = 15; var candyTypes = ['red', 'blue', 'green', 'yellow', 'purple', 'orange']; var BOMB_SPAWN_CHANCE = 0.013; // 1.3% initial chance to spawn a bomb var DISCO_BALL_SPAWN_CHANCE = 0.006; // 0.6% chance to spawn a disco ball var discoEffectActive = false; var cellSize = 130; var gridStartX = (2048 - GRID_SIZE * cellSize) / 2; var gridStartY = (2732 - GRID_SIZE * cellSize) / 2; var grid = []; var selectedCandy = null; var isProcessing = false; var animatingCandies = 0; var gameEnded = false; var noMovesLeft = false; // Play background music LK.playMusic('Happymusic'); // Initialize the game var _loop = function _loop() { grid[i] = []; for (j = 0; j < GRID_SIZE; j++) { grid[i][j] = null; } ; }, j; for (var i = 0; i < GRID_SIZE; i++) { _loop(); } // UI Elements - Scoreboard var highScoreText = new Text2('High Score: 0', { size: 80, fill: 0x000000 }); highScoreText.anchor.set(0.5, 0); var scoreText = new Text2('Score: 0', { size: 80, fill: 0x000000 }); scoreText.anchor.set(0.5, 0); scoreText.x = 0; highScoreText.y = 150; LK.gui.top.addChild(highScoreText); scoreText.y = 90; LK.gui.top.addChild(scoreText); // Create grid background var gridBg = game.attachAsset('grid_bg', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); // Create cell backgrounds for (var i = 0; i < GRID_SIZE; i++) { for (var j = 0; j < GRID_SIZE; j++) { var cellBg = game.attachAsset('cell_bg', { anchorX: 0.5, anchorY: 0.5, x: gridStartX + i * cellSize + cellSize / 2, y: gridStartY + j * cellSize + cellSize / 2 }); } } function getRandomCandyType() { var score = LK.getScore(); var dynamicBombSpawnChance = Math.max(0.013 - score * 0.0001, 0.007); // Decrease chance as score increases, but not below 0.7% var fireworkSpawnChance = 0.008; // 0.8% chance to spawn a firework var randomValue = Math.random(); // Game ending logic - between 36000 and 55000 score, randomly end the game if (score >= 36000 && score <= 55000) { var endGameScore = 36000 + Math.random() * 19000; // Random score between 36000-55000 if (score >= endGameScore && !gameEnded) { noMovesLeft = true; // Allow candies to be moved even when the game is supposed to end return 'red'; // Return a default candy type to ensure candies can still be moved } } if (randomValue < dynamicBombSpawnChance) { return 'bomb'; } else if (randomValue < dynamicBombSpawnChance + fireworkSpawnChance) { return 'firework'; } else if (randomValue < dynamicBombSpawnChance + fireworkSpawnChance + DISCO_BALL_SPAWN_CHANCE) { return 'disco_ball'; } // Progressive difficulty - reduce candy variety as score increases var availableCandyTypes = candyTypes.slice(); if (score >= 10000) { availableCandyTypes = candyTypes.slice(0, 5); // Remove one candy type } if (score >= 20000) { availableCandyTypes = candyTypes.slice(0, 4); // Remove two candy types } if (score >= 30000) { availableCandyTypes = candyTypes.slice(0, 3); // Remove three candy types } return availableCandyTypes[Math.floor(Math.random() * availableCandyTypes.length)]; } function createCandy(type, gridX, gridY) { var candy; if (type === 'bomb') { candy = new Bomb(); } else if (type === 'firework') { candy = new Firework(); } else if (type === 'disco_ball') { candy = new DiscoBall(); } else { candy = new Candy(type); } candy.setGridPosition(gridX, gridY); game.addChild(candy); return candy; } function fillGrid() { for (var x = 0; x < GRID_SIZE; x++) { for (var y = 0; y < GRID_SIZE; y++) { if (!grid[x][y]) { var type = getRandomCandyType(); if (type !== 'blue') { // Remove blue candies grid[x][y] = createCandy(type, x, y); } } } } } function isValidPosition(x, y) { return x >= 0 && x < GRID_SIZE && y >= 0 && y < GRID_SIZE; } 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; } function swapCandies(x1, y1, x2, y2) { var candy1 = grid[x1][y1]; var candy2 = grid[x2][y2]; grid[x1][y1] = candy2; grid[x2][y2] = candy1; animatingCandies += 2; candy1.animateToGridPosition(x2, y2, 200, function () { animatingCandies--; }); candy2.animateToGridPosition(x1, y1, 200, function () { animatingCandies--; }); } function findMatches() { var matches = []; var visited = []; for (var i = 0; i < GRID_SIZE; i++) { visited[i] = []; for (var j = 0; j < GRID_SIZE; j++) { visited[i][j] = false; } } // Check horizontal matches for (var y = 0; y < GRID_SIZE; y++) { for (var x = 0; x < GRID_SIZE - 2; x++) { if (grid[x][y] && grid[x + 1][y] && grid[x + 2][y] && grid[x][y].type === grid[x + 1][y].type && grid[x][y].type === grid[x + 2][y].type) { var match = []; var type = grid[x][y].type; var startX = x; while (x < GRID_SIZE && grid[x][y] && grid[x][y].type === type) { if (!visited[x][y]) { match.push({ x: x, y: y }); visited[x][y] = true; } x++; } x--; if (match.length >= 3) { matches.push(match); } } } } // Check vertical matches for (var x = 0; x < GRID_SIZE; x++) { for (var y = 0; y < GRID_SIZE - 2; y++) { if (grid[x][y] && grid[x][y + 1] && grid[x][y + 2] && grid[x][y].type === grid[x][y + 1].type && grid[x][y].type === grid[x][y + 2].type) { var match = []; var type = grid[x][y].type; var startY = y; while (y < GRID_SIZE && grid[x][y] && grid[x][y].type === type) { if (!visited[x][y]) { match.push({ x: x, y: y }); visited[x][y] = true; } y++; } y--; if (match.length >= 3) { matches.push(match); } } } } // Check diagonal matches (top-left to bottom-right) for (var x = 0; x < GRID_SIZE - 2; x++) { for (var y = 0; y < GRID_SIZE - 2; y++) { if (x + 2 < GRID_SIZE && y + 2 < GRID_SIZE && grid[x][y] && grid[x + 1][y + 1] && grid[x + 2][y + 2] && grid[x][y].type === grid[x + 1][y + 1].type && grid[x][y].type === grid[x + 2][y + 2].type) { var match = []; var type = grid[x][y].type; var startX = x; var startY = y; while (x < GRID_SIZE && y < GRID_SIZE && grid[x][y] && grid[x][y].type === type) { if (!visited[x][y]) { match.push({ x: x, y: y }); visited[x][y] = true; } x++; y++; } x--; y--; if (match.length >= 3) { matches.push(match); } } } } // Check diagonal matches (bottom-left to top-right) for (var x = 0; x < GRID_SIZE - 2; x++) { for (var y = 2; y < GRID_SIZE; y++) { if (y - 2 >= 0 && x + 2 < GRID_SIZE && grid[x][y] && grid[x + 1][y - 1] && grid[x + 2][y - 2] && grid[x][y].type === grid[x + 1][y - 1].type && grid[x][y].type === grid[x + 2][y - 2].type) { var match = []; var type = grid[x][y].type; var startX = x; var startY = y; while (x < GRID_SIZE && y >= 0 && grid[x][y] && grid[x][y].type === type) { if (!visited[x][y]) { match.push({ x: x, y: y }); visited[x][y] = true; } x++; y--; } x--; y++; if (match.length >= 3) { matches.push(match); } } } } return matches; } function clearMatches(matches) { var totalCleared = 0; for (var i = 0; i < matches.length; i++) { var match = matches[i]; for (var j = 0; j < match.length; j++) { var pos = match[j]; if (grid[pos.x][pos.y]) { grid[pos.x][pos.y].destroy(); grid[pos.x][pos.y] = null; totalCleared++; } } } if (totalCleared > 0) { LK.getSound('match').play(); LK.setScore(LK.getScore() + totalCleared * 10); scoreText.setText('Score: ' + LK.getScore()); // Update high score if current score is higher if (LK.getScore() > (storage.highScore || 0)) { storage.highScore = LK.getScore(); highScoreText.setText('High Score: ' + LK.getScore()); } } return totalCleared; } function applyGravity() { var moved = false; for (var x = 0; x < GRID_SIZE; x++) { for (var y = GRID_SIZE - 1; y >= 0; y--) { if (!grid[x][y]) { // Find candy above to fall down for (var above = y - 1; above >= 0; above--) { if (grid[x][above]) { grid[x][y] = grid[x][above]; grid[x][above] = null; animatingCandies++; grid[x][y].animateToGridPosition(x, y, 300, function () { animatingCandies--; }); moved = true; break; } } } } } return moved; } function fillEmptySpaces() { for (var x = 0; x < GRID_SIZE; x++) { for (var y = 0; y < GRID_SIZE; y++) { if (!grid[x][y]) { var type = getRandomCandyType(); grid[x][y] = createCandy(type, x, y - GRID_SIZE); animatingCandies++; grid[x][y].animateToGridPosition(x, y, 400, function () { animatingCandies--; }); } } } } function processMatches() { if (isProcessing || animatingCandies > 0) { return; } var matches = findMatches(); if (matches.length > 0) { isProcessing = true; clearMatches(matches); LK.setTimeout(function () { applyGravity(); LK.setTimeout(function () { fillEmptySpaces(); LK.setTimeout(function () { isProcessing = false; }, 500); }, 400); }, 200); } } function getCandyAtPosition(x, y) { var localX = x - gridStartX; var localY = y - gridStartY; if (localX < 0 || localY < 0 || localX >= GRID_SIZE * cellSize || localY >= GRID_SIZE * cellSize) { return null; } var gridX = Math.floor(localX / cellSize); var gridY = Math.floor(localY / cellSize); return grid[gridX][gridY]; } function getGridPosition(x, y) { var localX = x - gridStartX; var localY = y - gridStartY; if (localX < 0 || localY < 0 || localX >= GRID_SIZE * cellSize || localY >= GRID_SIZE * cellSize) { return null; } var gridX = Math.floor(localX / cellSize); var gridY = Math.floor(localY / cellSize); return { x: gridX, y: gridY }; } function explodeBomb(centerX, centerY) { var exploded = 0; // Explode 3x3 area around the bomb for (var dx = -1; dx <= 1; dx++) { for (var dy = -1; dy <= 1; dy++) { var x = centerX + dx; var y = centerY + dy; if (isValidPosition(x, y) && grid[x][y]) { grid[x][y].destroy(); grid[x][y] = null; exploded++; } } } if (exploded > 0) { LK.getSound('explode').play(); LK.setScore(LK.getScore() + exploded * 15); scoreText.setText('Score: ' + LK.getScore()); // Update high score if current score is higher if (LK.getScore() > (storage.highScore || 0)) { storage.highScore = LK.getScore(); highScoreText.setText('High Score: ' + LK.getScore()); } } } game.down = function (x, y, obj) { if (isProcessing || animatingCandies > 0 || gameEnded) { return; } var candy = getCandyAtPosition(x, y); if (candy) { // Check if clicked candy is a bomb if (candy.type === 'bomb') { explodeBomb(candy.gridX, candy.gridY); candy.destroy(); grid[candy.gridX][candy.gridY] = null; LK.setTimeout(function () { applyGravity(); LK.setTimeout(function () { fillEmptySpaces(); LK.setTimeout(function () { processMatches(); }, 500); }, 400); }, 200); return; } if (selectedCandy) { selectedCandy.alpha = 1.0; } selectedCandy = candy; selectedCandy.alpha = 0.7; LK.getSound('swap').play(); } }; game.up = function (x, y, obj) { if (isProcessing || animatingCandies > 0 || gameEnded) { return; } var candy = getCandyAtPosition(x, y); if (selectedCandy && candy && selectedCandy !== candy) { // Don't allow swapping with bombs if (selectedCandy.type === 'bomb' || candy.type === 'bomb') { selectedCandy.alpha = 1.0; selectedCandy = null; return; } var pos1 = { x: selectedCandy.gridX, y: selectedCandy.gridY }; var pos2 = { x: candy.gridX, y: candy.gridY }; if (areAdjacent(pos1.x, pos1.y, pos2.x, pos2.y)) { // Test swap var temp = grid[pos1.x][pos1.y]; grid[pos1.x][pos1.y] = grid[pos2.x][pos2.y]; grid[pos2.x][pos2.y] = temp; var matches = findMatches(); // Revert test swap grid[pos2.x][pos2.y] = grid[pos1.x][pos1.y]; grid[pos1.x][pos1.y] = temp; if (matches.length > 0) { swapCandies(pos1.x, pos1.y, pos2.x, pos2.y); LK.setTimeout(function () { processMatches(); }, 250); } } selectedCandy.alpha = 1.0; selectedCandy = null; } else if (selectedCandy) { selectedCandy.alpha = 1.0; selectedCandy = null; } }; game.update = function () { if (gameEnded) { return; } if (animatingCandies === 0 && !isProcessing) { processMatches(); // Check if game should end due to no moves left or score threshold if (noMovesLeft || !hasValidMoves()) { gameEnded = true; LK.setTimeout(function () { LK.showGameOver(); }, 1000); } } }; // Initialize high score from storage var savedHighScore = storage.highScore || 0; highScoreText.setText('High Score: ' + savedHighScore); // Initialize the game fillGrid(); ; function hasValidMoves() { // Check all possible swaps between adjacent candies for (var x = 0; x < GRID_SIZE; x++) { for (var y = 0; y < GRID_SIZE; y++) { if (grid[x][y] && grid[x][y].type !== 'bomb' && grid[x][y].type !== 'firework' && grid[x][y].type !== 'blue') { // Check right neighbor if (x + 1 < GRID_SIZE && grid[x + 1][y] && grid[x + 1][y].type !== 'bomb' && grid[x + 1][y].type !== 'firework') { // Test swap var temp = grid[x][y]; grid[x][y] = grid[x + 1][y]; grid[x + 1][y] = temp; var matches = findMatches(); // Revert swap grid[x + 1][y] = grid[x][y]; grid[x][y] = temp; if (matches.length > 0) { return true; } } // Check bottom neighbor if (y + 1 < GRID_SIZE && grid[x][y + 1] && grid[x][y + 1].type !== 'bomb' && grid[x][y + 1].type !== 'firework') { // Test swap var temp = grid[x][y]; grid[x][y] = grid[x][y + 1]; grid[x][y + 1] = temp; var matches = findMatches(); // Revert swap grid[x][y + 1] = grid[x][y]; grid[x][y] = temp; if (matches.length > 0) { return true; } } } } } return false; } function explodeFirework(centerX, centerY) { var exploded = 0; // Explode entire row and column for (var x = 0; x < GRID_SIZE; x++) { if (isValidPosition(x, centerY) && grid[x][centerY]) { grid[x][centerY].destroy(); grid[x][centerY] = null; exploded++; } } for (var y = 0; y < GRID_SIZE; y++) { if (isValidPosition(centerX, y) && grid[centerX][y]) { grid[centerX][y].destroy(); grid[centerX][y] = null; exploded++; } } if (exploded > 0) { LK.getSound('explode').play(); LK.setScore(LK.getScore() + exploded * 15); scoreText.setText('Score: ' + LK.getScore()); // Update high score if current score is higher if (LK.getScore() > (storage.highScore || 0)) { storage.highScore = LK.getScore(); highScoreText.setText('High Score: ' + LK.getScore()); } } }
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Bomb = Container.expand(function () {
var self = Container.call(this);
var bombGraphics = self.attachAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5
});
self.gridX = 0;
self.gridY = 0;
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
var targetX = gridStartX + gridX * cellSize + cellSize / 2;
var targetY = gridStartY + gridY * cellSize + cellSize / 2;
self.x = targetX;
self.y = targetY;
};
self.animateToGridPosition = function (gridX, gridY, duration, onComplete) {
self.gridX = gridX;
self.gridY = gridY;
var targetX = gridStartX + gridX * cellSize + cellSize / 2;
var targetY = gridStartY + gridY * cellSize + cellSize / 2;
self.animateToPosition(targetX, targetY, duration, onComplete);
};
self.animateToPosition = function (targetX, targetY, duration, onComplete) {
tween(self, {
x: targetX,
y: targetY
}, {
duration: duration || 300,
easing: tween.easeOut,
onFinish: function onFinish() {
if (onComplete) {
onComplete();
}
}
});
};
self.down = function () {
explodeBomb(self.gridX, self.gridY);
self.destroy();
grid[self.gridX][self.gridY] = null;
LK.setTimeout(function () {
applyGravity();
LK.setTimeout(function () {
fillEmptySpaces();
LK.setTimeout(function () {
processMatches();
}, 500);
}, 400);
}, 200);
};
return self;
});
var Candy = Container.expand(function (type) {
var self = Container.call(this);
self.type = type;
self.gridX = 0;
self.gridY = 0;
self.isAnimating = false;
var candyGraphics = self.attachAsset('candy_' + type, {
anchorX: 0.5,
anchorY: 0.5
});
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
var targetX = gridStartX + gridX * cellSize + cellSize / 2;
var targetY = gridStartY + gridY * cellSize + cellSize / 2;
self.x = targetX;
self.y = targetY;
};
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.animateToGridPosition = function (gridX, gridY, duration, onComplete) {
self.gridX = gridX;
self.gridY = gridY;
var targetX = gridStartX + gridX * cellSize + cellSize / 2;
var targetY = gridStartY + gridY * cellSize + cellSize / 2;
self.animateToPosition(targetX, targetY, duration, onComplete);
};
return self;
});
var DiscoBall = Container.expand(function () {
var self = Container.call(this);
var discoBallGraphics = self.attachAsset('disco_ball', {
anchorX: 0.5,
anchorY: 0.5
});
self.gridX = 0;
self.gridY = 0;
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
var targetX = gridStartX + gridX * cellSize + cellSize / 2;
var targetY = gridStartY + gridY * cellSize + cellSize / 2;
self.x = targetX;
self.y = targetY;
};
self.animateToGridPosition = function (gridX, gridY, duration, onComplete) {
self.gridX = gridX;
self.gridY = gridY;
var targetX = gridStartX + gridX * cellSize + cellSize / 2;
var targetY = gridStartY + gridY * cellSize + cellSize / 2;
self.animateToPosition(targetX, targetY, duration, onComplete);
};
self.animateToPosition = function (targetX, targetY, duration, onComplete) {
tween(self, {
x: targetX,
y: targetY
}, {
duration: duration || 300,
easing: tween.easeOut,
onFinish: function onFinish() {
if (onComplete) {
onComplete();
}
}
});
};
self.down = function () {
// Play disco sound
LK.getSound('Disco').play();
// Play music 'a' only once
if (!self.musicAPlayed) {
LK.playMusic('a');
self.musicAPlayed = true;
}
// Stop music 'a' after 15 seconds
LK.setTimeout(function () {
LK.stopMusic();
}, 15000); // Play for 15 seconds
// Award 3500 points immediately
LK.setScore(LK.getScore() + 3500);
scoreText.setText('Score: ' + LK.getScore());
// Update high score if current score is higher
if (LK.getScore() > (storage.highScore || 0)) {
storage.highScore = LK.getScore();
highScoreText.setText('High Score: ' + LK.getScore());
}
// Start disco ball effect for 15 seconds
startDiscoBallEffect();
// Remove disco ball from grid
self.destroy();
grid[self.gridX][self.gridY] = null;
LK.setTimeout(function () {
applyGravity();
LK.setTimeout(function () {
fillEmptySpaces();
LK.setTimeout(function () {
processMatches();
}, 500);
}, 400);
}, 200);
};
return self;
});
var Firework = Container.expand(function () {
var self = Container.call(this);
var fireworkGraphics = self.attachAsset('firework', {
anchorX: 0.5,
anchorY: 0.5
});
self.gridX = 0;
self.gridY = 0;
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
var targetX = gridStartX + gridX * cellSize + cellSize / 2;
var targetY = gridStartY + gridY * cellSize + cellSize / 2;
self.x = targetX;
self.y = targetY;
};
self.animateToGridPosition = function (gridX, gridY, duration, onComplete) {
self.gridX = gridX;
self.gridY = gridY;
var targetX = gridStartX + gridX * cellSize + cellSize / 2;
var targetY = gridStartY + gridY * cellSize + cellSize / 2;
self.animateToPosition(targetX, targetY, duration, onComplete);
};
self.animateToPosition = function (targetX, targetY, duration, onComplete) {
tween(self, {
x: targetX,
y: targetY
}, {
duration: duration || 300,
easing: tween.easeOut,
onFinish: function onFinish() {
if (onComplete) {
onComplete();
}
}
});
};
self.down = function () {
explodeFirework(self.gridX, self.gridY);
self.destroy();
grid[self.gridX][self.gridY] = null;
LK.setTimeout(function () {
applyGravity();
LK.setTimeout(function () {
fillEmptySpaces();
LK.setTimeout(function () {
processMatches();
}, 500);
}, 400);
}, 200);
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2E8B57
});
/****
* Game Code
****/
function startDiscoBallEffect() {
discoEffectActive = true;
var effectDuration = 15000; // 15 seconds
var destroyInterval = 150; // Destroy candies every 150ms
var destroyTimer = LK.setInterval(function () {
if (!discoEffectActive) return;
// Find all regular candies (not bombs, fireworks, or disco balls)
var regularCandies = [];
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[x][y] && grid[x][y].type && grid[x][y].type !== 'bomb' && grid[x][y].type !== 'firework' && grid[x][y].type !== 'disco_ball') {
regularCandies.push({
x: x,
y: y
});
}
}
}
// Destroy 1-3 random candies
var numToDestroy = Math.min(Math.floor(Math.random() * 3) + 1, regularCandies.length);
for (var i = 0; i < numToDestroy; i++) {
if (regularCandies.length > 0) {
var randomIndex = Math.floor(Math.random() * regularCandies.length);
var pos = regularCandies[randomIndex];
if (grid[pos.x][pos.y]) {
grid[pos.x][pos.y].destroy();
grid[pos.x][pos.y] = null;
// Add visual effect
LK.effects.flashObject(game, 0xFFFF00, 100);
}
regularCandies.splice(randomIndex, 1);
}
}
// Apply gravity and fill after destroying candies
LK.setTimeout(function () {
applyGravity();
LK.setTimeout(function () {
fillEmptySpaces();
}, 200);
}, 50);
}, destroyInterval);
// Stop effect after 15 seconds
LK.setTimeout(function () {
discoEffectActive = false;
LK.clearInterval(destroyTimer);
}, effectDuration);
}
var GRID_SIZE = 15;
var candyTypes = ['red', 'blue', 'green', 'yellow', 'purple', 'orange'];
var BOMB_SPAWN_CHANCE = 0.013; // 1.3% initial chance to spawn a bomb
var DISCO_BALL_SPAWN_CHANCE = 0.006; // 0.6% chance to spawn a disco ball
var discoEffectActive = false;
var cellSize = 130;
var gridStartX = (2048 - GRID_SIZE * cellSize) / 2;
var gridStartY = (2732 - GRID_SIZE * cellSize) / 2;
var grid = [];
var selectedCandy = null;
var isProcessing = false;
var animatingCandies = 0;
var gameEnded = false;
var noMovesLeft = false;
// Play background music
LK.playMusic('Happymusic');
// Initialize the game
var _loop = function _loop() {
grid[i] = [];
for (j = 0; j < GRID_SIZE; j++) {
grid[i][j] = null;
}
;
},
j;
for (var i = 0; i < GRID_SIZE; i++) {
_loop();
}
// UI Elements - Scoreboard
var highScoreText = new Text2('High Score: 0', {
size: 80,
fill: 0x000000
});
highScoreText.anchor.set(0.5, 0);
var scoreText = new Text2('Score: 0', {
size: 80,
fill: 0x000000
});
scoreText.anchor.set(0.5, 0);
scoreText.x = 0;
highScoreText.y = 150;
LK.gui.top.addChild(highScoreText);
scoreText.y = 90;
LK.gui.top.addChild(scoreText);
// Create grid background
var gridBg = game.attachAsset('grid_bg', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
// Create cell backgrounds
for (var i = 0; i < GRID_SIZE; i++) {
for (var j = 0; j < GRID_SIZE; j++) {
var cellBg = game.attachAsset('cell_bg', {
anchorX: 0.5,
anchorY: 0.5,
x: gridStartX + i * cellSize + cellSize / 2,
y: gridStartY + j * cellSize + cellSize / 2
});
}
}
function getRandomCandyType() {
var score = LK.getScore();
var dynamicBombSpawnChance = Math.max(0.013 - score * 0.0001, 0.007); // Decrease chance as score increases, but not below 0.7%
var fireworkSpawnChance = 0.008; // 0.8% chance to spawn a firework
var randomValue = Math.random();
// Game ending logic - between 36000 and 55000 score, randomly end the game
if (score >= 36000 && score <= 55000) {
var endGameScore = 36000 + Math.random() * 19000; // Random score between 36000-55000
if (score >= endGameScore && !gameEnded) {
noMovesLeft = true;
// Allow candies to be moved even when the game is supposed to end
return 'red'; // Return a default candy type to ensure candies can still be moved
}
}
if (randomValue < dynamicBombSpawnChance) {
return 'bomb';
} else if (randomValue < dynamicBombSpawnChance + fireworkSpawnChance) {
return 'firework';
} else if (randomValue < dynamicBombSpawnChance + fireworkSpawnChance + DISCO_BALL_SPAWN_CHANCE) {
return 'disco_ball';
}
// Progressive difficulty - reduce candy variety as score increases
var availableCandyTypes = candyTypes.slice();
if (score >= 10000) {
availableCandyTypes = candyTypes.slice(0, 5); // Remove one candy type
}
if (score >= 20000) {
availableCandyTypes = candyTypes.slice(0, 4); // Remove two candy types
}
if (score >= 30000) {
availableCandyTypes = candyTypes.slice(0, 3); // Remove three candy types
}
return availableCandyTypes[Math.floor(Math.random() * availableCandyTypes.length)];
}
function createCandy(type, gridX, gridY) {
var candy;
if (type === 'bomb') {
candy = new Bomb();
} else if (type === 'firework') {
candy = new Firework();
} else if (type === 'disco_ball') {
candy = new DiscoBall();
} else {
candy = new Candy(type);
}
candy.setGridPosition(gridX, gridY);
game.addChild(candy);
return candy;
}
function fillGrid() {
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (!grid[x][y]) {
var type = getRandomCandyType();
if (type !== 'blue') {
// Remove blue candies
grid[x][y] = createCandy(type, x, y);
}
}
}
}
}
function isValidPosition(x, y) {
return x >= 0 && x < GRID_SIZE && y >= 0 && y < GRID_SIZE;
}
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;
}
function swapCandies(x1, y1, x2, y2) {
var candy1 = grid[x1][y1];
var candy2 = grid[x2][y2];
grid[x1][y1] = candy2;
grid[x2][y2] = candy1;
animatingCandies += 2;
candy1.animateToGridPosition(x2, y2, 200, function () {
animatingCandies--;
});
candy2.animateToGridPosition(x1, y1, 200, function () {
animatingCandies--;
});
}
function findMatches() {
var matches = [];
var visited = [];
for (var i = 0; i < GRID_SIZE; i++) {
visited[i] = [];
for (var j = 0; j < GRID_SIZE; j++) {
visited[i][j] = false;
}
}
// Check horizontal matches
for (var y = 0; y < GRID_SIZE; y++) {
for (var x = 0; x < GRID_SIZE - 2; x++) {
if (grid[x][y] && grid[x + 1][y] && grid[x + 2][y] && grid[x][y].type === grid[x + 1][y].type && grid[x][y].type === grid[x + 2][y].type) {
var match = [];
var type = grid[x][y].type;
var startX = x;
while (x < GRID_SIZE && grid[x][y] && grid[x][y].type === type) {
if (!visited[x][y]) {
match.push({
x: x,
y: y
});
visited[x][y] = true;
}
x++;
}
x--;
if (match.length >= 3) {
matches.push(match);
}
}
}
}
// Check vertical matches
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE - 2; y++) {
if (grid[x][y] && grid[x][y + 1] && grid[x][y + 2] && grid[x][y].type === grid[x][y + 1].type && grid[x][y].type === grid[x][y + 2].type) {
var match = [];
var type = grid[x][y].type;
var startY = y;
while (y < GRID_SIZE && grid[x][y] && grid[x][y].type === type) {
if (!visited[x][y]) {
match.push({
x: x,
y: y
});
visited[x][y] = true;
}
y++;
}
y--;
if (match.length >= 3) {
matches.push(match);
}
}
}
}
// Check diagonal matches (top-left to bottom-right)
for (var x = 0; x < GRID_SIZE - 2; x++) {
for (var y = 0; y < GRID_SIZE - 2; y++) {
if (x + 2 < GRID_SIZE && y + 2 < GRID_SIZE && grid[x][y] && grid[x + 1][y + 1] && grid[x + 2][y + 2] && grid[x][y].type === grid[x + 1][y + 1].type && grid[x][y].type === grid[x + 2][y + 2].type) {
var match = [];
var type = grid[x][y].type;
var startX = x;
var startY = y;
while (x < GRID_SIZE && y < GRID_SIZE && grid[x][y] && grid[x][y].type === type) {
if (!visited[x][y]) {
match.push({
x: x,
y: y
});
visited[x][y] = true;
}
x++;
y++;
}
x--;
y--;
if (match.length >= 3) {
matches.push(match);
}
}
}
}
// Check diagonal matches (bottom-left to top-right)
for (var x = 0; x < GRID_SIZE - 2; x++) {
for (var y = 2; y < GRID_SIZE; y++) {
if (y - 2 >= 0 && x + 2 < GRID_SIZE && grid[x][y] && grid[x + 1][y - 1] && grid[x + 2][y - 2] && grid[x][y].type === grid[x + 1][y - 1].type && grid[x][y].type === grid[x + 2][y - 2].type) {
var match = [];
var type = grid[x][y].type;
var startX = x;
var startY = y;
while (x < GRID_SIZE && y >= 0 && grid[x][y] && grid[x][y].type === type) {
if (!visited[x][y]) {
match.push({
x: x,
y: y
});
visited[x][y] = true;
}
x++;
y--;
}
x--;
y++;
if (match.length >= 3) {
matches.push(match);
}
}
}
}
return matches;
}
function clearMatches(matches) {
var totalCleared = 0;
for (var i = 0; i < matches.length; i++) {
var match = matches[i];
for (var j = 0; j < match.length; j++) {
var pos = match[j];
if (grid[pos.x][pos.y]) {
grid[pos.x][pos.y].destroy();
grid[pos.x][pos.y] = null;
totalCleared++;
}
}
}
if (totalCleared > 0) {
LK.getSound('match').play();
LK.setScore(LK.getScore() + totalCleared * 10);
scoreText.setText('Score: ' + LK.getScore());
// Update high score if current score is higher
if (LK.getScore() > (storage.highScore || 0)) {
storage.highScore = LK.getScore();
highScoreText.setText('High Score: ' + LK.getScore());
}
}
return totalCleared;
}
function applyGravity() {
var moved = false;
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = GRID_SIZE - 1; y >= 0; y--) {
if (!grid[x][y]) {
// Find candy above to fall down
for (var above = y - 1; above >= 0; above--) {
if (grid[x][above]) {
grid[x][y] = grid[x][above];
grid[x][above] = null;
animatingCandies++;
grid[x][y].animateToGridPosition(x, y, 300, function () {
animatingCandies--;
});
moved = true;
break;
}
}
}
}
}
return moved;
}
function fillEmptySpaces() {
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (!grid[x][y]) {
var type = getRandomCandyType();
grid[x][y] = createCandy(type, x, y - GRID_SIZE);
animatingCandies++;
grid[x][y].animateToGridPosition(x, y, 400, function () {
animatingCandies--;
});
}
}
}
}
function processMatches() {
if (isProcessing || animatingCandies > 0) {
return;
}
var matches = findMatches();
if (matches.length > 0) {
isProcessing = true;
clearMatches(matches);
LK.setTimeout(function () {
applyGravity();
LK.setTimeout(function () {
fillEmptySpaces();
LK.setTimeout(function () {
isProcessing = false;
}, 500);
}, 400);
}, 200);
}
}
function getCandyAtPosition(x, y) {
var localX = x - gridStartX;
var localY = y - gridStartY;
if (localX < 0 || localY < 0 || localX >= GRID_SIZE * cellSize || localY >= GRID_SIZE * cellSize) {
return null;
}
var gridX = Math.floor(localX / cellSize);
var gridY = Math.floor(localY / cellSize);
return grid[gridX][gridY];
}
function getGridPosition(x, y) {
var localX = x - gridStartX;
var localY = y - gridStartY;
if (localX < 0 || localY < 0 || localX >= GRID_SIZE * cellSize || localY >= GRID_SIZE * cellSize) {
return null;
}
var gridX = Math.floor(localX / cellSize);
var gridY = Math.floor(localY / cellSize);
return {
x: gridX,
y: gridY
};
}
function explodeBomb(centerX, centerY) {
var exploded = 0;
// Explode 3x3 area around the bomb
for (var dx = -1; dx <= 1; dx++) {
for (var dy = -1; dy <= 1; dy++) {
var x = centerX + dx;
var y = centerY + dy;
if (isValidPosition(x, y) && grid[x][y]) {
grid[x][y].destroy();
grid[x][y] = null;
exploded++;
}
}
}
if (exploded > 0) {
LK.getSound('explode').play();
LK.setScore(LK.getScore() + exploded * 15);
scoreText.setText('Score: ' + LK.getScore());
// Update high score if current score is higher
if (LK.getScore() > (storage.highScore || 0)) {
storage.highScore = LK.getScore();
highScoreText.setText('High Score: ' + LK.getScore());
}
}
}
game.down = function (x, y, obj) {
if (isProcessing || animatingCandies > 0 || gameEnded) {
return;
}
var candy = getCandyAtPosition(x, y);
if (candy) {
// Check if clicked candy is a bomb
if (candy.type === 'bomb') {
explodeBomb(candy.gridX, candy.gridY);
candy.destroy();
grid[candy.gridX][candy.gridY] = null;
LK.setTimeout(function () {
applyGravity();
LK.setTimeout(function () {
fillEmptySpaces();
LK.setTimeout(function () {
processMatches();
}, 500);
}, 400);
}, 200);
return;
}
if (selectedCandy) {
selectedCandy.alpha = 1.0;
}
selectedCandy = candy;
selectedCandy.alpha = 0.7;
LK.getSound('swap').play();
}
};
game.up = function (x, y, obj) {
if (isProcessing || animatingCandies > 0 || gameEnded) {
return;
}
var candy = getCandyAtPosition(x, y);
if (selectedCandy && candy && selectedCandy !== candy) {
// Don't allow swapping with bombs
if (selectedCandy.type === 'bomb' || candy.type === 'bomb') {
selectedCandy.alpha = 1.0;
selectedCandy = null;
return;
}
var pos1 = {
x: selectedCandy.gridX,
y: selectedCandy.gridY
};
var pos2 = {
x: candy.gridX,
y: candy.gridY
};
if (areAdjacent(pos1.x, pos1.y, pos2.x, pos2.y)) {
// Test swap
var temp = grid[pos1.x][pos1.y];
grid[pos1.x][pos1.y] = grid[pos2.x][pos2.y];
grid[pos2.x][pos2.y] = temp;
var matches = findMatches();
// Revert test swap
grid[pos2.x][pos2.y] = grid[pos1.x][pos1.y];
grid[pos1.x][pos1.y] = temp;
if (matches.length > 0) {
swapCandies(pos1.x, pos1.y, pos2.x, pos2.y);
LK.setTimeout(function () {
processMatches();
}, 250);
}
}
selectedCandy.alpha = 1.0;
selectedCandy = null;
} else if (selectedCandy) {
selectedCandy.alpha = 1.0;
selectedCandy = null;
}
};
game.update = function () {
if (gameEnded) {
return;
}
if (animatingCandies === 0 && !isProcessing) {
processMatches();
// Check if game should end due to no moves left or score threshold
if (noMovesLeft || !hasValidMoves()) {
gameEnded = true;
LK.setTimeout(function () {
LK.showGameOver();
}, 1000);
}
}
};
// Initialize high score from storage
var savedHighScore = storage.highScore || 0;
highScoreText.setText('High Score: ' + savedHighScore);
// Initialize the game
fillGrid();
;
function hasValidMoves() {
// Check all possible swaps between adjacent candies
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[x][y] && grid[x][y].type !== 'bomb' && grid[x][y].type !== 'firework' && grid[x][y].type !== 'blue') {
// Check right neighbor
if (x + 1 < GRID_SIZE && grid[x + 1][y] && grid[x + 1][y].type !== 'bomb' && grid[x + 1][y].type !== 'firework') {
// Test swap
var temp = grid[x][y];
grid[x][y] = grid[x + 1][y];
grid[x + 1][y] = temp;
var matches = findMatches();
// Revert swap
grid[x + 1][y] = grid[x][y];
grid[x][y] = temp;
if (matches.length > 0) {
return true;
}
}
// Check bottom neighbor
if (y + 1 < GRID_SIZE && grid[x][y + 1] && grid[x][y + 1].type !== 'bomb' && grid[x][y + 1].type !== 'firework') {
// Test swap
var temp = grid[x][y];
grid[x][y] = grid[x][y + 1];
grid[x][y + 1] = temp;
var matches = findMatches();
// Revert swap
grid[x][y + 1] = grid[x][y];
grid[x][y] = temp;
if (matches.length > 0) {
return true;
}
}
}
}
}
return false;
}
function explodeFirework(centerX, centerY) {
var exploded = 0;
// Explode entire row and column
for (var x = 0; x < GRID_SIZE; x++) {
if (isValidPosition(x, centerY) && grid[x][centerY]) {
grid[x][centerY].destroy();
grid[x][centerY] = null;
exploded++;
}
}
for (var y = 0; y < GRID_SIZE; y++) {
if (isValidPosition(centerX, y) && grid[centerX][y]) {
grid[centerX][y].destroy();
grid[centerX][y] = null;
exploded++;
}
}
if (exploded > 0) {
LK.getSound('explode').play();
LK.setScore(LK.getScore() + exploded * 15);
scoreText.setText('Score: ' + LK.getScore());
// Update high score if current score is higher
if (LK.getScore() > (storage.highScore || 0)) {
storage.highScore = LK.getScore();
highScoreText.setText('High Score: ' + LK.getScore());
}
}
}