/**** * 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