Code edit (1 edits merged)
Please save this source code
User prompt
Candy Match Mania
Initial prompt
Create a Candy Crush-style match-3 puzzle game with a vibrant candy-themed design. The game should feature a grid of colorful candies (e.g., striped, wrapped, bomb, and rainbow variants) that players swap horizontally or vertically to match 3 or more of the same type. Include mechanics like level progression with increasing difficulty, unique objectives (e.g., clear jelly, collect specific candies, reach a target score), and obstacles (e.g., chocolate blockers, ice barriers, timed bombs). Add power-ups generated by matching 4+ candies (e.g., striped candy blasts, color bombs that clear all candies of a chosen color). Implement a lives system, daily rewards, and a boosters shop using in-game currency. Ensure smooth animations for candy swaps, matches, and explosions, along with cheerful sound effects and background music. The game must be mobile-responsive with touch controls and include a level editor for future content updates
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Candy class var Candy = Container.expand(function () { var self = Container.call(this); // Properties self.type = getRandomCandyType(); self.special = SPECIAL_NONE; self.blocker = BLOCKER_NONE; self.row = 0; self.col = 0; self.isFalling = false; self.isMatched = false; self.isSelected = false; self.asset = null; // Attach asset function updateAsset() { if (self.asset) { self.removeChild(self.asset); self.asset.destroy(); } var assetId = self.type; if (self.special === SPECIAL_STRIPED) assetId = 'candy_striped'; if (self.special === SPECIAL_BOMB) assetId = 'candy_bomb'; if (self.special === SPECIAL_RAINBOW) assetId = 'candy_rainbow'; if (self.blocker === BLOCKER_CHOCOLATE) assetId = 'blocker_chocolate'; if (self.blocker === BLOCKER_ICE) assetId = 'blocker_ice'; self.asset = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); if (self.isSelected) { self.asset.scaleX = 1.15; self.asset.scaleY = 1.15; } else { self.asset.scaleX = 1; self.asset.scaleY = 1; } } // Set type self.setType = function (type, special, blocker) { self.type = type || getRandomCandyType(); self.special = special || SPECIAL_NONE; self.blocker = blocker || BLOCKER_NONE; updateAsset(); }; // Set selected self.setSelected = function (selected) { self.isSelected = selected; updateAsset(); }; // Animate to position self.moveTo = function (x, y, duration, onFinish) { tween(self, { x: x, y: y }, { duration: duration || 200, easing: tween.easeInOut, onFinish: onFinish }); }; // Destroy self.destroyCandy = function () { if (self.asset) { self.removeChild(self.asset); self.asset.destroy(); self.asset = null; } self.destroy(); }; // Init updateAsset(); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222244 }); /**** * Game Code ****/ // Candy types // Music // Sounds // Blockers // Special candies // Candy shapes/colors // Board data var CANDY_TYPES = ['candy_red', 'candy_green', 'candy_blue', 'candy_yellow', 'candy_purple', 'candy_orange']; // Special types var SPECIAL_NONE = 0; var SPECIAL_STRIPED = 1; var SPECIAL_BOMB = 2; var SPECIAL_RAINBOW = 3; // Blocker types var BLOCKER_NONE = 0; var BLOCKER_CHOCOLATE = 1; var BLOCKER_ICE = 2; // Board size var BOARD_COLS = 7; var BOARD_ROWS = 9; var CELL_SIZE = 200; var BOARD_OFFSET_X = Math.floor((2048 - BOARD_COLS * CELL_SIZE) / 2); var BOARD_OFFSET_Y = 300; // Helper: get random candy type function getRandomCandyType() { return CANDY_TYPES[Math.floor(Math.random() * CANDY_TYPES.length)]; } // Helper: get random int function randInt(a, b) { return a + Math.floor(Math.random() * (b - a + 1)); } var board = []; var candies = []; var selectedCandy = null; var swapping = false; var animating = false; var movesLeft = 20; var targetScore = 5000; var score = 0; var scoreTxt = null; var movesTxt = null; var targetTxt = null; var boardContainer = null; var matchQueue = []; var refillQueue = []; var isProcessing = false; // GUI scoreTxt = new Text2('Score: 0', { size: 90, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); movesTxt = new Text2('Moves: 20', { size: 70, fill: "#fff" }); movesTxt.anchor.set(0.5, 0); LK.gui.top.addChild(movesTxt); movesTxt.y = 110; targetTxt = new Text2('Target: 5000', { size: 60, fill: "#fff" }); targetTxt.anchor.set(0.5, 0); LK.gui.top.addChild(targetTxt); targetTxt.y = 180; // Board container boardContainer = new Container(); game.addChild(boardContainer); boardContainer.x = BOARD_OFFSET_X; boardContainer.y = BOARD_OFFSET_Y; // Initialize board function initBoard() { // Clear previous for (var i = 0; i < candies.length; ++i) { if (candies[i]) candies[i].destroyCandy(); } candies = []; board = []; for (var row = 0; row < BOARD_ROWS; ++row) { board[row] = []; for (var col = 0; col < BOARD_COLS; ++col) { var candy = new Candy(); candy.row = row; candy.col = col; candy.x = col * CELL_SIZE + CELL_SIZE / 2; candy.y = row * CELL_SIZE + CELL_SIZE / 2; boardContainer.addChild(candy); candies.push(candy); board[row][col] = candy; } } // Remove initial matches removeInitialMatches(); } // Remove initial matches to avoid auto-matches at start function removeInitialMatches() { for (var row = 0; row < BOARD_ROWS; ++row) { for (var col = 0; col < BOARD_COLS; ++col) { var type = board[row][col].type; // Check left if (col >= 2 && board[row][col - 1].type === type && board[row][col - 2].type === type) { var newType = getRandomCandyType(); while (newType === type) newType = getRandomCandyType(); board[row][col].setType(newType); } // Check up if (row >= 2 && board[row - 1][col].type === type && board[row - 2][col].type === type) { var newType = getRandomCandyType(); while (newType === type) newType = getRandomCandyType(); board[row][col].setType(newType); } } } } // Get candy at board position function getCandyAt(row, col) { if (row < 0 || row >= BOARD_ROWS || col < 0 || col >= BOARD_COLS) return null; return board[row][col]; } // Swap two candies function swapCandies(c1, c2, cb) { swapping = true; var r1 = c1.row, c1c = c1.col, r2 = c2.row, c2c = c2.col; // Swap in board board[r1][c1c] = c2; board[r2][c2c] = c1; // Swap row/col var tmpRow = c1.row, tmpCol = c1.col; c1.row = c2.row; c1.col = c2.col; c2.row = tmpRow; c2.col = tmpCol; // Animate var done = 0; c1.moveTo(c1.col * CELL_SIZE + CELL_SIZE / 2, c1.row * CELL_SIZE + CELL_SIZE / 2, 180, function () { done++; if (done === 2 && cb) cb(); }); c2.moveTo(c2.col * CELL_SIZE + CELL_SIZE / 2, c2.row * CELL_SIZE + CELL_SIZE / 2, 180, function () { done++; if (done === 2 && cb) cb(); }); LK.getSound('swap').play(); } // Check if two candies are adjacent function areAdjacent(c1, c2) { var dr = Math.abs(c1.row - c2.row); var dc = Math.abs(c1.col - c2.col); return dr + dc === 1; } // Find all matches on the board function findMatches() { var matches = []; // Horizontal for (var row = 0; row < BOARD_ROWS; ++row) { var count = 1; for (var col = 1; col < BOARD_COLS; ++col) { var prev = board[row][col - 1]; var curr = board[row][col]; if (curr.type === prev.type && curr.blocker === BLOCKER_NONE && prev.blocker === BLOCKER_NONE) { count++; } else { if (count >= 3) { for (var k = 0; k < count; ++k) { matches.push(board[row][col - 1 - k]); } } count = 1; } } if (count >= 3) { for (var k = 0; k < count; ++k) { matches.push(board[row][BOARD_COLS - 1 - k]); } } } // Vertical for (var col = 0; col < BOARD_COLS; ++col) { var count = 1; for (var row = 1; row < BOARD_ROWS; ++row) { var prev = board[row - 1][col]; var curr = board[row][col]; if (curr.type === prev.type && curr.blocker === BLOCKER_NONE && prev.blocker === BLOCKER_NONE) { count++; } else { if (count >= 3) { for (var k = 0; k < count; ++k) { matches.push(board[row - 1 - k][col]); } } count = 1; } } if (count >= 3) { for (var k = 0; k < count; ++k) { matches.push(board[BOARD_ROWS - 1 - k][col]); } } } // Remove duplicates var unique = []; for (var i = 0; i < matches.length; ++i) { if (unique.indexOf(matches[i]) === -1) unique.push(matches[i]); } return unique; } // Remove matched candies and animate function removeMatches(matches, cb) { if (!matches || matches.length === 0) { if (cb) cb(); return; } LK.getSound('match').play(); var done = 0; for (var i = 0; i < matches.length; ++i) { var candy = matches[i]; candy.isMatched = true; // Animate scale down and fade tween(candy, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 200, easing: tween.easeIn, onFinish: function (candy) { return function () { if (candy.asset) { candy.removeChild(candy.asset); candy.asset.destroy(); candy.asset = null; } candy.visible = false; done++; if (done === matches.length && cb) cb(); }; }(candy) }); // Add score score += 100; } } // Drop candies to fill empty spaces function dropCandies(cb) { var moved = false; for (var col = 0; col < BOARD_COLS; ++col) { for (var row = BOARD_ROWS - 1; row >= 0; --row) { var candy = board[row][col]; if (!candy.isMatched && candy.visible) continue; // Find nearest above for (var above = row - 1; above >= 0; --above) { var aboveCandy = board[above][col]; if (!aboveCandy.isMatched && aboveCandy.visible) { // Move aboveCandy down board[row][col] = aboveCandy; aboveCandy.row = row; aboveCandy.col = col; aboveCandy.moveTo(col * CELL_SIZE + CELL_SIZE / 2, row * CELL_SIZE + CELL_SIZE / 2, 180); board[above][col] = candy; moved = true; break; } } } } if (cb) LK.setTimeout(cb, moved ? 200 : 0); } // Fill empty spaces with new candies function refillBoard(cb) { var created = false; for (var col = 0; col < BOARD_COLS; ++col) { for (var row = 0; row < BOARD_ROWS; ++row) { var candy = board[row][col]; if (!candy.isMatched && candy.visible) continue; // Create new candy var newCandy = new Candy(); newCandy.row = row; newCandy.col = col; newCandy.x = col * CELL_SIZE + CELL_SIZE / 2; newCandy.y = -CELL_SIZE + CELL_SIZE / 2; boardContainer.addChild(newCandy); candies.push(newCandy); board[row][col] = newCandy; newCandy.moveTo(col * CELL_SIZE + CELL_SIZE / 2, row * CELL_SIZE + CELL_SIZE / 2, 220); created = true; } } if (cb) LK.setTimeout(cb, created ? 220 : 0); } // Remove matched candies from board function clearMatchedCandies() { for (var row = 0; row < BOARD_ROWS; ++row) { for (var col = 0; col < BOARD_COLS; ++col) { var candy = board[row][col]; if (candy.isMatched) { candy.destroyCandy(); // Remove from candies array for (var i = 0; i < candies.length; ++i) { if (candies[i] === candy) { candies.splice(i, 1); break; } } // Replace with dummy invisible candy for drop logic var dummy = new Candy(); dummy.row = row; dummy.col = col; dummy.visible = false; board[row][col] = dummy; } } } } // Deselect all candies function deselectAll() { for (var i = 0; i < candies.length; ++i) { candies[i].setSelected(false); } selectedCandy = null; } // Handle user tap function handleTap(x, y, obj) { if (swapping || animating || isProcessing) return; // Convert to board coordinates var local = boardContainer.toLocal({ x: x, y: y }); var col = Math.floor(local.x / CELL_SIZE); var row = Math.floor(local.y / CELL_SIZE); if (col < 0 || col >= BOARD_COLS || row < 0 || row >= BOARD_ROWS) return; var candy = board[row][col]; if (!candy || candy.blocker !== BLOCKER_NONE) return; if (!selectedCandy) { selectedCandy = candy; candy.setSelected(true); } else if (selectedCandy === candy) { deselectAll(); } else if (areAdjacent(selectedCandy, candy)) { // Swap selectedCandy.setSelected(false); candy.setSelected(false); swapCandies(selectedCandy, candy, function () { // Check for matches var matches = findMatches(); if (matches.length > 0) { processMatches(); } else { // No match, swap back LK.getSound('fail').play(); swapCandies(selectedCandy, candy, function () { swapping = false; deselectAll(); }); } movesLeft--; updateGUI(); }); deselectAll(); } else { deselectAll(); selectedCandy = candy; candy.setSelected(true); } } // Process matches and refill function processMatches() { isProcessing = true; var matches = findMatches(); if (matches.length === 0) { swapping = false; isProcessing = false; checkGameEnd(); return; } removeMatches(matches, function () { clearMatchedCandies(); dropCandies(function () { refillBoard(function () { processMatches(); }); }); }); } // Update GUI function updateGUI() { scoreTxt.setText('Score: ' + score); movesTxt.setText('Moves: ' + movesLeft); targetTxt.setText('Target: ' + targetScore); } // Check for win/lose function checkGameEnd() { if (score >= targetScore) { LK.showYouWin(); } else if (movesLeft <= 0) { LK.showGameOver(); } } // Game event handlers game.down = function (x, y, obj) { // Don't allow tap in top left 100x100 if (x < 100 && y < 100) return; handleTap(x, y, obj); }; // No drag/move for now // Game update game.update = function () { // No per-frame logic needed for MVP }; // Start music LK.playMusic('bgmusic', { fade: { start: 0, end: 0.7, duration: 1000 } }); // Start game initBoard(); updateGUI();
===================================================================
--- original.js
+++ change.js
@@ -1,6 +1,518 @@
-/****
+/****
+* Plugins
+****/
+var tween = LK.import("@upit/tween.v1");
+
+/****
+* Classes
+****/
+// Candy class
+var Candy = Container.expand(function () {
+ var self = Container.call(this);
+ // Properties
+ self.type = getRandomCandyType();
+ self.special = SPECIAL_NONE;
+ self.blocker = BLOCKER_NONE;
+ self.row = 0;
+ self.col = 0;
+ self.isFalling = false;
+ self.isMatched = false;
+ self.isSelected = false;
+ self.asset = null;
+ // Attach asset
+ function updateAsset() {
+ if (self.asset) {
+ self.removeChild(self.asset);
+ self.asset.destroy();
+ }
+ var assetId = self.type;
+ if (self.special === SPECIAL_STRIPED) assetId = 'candy_striped';
+ if (self.special === SPECIAL_BOMB) assetId = 'candy_bomb';
+ if (self.special === SPECIAL_RAINBOW) assetId = 'candy_rainbow';
+ if (self.blocker === BLOCKER_CHOCOLATE) assetId = 'blocker_chocolate';
+ if (self.blocker === BLOCKER_ICE) assetId = 'blocker_ice';
+ self.asset = self.attachAsset(assetId, {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ if (self.isSelected) {
+ self.asset.scaleX = 1.15;
+ self.asset.scaleY = 1.15;
+ } else {
+ self.asset.scaleX = 1;
+ self.asset.scaleY = 1;
+ }
+ }
+ // Set type
+ self.setType = function (type, special, blocker) {
+ self.type = type || getRandomCandyType();
+ self.special = special || SPECIAL_NONE;
+ self.blocker = blocker || BLOCKER_NONE;
+ updateAsset();
+ };
+ // Set selected
+ self.setSelected = function (selected) {
+ self.isSelected = selected;
+ updateAsset();
+ };
+ // Animate to position
+ self.moveTo = function (x, y, duration, onFinish) {
+ tween(self, {
+ x: x,
+ y: y
+ }, {
+ duration: duration || 200,
+ easing: tween.easeInOut,
+ onFinish: onFinish
+ });
+ };
+ // Destroy
+ self.destroyCandy = function () {
+ if (self.asset) {
+ self.removeChild(self.asset);
+ self.asset.destroy();
+ self.asset = null;
+ }
+ self.destroy();
+ };
+ // Init
+ updateAsset();
+ return self;
+});
+
+/****
* Initialize Game
-****/
+****/
var game = new LK.Game({
- backgroundColor: 0x000000
-});
\ No newline at end of file
+ backgroundColor: 0x222244
+});
+
+/****
+* Game Code
+****/
+// Candy types
+// Music
+// Sounds
+// Blockers
+// Special candies
+// Candy shapes/colors
+// Board data
+var CANDY_TYPES = ['candy_red', 'candy_green', 'candy_blue', 'candy_yellow', 'candy_purple', 'candy_orange'];
+// Special types
+var SPECIAL_NONE = 0;
+var SPECIAL_STRIPED = 1;
+var SPECIAL_BOMB = 2;
+var SPECIAL_RAINBOW = 3;
+// Blocker types
+var BLOCKER_NONE = 0;
+var BLOCKER_CHOCOLATE = 1;
+var BLOCKER_ICE = 2;
+// Board size
+var BOARD_COLS = 7;
+var BOARD_ROWS = 9;
+var CELL_SIZE = 200;
+var BOARD_OFFSET_X = Math.floor((2048 - BOARD_COLS * CELL_SIZE) / 2);
+var BOARD_OFFSET_Y = 300;
+// Helper: get random candy type
+function getRandomCandyType() {
+ return CANDY_TYPES[Math.floor(Math.random() * CANDY_TYPES.length)];
+}
+// Helper: get random int
+function randInt(a, b) {
+ return a + Math.floor(Math.random() * (b - a + 1));
+}
+var board = [];
+var candies = [];
+var selectedCandy = null;
+var swapping = false;
+var animating = false;
+var movesLeft = 20;
+var targetScore = 5000;
+var score = 0;
+var scoreTxt = null;
+var movesTxt = null;
+var targetTxt = null;
+var boardContainer = null;
+var matchQueue = [];
+var refillQueue = [];
+var isProcessing = false;
+// GUI
+scoreTxt = new Text2('Score: 0', {
+ size: 90,
+ fill: "#fff"
+});
+scoreTxt.anchor.set(0.5, 0);
+LK.gui.top.addChild(scoreTxt);
+movesTxt = new Text2('Moves: 20', {
+ size: 70,
+ fill: "#fff"
+});
+movesTxt.anchor.set(0.5, 0);
+LK.gui.top.addChild(movesTxt);
+movesTxt.y = 110;
+targetTxt = new Text2('Target: 5000', {
+ size: 60,
+ fill: "#fff"
+});
+targetTxt.anchor.set(0.5, 0);
+LK.gui.top.addChild(targetTxt);
+targetTxt.y = 180;
+// Board container
+boardContainer = new Container();
+game.addChild(boardContainer);
+boardContainer.x = BOARD_OFFSET_X;
+boardContainer.y = BOARD_OFFSET_Y;
+// Initialize board
+function initBoard() {
+ // Clear previous
+ for (var i = 0; i < candies.length; ++i) {
+ if (candies[i]) candies[i].destroyCandy();
+ }
+ candies = [];
+ board = [];
+ for (var row = 0; row < BOARD_ROWS; ++row) {
+ board[row] = [];
+ for (var col = 0; col < BOARD_COLS; ++col) {
+ var candy = new Candy();
+ candy.row = row;
+ candy.col = col;
+ candy.x = col * CELL_SIZE + CELL_SIZE / 2;
+ candy.y = row * CELL_SIZE + CELL_SIZE / 2;
+ boardContainer.addChild(candy);
+ candies.push(candy);
+ board[row][col] = candy;
+ }
+ }
+ // Remove initial matches
+ removeInitialMatches();
+}
+// Remove initial matches to avoid auto-matches at start
+function removeInitialMatches() {
+ for (var row = 0; row < BOARD_ROWS; ++row) {
+ for (var col = 0; col < BOARD_COLS; ++col) {
+ var type = board[row][col].type;
+ // Check left
+ if (col >= 2 && board[row][col - 1].type === type && board[row][col - 2].type === type) {
+ var newType = getRandomCandyType();
+ while (newType === type) newType = getRandomCandyType();
+ board[row][col].setType(newType);
+ }
+ // Check up
+ if (row >= 2 && board[row - 1][col].type === type && board[row - 2][col].type === type) {
+ var newType = getRandomCandyType();
+ while (newType === type) newType = getRandomCandyType();
+ board[row][col].setType(newType);
+ }
+ }
+ }
+}
+// Get candy at board position
+function getCandyAt(row, col) {
+ if (row < 0 || row >= BOARD_ROWS || col < 0 || col >= BOARD_COLS) return null;
+ return board[row][col];
+}
+// Swap two candies
+function swapCandies(c1, c2, cb) {
+ swapping = true;
+ var r1 = c1.row,
+ c1c = c1.col,
+ r2 = c2.row,
+ c2c = c2.col;
+ // Swap in board
+ board[r1][c1c] = c2;
+ board[r2][c2c] = c1;
+ // Swap row/col
+ var tmpRow = c1.row,
+ tmpCol = c1.col;
+ c1.row = c2.row;
+ c1.col = c2.col;
+ c2.row = tmpRow;
+ c2.col = tmpCol;
+ // Animate
+ var done = 0;
+ c1.moveTo(c1.col * CELL_SIZE + CELL_SIZE / 2, c1.row * CELL_SIZE + CELL_SIZE / 2, 180, function () {
+ done++;
+ if (done === 2 && cb) cb();
+ });
+ c2.moveTo(c2.col * CELL_SIZE + CELL_SIZE / 2, c2.row * CELL_SIZE + CELL_SIZE / 2, 180, function () {
+ done++;
+ if (done === 2 && cb) cb();
+ });
+ LK.getSound('swap').play();
+}
+// Check if two candies are adjacent
+function areAdjacent(c1, c2) {
+ var dr = Math.abs(c1.row - c2.row);
+ var dc = Math.abs(c1.col - c2.col);
+ return dr + dc === 1;
+}
+// Find all matches on the board
+function findMatches() {
+ var matches = [];
+ // Horizontal
+ for (var row = 0; row < BOARD_ROWS; ++row) {
+ var count = 1;
+ for (var col = 1; col < BOARD_COLS; ++col) {
+ var prev = board[row][col - 1];
+ var curr = board[row][col];
+ if (curr.type === prev.type && curr.blocker === BLOCKER_NONE && prev.blocker === BLOCKER_NONE) {
+ count++;
+ } else {
+ if (count >= 3) {
+ for (var k = 0; k < count; ++k) {
+ matches.push(board[row][col - 1 - k]);
+ }
+ }
+ count = 1;
+ }
+ }
+ if (count >= 3) {
+ for (var k = 0; k < count; ++k) {
+ matches.push(board[row][BOARD_COLS - 1 - k]);
+ }
+ }
+ }
+ // Vertical
+ for (var col = 0; col < BOARD_COLS; ++col) {
+ var count = 1;
+ for (var row = 1; row < BOARD_ROWS; ++row) {
+ var prev = board[row - 1][col];
+ var curr = board[row][col];
+ if (curr.type === prev.type && curr.blocker === BLOCKER_NONE && prev.blocker === BLOCKER_NONE) {
+ count++;
+ } else {
+ if (count >= 3) {
+ for (var k = 0; k < count; ++k) {
+ matches.push(board[row - 1 - k][col]);
+ }
+ }
+ count = 1;
+ }
+ }
+ if (count >= 3) {
+ for (var k = 0; k < count; ++k) {
+ matches.push(board[BOARD_ROWS - 1 - k][col]);
+ }
+ }
+ }
+ // Remove duplicates
+ var unique = [];
+ for (var i = 0; i < matches.length; ++i) {
+ if (unique.indexOf(matches[i]) === -1) unique.push(matches[i]);
+ }
+ return unique;
+}
+// Remove matched candies and animate
+function removeMatches(matches, cb) {
+ if (!matches || matches.length === 0) {
+ if (cb) cb();
+ return;
+ }
+ LK.getSound('match').play();
+ var done = 0;
+ for (var i = 0; i < matches.length; ++i) {
+ var candy = matches[i];
+ candy.isMatched = true;
+ // Animate scale down and fade
+ tween(candy, {
+ scaleX: 0,
+ scaleY: 0,
+ alpha: 0
+ }, {
+ duration: 200,
+ easing: tween.easeIn,
+ onFinish: function (candy) {
+ return function () {
+ if (candy.asset) {
+ candy.removeChild(candy.asset);
+ candy.asset.destroy();
+ candy.asset = null;
+ }
+ candy.visible = false;
+ done++;
+ if (done === matches.length && cb) cb();
+ };
+ }(candy)
+ });
+ // Add score
+ score += 100;
+ }
+}
+// Drop candies to fill empty spaces
+function dropCandies(cb) {
+ var moved = false;
+ for (var col = 0; col < BOARD_COLS; ++col) {
+ for (var row = BOARD_ROWS - 1; row >= 0; --row) {
+ var candy = board[row][col];
+ if (!candy.isMatched && candy.visible) continue;
+ // Find nearest above
+ for (var above = row - 1; above >= 0; --above) {
+ var aboveCandy = board[above][col];
+ if (!aboveCandy.isMatched && aboveCandy.visible) {
+ // Move aboveCandy down
+ board[row][col] = aboveCandy;
+ aboveCandy.row = row;
+ aboveCandy.col = col;
+ aboveCandy.moveTo(col * CELL_SIZE + CELL_SIZE / 2, row * CELL_SIZE + CELL_SIZE / 2, 180);
+ board[above][col] = candy;
+ moved = true;
+ break;
+ }
+ }
+ }
+ }
+ if (cb) LK.setTimeout(cb, moved ? 200 : 0);
+}
+// Fill empty spaces with new candies
+function refillBoard(cb) {
+ var created = false;
+ for (var col = 0; col < BOARD_COLS; ++col) {
+ for (var row = 0; row < BOARD_ROWS; ++row) {
+ var candy = board[row][col];
+ if (!candy.isMatched && candy.visible) continue;
+ // Create new candy
+ var newCandy = new Candy();
+ newCandy.row = row;
+ newCandy.col = col;
+ newCandy.x = col * CELL_SIZE + CELL_SIZE / 2;
+ newCandy.y = -CELL_SIZE + CELL_SIZE / 2;
+ boardContainer.addChild(newCandy);
+ candies.push(newCandy);
+ board[row][col] = newCandy;
+ newCandy.moveTo(col * CELL_SIZE + CELL_SIZE / 2, row * CELL_SIZE + CELL_SIZE / 2, 220);
+ created = true;
+ }
+ }
+ if (cb) LK.setTimeout(cb, created ? 220 : 0);
+}
+// Remove matched candies from board
+function clearMatchedCandies() {
+ for (var row = 0; row < BOARD_ROWS; ++row) {
+ for (var col = 0; col < BOARD_COLS; ++col) {
+ var candy = board[row][col];
+ if (candy.isMatched) {
+ candy.destroyCandy();
+ // Remove from candies array
+ for (var i = 0; i < candies.length; ++i) {
+ if (candies[i] === candy) {
+ candies.splice(i, 1);
+ break;
+ }
+ }
+ // Replace with dummy invisible candy for drop logic
+ var dummy = new Candy();
+ dummy.row = row;
+ dummy.col = col;
+ dummy.visible = false;
+ board[row][col] = dummy;
+ }
+ }
+ }
+}
+// Deselect all candies
+function deselectAll() {
+ for (var i = 0; i < candies.length; ++i) {
+ candies[i].setSelected(false);
+ }
+ selectedCandy = null;
+}
+// Handle user tap
+function handleTap(x, y, obj) {
+ if (swapping || animating || isProcessing) return;
+ // Convert to board coordinates
+ var local = boardContainer.toLocal({
+ x: x,
+ y: y
+ });
+ var col = Math.floor(local.x / CELL_SIZE);
+ var row = Math.floor(local.y / CELL_SIZE);
+ if (col < 0 || col >= BOARD_COLS || row < 0 || row >= BOARD_ROWS) return;
+ var candy = board[row][col];
+ if (!candy || candy.blocker !== BLOCKER_NONE) return;
+ if (!selectedCandy) {
+ selectedCandy = candy;
+ candy.setSelected(true);
+ } else if (selectedCandy === candy) {
+ deselectAll();
+ } else if (areAdjacent(selectedCandy, candy)) {
+ // Swap
+ selectedCandy.setSelected(false);
+ candy.setSelected(false);
+ swapCandies(selectedCandy, candy, function () {
+ // Check for matches
+ var matches = findMatches();
+ if (matches.length > 0) {
+ processMatches();
+ } else {
+ // No match, swap back
+ LK.getSound('fail').play();
+ swapCandies(selectedCandy, candy, function () {
+ swapping = false;
+ deselectAll();
+ });
+ }
+ movesLeft--;
+ updateGUI();
+ });
+ deselectAll();
+ } else {
+ deselectAll();
+ selectedCandy = candy;
+ candy.setSelected(true);
+ }
+}
+// Process matches and refill
+function processMatches() {
+ isProcessing = true;
+ var matches = findMatches();
+ if (matches.length === 0) {
+ swapping = false;
+ isProcessing = false;
+ checkGameEnd();
+ return;
+ }
+ removeMatches(matches, function () {
+ clearMatchedCandies();
+ dropCandies(function () {
+ refillBoard(function () {
+ processMatches();
+ });
+ });
+ });
+}
+// Update GUI
+function updateGUI() {
+ scoreTxt.setText('Score: ' + score);
+ movesTxt.setText('Moves: ' + movesLeft);
+ targetTxt.setText('Target: ' + targetScore);
+}
+// Check for win/lose
+function checkGameEnd() {
+ if (score >= targetScore) {
+ LK.showYouWin();
+ } else if (movesLeft <= 0) {
+ LK.showGameOver();
+ }
+}
+// Game event handlers
+game.down = function (x, y, obj) {
+ // Don't allow tap in top left 100x100
+ if (x < 100 && y < 100) return;
+ handleTap(x, y, obj);
+};
+// No drag/move for now
+// Game update
+game.update = function () {
+ // No per-frame logic needed for MVP
+};
+// Start music
+LK.playMusic('bgmusic', {
+ fade: {
+ start: 0,
+ end: 0.7,
+ duration: 1000
+ }
+});
+// Start game
+initBoard();
+updateGUI();
\ No newline at end of file