/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Candy class: represents a single candy on the board var Candy = Container.expand(function () { var self = Container.call(this); // Properties self.type = 0; // 0-5 for normal candies, 6+ for special self.row = 0; self.col = 0; self.selected = false; self.special = null; // null, 'row', 'col', 'bomb' // Attach asset var candyColors = [0xff4d4d, // Red (pentagon) 0x4db8ff, // Blue 0xffe14d, // Yellow 0x7cff4d, // Green 0xff4df7, // Pink 0xff914d // Orange ]; // For special candies, use a white border function getCandyAsset(type, special) { var color = candyColors[type % candyColors.length]; var asset = LK.getAsset('candy_' + type, { width: candySize, height: candySize, color: color, shape: 'ellipse', anchorX: 0.5, anchorY: 0.5 }); if (special) { // Add a border by overlaying a slightly larger ellipse var border = LK.getAsset('candy_border', { width: candySize + 16, height: candySize + 16, color: 0xffffff, shape: 'ellipse', anchorX: 0.5, anchorY: 0.5 }); var group = new Container(); group.addChild(border); group.addChild(asset); return group; } return asset; } self.setType = function (type, special) { self.type = type; self.special = special || null; // Remove old graphics while (self.children.length) self.removeChild(self.children[0]); var asset = getCandyAsset(type, special); self.addChild(asset); }; // Highlight for selection self.setSelected = function (selected) { self.selected = selected; if (self.children.length) { if (selected) { tween(self, { scaleX: 1.2, scaleY: 1.2 }, { duration: 120, easing: tween.easeOut }); } else { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeOut }); } } }; // Animate removal self.animateRemove = function (_onFinish) { tween(self, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 180, easing: tween.easeIn, onFinish: function onFinish() { if (_onFinish) _onFinish(); } }); }; // Animate drop self.animateDrop = function (targetY, onFinish) { tween(self, { y: targetY }, { duration: 180, easing: tween.bounceOut, onFinish: onFinish }); }; // Animate spawn self.animateSpawn = function () { self.scaleX = self.scaleY = 0; tween(self, { scaleX: 1, scaleY: 1 }, { duration: 180, easing: tween.easeOut }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222244 }); /**** * Game Code ****/ // Board settings var boardRows = 9; var boardCols = 7; var candyTypes = 6; var candySize = 180; var boardOffsetX = Math.floor((2048 - boardCols * candySize) / 2); var boardOffsetY = 320; var moveLimit = 25; var targetScore = 3000; // Game state var board = []; var candies = []; var selectedCandy = null; var swapping = false; var animating = false; var movesLeft = moveLimit; var score = 0; // UI var scoreTxt = new Text2('Score: 0', { size: 90, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); var movesTxt = new Text2('Moves: ' + movesLeft, { size: 70, fill: "#fff" }); movesTxt.anchor.set(0.5, 0); LK.gui.top.addChild(movesTxt); movesTxt.y = 110; // Helper: get board position from x/y function getBoardPos(x, y) { var col = Math.floor((x - boardOffsetX) / candySize); var row = Math.floor((y - boardOffsetY) / candySize); if (col < 0 || col >= boardCols || row < 0 || row >= boardRows) return null; return { row: row, col: col }; } // Helper: get candy at row,col function getCandy(row, col) { if (row < 0 || row >= boardRows || col < 0 || col >= boardCols) return null; return board[row][col]; } // Helper: swap candies in board and update their row/col function swapCandies(c1, c2) { var r1 = c1.row, c1c = c1.col, r2 = c2.row, c2c = c2.col; board[r1][c1c] = c2; board[r2][c2c] = c1; c1.row = r2; c1.col = c2c; c2.row = r1; c2.col = c1c; } // Helper: check if two candies are adjacent function areAdjacent(c1, c2) { return Math.abs(c1.row - c2.row) + Math.abs(c1.col - c2.col) === 1; } // Helper: find all matches on the board function findMatches() { var matches = []; // Horizontal for (var r = 0; r < boardRows; r++) { var streak = 1; for (var c = 1; c < boardCols; c++) { if (board[r][c].type === board[r][c - 1].type) { streak++; } else { if (streak >= 3) { matches.push({ row: r, col: c - 1, len: streak, dir: 'h' }); } streak = 1; } } if (streak >= 3) { matches.push({ row: r, col: boardCols - 1, len: streak, dir: 'h' }); } } // Vertical for (var c = 0; c < boardCols; c++) { var streak = 1; for (var r = 1; r < boardRows; r++) { if (board[r][c].type === board[r - 1][c].type) { streak++; } else { if (streak >= 3) { matches.push({ row: r - 1, col: c, len: streak, dir: 'v' }); } streak = 1; } } if (streak >= 3) { matches.push({ row: boardRows - 1, col: c, len: streak, dir: 'v' }); } } return matches; } // Helper: mark candies to be removed, and create special candies if needed function markMatches(matches) { var removeMap = []; for (var r = 0; r < boardRows; r++) { removeMap[r] = []; for (var c = 0; c < boardCols; c++) { removeMap[r][c] = false; } } // For special candy creation var specials = []; for (var i = 0; i < matches.length; i++) { var m = matches[i]; if (m.dir === 'h') { for (var k = 0; k < m.len; k++) { removeMap[m.row][m.col - k] = true; } // Create special for 4/5 if (m.len === 4) { specials.push({ row: m.row, col: m.col - 1, special: 'row' }); } else if (m.len >= 5) { specials.push({ row: m.row, col: m.col - 2, special: 'bomb' }); } } else if (m.dir === 'v') { for (var k = 0; k < m.len; k++) { removeMap[m.row - k][m.col] = true; } if (m.len === 4) { specials.push({ row: m.row - 1, col: m.col, special: 'col' }); } else if (m.len >= 5) { specials.push({ row: m.row - 2, col: m.col, special: 'bomb' }); } } } return { removeMap: removeMap, specials: specials }; } // Helper: remove candies, animate, and return positions to refill function removeAndScore(removeMap, specials) { var toRemove = []; for (var r = 0; r < boardRows; r++) { for (var c = 0; c < boardCols; c++) { if (removeMap[r][c]) { toRemove.push(board[r][c]); } } } // Score: 60 per candy, + bonus for special var gained = toRemove.length * 60; score += gained; LK.setScore(score); scoreTxt.setText('Score: ' + score); // Place specials for (var i = 0; i < specials.length; i++) { var s = specials[i]; var candy = board[s.row][s.col]; if (candy) { candy.setType(candy.type, s.special); } } // Animate and remove var removed = 0; for (var i = 0; i < toRemove.length; i++) { var c = toRemove[i]; c.animateRemove(function () { removed++; if (removed === toRemove.length) { refillBoard(); } }); // Remove from board after animation board[c.row][c.col] = null; } // If nothing to remove, refill immediately if (toRemove.length === 0) { refillBoard(); } } // Helper: refill board after removals function refillBoard() { // Drop candies down for (var c = 0; c < boardCols; c++) { var empty = 0; for (var r = boardRows - 1; r >= 0; r--) { if (!board[r][c]) { empty++; } else if (empty > 0) { var candy = board[r][c]; board[r + empty][c] = candy; candy.row = r + empty; candy.col = c; board[r][c] = null; var targetY = boardOffsetY + (r + empty) * candySize + candySize / 2; candy.animateDrop(targetY); } } // Add new candies at top for (var e = 0; e < empty; e++) { var type = Math.floor(Math.random() * candyTypes); var newCandy = new Candy(); newCandy.setType(type); newCandy.row = e; newCandy.col = c; var x = boardOffsetX + c * candySize + candySize / 2; var y = boardOffsetY + e * candySize + candySize / 2 - empty * candySize; newCandy.x = x; newCandy.y = y; newCandy.scaleX = newCandy.scaleY = 1; newCandy.alpha = 1; game.addChild(newCandy); candies.push(newCandy); board[e][c] = newCandy; // Animate drop to correct position var targetY = boardOffsetY + e * candySize + candySize / 2; newCandy.animateDrop(targetY, function () {}); newCandy.animateSpawn(); } } // After all drops, check for new matches LK.setTimeout(function () { resolveBoard(); }, 220); } // Helper: resolve board (find matches, remove, refill) function resolveBoard() { var matches = findMatches(); if (matches.length > 0) { var _markMatches = markMatches(matches), removeMap = _markMatches.removeMap, specials = _markMatches.specials; removeAndScore(removeMap, specials); } else { animating = false; // Check for win/lose if (score >= targetScore) { LK.showYouWin(); } else if (movesLeft <= 0) { LK.showGameOver(); } } } // Helper: deselect all candies function deselectAll() { for (var i = 0; i < candies.length; i++) { candies[i].setSelected(false); } selectedCandy = null; } // Helper: get candy at x,y function getCandyAt(x, y) { var pos = getBoardPos(x, y); if (!pos) return null; return board[pos.row][pos.col]; } // Initialize board function initBoard() { // Clear old for (var i = 0; i < candies.length; i++) { candies[i].destroy(); } candies = []; board = []; for (var r = 0; r < boardRows; r++) { board[r] = []; for (var c = 0; c < boardCols; c++) { var type = Math.floor(Math.random() * candyTypes); var candy = new Candy(); candy.setType(type); candy.row = r; candy.col = c; var x = boardOffsetX + c * candySize + candySize / 2; var y = boardOffsetY + r * candySize + candySize / 2; candy.x = x; candy.y = y; candy.scaleX = candy.scaleY = 1; candy.alpha = 1; game.addChild(candy); candies.push(candy); board[r][c] = candy; } } // Remove any initial matches LK.setTimeout(function () { resolveBoard(); }, 100); } // Handle touch/mouse down game.down = function (x, y, obj) { if (animating) return; var candy = getCandyAt(x, y); if (!candy) { deselectAll(); return; } if (!selectedCandy) { selectedCandy = candy; candy.setSelected(true); } else if (selectedCandy === candy) { deselectAll(); } else if (areAdjacent(selectedCandy, candy)) { swapping = true; animating = true; // Swap visually var c1 = selectedCandy, c2 = candy; var c1x = c1.x, c1y = c1.y, c2x = c2.x, c2y = c2.y; tween(c1, { x: c2x, y: c2y }, { duration: 180, easing: tween.easeInOut }); tween(c2, { x: c1x, y: c1y }, { duration: 180, easing: tween.easeInOut, onFinish: function onFinish() { // Swap in board swapCandies(c1, c2); // Check for matches var matches = findMatches(); if (matches.length > 0) { movesLeft--; movesTxt.setText('Moves: ' + movesLeft); deselectAll(); var _markMatches2 = markMatches(matches), removeMap = _markMatches2.removeMap, specials = _markMatches2.specials; removeAndScore(removeMap, specials); } else { // No match, swap back tween(c1, { x: c1x, y: c1y }, { duration: 180, easing: tween.easeInOut }); tween(c2, { x: c2x, y: c2y }, { duration: 180, easing: tween.easeInOut, onFinish: function onFinish() { swapCandies(c1, c2); deselectAll(); animating = false; } }); } swapping = false; } }); } else { deselectAll(); selectedCandy = candy; candy.setSelected(true); } }; // Prevent drag game.move = function (x, y, obj) { // No drag for match-3 }; // Prevent up game.up = function (x, y, obj) {}; // Game update game.update = function () { // No per-frame logic needed }; // Start game function startGame() { score = 0; movesLeft = moveLimit; LK.setScore(score); scoreTxt.setText('Score: ' + score); movesTxt.setText('Moves: ' + movesLeft); animating = false; swapping = false; selectedCandy = null; initBoard(); } startGame();
===================================================================
--- original.js
+++ change.js
@@ -16,9 +16,9 @@
self.selected = false;
self.special = null; // null, 'row', 'col', 'bomb'
// Attach asset
var candyColors = [0xff4d4d,
- // Red
+ // Red (pentagon)
0x4db8ff,
// Blue
0xffe14d,
// Yellow
candy . No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
candy. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
black bomb candy. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
yellow candy. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
red candy. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat