/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Candy = Container.expand(function (type, gridX, gridY) {
var self = Container.call(this);
self.candyType = type;
self.gridX = gridX;
self.gridY = gridY;
self.isSpecial = false;
self.specialType = null; // 'lineH', 'lineV', 'explosive'
self.falling = false;
var assetId = 'candy' + type;
var candyGraphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
self.setSpecialType = function (specialType) {
self.isSpecial = true;
self.specialType = specialType;
self.removeChild(candyGraphics);
var newAssetId;
if (specialType === 'lineH') newAssetId = 'lineCandyH';else if (specialType === 'lineV') newAssetId = 'lineCandyV';else if (specialType === 'explosive') newAssetId = 'explosiveCandy';else newAssetId = 'candy' + type;
candyGraphics = self.attachAsset(newAssetId, {
anchorX: 0.5,
anchorY: 0.5
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2a1810
});
/****
* Game Code
****/
var GRID_SIZE = 8;
var CELL_SIZE = 200;
var CANDY_TYPES = 6;
var TARGET_SCORE = 5000;
var MAX_MOVES = 30;
var grid = [];
var score = 0;
var moves = 0;
var selectedCandy = null;
var isProcessing = false;
var gridOffsetX = (2048 - GRID_SIZE * CELL_SIZE) / 2;
var gridOffsetY = 400;
// UI Elements
var scoreTxt = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var movesTxt = new Text2('Moves: ' + MAX_MOVES, {
size: 80,
fill: 0xFFFFFF
});
movesTxt.anchor.set(0.5, 0);
movesTxt.y = 100;
LK.gui.top.addChild(movesTxt);
var targetTxt = new Text2('Target: ' + TARGET_SCORE, {
size: 60,
fill: 0xFFFF00
});
targetTxt.anchor.set(0.5, 0);
targetTxt.y = 200;
LK.gui.top.addChild(targetTxt);
// Music mute button
var isMusicMuted = false;
var muteButton = new Text2('π', {
size: 80,
fill: 0xFFFFFF
});
muteButton.anchor.set(1, 0);
muteButton.x = -50; // Position from right edge
muteButton.y = 50;
LK.gui.topRight.addChild(muteButton);
// Create grid background
for (var row = 0; row < GRID_SIZE; row++) {
for (var col = 0; col < GRID_SIZE; col++) {
var bgTile = game.addChild(LK.getAsset('gridBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: gridOffsetX + col * CELL_SIZE + CELL_SIZE / 2,
y: gridOffsetY + row * CELL_SIZE + CELL_SIZE / 2,
alpha: 0.3
}));
}
}
function getBalancedCandyType(row, col) {
// First check for immediate matches that would be created
var forbiddenTypes = [];
// Check horizontal matches (left and right)
if (col >= 2 && grid[row] && grid[row][col - 1] && grid[row][col - 2] && grid[row][col - 1].candyType === grid[row][col - 2].candyType) {
forbiddenTypes.push(grid[row][col - 1].candyType);
}
// Check vertical matches (up)
if (row >= 2 && grid[row - 1] && grid[row - 1][col] && grid[row - 2] && grid[row - 2][col] && grid[row - 1][col].candyType === grid[row - 2][col].candyType) {
forbiddenTypes.push(grid[row - 1][col].candyType);
}
// Count only immediate neighbors to prevent excessive clustering
var typeCounts = {};
for (var i = 1; i <= CANDY_TYPES; i++) {
typeCounts[i] = 0;
}
// Check only direct adjacent cells (not 3x3 area)
var adjacentPositions = [{
r: row - 1,
c: col
}, {
r: row + 1,
c: col
}, {
r: row,
c: col - 1
}, {
r: row,
c: col + 1
}];
for (var i = 0; i < adjacentPositions.length; i++) {
var pos = adjacentPositions[i];
if (pos.r >= 0 && pos.r < GRID_SIZE && pos.c >= 0 && pos.c < GRID_SIZE && grid[pos.r] && grid[pos.r][pos.c] && grid[pos.r][pos.c].candyType) {
typeCounts[grid[pos.r][pos.c].candyType]++;
}
}
// Create available types list, excluding forbidden and over-represented types
var availableTypes = [];
for (var type = 1; type <= CANDY_TYPES; type++) {
// Don't use forbidden types (would create immediate matches)
if (forbiddenTypes.indexOf(type) !== -1) continue;
// Don't use types that appear in 3+ adjacent cells
if (typeCounts[type] >= 3) continue;
availableTypes.push(type);
}
// If all types are forbidden/over-represented, allow types with fewer adjacent occurrences
if (availableTypes.length === 0) {
var minCount = Math.min.apply(Math, Object.values(typeCounts));
for (var type = 1; type <= CANDY_TYPES; type++) {
if (forbiddenTypes.indexOf(type) === -1 && typeCounts[type] === minCount) {
availableTypes.push(type);
}
}
}
// If still no available types, just pick randomly (should not happen)
if (availableTypes.length === 0) {
availableTypes = [1, 2, 3, 4, 5, 6];
}
// Return random type from available ones
return availableTypes[Math.floor(Math.random() * availableTypes.length)];
}
function initializeGrid() {
grid = [];
for (var row = 0; row < GRID_SIZE; row++) {
grid[row] = [];
for (var col = 0; col < GRID_SIZE; col++) {
var candyType = getBalancedCandyType(row, col);
var candy = new Candy(candyType, col, row);
candy.x = gridOffsetX + col * CELL_SIZE + CELL_SIZE / 2;
candy.y = gridOffsetY + row * CELL_SIZE + CELL_SIZE / 2;
grid[row][col] = candy;
game.addChild(candy);
}
}
// Remove initial matches
var hasMatches = true;
while (hasMatches) {
hasMatches = false;
for (var row = 0; row < GRID_SIZE; row++) {
for (var col = 0; col < GRID_SIZE; col++) {
if (checkMatch(row, col).length >= 3) {
grid[row][col].candyType = getBalancedCandyType(row, col);
hasMatches = true;
}
}
}
}
}
function getGridPosition(x, y) {
var col = Math.floor((x - gridOffsetX) / CELL_SIZE);
var row = Math.floor((y - gridOffsetY) / CELL_SIZE);
if (col >= 0 && col < GRID_SIZE && row >= 0 && row < GRID_SIZE) {
return {
row: row,
col: col
};
}
return null;
}
function checkMatch(row, col) {
// Validate input parameters
if (row < 0 || row >= GRID_SIZE || col < 0 || col >= GRID_SIZE) {
return [];
}
var candy = grid[row][col];
if (!candy || candy.falling || !candy.candyType) return [];
var type = candy.candyType;
var horizontalMatches = [candy];
var verticalMatches = [candy];
// Check horizontal matches
var left = col - 1;
while (left >= 0 && grid[row] && grid[row][left] && !grid[row][left].falling && grid[row][left].candyType && grid[row][left].candyType === type) {
horizontalMatches.push(grid[row][left]);
left--;
}
var right = col + 1;
while (right < GRID_SIZE && grid[row] && grid[row][right] && !grid[row][right].falling && grid[row][right].candyType && grid[row][right].candyType === type) {
horizontalMatches.push(grid[row][right]);
right++;
}
// Check vertical matches
var up = row - 1;
while (up >= 0 && grid[up] && grid[up][col] && !grid[up][col].falling && grid[up][col].candyType && grid[up][col].candyType === type) {
verticalMatches.push(grid[up][col]);
up--;
}
var down = row + 1;
while (down < GRID_SIZE && grid[down] && grid[down][col] && !grid[down][col].falling && grid[down][col].candyType && grid[down][col].candyType === type) {
verticalMatches.push(grid[down][col]);
down++;
}
// Return the larger match group if either is 3 or more
if (horizontalMatches.length >= 3 && verticalMatches.length >= 3) {
// Return combined matches for cross patterns
var allMatches = [];
for (var i = 0; i < horizontalMatches.length; i++) {
if (allMatches.indexOf(horizontalMatches[i]) === -1) {
allMatches.push(horizontalMatches[i]);
}
}
for (var i = 0; i < verticalMatches.length; i++) {
if (allMatches.indexOf(verticalMatches[i]) === -1) {
allMatches.push(verticalMatches[i]);
}
}
return allMatches;
} else if (horizontalMatches.length >= 3) {
return horizontalMatches;
} else if (verticalMatches.length >= 3) {
return verticalMatches;
}
return [];
}
function findAllMatches() {
var allMatches = [];
var processed = [];
for (var row = 0; row < GRID_SIZE; row++) {
processed[row] = [];
for (var col = 0; col < GRID_SIZE; col++) {
processed[row][col] = false;
}
}
for (var row = 0; row < GRID_SIZE; row++) {
for (var col = 0; col < GRID_SIZE; col++) {
if (!processed[row][col] && grid[row] && grid[row][col] && !grid[row][col].falling) {
var matches = checkMatch(row, col);
if (matches.length >= 3) {
allMatches.push(matches);
for (var i = 0; i < matches.length; i++) {
var match = matches[i];
if (match && match.gridY >= 0 && match.gridY < GRID_SIZE && match.gridX >= 0 && match.gridX < GRID_SIZE) {
processed[match.gridY][match.gridX] = true;
}
}
}
}
}
}
return allMatches;
}
function removeMatches(matchGroups) {
var totalScore = 0;
for (var g = 0; g < matchGroups.length; g++) {
var matches = matchGroups[g];
var matchScore = 0;
if (matches.length === 3) matchScore = 100;else if (matches.length === 4) matchScore = 200;else if (matches.length >= 5) matchScore = 500;
totalScore += matchScore;
// Create special candy for 4+ matches
var specialCandy = null;
if (matches.length >= 4) {
var centerCandy = matches[Math.floor(matches.length / 2)];
if (matches.length === 4) {
// Create line candy based on match direction
var isHorizontal = matches[0].gridY === matches[1].gridY;
centerCandy.setSpecialType(isHorizontal ? 'lineH' : 'lineV');
} else if (matches.length >= 5) {
centerCandy.setSpecialType('explosive');
}
specialCandy = centerCandy;
}
// Animate candy destruction with satisfying effects
for (var i = 0; i < matches.length; i++) {
var candy = matches[i];
if (matches.length < 4 || candy !== specialCandy) {
// Create particle effect before destroying
createParticleEffect(candy.x, candy.y, candy.candyType);
// Animate candy scaling down with bounce
tween(candy, {
scaleX: 0,
scaleY: 0,
rotation: Math.PI * 2,
alpha: 0
}, {
duration: 300,
easing: tween.bounceIn,
onFinish: function onFinish() {
if (candy.parent) {
candy.destroy();
}
}
});
// Set grid position to null immediately
grid[candy.gridY][candy.gridX] = null;
} else {
// Special candy creation effect
tween(candy, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.bounceOut,
onFinish: function onFinish() {
tween(candy, {
scaleX: 1,
scaleY: 1
}, {
duration: 150,
easing: tween.easeOut
});
}
});
LK.effects.flashObject(candy, 0xFFD700, 500);
}
}
}
if (totalScore > 0) {
LK.getSound('match').play();
LK.effects.flashScreen(0xFFFFFF, 200);
}
return totalScore;
}
function applyGravity() {
var moved = false;
var animationDelay = 0;
for (var col = 0; col < GRID_SIZE; col++) {
var writePos = GRID_SIZE - 1;
var colDelay = col * 30; // Stagger columns slightly
for (var row = GRID_SIZE - 1; row >= 0; row--) {
if (grid[row][col] !== null) {
if (row !== writePos) {
var candy = grid[row][col];
grid[writePos][col] = candy;
grid[row][col] = null;
candy.gridY = writePos;
candy.gridX = col;
candy.falling = true;
var fallDistance = writePos - row;
var fallDuration = 200 + fallDistance * 50; // Longer falls take more time
LK.setTimeout(function (candy, writePos, fallDuration) {
return function () {
tween(candy, {
y: gridOffsetY + writePos * CELL_SIZE + CELL_SIZE / 2
}, {
duration: fallDuration,
easing: tween.bounceOut,
onFinish: function onFinish() {
candy.falling = false;
}
});
};
}(candy, writePos, fallDuration), colDelay);
moved = true;
}
writePos--;
}
}
// Fill empty spaces with new candies
for (var emptyRow = writePos; emptyRow >= 0; emptyRow--) {
var candyType = getBalancedCandyType(emptyRow, col);
var newCandy = new Candy(candyType, col, emptyRow);
newCandy.x = gridOffsetX + col * CELL_SIZE + CELL_SIZE / 2;
newCandy.y = gridOffsetY + (emptyRow - GRID_SIZE) * CELL_SIZE + CELL_SIZE / 2;
newCandy.falling = true;
grid[emptyRow][col] = newCandy;
game.addChild(newCandy);
var dropDelay = colDelay + (GRID_SIZE - emptyRow) * 50;
LK.setTimeout(function (newCandy, emptyRow) {
return function () {
tween(newCandy, {
y: gridOffsetY + emptyRow * CELL_SIZE + CELL_SIZE / 2
}, {
duration: 300,
easing: tween.bounceOut,
onFinish: function onFinish() {
newCandy.falling = false;
}
});
};
}(newCandy, emptyRow), dropDelay);
moved = true;
}
}
return moved;
}
function swapCandies(candy1, candy2) {
if (isProcessing) return false;
// Validate candies exist and have proper grid positions
if (!candy1 || !candy2 || candy1.falling || candy2.falling) return false;
if (candy1.gridX < 0 || candy1.gridX >= GRID_SIZE || candy1.gridY < 0 || candy1.gridY >= GRID_SIZE) return false;
if (candy2.gridX < 0 || candy2.gridX >= GRID_SIZE || candy2.gridY < 0 || candy2.gridY >= GRID_SIZE) return false;
var tempGridX = candy1.gridX;
var tempGridY = candy1.gridY;
candy1.gridX = candy2.gridX;
candy1.gridY = candy2.gridY;
candy2.gridX = tempGridX;
candy2.gridY = tempGridY;
grid[candy1.gridY][candy1.gridX] = candy1;
grid[candy2.gridY][candy2.gridX] = candy2;
var pos1 = {
x: gridOffsetX + candy1.gridX * CELL_SIZE + CELL_SIZE / 2,
y: gridOffsetY + candy1.gridY * CELL_SIZE + CELL_SIZE / 2
};
var pos2 = {
x: gridOffsetX + candy2.gridX * CELL_SIZE + CELL_SIZE / 2,
y: gridOffsetY + candy2.gridY * CELL_SIZE + CELL_SIZE / 2
};
isProcessing = true;
tween(candy1, pos1, {
duration: 200,
easing: tween.easeInOut
});
tween(candy2, pos2, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
var matches1 = checkMatch(candy1.gridY, candy1.gridX);
var matches2 = checkMatch(candy2.gridY, candy2.gridX);
if (matches1.length >= 3 || matches2.length >= 3) {
LK.getSound('swap').play();
moves++;
movesTxt.setText('Moves: ' + (MAX_MOVES - moves));
processMatches();
} else {
// Wrong match - penalize score and play sound
score -= 300;
if (score < 0) score = 0; // Don't let score go negative
scoreTxt.setText('Score: ' + score);
LK.getSound('wrongMatch').play();
// Flash screen red to indicate wrong match
LK.effects.flashScreen(0xFF0000, 300);
// Swap back
var tempGridX = candy1.gridX;
var tempGridY = candy1.gridY;
candy1.gridX = candy2.gridX;
candy1.gridY = candy2.gridY;
candy2.gridX = tempGridX;
candy2.gridY = tempGridY;
grid[candy1.gridY][candy1.gridX] = candy1;
grid[candy2.gridY][candy2.gridX] = candy2;
tween(candy1, pos2, {
duration: 200
});
tween(candy2, pos1, {
duration: 200,
onFinish: function onFinish() {
isProcessing = false;
}
});
}
}
});
return true;
}
function processMatches() {
LK.setTimeout(function () {
var matchGroups = findAllMatches();
if (matchGroups.length > 0) {
var matchScore = removeMatches(matchGroups);
score += matchScore;
scoreTxt.setText('Score: ' + score);
LK.setTimeout(function () {
var moved = applyGravity();
if (moved) {
LK.setTimeout(function () {
processMatches();
}, 400);
} else {
isProcessing = false;
checkGameEnd();
}
}, 100);
} else {
isProcessing = false;
checkGameEnd();
}
}, 100);
}
function createParticleEffect(x, y, candyType) {
// Create multiple particle sprites for a juicy effect
var colors = [0xFF6B6B, 0x4ECDC4, 0x45B7D1, 0x96CEB4, 0xFECE2F, 0xFF8A80];
var particleColor = colors[candyType - 1] || 0xFFFFFF;
for (var i = 0; i < 8; i++) {
var particle = game.addChild(LK.getAsset('candy' + candyType, {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
scaleX: 0.3,
scaleY: 0.3,
alpha: 0.8
}));
// Random direction and distance for particles
var angle = Math.PI * 2 * i / 8;
var distance = 50 + Math.random() * 100;
var targetX = x + Math.cos(angle) * distance;
var targetY = y + Math.sin(angle) * distance;
// Animate particles flying out and fading
tween(particle, {
x: targetX,
y: targetY,
scaleX: 0,
scaleY: 0,
alpha: 0,
rotation: Math.random() * Math.PI * 4
}, {
duration: 400 + Math.random() * 200,
easing: tween.easeOut,
onFinish: function onFinish() {
if (particle.parent) {
particle.destroy();
}
}
});
}
}
function checkGameEnd() {
if (score >= TARGET_SCORE) {
LK.showYouWin();
} else if (moves >= MAX_MOVES) {
LK.showGameOver();
}
}
function isAdjacent(candy1, candy2) {
var dx = Math.abs(candy1.gridX - candy2.gridX);
var dy = Math.abs(candy1.gridY - candy2.gridY);
return dx === 1 && dy === 0 || dx === 0 && dy === 1;
}
// Mute button click handler
muteButton.down = function (x, y, obj) {
if (isMusicMuted) {
LK.playMusic('backgroundMusic');
muteButton.setText('π');
isMusicMuted = false;
} else {
LK.stopMusic();
muteButton.setText('π');
isMusicMuted = true;
}
// Add visual feedback
tween(muteButton, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(muteButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
easing: tween.easeOut
});
}
});
};
game.down = function (x, y, obj) {
if (isProcessing) return;
var gridPos = getGridPosition(x, y);
if (!gridPos) return;
var candy = grid[gridPos.row][gridPos.col];
if (!candy || candy.falling) return;
if (selectedCandy === null) {
selectedCandy = candy;
tween(candy, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 150,
easing: tween.bounceOut
});
LK.effects.flashObject(candy, 0xFFFFFF, 300);
} else if (selectedCandy === candy) {
tween(candy, {
scaleX: 1,
scaleY: 1
}, {
duration: 150,
easing: tween.easeOut
});
selectedCandy = null;
} else if (isAdjacent(selectedCandy, candy)) {
tween(selectedCandy, {
scaleX: 1,
scaleY: 1
}, {
duration: 150,
easing: tween.easeOut
});
swapCandies(selectedCandy, candy);
selectedCandy = null;
} else {
tween(selectedCandy, {
scaleX: 1,
scaleY: 1
}, {
duration: 150,
easing: tween.easeOut
});
selectedCandy = candy;
tween(candy, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 150,
easing: tween.bounceOut
});
LK.effects.flashObject(candy, 0xFFFFFF, 300);
}
};
// Add background image
var backgroundImage = game.addChild(LK.getAsset('gameBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0.8
}));
// Initialize the game
initializeGrid();
// Start background music after mute button is set up
LK.playMusic('backgroundMusic');
// Game update function to automatically detect and process matches
game.update = function () {
// Only check for automatic matches when not processing and no candies are falling
if (!isProcessing) {
var anyFalling = false;
for (var row = 0; row < GRID_SIZE; row++) {
for (var col = 0; col < GRID_SIZE; col++) {
if (grid[row][col] && grid[row][col].falling) {
anyFalling = true;
break;
}
}
if (anyFalling) break;
}
// If no candies are falling, check for automatic matches
if (!anyFalling) {
var matchGroups = findAllMatches();
if (matchGroups.length > 0) {
// Automatically process the matches found
processMatches();
}
}
}
};
; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Candy = Container.expand(function (type, gridX, gridY) {
var self = Container.call(this);
self.candyType = type;
self.gridX = gridX;
self.gridY = gridY;
self.isSpecial = false;
self.specialType = null; // 'lineH', 'lineV', 'explosive'
self.falling = false;
var assetId = 'candy' + type;
var candyGraphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
self.setSpecialType = function (specialType) {
self.isSpecial = true;
self.specialType = specialType;
self.removeChild(candyGraphics);
var newAssetId;
if (specialType === 'lineH') newAssetId = 'lineCandyH';else if (specialType === 'lineV') newAssetId = 'lineCandyV';else if (specialType === 'explosive') newAssetId = 'explosiveCandy';else newAssetId = 'candy' + type;
candyGraphics = self.attachAsset(newAssetId, {
anchorX: 0.5,
anchorY: 0.5
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2a1810
});
/****
* Game Code
****/
var GRID_SIZE = 8;
var CELL_SIZE = 200;
var CANDY_TYPES = 6;
var TARGET_SCORE = 5000;
var MAX_MOVES = 30;
var grid = [];
var score = 0;
var moves = 0;
var selectedCandy = null;
var isProcessing = false;
var gridOffsetX = (2048 - GRID_SIZE * CELL_SIZE) / 2;
var gridOffsetY = 400;
// UI Elements
var scoreTxt = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var movesTxt = new Text2('Moves: ' + MAX_MOVES, {
size: 80,
fill: 0xFFFFFF
});
movesTxt.anchor.set(0.5, 0);
movesTxt.y = 100;
LK.gui.top.addChild(movesTxt);
var targetTxt = new Text2('Target: ' + TARGET_SCORE, {
size: 60,
fill: 0xFFFF00
});
targetTxt.anchor.set(0.5, 0);
targetTxt.y = 200;
LK.gui.top.addChild(targetTxt);
// Music mute button
var isMusicMuted = false;
var muteButton = new Text2('π', {
size: 80,
fill: 0xFFFFFF
});
muteButton.anchor.set(1, 0);
muteButton.x = -50; // Position from right edge
muteButton.y = 50;
LK.gui.topRight.addChild(muteButton);
// Create grid background
for (var row = 0; row < GRID_SIZE; row++) {
for (var col = 0; col < GRID_SIZE; col++) {
var bgTile = game.addChild(LK.getAsset('gridBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: gridOffsetX + col * CELL_SIZE + CELL_SIZE / 2,
y: gridOffsetY + row * CELL_SIZE + CELL_SIZE / 2,
alpha: 0.3
}));
}
}
function getBalancedCandyType(row, col) {
// First check for immediate matches that would be created
var forbiddenTypes = [];
// Check horizontal matches (left and right)
if (col >= 2 && grid[row] && grid[row][col - 1] && grid[row][col - 2] && grid[row][col - 1].candyType === grid[row][col - 2].candyType) {
forbiddenTypes.push(grid[row][col - 1].candyType);
}
// Check vertical matches (up)
if (row >= 2 && grid[row - 1] && grid[row - 1][col] && grid[row - 2] && grid[row - 2][col] && grid[row - 1][col].candyType === grid[row - 2][col].candyType) {
forbiddenTypes.push(grid[row - 1][col].candyType);
}
// Count only immediate neighbors to prevent excessive clustering
var typeCounts = {};
for (var i = 1; i <= CANDY_TYPES; i++) {
typeCounts[i] = 0;
}
// Check only direct adjacent cells (not 3x3 area)
var adjacentPositions = [{
r: row - 1,
c: col
}, {
r: row + 1,
c: col
}, {
r: row,
c: col - 1
}, {
r: row,
c: col + 1
}];
for (var i = 0; i < adjacentPositions.length; i++) {
var pos = adjacentPositions[i];
if (pos.r >= 0 && pos.r < GRID_SIZE && pos.c >= 0 && pos.c < GRID_SIZE && grid[pos.r] && grid[pos.r][pos.c] && grid[pos.r][pos.c].candyType) {
typeCounts[grid[pos.r][pos.c].candyType]++;
}
}
// Create available types list, excluding forbidden and over-represented types
var availableTypes = [];
for (var type = 1; type <= CANDY_TYPES; type++) {
// Don't use forbidden types (would create immediate matches)
if (forbiddenTypes.indexOf(type) !== -1) continue;
// Don't use types that appear in 3+ adjacent cells
if (typeCounts[type] >= 3) continue;
availableTypes.push(type);
}
// If all types are forbidden/over-represented, allow types with fewer adjacent occurrences
if (availableTypes.length === 0) {
var minCount = Math.min.apply(Math, Object.values(typeCounts));
for (var type = 1; type <= CANDY_TYPES; type++) {
if (forbiddenTypes.indexOf(type) === -1 && typeCounts[type] === minCount) {
availableTypes.push(type);
}
}
}
// If still no available types, just pick randomly (should not happen)
if (availableTypes.length === 0) {
availableTypes = [1, 2, 3, 4, 5, 6];
}
// Return random type from available ones
return availableTypes[Math.floor(Math.random() * availableTypes.length)];
}
function initializeGrid() {
grid = [];
for (var row = 0; row < GRID_SIZE; row++) {
grid[row] = [];
for (var col = 0; col < GRID_SIZE; col++) {
var candyType = getBalancedCandyType(row, col);
var candy = new Candy(candyType, col, row);
candy.x = gridOffsetX + col * CELL_SIZE + CELL_SIZE / 2;
candy.y = gridOffsetY + row * CELL_SIZE + CELL_SIZE / 2;
grid[row][col] = candy;
game.addChild(candy);
}
}
// Remove initial matches
var hasMatches = true;
while (hasMatches) {
hasMatches = false;
for (var row = 0; row < GRID_SIZE; row++) {
for (var col = 0; col < GRID_SIZE; col++) {
if (checkMatch(row, col).length >= 3) {
grid[row][col].candyType = getBalancedCandyType(row, col);
hasMatches = true;
}
}
}
}
}
function getGridPosition(x, y) {
var col = Math.floor((x - gridOffsetX) / CELL_SIZE);
var row = Math.floor((y - gridOffsetY) / CELL_SIZE);
if (col >= 0 && col < GRID_SIZE && row >= 0 && row < GRID_SIZE) {
return {
row: row,
col: col
};
}
return null;
}
function checkMatch(row, col) {
// Validate input parameters
if (row < 0 || row >= GRID_SIZE || col < 0 || col >= GRID_SIZE) {
return [];
}
var candy = grid[row][col];
if (!candy || candy.falling || !candy.candyType) return [];
var type = candy.candyType;
var horizontalMatches = [candy];
var verticalMatches = [candy];
// Check horizontal matches
var left = col - 1;
while (left >= 0 && grid[row] && grid[row][left] && !grid[row][left].falling && grid[row][left].candyType && grid[row][left].candyType === type) {
horizontalMatches.push(grid[row][left]);
left--;
}
var right = col + 1;
while (right < GRID_SIZE && grid[row] && grid[row][right] && !grid[row][right].falling && grid[row][right].candyType && grid[row][right].candyType === type) {
horizontalMatches.push(grid[row][right]);
right++;
}
// Check vertical matches
var up = row - 1;
while (up >= 0 && grid[up] && grid[up][col] && !grid[up][col].falling && grid[up][col].candyType && grid[up][col].candyType === type) {
verticalMatches.push(grid[up][col]);
up--;
}
var down = row + 1;
while (down < GRID_SIZE && grid[down] && grid[down][col] && !grid[down][col].falling && grid[down][col].candyType && grid[down][col].candyType === type) {
verticalMatches.push(grid[down][col]);
down++;
}
// Return the larger match group if either is 3 or more
if (horizontalMatches.length >= 3 && verticalMatches.length >= 3) {
// Return combined matches for cross patterns
var allMatches = [];
for (var i = 0; i < horizontalMatches.length; i++) {
if (allMatches.indexOf(horizontalMatches[i]) === -1) {
allMatches.push(horizontalMatches[i]);
}
}
for (var i = 0; i < verticalMatches.length; i++) {
if (allMatches.indexOf(verticalMatches[i]) === -1) {
allMatches.push(verticalMatches[i]);
}
}
return allMatches;
} else if (horizontalMatches.length >= 3) {
return horizontalMatches;
} else if (verticalMatches.length >= 3) {
return verticalMatches;
}
return [];
}
function findAllMatches() {
var allMatches = [];
var processed = [];
for (var row = 0; row < GRID_SIZE; row++) {
processed[row] = [];
for (var col = 0; col < GRID_SIZE; col++) {
processed[row][col] = false;
}
}
for (var row = 0; row < GRID_SIZE; row++) {
for (var col = 0; col < GRID_SIZE; col++) {
if (!processed[row][col] && grid[row] && grid[row][col] && !grid[row][col].falling) {
var matches = checkMatch(row, col);
if (matches.length >= 3) {
allMatches.push(matches);
for (var i = 0; i < matches.length; i++) {
var match = matches[i];
if (match && match.gridY >= 0 && match.gridY < GRID_SIZE && match.gridX >= 0 && match.gridX < GRID_SIZE) {
processed[match.gridY][match.gridX] = true;
}
}
}
}
}
}
return allMatches;
}
function removeMatches(matchGroups) {
var totalScore = 0;
for (var g = 0; g < matchGroups.length; g++) {
var matches = matchGroups[g];
var matchScore = 0;
if (matches.length === 3) matchScore = 100;else if (matches.length === 4) matchScore = 200;else if (matches.length >= 5) matchScore = 500;
totalScore += matchScore;
// Create special candy for 4+ matches
var specialCandy = null;
if (matches.length >= 4) {
var centerCandy = matches[Math.floor(matches.length / 2)];
if (matches.length === 4) {
// Create line candy based on match direction
var isHorizontal = matches[0].gridY === matches[1].gridY;
centerCandy.setSpecialType(isHorizontal ? 'lineH' : 'lineV');
} else if (matches.length >= 5) {
centerCandy.setSpecialType('explosive');
}
specialCandy = centerCandy;
}
// Animate candy destruction with satisfying effects
for (var i = 0; i < matches.length; i++) {
var candy = matches[i];
if (matches.length < 4 || candy !== specialCandy) {
// Create particle effect before destroying
createParticleEffect(candy.x, candy.y, candy.candyType);
// Animate candy scaling down with bounce
tween(candy, {
scaleX: 0,
scaleY: 0,
rotation: Math.PI * 2,
alpha: 0
}, {
duration: 300,
easing: tween.bounceIn,
onFinish: function onFinish() {
if (candy.parent) {
candy.destroy();
}
}
});
// Set grid position to null immediately
grid[candy.gridY][candy.gridX] = null;
} else {
// Special candy creation effect
tween(candy, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.bounceOut,
onFinish: function onFinish() {
tween(candy, {
scaleX: 1,
scaleY: 1
}, {
duration: 150,
easing: tween.easeOut
});
}
});
LK.effects.flashObject(candy, 0xFFD700, 500);
}
}
}
if (totalScore > 0) {
LK.getSound('match').play();
LK.effects.flashScreen(0xFFFFFF, 200);
}
return totalScore;
}
function applyGravity() {
var moved = false;
var animationDelay = 0;
for (var col = 0; col < GRID_SIZE; col++) {
var writePos = GRID_SIZE - 1;
var colDelay = col * 30; // Stagger columns slightly
for (var row = GRID_SIZE - 1; row >= 0; row--) {
if (grid[row][col] !== null) {
if (row !== writePos) {
var candy = grid[row][col];
grid[writePos][col] = candy;
grid[row][col] = null;
candy.gridY = writePos;
candy.gridX = col;
candy.falling = true;
var fallDistance = writePos - row;
var fallDuration = 200 + fallDistance * 50; // Longer falls take more time
LK.setTimeout(function (candy, writePos, fallDuration) {
return function () {
tween(candy, {
y: gridOffsetY + writePos * CELL_SIZE + CELL_SIZE / 2
}, {
duration: fallDuration,
easing: tween.bounceOut,
onFinish: function onFinish() {
candy.falling = false;
}
});
};
}(candy, writePos, fallDuration), colDelay);
moved = true;
}
writePos--;
}
}
// Fill empty spaces with new candies
for (var emptyRow = writePos; emptyRow >= 0; emptyRow--) {
var candyType = getBalancedCandyType(emptyRow, col);
var newCandy = new Candy(candyType, col, emptyRow);
newCandy.x = gridOffsetX + col * CELL_SIZE + CELL_SIZE / 2;
newCandy.y = gridOffsetY + (emptyRow - GRID_SIZE) * CELL_SIZE + CELL_SIZE / 2;
newCandy.falling = true;
grid[emptyRow][col] = newCandy;
game.addChild(newCandy);
var dropDelay = colDelay + (GRID_SIZE - emptyRow) * 50;
LK.setTimeout(function (newCandy, emptyRow) {
return function () {
tween(newCandy, {
y: gridOffsetY + emptyRow * CELL_SIZE + CELL_SIZE / 2
}, {
duration: 300,
easing: tween.bounceOut,
onFinish: function onFinish() {
newCandy.falling = false;
}
});
};
}(newCandy, emptyRow), dropDelay);
moved = true;
}
}
return moved;
}
function swapCandies(candy1, candy2) {
if (isProcessing) return false;
// Validate candies exist and have proper grid positions
if (!candy1 || !candy2 || candy1.falling || candy2.falling) return false;
if (candy1.gridX < 0 || candy1.gridX >= GRID_SIZE || candy1.gridY < 0 || candy1.gridY >= GRID_SIZE) return false;
if (candy2.gridX < 0 || candy2.gridX >= GRID_SIZE || candy2.gridY < 0 || candy2.gridY >= GRID_SIZE) return false;
var tempGridX = candy1.gridX;
var tempGridY = candy1.gridY;
candy1.gridX = candy2.gridX;
candy1.gridY = candy2.gridY;
candy2.gridX = tempGridX;
candy2.gridY = tempGridY;
grid[candy1.gridY][candy1.gridX] = candy1;
grid[candy2.gridY][candy2.gridX] = candy2;
var pos1 = {
x: gridOffsetX + candy1.gridX * CELL_SIZE + CELL_SIZE / 2,
y: gridOffsetY + candy1.gridY * CELL_SIZE + CELL_SIZE / 2
};
var pos2 = {
x: gridOffsetX + candy2.gridX * CELL_SIZE + CELL_SIZE / 2,
y: gridOffsetY + candy2.gridY * CELL_SIZE + CELL_SIZE / 2
};
isProcessing = true;
tween(candy1, pos1, {
duration: 200,
easing: tween.easeInOut
});
tween(candy2, pos2, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
var matches1 = checkMatch(candy1.gridY, candy1.gridX);
var matches2 = checkMatch(candy2.gridY, candy2.gridX);
if (matches1.length >= 3 || matches2.length >= 3) {
LK.getSound('swap').play();
moves++;
movesTxt.setText('Moves: ' + (MAX_MOVES - moves));
processMatches();
} else {
// Wrong match - penalize score and play sound
score -= 300;
if (score < 0) score = 0; // Don't let score go negative
scoreTxt.setText('Score: ' + score);
LK.getSound('wrongMatch').play();
// Flash screen red to indicate wrong match
LK.effects.flashScreen(0xFF0000, 300);
// Swap back
var tempGridX = candy1.gridX;
var tempGridY = candy1.gridY;
candy1.gridX = candy2.gridX;
candy1.gridY = candy2.gridY;
candy2.gridX = tempGridX;
candy2.gridY = tempGridY;
grid[candy1.gridY][candy1.gridX] = candy1;
grid[candy2.gridY][candy2.gridX] = candy2;
tween(candy1, pos2, {
duration: 200
});
tween(candy2, pos1, {
duration: 200,
onFinish: function onFinish() {
isProcessing = false;
}
});
}
}
});
return true;
}
function processMatches() {
LK.setTimeout(function () {
var matchGroups = findAllMatches();
if (matchGroups.length > 0) {
var matchScore = removeMatches(matchGroups);
score += matchScore;
scoreTxt.setText('Score: ' + score);
LK.setTimeout(function () {
var moved = applyGravity();
if (moved) {
LK.setTimeout(function () {
processMatches();
}, 400);
} else {
isProcessing = false;
checkGameEnd();
}
}, 100);
} else {
isProcessing = false;
checkGameEnd();
}
}, 100);
}
function createParticleEffect(x, y, candyType) {
// Create multiple particle sprites for a juicy effect
var colors = [0xFF6B6B, 0x4ECDC4, 0x45B7D1, 0x96CEB4, 0xFECE2F, 0xFF8A80];
var particleColor = colors[candyType - 1] || 0xFFFFFF;
for (var i = 0; i < 8; i++) {
var particle = game.addChild(LK.getAsset('candy' + candyType, {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
scaleX: 0.3,
scaleY: 0.3,
alpha: 0.8
}));
// Random direction and distance for particles
var angle = Math.PI * 2 * i / 8;
var distance = 50 + Math.random() * 100;
var targetX = x + Math.cos(angle) * distance;
var targetY = y + Math.sin(angle) * distance;
// Animate particles flying out and fading
tween(particle, {
x: targetX,
y: targetY,
scaleX: 0,
scaleY: 0,
alpha: 0,
rotation: Math.random() * Math.PI * 4
}, {
duration: 400 + Math.random() * 200,
easing: tween.easeOut,
onFinish: function onFinish() {
if (particle.parent) {
particle.destroy();
}
}
});
}
}
function checkGameEnd() {
if (score >= TARGET_SCORE) {
LK.showYouWin();
} else if (moves >= MAX_MOVES) {
LK.showGameOver();
}
}
function isAdjacent(candy1, candy2) {
var dx = Math.abs(candy1.gridX - candy2.gridX);
var dy = Math.abs(candy1.gridY - candy2.gridY);
return dx === 1 && dy === 0 || dx === 0 && dy === 1;
}
// Mute button click handler
muteButton.down = function (x, y, obj) {
if (isMusicMuted) {
LK.playMusic('backgroundMusic');
muteButton.setText('π');
isMusicMuted = false;
} else {
LK.stopMusic();
muteButton.setText('π');
isMusicMuted = true;
}
// Add visual feedback
tween(muteButton, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(muteButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
easing: tween.easeOut
});
}
});
};
game.down = function (x, y, obj) {
if (isProcessing) return;
var gridPos = getGridPosition(x, y);
if (!gridPos) return;
var candy = grid[gridPos.row][gridPos.col];
if (!candy || candy.falling) return;
if (selectedCandy === null) {
selectedCandy = candy;
tween(candy, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 150,
easing: tween.bounceOut
});
LK.effects.flashObject(candy, 0xFFFFFF, 300);
} else if (selectedCandy === candy) {
tween(candy, {
scaleX: 1,
scaleY: 1
}, {
duration: 150,
easing: tween.easeOut
});
selectedCandy = null;
} else if (isAdjacent(selectedCandy, candy)) {
tween(selectedCandy, {
scaleX: 1,
scaleY: 1
}, {
duration: 150,
easing: tween.easeOut
});
swapCandies(selectedCandy, candy);
selectedCandy = null;
} else {
tween(selectedCandy, {
scaleX: 1,
scaleY: 1
}, {
duration: 150,
easing: tween.easeOut
});
selectedCandy = candy;
tween(candy, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 150,
easing: tween.bounceOut
});
LK.effects.flashObject(candy, 0xFFFFFF, 300);
}
};
// Add background image
var backgroundImage = game.addChild(LK.getAsset('gameBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0.8
}));
// Initialize the game
initializeGrid();
// Start background music after mute button is set up
LK.playMusic('backgroundMusic');
// Game update function to automatically detect and process matches
game.update = function () {
// Only check for automatic matches when not processing and no candies are falling
if (!isProcessing) {
var anyFalling = false;
for (var row = 0; row < GRID_SIZE; row++) {
for (var col = 0; col < GRID_SIZE; col++) {
if (grid[row][col] && grid[row][col].falling) {
anyFalling = true;
break;
}
}
if (anyFalling) break;
}
// If no candies are falling, check for automatic matches
if (!anyFalling) {
var matchGroups = findAllMatches();
if (matchGroups.length > 0) {
// Automatically process the matches found
processMatches();
}
}
}
};
;
green gummy bear candy shiny and cute (sitting). No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
blue foot candy looks funny and shiny. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
yellow appetite enhancer candy looks shiny and delicous. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
pink cotton candy looks shiny and delicous. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
blue shiny and delicous lollipop, no stick. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
bomb and lollipop mix candy . No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
blurry beautiful snow view background image. In-Game asset. 2d. High contrast. No shadows
blurry ice block texture. In-Game asset. 2d. High contrast. No shadows
stick candy. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
stick candy with green lines . No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
bean candy red and shiny . No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat