/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Candy = Container.expand(function (type, isSpecial) {
var self = Container.call(this);
self.candyType = type;
self.isSpecial = isSpecial || false;
self.gridX = -1;
self.gridY = -1;
self.isMatched = false;
self.isMoving = false;
self.isDragging = false;
// Background removed - only candy graphics remain
var candyGraphic;
if (self.isSpecial) {
candyGraphic = self.attachAsset('specialCandy', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
candyGraphic = self.attachAsset('candy' + type, {
anchorX: 0.5,
anchorY: 0.5
});
}
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = GRID_START_X + gridX * CELL_SIZE;
self.y = GRID_START_Y + gridY * CELL_SIZE;
};
self.animateToPosition = function (targetX, targetY, onComplete) {
self.isMoving = true;
tween(self, {
x: targetX,
y: targetY
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
self.isMoving = false;
if (onComplete) onComplete();
}
});
};
self.markForDestroy = function () {
self.isMatched = true;
tween(self, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
self.destroy();
}
});
};
self.down = function (x, y, obj) {
if (gameState !== 'playing' || self.isMoving) return;
if (self.isSpecial) {
// Special candy clicked - clear 2x2 area around it
var centerX = self.gridX;
var centerY = self.gridY;
var candiesDestroyed = 0;
// Clear 2x2 area around the special candy (including the special candy itself)
for (var dx = -1; dx <= 1; dx++) {
for (var dy = -1; dy <= 1; dy++) {
var targetX = centerX + dx;
var targetY = centerY + dy;
if (targetX >= 0 && targetX < GRID_SIZE && targetY >= 0 && targetY < GRID_SIZE) {
var targetCandy = grid[targetX][targetY];
if (targetCandy && !targetCandy.isMatched) {
targetCandy.markForDestroy();
grid[targetX][targetY] = null;
candiesDestroyed++;
score += 15; // Bonus points for special candy destruction
}
}
}
}
scoreText.setText('Score: ' + score);
LK.getSound('specialCandy').play();
// Drop candies after delay
LK.setTimeout(function () {
dropCandies();
}, 350);
return; // Exit early for special candies
}
selectedCandy = self;
self.scaleX = 1.1;
self.scaleY = 1.1;
self.isDragging = true;
self.dragStartX = x;
self.dragStartY = y;
self.startX = self.x;
self.startY = self.y;
};
self.move = function (x, y, obj) {
if (self.isDragging && gameState === 'playing' && !self.isMoving) {
var deltaX = x - self.dragStartX;
var deltaY = y - self.dragStartY;
// Determine which direction has the larger movement
var absDeltaX = Math.abs(deltaX);
var absDeltaY = Math.abs(deltaY);
var targetX = self.startX;
var targetY = self.startY;
// Only move in the direction with the larger delta, and limit to one cell
if (absDeltaX > absDeltaY) {
// Horizontal movement
if (deltaX > CELL_SIZE / 2 && self.gridX < GRID_SIZE - 1) {
targetX = self.startX + CELL_SIZE;
} else if (deltaX < -CELL_SIZE / 2 && self.gridX > 0) {
targetX = self.startX - CELL_SIZE;
}
} else {
// Vertical movement
if (deltaY > CELL_SIZE / 2 && self.gridY < GRID_SIZE - 1) {
targetY = self.startY + CELL_SIZE;
} else if (deltaY < -CELL_SIZE / 2 && self.gridY > 0) {
targetY = self.startY - CELL_SIZE;
}
}
self.x = targetX;
self.y = targetY;
}
};
self.up = function (x, y, obj) {
if (gameState !== 'playing' || self.isMoving) return;
if (self.isDragging) {
self.isDragging = false;
// Find target grid position based on current visual position
var targetX = Math.round((self.x - GRID_START_X) / CELL_SIZE);
var targetY = Math.round((self.y - GRID_START_Y) / CELL_SIZE);
// Clamp to grid bounds
targetX = Math.max(0, Math.min(GRID_SIZE - 1, targetX));
targetY = Math.max(0, Math.min(GRID_SIZE - 1, targetY));
// If position changed, try to swap
if (targetX !== self.gridX || targetY !== self.gridY) {
var targetCandy = grid[targetX][targetY];
if (targetCandy && targetCandy !== self) {
// Store original positions
var originalSelfX = self.gridX;
var originalSelfY = self.gridY;
var originalTargetX = targetCandy.gridX;
var originalTargetY = targetCandy.gridY;
// Temporarily swap candies in grid to check for matches
grid[self.gridX][self.gridY] = targetCandy;
grid[targetX][targetY] = self;
var tempGridX = self.gridX;
var tempGridY = self.gridY;
self.gridX = targetX;
self.gridY = targetY;
targetCandy.gridX = tempGridX;
targetCandy.gridY = tempGridY;
// Check if this swap creates any matches
var selfMatches = findMatchesAt(self.gridX, self.gridY, self.candyType);
var targetMatches = findMatchesAt(targetCandy.gridX, targetCandy.gridY, targetCandy.candyType);
if (selfMatches.length >= 3 || targetMatches.length >= 3) {
// Valid move - animate both candies to their new positions
var targetX1 = GRID_START_X + self.gridX * CELL_SIZE;
var targetY1 = GRID_START_Y + self.gridY * CELL_SIZE;
var targetX2 = GRID_START_X + targetCandy.gridX * CELL_SIZE;
var targetY2 = GRID_START_Y + targetCandy.gridY * CELL_SIZE;
self.animateToPosition(targetX1, targetY1, function () {
checkForMatches();
});
targetCandy.animateToPosition(targetX2, targetY2);
} else {
// Invalid move - revert grid positions and return both candies to original positions
self.gridX = originalSelfX;
self.gridY = originalSelfY;
targetCandy.gridX = originalTargetX;
targetCandy.gridY = originalTargetY;
grid[originalSelfX][originalSelfY] = self;
grid[originalTargetX][originalTargetY] = targetCandy;
// Return both candies to original positions
tween(self, {
x: GRID_START_X + self.gridX * CELL_SIZE,
y: GRID_START_Y + self.gridY * CELL_SIZE
}, {
duration: 300,
easing: tween.easeOut
});
tween(targetCandy, {
x: GRID_START_X + targetCandy.gridX * CELL_SIZE,
y: GRID_START_Y + targetCandy.gridY * CELL_SIZE
}, {
duration: 300,
easing: tween.easeOut
});
}
} else {
// Return to original position
tween(self, {
x: GRID_START_X + self.gridX * CELL_SIZE,
y: GRID_START_Y + self.gridY * CELL_SIZE
}, {
duration: 300,
easing: tween.easeOut
});
}
} else {
// Return to original position
tween(self, {
x: GRID_START_X + self.gridX * CELL_SIZE,
y: GRID_START_Y + self.gridY * CELL_SIZE
}, {
duration: 300,
easing: tween.easeOut
});
}
self.scaleX = 1;
self.scaleY = 1;
selectedCandy = null;
} else if (selectedCandy === self) {
self.scaleX = 1;
self.scaleY = 1;
selectedCandy = null;
} else if (selectedCandy && isAdjacent(selectedCandy, self)) {
attemptSwap(selectedCandy, self);
selectedCandy.scaleX = 1;
selectedCandy.scaleY = 1;
selectedCandy = null;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game();
/****
* Game Code
****/
// Bright Red
// Bright Green
// Bright Blue
// Bright Yellow
// Bright Magenta
// Bright Cyan
var GRID_SIZE = 12;
var CELL_SIZE = 140;
var GRID_START_X = (2048 - GRID_SIZE * CELL_SIZE) / 2;
var GRID_START_Y = (2732 - GRID_SIZE * CELL_SIZE) / 2;
var CANDY_TYPES = 6;
var grid = [];
var selectedCandy = null;
var gameState = 'playing';
var score = 0;
var moves = 30;
var targetScore = 1000;
// Add minimum score requirement text
var minScoreText = new Text2('Min 4000', {
size: 60,
fill: 0xFFD700
});
minScoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(minScoreText);
minScoreText.y = 50;
// Add score display
var scoreText = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
scoreText.y = 130;
// Add timer display
var timeRemaining = 180; // 3 minutes in seconds
var timerText = new Text2('Time: 3:00', {
size: 80,
fill: 0xFFFFFF
});
timerText.anchor.set(0.5, 0);
LK.gui.top.addChild(timerText);
timerText.y = 230;
function initializeGrid() {
grid = [];
for (var x = 0; x < GRID_SIZE; x++) {
grid[x] = [];
for (var y = 0; y < GRID_SIZE; y++) {
var candyType = Math.floor(Math.random() * CANDY_TYPES) + 1;
var candy = new Candy(candyType);
candy.setGridPosition(x, y);
grid[x][y] = candy;
game.addChild(candy);
}
}
// Remove initial matches
removeInitialMatches();
}
function removeInitialMatches() {
var hasMatches = true;
var iterations = 0;
while (hasMatches && iterations < 10) {
hasMatches = false;
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (wouldCreateMatch(x, y, grid[x][y].candyType)) {
var newType = Math.floor(Math.random() * CANDY_TYPES) + 1;
grid[x][y].destroy();
var candy = new Candy(newType);
candy.setGridPosition(x, y);
grid[x][y] = candy;
game.addChild(candy);
hasMatches = true;
}
}
}
iterations++;
}
}
function wouldCreateMatch(x, y, type) {
// Check horizontal matches
var horizontalCount = 1;
var left = x - 1;
while (left >= 0 && grid[left][y] && grid[left][y].candyType === type) {
horizontalCount++;
left--;
}
var right = x + 1;
while (right < GRID_SIZE && grid[right][y] && grid[right][y].candyType === type) {
horizontalCount++;
right++;
}
// Check vertical matches
var verticalCount = 1;
var up = y - 1;
while (up >= 0 && grid[x][up] && grid[x][up].candyType === type) {
verticalCount++;
up--;
}
var down = y + 1;
while (down < GRID_SIZE && grid[x][down] && grid[x][down].candyType === type) {
verticalCount++;
down++;
}
return horizontalCount >= 3 || verticalCount >= 3;
}
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;
}
function attemptSwap(candy1, candy2) {
if (moves <= 0) return;
// Temporarily swap positions
var tempX = candy1.gridX;
var tempY = candy1.gridY;
candy1.gridX = candy2.gridX;
candy1.gridY = candy2.gridY;
candy2.gridX = tempX;
candy2.gridY = tempY;
grid[candy1.gridX][candy1.gridY] = candy1;
grid[candy2.gridX][candy2.gridY] = candy2;
// Check if this creates matches
var matches1 = findMatches(candy1.gridX, candy1.gridY);
var matches2 = findMatches(candy2.gridX, candy2.gridY);
if (matches1.length > 0 || matches2.length > 0) {
// Valid swap - animate
moves--;
// Moves text removed
var targetX1 = GRID_START_X + candy1.gridX * CELL_SIZE;
var targetY1 = GRID_START_Y + candy1.gridY * CELL_SIZE;
var targetX2 = GRID_START_X + candy2.gridX * CELL_SIZE;
var targetY2 = GRID_START_Y + candy2.gridY * CELL_SIZE;
candy1.animateToPosition(targetX1, targetY1);
candy2.animateToPosition(targetX2, targetY2, function () {
LK.getSound('swap').play();
processMatches();
});
} else {
// Invalid swap - revert
candy1.gridX = tempX;
candy1.gridY = tempY;
candy2.gridX = candy2.gridX;
candy2.gridY = candy2.gridY;
grid[candy1.gridX][candy1.gridY] = candy1;
grid[candy2.gridX][candy2.gridY] = candy2;
}
}
function findMatches(x, y) {
var matches = [];
var candy = grid[x][y];
if (!candy) return matches;
var type = candy.candyType;
// Check horizontal matches
var horizontal = [candy];
var left = x - 1;
while (left >= 0 && grid[left][y] && grid[left][y].candyType === type) {
horizontal.unshift(grid[left][y]);
left--;
}
var right = x + 1;
while (right < GRID_SIZE && grid[right][y] && grid[right][y].candyType === type) {
horizontal.push(grid[right][y]);
right++;
}
if (horizontal.length >= 3) {
matches = matches.concat(horizontal);
}
// Check vertical matches
var vertical = [candy];
var up = y - 1;
while (up >= 0 && grid[x][up] && grid[x][up].candyType === type) {
vertical.unshift(grid[x][up]);
up--;
}
var down = y + 1;
while (down < GRID_SIZE && grid[x][down] && grid[x][down].candyType === type) {
vertical.push(grid[x][down]);
down++;
}
if (vertical.length >= 3) {
matches = matches.concat(vertical);
}
return matches;
}
function processMatches() {
var allMatches = [];
var specialCandyPositions = [];
var processedCandies = [];
// Find all matches on the grid
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[x][y] && !grid[x][y].isMatched) {
// Check horizontal matches (3+ in a row)
var horizontalMatches = [];
var currentType = grid[x][y].candyType;
var startX = x;
// Find consecutive candies of same type horizontally
while (startX < GRID_SIZE && grid[startX][y] && grid[startX][y].candyType === currentType) {
horizontalMatches.push(grid[startX][y]);
startX++;
}
// If we found 4 or more consecutive horizontal matches, mark for special candy
if (horizontalMatches.length >= 4) {
// Create special candy at the center of the match
var centerX = Math.floor(x + (horizontalMatches.length - 1) / 2);
specialCandyPositions.push({
x: centerX,
y: y,
type: currentType
});
}
// If we found 3 or more consecutive horizontal matches
if (horizontalMatches.length >= 3) {
for (var i = 0; i < horizontalMatches.length; i++) {
if (allMatches.indexOf(horizontalMatches[i]) === -1) {
allMatches.push(horizontalMatches[i]);
}
}
}
// Check vertical matches (3+ in a column)
var verticalMatches = [];
var startY = y;
// Find consecutive candies of same type vertically
while (startY < GRID_SIZE && grid[x][startY] && grid[x][startY].candyType === currentType) {
verticalMatches.push(grid[x][startY]);
startY++;
}
// If we found 4 or more consecutive vertical matches, mark for special candy
if (verticalMatches.length >= 4) {
// Create special candy at the center of the match
var centerY = Math.floor(y + (verticalMatches.length - 1) / 2);
specialCandyPositions.push({
x: x,
y: centerY,
type: currentType
});
}
// If we found 3 or more consecutive vertical matches
if (verticalMatches.length >= 3) {
for (var i = 0; i < verticalMatches.length; i++) {
if (allMatches.indexOf(verticalMatches[i]) === -1) {
allMatches.push(verticalMatches[i]);
}
}
}
}
}
}
if (allMatches.length > 0) {
// Mark matches for destruction and update score
for (var i = 0; i < allMatches.length; i++) {
var candy = allMatches[i];
if (!candy.isSpecial) {
candy.markForDestroy();
grid[candy.gridX][candy.gridY] = null;
score += 10;
}
}
// Create only one special candy from the first valid position
if (specialCandyPositions.length > 0) {
var pos = specialCandyPositions[0]; // Only use the first position
if (grid[pos.x][pos.y] === null) {
var specialCandy = new Candy(pos.type, true);
specialCandy.setGridPosition(pos.x, pos.y);
grid[pos.x][pos.y] = specialCandy;
game.addChild(specialCandy);
// Add sparkle effect to special candy
tween(specialCandy, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(specialCandy, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
});
}
}
scoreText.setText('Score: ' + score);
LK.getSound('match').play();
// Drop candies after delay
LK.setTimeout(function () {
dropCandies();
}, 350);
}
}
function dropCandies() {
var hasDropped = false;
// Drop existing candies
for (var x = 0; x < GRID_SIZE; x++) {
var writeIndex = GRID_SIZE - 1;
for (var y = GRID_SIZE - 1; y >= 0; y--) {
if (grid[x][y] !== null) {
if (y !== writeIndex) {
grid[x][writeIndex] = grid[x][y];
grid[x][y] = null;
grid[x][writeIndex].gridX = x;
grid[x][writeIndex].gridY = writeIndex;
var targetX = GRID_START_X + x * CELL_SIZE;
var targetY = GRID_START_Y + writeIndex * CELL_SIZE;
grid[x][writeIndex].animateToPosition(targetX, targetY);
hasDropped = true;
}
writeIndex--;
}
}
// Fill empty spaces with new candies
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[x][y] === null) {
var candyType = Math.floor(Math.random() * CANDY_TYPES) + 1;
var candy = new Candy(candyType);
candy.gridX = x;
candy.gridY = y;
candy.x = GRID_START_X + x * CELL_SIZE;
candy.y = GRID_START_Y - (GRID_SIZE - y) * CELL_SIZE;
var targetY = GRID_START_Y + y * CELL_SIZE;
candy.animateToPosition(candy.x, targetY);
grid[x][y] = candy;
game.addChild(candy);
hasDropped = true;
}
}
}
if (hasDropped) {
LK.setTimeout(function () {
processMatches();
}, 250);
} else {
checkGameState();
}
}
function checkForMatches() {
var allMatches = [];
var specialCandyPositions = [];
// Check all positions for matches
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[x][y] && !grid[x][y].isMatched) {
var candy = grid[x][y];
var matches = findMatchesAt(x, y, candy.candyType);
// Check if this creates a 4-in-a-row for special candy
if (matches.length >= 4) {
// Check if it's horizontal or vertical match
var isHorizontal = false;
var isVertical = false;
for (var i = 1; i < matches.length; i++) {
if (matches[i].gridY === matches[0].gridY) isHorizontal = true;
if (matches[i].gridX === matches[0].gridX) isVertical = true;
}
if (isHorizontal || isVertical) {
specialCandyPositions.push({
x: x,
y: y,
type: candy.candyType
});
}
}
// Add unique matches to allMatches
for (var i = 0; i < matches.length; i++) {
if (allMatches.indexOf(matches[i]) === -1) {
allMatches.push(matches[i]);
}
}
}
}
}
if (allMatches.length > 0) {
// Mark matches for destruction and update score
for (var i = 0; i < allMatches.length; i++) {
var candy = allMatches[i];
if (!candy.isSpecial) {
candy.markForDestroy();
grid[candy.gridX][candy.gridY] = null;
score += 10;
}
}
// Create only one special candy from the first valid position
if (specialCandyPositions.length > 0) {
var pos = specialCandyPositions[0]; // Only use the first position
if (grid[pos.x][pos.y] === null) {
var specialCandy = new Candy(pos.type, true);
specialCandy.setGridPosition(pos.x, pos.y);
grid[pos.x][pos.y] = specialCandy;
game.addChild(specialCandy);
// Add sparkle effect to special candy
tween(specialCandy, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(specialCandy, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
});
}
}
scoreText.setText('Score: ' + score);
LK.getSound('match').play();
// Drop candies after delay
LK.setTimeout(function () {
dropCandies();
}, 350);
}
}
function findMatchesAt(x, y, candyType) {
var matches = [];
// Check horizontal matches (3+ in a row)
var horizontalMatches = [grid[x][y]];
// Check left
var left = x - 1;
while (left >= 0 && grid[left][y] && grid[left][y].candyType === candyType && !grid[left][y].isMatched) {
horizontalMatches.unshift(grid[left][y]);
left--;
}
// Check right
var right = x + 1;
while (right < GRID_SIZE && grid[right][y] && grid[right][y].candyType === candyType && !grid[right][y].isMatched) {
horizontalMatches.push(grid[right][y]);
right++;
}
if (horizontalMatches.length >= 3) {
matches = matches.concat(horizontalMatches);
}
// Check vertical matches (3+ in a column)
var verticalMatches = [grid[x][y]];
// Check up
var up = y - 1;
while (up >= 0 && grid[x][up] && grid[x][up].candyType === candyType && !grid[x][up].isMatched) {
verticalMatches.unshift(grid[x][up]);
up--;
}
// Check down
var down = y + 1;
while (down < GRID_SIZE && grid[x][down] && grid[x][down].candyType === candyType && !grid[x][down].isMatched) {
verticalMatches.push(grid[x][down]);
down++;
}
if (verticalMatches.length >= 3) {
// Remove duplicates if candy is part of both horizontal and vertical match
for (var i = 0; i < verticalMatches.length; i++) {
if (matches.indexOf(verticalMatches[i]) === -1) {
matches.push(verticalMatches[i]);
}
}
}
return matches;
}
function checkGameState() {
// Win condition removed - only check for timer-based win/lose at timer end
if (moves <= 0) {
gameState = 'lost';
LK.showGameOver();
}
}
// Timer countdown
var timerInterval = LK.setInterval(function () {
if (gameState === 'playing' && timeRemaining > 0) {
timeRemaining--;
var minutes = Math.floor(timeRemaining / 60);
var seconds = timeRemaining % 60;
var timeString = minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
timerText.setText('Time: ' + timeString);
}
if (timeRemaining <= 0 && gameState === 'playing') {
if (score >= 4000) {
gameState = 'won';
LK.showYouWin();
} else {
gameState = 'lost';
LK.showGameOver();
}
}
}, 1000);
// Initialize the game
initializeGrid();
game.update = function () {
// Game loop updates handled by individual object updates
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Candy = Container.expand(function (type, isSpecial) {
var self = Container.call(this);
self.candyType = type;
self.isSpecial = isSpecial || false;
self.gridX = -1;
self.gridY = -1;
self.isMatched = false;
self.isMoving = false;
self.isDragging = false;
// Background removed - only candy graphics remain
var candyGraphic;
if (self.isSpecial) {
candyGraphic = self.attachAsset('specialCandy', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
candyGraphic = self.attachAsset('candy' + type, {
anchorX: 0.5,
anchorY: 0.5
});
}
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = GRID_START_X + gridX * CELL_SIZE;
self.y = GRID_START_Y + gridY * CELL_SIZE;
};
self.animateToPosition = function (targetX, targetY, onComplete) {
self.isMoving = true;
tween(self, {
x: targetX,
y: targetY
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
self.isMoving = false;
if (onComplete) onComplete();
}
});
};
self.markForDestroy = function () {
self.isMatched = true;
tween(self, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
self.destroy();
}
});
};
self.down = function (x, y, obj) {
if (gameState !== 'playing' || self.isMoving) return;
if (self.isSpecial) {
// Special candy clicked - clear 2x2 area around it
var centerX = self.gridX;
var centerY = self.gridY;
var candiesDestroyed = 0;
// Clear 2x2 area around the special candy (including the special candy itself)
for (var dx = -1; dx <= 1; dx++) {
for (var dy = -1; dy <= 1; dy++) {
var targetX = centerX + dx;
var targetY = centerY + dy;
if (targetX >= 0 && targetX < GRID_SIZE && targetY >= 0 && targetY < GRID_SIZE) {
var targetCandy = grid[targetX][targetY];
if (targetCandy && !targetCandy.isMatched) {
targetCandy.markForDestroy();
grid[targetX][targetY] = null;
candiesDestroyed++;
score += 15; // Bonus points for special candy destruction
}
}
}
}
scoreText.setText('Score: ' + score);
LK.getSound('specialCandy').play();
// Drop candies after delay
LK.setTimeout(function () {
dropCandies();
}, 350);
return; // Exit early for special candies
}
selectedCandy = self;
self.scaleX = 1.1;
self.scaleY = 1.1;
self.isDragging = true;
self.dragStartX = x;
self.dragStartY = y;
self.startX = self.x;
self.startY = self.y;
};
self.move = function (x, y, obj) {
if (self.isDragging && gameState === 'playing' && !self.isMoving) {
var deltaX = x - self.dragStartX;
var deltaY = y - self.dragStartY;
// Determine which direction has the larger movement
var absDeltaX = Math.abs(deltaX);
var absDeltaY = Math.abs(deltaY);
var targetX = self.startX;
var targetY = self.startY;
// Only move in the direction with the larger delta, and limit to one cell
if (absDeltaX > absDeltaY) {
// Horizontal movement
if (deltaX > CELL_SIZE / 2 && self.gridX < GRID_SIZE - 1) {
targetX = self.startX + CELL_SIZE;
} else if (deltaX < -CELL_SIZE / 2 && self.gridX > 0) {
targetX = self.startX - CELL_SIZE;
}
} else {
// Vertical movement
if (deltaY > CELL_SIZE / 2 && self.gridY < GRID_SIZE - 1) {
targetY = self.startY + CELL_SIZE;
} else if (deltaY < -CELL_SIZE / 2 && self.gridY > 0) {
targetY = self.startY - CELL_SIZE;
}
}
self.x = targetX;
self.y = targetY;
}
};
self.up = function (x, y, obj) {
if (gameState !== 'playing' || self.isMoving) return;
if (self.isDragging) {
self.isDragging = false;
// Find target grid position based on current visual position
var targetX = Math.round((self.x - GRID_START_X) / CELL_SIZE);
var targetY = Math.round((self.y - GRID_START_Y) / CELL_SIZE);
// Clamp to grid bounds
targetX = Math.max(0, Math.min(GRID_SIZE - 1, targetX));
targetY = Math.max(0, Math.min(GRID_SIZE - 1, targetY));
// If position changed, try to swap
if (targetX !== self.gridX || targetY !== self.gridY) {
var targetCandy = grid[targetX][targetY];
if (targetCandy && targetCandy !== self) {
// Store original positions
var originalSelfX = self.gridX;
var originalSelfY = self.gridY;
var originalTargetX = targetCandy.gridX;
var originalTargetY = targetCandy.gridY;
// Temporarily swap candies in grid to check for matches
grid[self.gridX][self.gridY] = targetCandy;
grid[targetX][targetY] = self;
var tempGridX = self.gridX;
var tempGridY = self.gridY;
self.gridX = targetX;
self.gridY = targetY;
targetCandy.gridX = tempGridX;
targetCandy.gridY = tempGridY;
// Check if this swap creates any matches
var selfMatches = findMatchesAt(self.gridX, self.gridY, self.candyType);
var targetMatches = findMatchesAt(targetCandy.gridX, targetCandy.gridY, targetCandy.candyType);
if (selfMatches.length >= 3 || targetMatches.length >= 3) {
// Valid move - animate both candies to their new positions
var targetX1 = GRID_START_X + self.gridX * CELL_SIZE;
var targetY1 = GRID_START_Y + self.gridY * CELL_SIZE;
var targetX2 = GRID_START_X + targetCandy.gridX * CELL_SIZE;
var targetY2 = GRID_START_Y + targetCandy.gridY * CELL_SIZE;
self.animateToPosition(targetX1, targetY1, function () {
checkForMatches();
});
targetCandy.animateToPosition(targetX2, targetY2);
} else {
// Invalid move - revert grid positions and return both candies to original positions
self.gridX = originalSelfX;
self.gridY = originalSelfY;
targetCandy.gridX = originalTargetX;
targetCandy.gridY = originalTargetY;
grid[originalSelfX][originalSelfY] = self;
grid[originalTargetX][originalTargetY] = targetCandy;
// Return both candies to original positions
tween(self, {
x: GRID_START_X + self.gridX * CELL_SIZE,
y: GRID_START_Y + self.gridY * CELL_SIZE
}, {
duration: 300,
easing: tween.easeOut
});
tween(targetCandy, {
x: GRID_START_X + targetCandy.gridX * CELL_SIZE,
y: GRID_START_Y + targetCandy.gridY * CELL_SIZE
}, {
duration: 300,
easing: tween.easeOut
});
}
} else {
// Return to original position
tween(self, {
x: GRID_START_X + self.gridX * CELL_SIZE,
y: GRID_START_Y + self.gridY * CELL_SIZE
}, {
duration: 300,
easing: tween.easeOut
});
}
} else {
// Return to original position
tween(self, {
x: GRID_START_X + self.gridX * CELL_SIZE,
y: GRID_START_Y + self.gridY * CELL_SIZE
}, {
duration: 300,
easing: tween.easeOut
});
}
self.scaleX = 1;
self.scaleY = 1;
selectedCandy = null;
} else if (selectedCandy === self) {
self.scaleX = 1;
self.scaleY = 1;
selectedCandy = null;
} else if (selectedCandy && isAdjacent(selectedCandy, self)) {
attemptSwap(selectedCandy, self);
selectedCandy.scaleX = 1;
selectedCandy.scaleY = 1;
selectedCandy = null;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game();
/****
* Game Code
****/
// Bright Red
// Bright Green
// Bright Blue
// Bright Yellow
// Bright Magenta
// Bright Cyan
var GRID_SIZE = 12;
var CELL_SIZE = 140;
var GRID_START_X = (2048 - GRID_SIZE * CELL_SIZE) / 2;
var GRID_START_Y = (2732 - GRID_SIZE * CELL_SIZE) / 2;
var CANDY_TYPES = 6;
var grid = [];
var selectedCandy = null;
var gameState = 'playing';
var score = 0;
var moves = 30;
var targetScore = 1000;
// Add minimum score requirement text
var minScoreText = new Text2('Min 4000', {
size: 60,
fill: 0xFFD700
});
minScoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(minScoreText);
minScoreText.y = 50;
// Add score display
var scoreText = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
scoreText.y = 130;
// Add timer display
var timeRemaining = 180; // 3 minutes in seconds
var timerText = new Text2('Time: 3:00', {
size: 80,
fill: 0xFFFFFF
});
timerText.anchor.set(0.5, 0);
LK.gui.top.addChild(timerText);
timerText.y = 230;
function initializeGrid() {
grid = [];
for (var x = 0; x < GRID_SIZE; x++) {
grid[x] = [];
for (var y = 0; y < GRID_SIZE; y++) {
var candyType = Math.floor(Math.random() * CANDY_TYPES) + 1;
var candy = new Candy(candyType);
candy.setGridPosition(x, y);
grid[x][y] = candy;
game.addChild(candy);
}
}
// Remove initial matches
removeInitialMatches();
}
function removeInitialMatches() {
var hasMatches = true;
var iterations = 0;
while (hasMatches && iterations < 10) {
hasMatches = false;
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (wouldCreateMatch(x, y, grid[x][y].candyType)) {
var newType = Math.floor(Math.random() * CANDY_TYPES) + 1;
grid[x][y].destroy();
var candy = new Candy(newType);
candy.setGridPosition(x, y);
grid[x][y] = candy;
game.addChild(candy);
hasMatches = true;
}
}
}
iterations++;
}
}
function wouldCreateMatch(x, y, type) {
// Check horizontal matches
var horizontalCount = 1;
var left = x - 1;
while (left >= 0 && grid[left][y] && grid[left][y].candyType === type) {
horizontalCount++;
left--;
}
var right = x + 1;
while (right < GRID_SIZE && grid[right][y] && grid[right][y].candyType === type) {
horizontalCount++;
right++;
}
// Check vertical matches
var verticalCount = 1;
var up = y - 1;
while (up >= 0 && grid[x][up] && grid[x][up].candyType === type) {
verticalCount++;
up--;
}
var down = y + 1;
while (down < GRID_SIZE && grid[x][down] && grid[x][down].candyType === type) {
verticalCount++;
down++;
}
return horizontalCount >= 3 || verticalCount >= 3;
}
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;
}
function attemptSwap(candy1, candy2) {
if (moves <= 0) return;
// Temporarily swap positions
var tempX = candy1.gridX;
var tempY = candy1.gridY;
candy1.gridX = candy2.gridX;
candy1.gridY = candy2.gridY;
candy2.gridX = tempX;
candy2.gridY = tempY;
grid[candy1.gridX][candy1.gridY] = candy1;
grid[candy2.gridX][candy2.gridY] = candy2;
// Check if this creates matches
var matches1 = findMatches(candy1.gridX, candy1.gridY);
var matches2 = findMatches(candy2.gridX, candy2.gridY);
if (matches1.length > 0 || matches2.length > 0) {
// Valid swap - animate
moves--;
// Moves text removed
var targetX1 = GRID_START_X + candy1.gridX * CELL_SIZE;
var targetY1 = GRID_START_Y + candy1.gridY * CELL_SIZE;
var targetX2 = GRID_START_X + candy2.gridX * CELL_SIZE;
var targetY2 = GRID_START_Y + candy2.gridY * CELL_SIZE;
candy1.animateToPosition(targetX1, targetY1);
candy2.animateToPosition(targetX2, targetY2, function () {
LK.getSound('swap').play();
processMatches();
});
} else {
// Invalid swap - revert
candy1.gridX = tempX;
candy1.gridY = tempY;
candy2.gridX = candy2.gridX;
candy2.gridY = candy2.gridY;
grid[candy1.gridX][candy1.gridY] = candy1;
grid[candy2.gridX][candy2.gridY] = candy2;
}
}
function findMatches(x, y) {
var matches = [];
var candy = grid[x][y];
if (!candy) return matches;
var type = candy.candyType;
// Check horizontal matches
var horizontal = [candy];
var left = x - 1;
while (left >= 0 && grid[left][y] && grid[left][y].candyType === type) {
horizontal.unshift(grid[left][y]);
left--;
}
var right = x + 1;
while (right < GRID_SIZE && grid[right][y] && grid[right][y].candyType === type) {
horizontal.push(grid[right][y]);
right++;
}
if (horizontal.length >= 3) {
matches = matches.concat(horizontal);
}
// Check vertical matches
var vertical = [candy];
var up = y - 1;
while (up >= 0 && grid[x][up] && grid[x][up].candyType === type) {
vertical.unshift(grid[x][up]);
up--;
}
var down = y + 1;
while (down < GRID_SIZE && grid[x][down] && grid[x][down].candyType === type) {
vertical.push(grid[x][down]);
down++;
}
if (vertical.length >= 3) {
matches = matches.concat(vertical);
}
return matches;
}
function processMatches() {
var allMatches = [];
var specialCandyPositions = [];
var processedCandies = [];
// Find all matches on the grid
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[x][y] && !grid[x][y].isMatched) {
// Check horizontal matches (3+ in a row)
var horizontalMatches = [];
var currentType = grid[x][y].candyType;
var startX = x;
// Find consecutive candies of same type horizontally
while (startX < GRID_SIZE && grid[startX][y] && grid[startX][y].candyType === currentType) {
horizontalMatches.push(grid[startX][y]);
startX++;
}
// If we found 4 or more consecutive horizontal matches, mark for special candy
if (horizontalMatches.length >= 4) {
// Create special candy at the center of the match
var centerX = Math.floor(x + (horizontalMatches.length - 1) / 2);
specialCandyPositions.push({
x: centerX,
y: y,
type: currentType
});
}
// If we found 3 or more consecutive horizontal matches
if (horizontalMatches.length >= 3) {
for (var i = 0; i < horizontalMatches.length; i++) {
if (allMatches.indexOf(horizontalMatches[i]) === -1) {
allMatches.push(horizontalMatches[i]);
}
}
}
// Check vertical matches (3+ in a column)
var verticalMatches = [];
var startY = y;
// Find consecutive candies of same type vertically
while (startY < GRID_SIZE && grid[x][startY] && grid[x][startY].candyType === currentType) {
verticalMatches.push(grid[x][startY]);
startY++;
}
// If we found 4 or more consecutive vertical matches, mark for special candy
if (verticalMatches.length >= 4) {
// Create special candy at the center of the match
var centerY = Math.floor(y + (verticalMatches.length - 1) / 2);
specialCandyPositions.push({
x: x,
y: centerY,
type: currentType
});
}
// If we found 3 or more consecutive vertical matches
if (verticalMatches.length >= 3) {
for (var i = 0; i < verticalMatches.length; i++) {
if (allMatches.indexOf(verticalMatches[i]) === -1) {
allMatches.push(verticalMatches[i]);
}
}
}
}
}
}
if (allMatches.length > 0) {
// Mark matches for destruction and update score
for (var i = 0; i < allMatches.length; i++) {
var candy = allMatches[i];
if (!candy.isSpecial) {
candy.markForDestroy();
grid[candy.gridX][candy.gridY] = null;
score += 10;
}
}
// Create only one special candy from the first valid position
if (specialCandyPositions.length > 0) {
var pos = specialCandyPositions[0]; // Only use the first position
if (grid[pos.x][pos.y] === null) {
var specialCandy = new Candy(pos.type, true);
specialCandy.setGridPosition(pos.x, pos.y);
grid[pos.x][pos.y] = specialCandy;
game.addChild(specialCandy);
// Add sparkle effect to special candy
tween(specialCandy, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(specialCandy, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
});
}
}
scoreText.setText('Score: ' + score);
LK.getSound('match').play();
// Drop candies after delay
LK.setTimeout(function () {
dropCandies();
}, 350);
}
}
function dropCandies() {
var hasDropped = false;
// Drop existing candies
for (var x = 0; x < GRID_SIZE; x++) {
var writeIndex = GRID_SIZE - 1;
for (var y = GRID_SIZE - 1; y >= 0; y--) {
if (grid[x][y] !== null) {
if (y !== writeIndex) {
grid[x][writeIndex] = grid[x][y];
grid[x][y] = null;
grid[x][writeIndex].gridX = x;
grid[x][writeIndex].gridY = writeIndex;
var targetX = GRID_START_X + x * CELL_SIZE;
var targetY = GRID_START_Y + writeIndex * CELL_SIZE;
grid[x][writeIndex].animateToPosition(targetX, targetY);
hasDropped = true;
}
writeIndex--;
}
}
// Fill empty spaces with new candies
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[x][y] === null) {
var candyType = Math.floor(Math.random() * CANDY_TYPES) + 1;
var candy = new Candy(candyType);
candy.gridX = x;
candy.gridY = y;
candy.x = GRID_START_X + x * CELL_SIZE;
candy.y = GRID_START_Y - (GRID_SIZE - y) * CELL_SIZE;
var targetY = GRID_START_Y + y * CELL_SIZE;
candy.animateToPosition(candy.x, targetY);
grid[x][y] = candy;
game.addChild(candy);
hasDropped = true;
}
}
}
if (hasDropped) {
LK.setTimeout(function () {
processMatches();
}, 250);
} else {
checkGameState();
}
}
function checkForMatches() {
var allMatches = [];
var specialCandyPositions = [];
// Check all positions for matches
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[x][y] && !grid[x][y].isMatched) {
var candy = grid[x][y];
var matches = findMatchesAt(x, y, candy.candyType);
// Check if this creates a 4-in-a-row for special candy
if (matches.length >= 4) {
// Check if it's horizontal or vertical match
var isHorizontal = false;
var isVertical = false;
for (var i = 1; i < matches.length; i++) {
if (matches[i].gridY === matches[0].gridY) isHorizontal = true;
if (matches[i].gridX === matches[0].gridX) isVertical = true;
}
if (isHorizontal || isVertical) {
specialCandyPositions.push({
x: x,
y: y,
type: candy.candyType
});
}
}
// Add unique matches to allMatches
for (var i = 0; i < matches.length; i++) {
if (allMatches.indexOf(matches[i]) === -1) {
allMatches.push(matches[i]);
}
}
}
}
}
if (allMatches.length > 0) {
// Mark matches for destruction and update score
for (var i = 0; i < allMatches.length; i++) {
var candy = allMatches[i];
if (!candy.isSpecial) {
candy.markForDestroy();
grid[candy.gridX][candy.gridY] = null;
score += 10;
}
}
// Create only one special candy from the first valid position
if (specialCandyPositions.length > 0) {
var pos = specialCandyPositions[0]; // Only use the first position
if (grid[pos.x][pos.y] === null) {
var specialCandy = new Candy(pos.type, true);
specialCandy.setGridPosition(pos.x, pos.y);
grid[pos.x][pos.y] = specialCandy;
game.addChild(specialCandy);
// Add sparkle effect to special candy
tween(specialCandy, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(specialCandy, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
});
}
}
scoreText.setText('Score: ' + score);
LK.getSound('match').play();
// Drop candies after delay
LK.setTimeout(function () {
dropCandies();
}, 350);
}
}
function findMatchesAt(x, y, candyType) {
var matches = [];
// Check horizontal matches (3+ in a row)
var horizontalMatches = [grid[x][y]];
// Check left
var left = x - 1;
while (left >= 0 && grid[left][y] && grid[left][y].candyType === candyType && !grid[left][y].isMatched) {
horizontalMatches.unshift(grid[left][y]);
left--;
}
// Check right
var right = x + 1;
while (right < GRID_SIZE && grid[right][y] && grid[right][y].candyType === candyType && !grid[right][y].isMatched) {
horizontalMatches.push(grid[right][y]);
right++;
}
if (horizontalMatches.length >= 3) {
matches = matches.concat(horizontalMatches);
}
// Check vertical matches (3+ in a column)
var verticalMatches = [grid[x][y]];
// Check up
var up = y - 1;
while (up >= 0 && grid[x][up] && grid[x][up].candyType === candyType && !grid[x][up].isMatched) {
verticalMatches.unshift(grid[x][up]);
up--;
}
// Check down
var down = y + 1;
while (down < GRID_SIZE && grid[x][down] && grid[x][down].candyType === candyType && !grid[x][down].isMatched) {
verticalMatches.push(grid[x][down]);
down++;
}
if (verticalMatches.length >= 3) {
// Remove duplicates if candy is part of both horizontal and vertical match
for (var i = 0; i < verticalMatches.length; i++) {
if (matches.indexOf(verticalMatches[i]) === -1) {
matches.push(verticalMatches[i]);
}
}
}
return matches;
}
function checkGameState() {
// Win condition removed - only check for timer-based win/lose at timer end
if (moves <= 0) {
gameState = 'lost';
LK.showGameOver();
}
}
// Timer countdown
var timerInterval = LK.setInterval(function () {
if (gameState === 'playing' && timeRemaining > 0) {
timeRemaining--;
var minutes = Math.floor(timeRemaining / 60);
var seconds = timeRemaining % 60;
var timeString = minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
timerText.setText('Time: ' + timeString);
}
if (timeRemaining <= 0 && gameState === 'playing') {
if (score >= 4000) {
gameState = 'won';
LK.showYouWin();
} else {
gameState = 'lost';
LK.showGameOver();
}
}
}, 1000);
// Initialize the game
initializeGrid();
game.update = function () {
// Game loop updates handled by individual object updates
};
Kırmızı 3d top. In-Game asset. 2d. High contrast. No shadows
Yeşil 3d top. In-Game asset. 2d. High contrast. No shadows
Mavi 3d yuvarlak top. In-Game asset. 2d. High contrast. No shadows
Sarı 2d top. In-Game asset. 2d. High contrast. No shadows
3d pembe daire. In-Game asset. 2d. High contrast. No shadows
Beyaz 3d daire. In-Game asset. 2d. High contrast. No shadows
Şeker. In-Game asset. 2d. High contrast. No shadows