User prompt
fix all failed problems
User prompt
fix bugs game sometimes freeze
User prompt
adjust the random rate of new boxes coming from above to reduce the probability of them exploding on their own
User prompt
fix bugs for opening
User prompt
try again
User prompt
fix bugs
User prompt
set random proportions of inboxes
User prompt
DON'T CREATE TOO MANY ASSETS, REDUCE THE NUMBER OF NEW ASSETS CREATED FROM EXISTING ONES, CONNECT NEW ASSETS TO MISSIONS IN DIFFERENT QUANTITIES
User prompt
WHEN A LEVEL IS GAINED, MOVE TO THE NEXT LEVEL 100 CHAPTERS ARE DIFFERENT FROM EACH OTHER WITH INCREASING DIFFICULTY AND CHANGING MISSIONS. NEXT LEVEL SCREEN AFTER PASSING THE CHAPTER
User prompt
IF WE ARE GOING TO DO THE ASSETS MISSION IN THE GAME, LET IT CONTINUE TO SPAWN RANDOMLY WITHIN THAT SECTION
User prompt
MISSIONS AND ASSETS CREATED IN THE GAME DO NOT HOLD EACH OTHER, MAP THEM. IF YOU WANT AN ASSET TO DISAPPEAR IN A CERTAIN NUMBER OF TIMES IN A CHAPTER, HAVE THAT ASSET IN THE GAME
User prompt
RECALCULATE AND REARRANGE THE NUMBER OF MOVES THE NUMBER OF MOVES CAN NEVER BE NEGATIVE. EACH TIME A LEVEL IS GAME OVER, INCREASE THE NUMBER OF MOVES BACK TO THE NUMBER REQUIRED FOR THE INITIAL LEVEL
User prompt
EDIT THE NUMBER OF MOVES EDIT ALL ACCORDING TO DIFFICULTY AND LEVEL
User prompt
RESTART THE CHAPTER BY RESTARTING EVERY TIME THE GAME OVER
User prompt
Ensure complete consistency between level objectives and game assets in the mechanical match-3 game. Rebuild the objective system to exclusively use existing mechanical-themed assets: valves (blue), pumps (orange), gauges (red), elbows (green), locked pipes (gray), and T-junctions (purple). Replace all candy-themed terminology with mechanical terms - 'destroy 15 valves' instead of 'candies'. Add missing assets for objective trackers: create 64x64px icons for each mechanical part using the same color scheme. Rigorously link objectives to asset names in code: when an objective says 'clear 20 locked pipes', only tile_locked assets should increment the counter. Implement real-time objective validation that triggers error logs if asset-objective mismatches occur. Add fallback shapes with type initials (V/P/G/E/L/T) for any missing tracker icons
User prompt
THE COUNTER IS IMMEDIATELY BELOW, IN THE CENTER AT THE END OF THE MATRIX AND VISIBLE
User prompt
REMOVE BLUE GEAR CODES AND RULES AND ASSETS FROM GAME
User prompt
CHANGE ALL LEVEL MİSSİONS AND FİX ALL OF THEM ASSETS AND CODES
User prompt
YOU SAY COLLECT 10 BLUE GEARS. YOU DON'T COUNT WHEN THE GEARS EXPLODE IN THE EPISODE. THERE IS A BUG. MATCH THE GIVEN TASKS WITH THE ASSETS. CODE AND ASSETS AND RULES SHOULD BE CONNECTED WITH EACH OTHER
User prompt
WRITE DOWN THE PROCESSES SUCH AS THE NUMBER OF TASKS PERFORMED AND HOW MANY ARE LEFT BELOW. ANOTHER IMPORTANT POINT IS TO LINK ALL TASKS TO ASSETS
User prompt
OTHER BOXES ARE VISIBLE BEHIND SOME BOXES. SOLVE THE BUG
User prompt
ADD BLUE GEARS İN GAME AND ASSETS,
User prompt
ADD THEİR ASSETS İN GAME
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.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.type === 'candy_blue' || self.type === 'blue_gear') assetId = 'blue_gear'; 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 ****/ //{2.1} // Music and sounds // UI assets // Blocker assets // Candy assets // Prism button asset sized for large UI button (500x160 in use, so use 500x160 for best quality) // Candy and blocker assets sized to fit CELL_SIZE (200x200) for main board // Candy types // Music // Sounds // Blockers // Special candies // Candy shapes/colors // Board data var CANDY_TYPES = ['candy_red', 'candy_green', 'blue_gear', '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; // --- Level Objective System --- // Objective types: 1) Collect Targets, 2) Clear Obstacles, 3) Rescue Items var OBJECTIVE_COLLECT = 1; var OBJECTIVE_CLEAR = 2; var OBJECTIVE_RESCUE = 3; // Example icons for objectives (use existing assets for now) var OBJECTIVE_ICONS = { blue: 'blue_gear', // blue gear asset green: 'candy_green', red: 'candy_red', yellow: 'candy_yellow', purple: 'candy_purple', orange: 'candy_orange', chocolate: 'blocker_chocolate', ice: 'blocker_ice', mechanic: 'candy_bomb' // placeholder for rescue }; // Per-level objectives // Each level can have multiple objectives, e.g. [{type: OBJECTIVE_COLLECT, color: 'blue', count: 20}] var levelObjectives = [ // Level 1: Collect 10 blue candies [{ type: OBJECTIVE_COLLECT, color: 'blue', count: 10 }], // Level 2: Clear 8 chocolate blockers [{ type: OBJECTIVE_CLEAR, blocker: BLOCKER_CHOCOLATE, count: 8 }], // Level 3: Rescue 3 mechanics (special candies at bottom row) [{ type: OBJECTIVE_RESCUE, rescueType: 'mechanic', count: 3 }], // Level 4: Collect 8 green, 8 red [{ type: OBJECTIVE_COLLECT, color: 'green', count: 8 }, { type: OBJECTIVE_COLLECT, color: 'red', count: 8 }], // Level 5: Clear 6 ice, collect 6 yellow [{ type: OBJECTIVE_CLEAR, blocker: BLOCKER_ICE, count: 6 }, { type: OBJECTIVE_COLLECT, color: 'yellow', count: 6 }], // Level 6: Rescue 2 mechanics, collect 5 purple [{ type: OBJECTIVE_RESCUE, rescueType: 'mechanic', count: 2 }, { type: OBJECTIVE_COLLECT, color: 'purple', count: 5 }], // Level 7: Collect 10 orange, clear 5 chocolate [{ type: OBJECTIVE_COLLECT, color: 'orange', count: 10 }, { type: OBJECTIVE_CLEAR, blocker: BLOCKER_CHOCOLATE, count: 5 }], // Level 8: Rescue 4 mechanics [{ type: OBJECTIVE_RESCUE, rescueType: 'mechanic', count: 4 }], // Level 9: Collect 12 blue, clear 6 ice [{ type: OBJECTIVE_COLLECT, color: 'blue', count: 12 }, { type: OBJECTIVE_CLEAR, blocker: BLOCKER_ICE, count: 6 }], // Level 10: Clear 10 chocolate, rescue 2 mechanics [{ type: OBJECTIVE_CLEAR, blocker: BLOCKER_CHOCOLATE, count: 10 }, { type: OBJECTIVE_RESCUE, rescueType: 'mechanic', count: 2 }] // ...repeat or randomize for higher levels ]; // For levels > 10, cycle or randomize objectives for demo function getObjectivesForLevel(level) { if (level < levelObjectives.length) return levelObjectives[level]; // For higher levels, mix and increase counts var base = levelObjectives[level % levelObjectives.length]; var newObj = []; for (var i = 0; i < base.length; ++i) { var o = {}; for (var k in base[i]) o[k] = base[i][k]; o.count = Math.ceil(o.count * (1 + level / 20)); newObj.push(o); } return newObj; } // Track progress for current objectives var currentObjectives = []; var currentObjectiveProgress = []; // Progress panel UI var progressPanel = new Container(); LK.gui.top.addChild(progressPanel); progressPanel.x = 0; progressPanel.y = 350; progressPanel.visible = false; // Process tracker panel (shows number of tasks performed and left, links to assets) var processTrackerPanel = new Container(); LK.gui.top.addChild(processTrackerPanel); processTrackerPanel.x = 0; processTrackerPanel.y = 520; processTrackerPanel.visible = false; // Star bonus UI var starPanel = new Container(); LK.gui.top.addChild(starPanel); starPanel.x = 0; starPanel.y = 420; starPanel.visible = false; var starIcons = []; for (var i = 0; i < 3; ++i) { var star = LK.getAsset('candy_rainbow', { anchorX: 0.5, anchorY: 0.5, x: 80 * i, y: 0, width: 60, height: 60, alpha: 0.5 }); starPanel.addChild(star); starIcons.push(star); } // Helper: update progress panel function updateProgressPanel() { // Remove old children while (progressPanel.children && progressPanel.children.length > 0) { var ch = progressPanel.children.pop(); ch.destroy && ch.destroy(); } // For each objective, show icon, progress, and animation if completed for (var i = 0; i < currentObjectives.length; ++i) { var obj = currentObjectives[i]; var iconId = null; if (obj.type === OBJECTIVE_COLLECT) iconId = OBJECTIVE_ICONS[obj.color]; if (obj.type === OBJECTIVE_CLEAR) iconId = obj.blocker === BLOCKER_CHOCOLATE ? OBJECTIVE_ICONS.chocolate : OBJECTIVE_ICONS.ice; if (obj.type === OBJECTIVE_RESCUE) iconId = OBJECTIVE_ICONS.mechanic; var icon = LK.getAsset(iconId, { anchorX: 0.5, anchorY: 0.5, x: 80 + i * 320, y: 0, width: 90, height: 90 }); progressPanel.addChild(icon); var txt = new Text2('', { size: 60, fill: "#fff" }); txt.anchor.set(0, 0.5); txt.x = 140 + i * 320; txt.y = 0; var val = currentObjectiveProgress[i]; var goal = obj.count; txt.setText(val + " / " + goal); progressPanel.addChild(txt); // If completed, flash icon if (val >= goal) { LK.effects.flashObject(icon, 0x00ff00, 600); } } } // --- Process Tracker Panel: show number of tasks performed and left, link to assets --- while (processTrackerPanel.children && processTrackerPanel.children.length > 0) { var ch = processTrackerPanel.children.pop(); ch.destroy && ch.destroy(); } // Count total and completed tasks var totalTasks = currentObjectives.length; var completedTasks = 0; for (var i = 0; i < currentObjectives.length; ++i) { if (currentObjectiveProgress[i] >= currentObjectives[i].count) completedTasks++; } // Show summary text var summaryTxt = new Text2("Tasks: " + completedTasks + " / " + totalTasks, { size: 54, fill: "#fff" }); summaryTxt.anchor.set(0, 0.5); summaryTxt.x = 0; summaryTxt.y = 0; processTrackerPanel.addChild(summaryTxt); // For each task, show icon and progress for (var i = 0; i < currentObjectives.length; ++i) { var obj = currentObjectives[i]; var iconId = null; if (obj.type === OBJECTIVE_COLLECT) iconId = OBJECTIVE_ICONS[obj.color]; if (obj.type === OBJECTIVE_CLEAR) iconId = obj.blocker === BLOCKER_CHOCOLATE ? OBJECTIVE_ICONS.chocolate : OBJECTIVE_ICONS.ice; if (obj.type === OBJECTIVE_RESCUE) iconId = OBJECTIVE_ICONS.mechanic; var icon = LK.getAsset(iconId, { anchorX: 0.5, anchorY: 0.5, x: 220 + i * 320, y: 0, width: 70, height: 70 }); processTrackerPanel.addChild(icon); var txt = new Text2(currentObjectiveProgress[i] + " / " + obj.count, { size: 44, fill: "#fff" }); txt.anchor.set(0, 0.5); txt.x = 260 + i * 320; txt.y = 0; processTrackerPanel.addChild(txt); // If completed, flash icon if (currentObjectiveProgress[i] >= obj.count) { LK.effects.flashObject(icon, 0x00ff00, 600); } } // Show/hide process tracker panel based on game state processTrackerPanel.visible = progressPanel.visible; // Helper: update star panel function updateStarPanel() { var percent = movesLeft / levels[currentLevel].moves; for (var i = 0; i < 3; ++i) { starIcons[i].alpha = 0.3; } if (percent > 0.5) { starIcons[0].alpha = 1; starIcons[1].alpha = 1; starIcons[2].alpha = 1; } else if (percent > 0.25) { starIcons[0].alpha = 1; starIcons[1].alpha = 1; } else if (percent > 0) { starIcons[0].alpha = 1; } } // Helper: check if all objectives are complete function allObjectivesComplete() { for (var i = 0; i < currentObjectives.length; ++i) { if (currentObjectiveProgress[i] < currentObjectives[i].count) return false; } return true; } // --- Timed Bombs and Conveyor Belts (mechanics) --- var timedBombs = []; // {candy, movesLeft} var conveyorActive = false; // Add a bomb to a random candy (for demo, add on level 4+) function addTimedBomb() { var candidates = []; for (var i = 0; i < candies.length; ++i) { var c = candies[i]; if (c.blocker === BLOCKER_NONE && c.special === SPECIAL_NONE) candidates.push(c); } if (candidates.length > 0) { var c = candidates[randInt(0, candidates.length - 1)]; c.special = SPECIAL_BOMB; c.setType(c.type, SPECIAL_BOMB, c.blocker); timedBombs.push({ candy: c, movesLeft: 3 }); } } // Conveyor: shift all candies right by 1 (for demo, level 6+) function shiftConveyor() { for (var row = 0; row < BOARD_ROWS; ++row) { var last = board[row][BOARD_COLS - 1]; for (var col = BOARD_COLS - 1; col > 0; --col) { board[row][col] = board[row][col - 1]; board[row][col].col = col; board[row][col].moveTo(col * CELL_SIZE + CELL_SIZE / 2, row * CELL_SIZE + CELL_SIZE / 2, 180); } board[row][0] = last; last.col = 0; last.moveTo(0 * CELL_SIZE + CELL_SIZE / 2, row * CELL_SIZE + CELL_SIZE / 2, 180); } } // --- Per-level winCondition helpers --- // Returns true if all blockers (chocolate/ice) are cleared from the board function allBlockersCleared() { for (var row = 0; row < BOARD_ROWS; ++row) { for (var col = 0; col < BOARD_COLS; ++col) { var c = board[row][col]; // Only count blockers that are visible and not matched if (c && c.visible !== false && c.isMatched !== true && (c.blocker === BLOCKER_CHOCOLATE || c.blocker === BLOCKER_ICE)) { // For ice, also check if it's not just cracked (must be fully removed) if (c.blocker === BLOCKER_ICE && !c._iceCracked) { return false; } if (c.blocker === BLOCKER_CHOCOLATE) { return false; } } } } return true; } // Returns true if all candies are cleared (for e.g. 'explode all boxes' levels) function allBoxesCleared() { for (var row = 0; row < BOARD_ROWS; ++row) { for (var col = 0; col < BOARD_COLS; ++col) { var c = board[row][col]; // If any visible, non-matched, non-blocker candy remains, not cleared if (c && c.visible !== false && c.isMatched !== true && c.blocker === BLOCKER_NONE) { return false; } } } return true; } // Example: fun feature - birds that must reach the bottom and be freed // Returns true if all birds are at the bottom row and visible function allBirdsFreed() { for (var i = 0; i < candies.length; ++i) { var c = candies[i]; if (c.isBird && c.row !== BOARD_ROWS - 1) { return false; } } return true; } // --- Level definitions: 100 levels, each with unique win condition and fixed moves --- // Level 1 is the easiest, level 100 is the hardest var levels = []; // Moves curve: start at 20, decrease to 8 by level 30, then 7-5 for 31-100, with special bumps for bosses/lucky var movesCurve = [20, 18, 16, 15, 14, 13, 12, 12, 11, 10, // 1-10 10, 10, 10, 10, 10, 9, 9, 9, 9, 9, // 11-20 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // 21-30 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // 31-40 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, // 41-50 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, // 51-60 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 61-70 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 71-80 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 81-90 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 // 91-100 ]; for (var i = 0; i < 100; ++i) { var level = {}; // Difficulty curve: more blockers, less moves, higher targets as level increases if (i === 0) { // Level 1: Easiest, no blockers level.moves = movesCurve[i]; level.target = 400; level.blockers = []; level.description = "Explode all boxes!\nClear the board to win.\nNo blockers."; } else if (i === 1) { // Level 2: Chocolate row level.moves = movesCurve[i]; level.target = 600; level.blockers = [{ row: 4, type: BLOCKER_CHOCOLATE }]; level.description = "Clear all chocolate!\nRemove every blocker to win."; } else if (i === 2) { // Level 3: Ice at bottom level.moves = movesCurve[i]; level.target = 800; level.blockers = [{ row: 7, type: BLOCKER_ICE }, { row: 8, type: BLOCKER_ICE }]; level.description = "Break all the ice!\nClear every blocker to win."; } else if (i === 3) { // Level 4: Zig-zag blockers level.moves = movesCurve[i]; level.target = 1000; level.blockers = [{ row: 2, type: BLOCKER_CHOCOLATE }, { row: 3, type: BLOCKER_ICE }, { row: 4, type: BLOCKER_CHOCOLATE }, { row: 5, type: BLOCKER_ICE }]; level.description = "Clear all blockers!\nZig-zag pattern challenge."; } else if (i === 4) { // Level 5: Score challenge level.moves = movesCurve[i]; level.target = 1400; level.blockers = [{ row: 2, type: BLOCKER_CHOCOLATE }, { row: 3, type: BLOCKER_ICE }, { row: 4, type: BLOCKER_CHOCOLATE }, { row: 5, type: BLOCKER_ICE }, { row: 6, type: BLOCKER_CHOCOLATE }]; level.description = "Score challenge!\nReach the target score to win."; } else if (i === 5) { // Level 6: Cross/diamond blockers level.moves = movesCurve[i]; level.target = 1700; level.blockers = [{ row: 0, type: BLOCKER_CHOCOLATE }, { row: 4, type: BLOCKER_ICE }, { row: 8, type: BLOCKER_CHOCOLATE }]; level.description = "Break all blockers!\nCross and diamond pattern."; } else if (i === 6) { // Level 7: U-shape blockers level.moves = movesCurve[i]; level.target = 1900; level.blockers = [{ row: 0, type: BLOCKER_CHOCOLATE }, { row: 8, type: BLOCKER_CHOCOLATE }, { row: 4, type: BLOCKER_ICE }, { row: 7, type: BLOCKER_ICE }]; level.description = "Clear all blockers!\nU-shape at the bottom."; } else if (i === 7) { // Level 8: Vertical stripe blockers level.moves = movesCurve[i]; level.target = 2100; level.blockers = [{ row: 1, type: BLOCKER_ICE }, { row: 3, type: BLOCKER_CHOCOLATE }, { row: 5, type: BLOCKER_ICE }, { row: 7, type: BLOCKER_CHOCOLATE }]; level.description = "Clear all blockers!\nVertical stripe in the center."; } else if (i === 8) { // Level 9: Spiral blockers level.moves = movesCurve[i]; level.target = 2400; level.blockers = [{ row: 0, type: BLOCKER_ICE }, { row: 1, type: BLOCKER_CHOCOLATE }, { row: 2, type: BLOCKER_ICE }, { row: 3, type: BLOCKER_CHOCOLATE }, { row: 4, type: BLOCKER_ICE }, { row: 5, type: BLOCKER_CHOCOLATE }, { row: 6, type: BLOCKER_ICE }, { row: 7, type: BLOCKER_CHOCOLATE }]; level.description = "Clear all blockers!\nSpiral pattern."; } else if (i === 9) { // Level 10: Boss level.moves = movesCurve[i]; level.target = 3000; level.blockers = [{ row: 0, type: BLOCKER_CHOCOLATE }, { row: 1, type: BLOCKER_ICE }, { row: 2, type: BLOCKER_CHOCOLATE }, { row: 3, type: BLOCKER_ICE }, { row: 4, type: BLOCKER_CHOCOLATE }, { row: 5, type: BLOCKER_ICE }, { row: 6, type: BLOCKER_CHOCOLATE }, { row: 7, type: BLOCKER_ICE }, { row: 8, type: BLOCKER_CHOCOLATE }]; level.description = "Boss Level!\nReach the target score to win."; } else { // Levels 11-100: Increase difficulty, alternate win conditions, more blockers, less moves, higher targets var base = levels[i % 10]; // Use movesCurve for main progression, but allow special bumps below level.moves = movesCurve[i]; level.target = base.target + 400 * i; // Copy blockers and add more for higher levels level.blockers = []; for (var b = 0; b < base.blockers.length; ++b) { level.blockers.push({ row: base.blockers[b].row, type: base.blockers[b].type }); } // Every 10th level: add extra blockers if (i > 0 && i % 10 === 0) { for (var r = 0; r < 3; ++r) { level.blockers.push({ row: r, type: i % 20 === 0 ? BLOCKER_CHOCOLATE : BLOCKER_ICE }); } level.description = "Super challenge!\nExtra blockers added.\nCan you win?"; // Give a small moves bump for every 10th level level.moves += 1; } else if (i > 0 && i % 5 === 0) { // Every 5th level: Boss, higher target level.target += 1000; level.description = "Boss Level!\nBlockers everywhere.\nShow your skills!"; // Give a moves bump for boss level.moves += 1; } else if (i > 0 && i % 25 === 0) { // Every 25th: Mega Boss level.target += 2000; level.moves = Math.max(5, level.moves - 2); level.description = "Mega Boss!\nThe ultimate test.\nGood luck!"; } else if (i > 0 && i % 3 === 0) { // Every 3rd: add random blocker row var extraRow = randInt(0, BOARD_ROWS - 1); var extraType = Math.random() < 0.5 ? BLOCKER_CHOCOLATE : BLOCKER_ICE; level.blockers.push({ row: extraRow, type: extraType }); level.description = "Surprise row!\nExtra blockers appear.\nKeep matching!"; } else if (i > 0 && i % 7 === 0) { // Every 7th: add two random blockers for (var r = 0; r < 2; ++r) { var randRow = randInt(0, BOARD_ROWS - 1); var randType = Math.random() < 0.5 ? BLOCKER_CHOCOLATE : BLOCKER_ICE; level.blockers.push({ row: randRow, type: randType }); } level.description = "Blocker surprise!\nRandom blockers added.\nStay sharp!"; } else if (i > 0 && i % 13 === 0) { // Every 13th: Lucky, more moves, higher target level.moves += 3; level.target += 1500; level.description = "Lucky Level!\nMore moves, higher target.\nGo for it!"; } else { // Default: inherit description level.description = base.description; } // No winCondition property at all } levels.push(level); } // Always start at level 1 for every player/session storage.currentLevel = 0; var currentLevel = 0; var movesLeft = typeof storage.movesLeft !== "undefined" ? storage.movesLeft : levels[currentLevel].moves; var targetScore = levels[currentLevel].target; var score = typeof storage.score !== "undefined" ? storage.score : 0; var scoreTxt = null; var movesTxt = null; var targetTxt = null; var boardContainer = null; var matchQueue = []; var refillQueue = []; var isProcessing = false; // --- Start Screen Overlay --- var startScreen = new Container(); LK.gui.center.addChild(startScreen); startScreen.visible = true; // Background overlay (semi-transparent) var startBg = LK.getAsset('blocker_ice', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, width: 1200, height: 1600, alpha: 0.85 }); startScreen.addChild(startBg); // Game title var titleTxt = new Text2('Candy Match Saga', { size: 180, fill: "#fff" }); titleTxt.anchor.set(0.5, 0.5); titleTxt.x = 0; titleTxt.y = -400; startScreen.addChild(titleTxt); // Subtitle var subtitleTxt = new Text2('Match candies, beat levels, have fun!', { size: 70, fill: "#fff" }); subtitleTxt.anchor.set(0.5, 0.5); subtitleTxt.x = 0; subtitleTxt.y = -250; startScreen.addChild(subtitleTxt); // Play button var playBtn = new Container(); var playBtnBg = LK.getAsset('start_button', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, width: 500, height: 160 }); playBtn.addChild(playBtnBg); // Removed the 'Start' label from the start screen play button playBtn.anchorX = 0.5; playBtn.anchorY = 0.5; playBtn.x = 0; playBtn.y = 200; playBtn.interactive = true; playBtn.buttonMode = true; playBtn.down = function (x, y, obj) { startScreen.visible = false; // Show game UI scoreTxt.visible = true; movesTxt.visible = true; targetTxt.visible = true; levelTopRightTxt.visible = true; bonusPanel.visible = true; boardContainer.visible = true; if (levelTxt) levelTxt.visible = true; if (prevLevelBtn) prevLevelBtn.visible = true; if (resetBtn) resetBtn.visible = true; // Removed reference to nextLevelBtn which is not defined if (window.levelDescTxt) window.levelDescTxt.visible = true; }; startScreen.addChild(playBtn); // Hide game UI until play is pressed // GUI scoreTxt = new Text2('Score: 0', { size: 90, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); scoreTxt.visible = false; movesTxt = new Text2('Moves: 20', { size: 70, fill: "#fff" }); movesTxt.anchor.set(0.5, 0); LK.gui.top.addChild(movesTxt); movesTxt.y = 110; movesTxt.visible = false; targetTxt = new Text2('Target: 5000', { size: 60, fill: "#fff" }); targetTxt.anchor.set(0.5, 0); LK.gui.top.addChild(targetTxt); targetTxt.y = 180; targetTxt.visible = false; // Add level label to top right var levelTopRightTxt = new Text2('Level: 1', { size: 70, fill: "#fff" }); levelTopRightTxt.anchor.set(1, 0); LK.gui.topRight.addChild(levelTopRightTxt); levelTopRightTxt.x = 0; levelTopRightTxt.y = 0; levelTopRightTxt.visible = false; // --- Bonus UI --- var bonusPanel = new Container(); LK.gui.top.addChild(bonusPanel); bonusPanel.x = 0; bonusPanel.y = 250; bonusPanel.visible = false; // Track which bonus is selected (null, "bomb", etc) var selectedBonus = null; // Bomb bonus button var bombBtnBg = LK.getAsset('candy_bomb', { anchorX: 0.5, anchorY: 0.5, x: 120, y: 0, width: 140, height: 140 }); bonusPanel.addChild(bombBtnBg); var bombBtnLabel = new Text2('Bomb', { size: 48, fill: "#fff" }); bombBtnLabel.anchor.set(0.5, 0); bombBtnLabel.x = 120; bombBtnLabel.y = 80; bonusPanel.addChild(bombBtnLabel); bombBtnBg.interactive = true; bombBtnBg.buttonMode = true; bombBtnBg.down = function (x, y, obj) { selectedBonus = selectedBonus === "bomb" ? null : "bomb"; // Visual feedback bombBtnBg.scaleX = bombBtnBg.scaleY = selectedBonus === "bomb" ? 1.2 : 1.0; }; // Optionally, add more bonuses here in the future // Board container boardContainer = new Container(); game.addChild(boardContainer); boardContainer.x = BOARD_OFFSET_X; boardContainer.y = BOARD_OFFSET_Y; boardContainer.visible = false; // Initialize board function initBoard() { // Clear previous for (var i = 0; i < candies.length; ++i) { if (candies[i]) candies[i].destroyCandy(); } candies = []; board = []; // Initialize objectives for this level currentObjectives = getObjectivesForLevel(currentLevel); currentObjectiveProgress = []; for (var i = 0; i < currentObjectives.length; ++i) currentObjectiveProgress[i] = 0; progressPanel.visible = true; updateProgressPanel(); starPanel.visible = true; updateStarPanel(); timedBombs = []; conveyorActive = currentLevel >= 5; // Enable conveyor on level 6+ 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; // Unique blocker logic per level var blockers = levels[currentLevel].blockers; var placedBlocker = false; // Guarantee at least one open path per row for passability // We'll use a random openCol for each row, but keep it consistent for the row var openCol = -1; if (blockers.length > 0) { // For each row, pick a random open column (or center for symmetry) openCol = Math.floor(BOARD_COLS / 2); // For more variety, you could use: openCol = randInt(0, BOARD_COLS - 1); } for (var b = 0; b < blockers.length; ++b) { if (row === blockers[b].row) { // Level-specific patterns // Level 5/10: checkerboard, but add a diagonal line of blockers for extra challenge if ((currentLevel === 4 || currentLevel === 9) && ((row + col) % 2 === 0 || row === col)) { // Always leave openCol open if (col !== openCol) { candy.setType(candy.type, SPECIAL_NONE, blockers[b].type); placedBlocker = true; } } // Level 6: cross pattern, but add a diamond shape in the center else if (currentLevel === 5 && (col === Math.floor(BOARD_COLS / 2) || row === Math.floor(BOARD_ROWS / 2) || Math.abs(col - Math.floor(BOARD_COLS / 2)) === Math.abs(row - Math.floor(BOARD_ROWS / 2)) && Math.abs(col - Math.floor(BOARD_COLS / 2)) <= 2)) { if (col !== openCol) { candy.setType(candy.type, SPECIAL_NONE, blockers[b].type); placedBlocker = true; } } // Level 7: chocolate on sides, ice in center, but add blockers in a U shape at the bottom else if (currentLevel === 6 && ((row === 0 || row === BOARD_ROWS - 1) && blockers[b].type === BLOCKER_CHOCOLATE && (col === 0 || col === BOARD_COLS - 1) || row >= BOARD_ROWS - 3 && (col === 0 || col === BOARD_COLS - 1 || row === BOARD_ROWS - 1 && col > 0 && col < BOARD_COLS - 1))) { if (col !== openCol) { candy.setType(candy.type, SPECIAL_NONE, blockers[b].type); placedBlocker = true; } } // Level 8: alternating rows, but add a vertical stripe in the center else if (currentLevel === 7 && (row % 2 === b % 2 || col === Math.floor(BOARD_COLS / 2))) { if (col !== openCol) { candy.setType(candy.type, SPECIAL_NONE, blockers[b].type); placedBlocker = true; } } // Level 9: spiral (simulate with increasing rows), but add a spiral arm from the bottom right else if (currentLevel === 8 && (col >= row && col < BOARD_COLS - row || row + col === BOARD_COLS + BOARD_ROWS - 2 - row && row > BOARD_ROWS / 2)) { if (col !== openCol) { candy.setType(candy.type, SPECIAL_NONE, blockers[b].type); placedBlocker = true; } } // Level 10: nearly every row blocked, but leave a zig-zag path open else if (currentLevel === 9 && !((row + col) % 2 === 1 && col !== 0 && col !== BOARD_COLS - 1)) { if (col !== openCol) { candy.setType(candy.type, SPECIAL_NONE, blockers[b].type); placedBlocker = true; } } // Default: alternate blockers on even/odd columns for variety, but add a random chance for double blockers else if (!placedBlocker && (blockers[b].type === BLOCKER_CHOCOLATE && col % 2 === 0 || blockers[b].type === BLOCKER_ICE && col % 2 === 1 || Math.random() < 0.07)) { if (col !== openCol) { candy.setType(candy.type, SPECIAL_NONE, blockers[b].type); placedBlocker = true; } } } } // Always add candies to the top of the boardContainer to ensure correct z-ordering boardContainer.addChild(candy); if (boardContainer.children && boardContainer.children.length > 1) { // Move the newly added candy to the top (end of children array) boardContainer.removeChild(candy); boardContainer.addChild(candy); } candies.push(candy); board[row][col] = candy; } } // Remove initial matches removeInitialMatches(); // Ensure no 3-in-a-row/col at board creation for (var row = 0; row < BOARD_ROWS; ++row) { //{aU.1} for (var col = 0; col < BOARD_COLS; ++col) { //{aU.2} // Only check non-blockers if (board[row][col].blocker !== BLOCKER_NONE) continue; //{aU.3} var forbiddenTypes = {}; // Check left if (col >= 2 && board[row][col - 1].type === board[row][col - 2].type) { forbiddenTypes[board[row][col - 1].type] = true; //{aU.4} } // Check up if (row >= 2 && board[row - 1][col].type === board[row - 2][col].type) { forbiddenTypes[board[row - 1][col].type] = true; //{aU.5} } // If current type is forbidden, reroll var tries = 0; while (forbiddenTypes[board[row][col].type] && tries < 10) { //{aU.6} var newType = getRandomCandyType(); // Avoid forbidden types while (forbiddenTypes[newType] && tries < 10) { newType = getRandomCandyType(); tries++; } board[row][col].setType(newType); tries++; } } //{aU.7} } //{aU.8} } // 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) { // Only check non-blockers if (board[row][col].blocker !== BLOCKER_NONE) continue; //{aW.1} var changed = false; var tries = 0; do { var type = board[row][col].type; var hasRowMatch = col >= 2 && board[row][col - 1].type === type && board[row][col - 2].type === type; var hasColMatch = row >= 2 && board[row - 1][col].type === type && board[row - 2][col].type === type; if (hasRowMatch || hasColMatch) { var forbiddenTypes = {}; if (hasRowMatch) forbiddenTypes[board[row][col - 1].type] = true; if (hasColMatch) forbiddenTypes[board[row - 1][col].type] = true; var newType = getRandomCandyType(); while ((forbiddenTypes[newType] || newType === type) && tries < 10) { newType = getRandomCandyType(); tries++; } board[row][col].setType(newType); changed = true; } else { changed = false; } tries++; } while (changed && tries < 10); } } } // 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 = r2; c1.col = c2c; c2.row = r1; c2.col = c1c; // Ensure board and candies are in sync after swap board[c1.row][c1.col] = c1; board[c2.row][c2.col] = c2; // 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) { swapping = false; cb(); } }); c2.moveTo(c2.col * CELL_SIZE + CELL_SIZE / 2, c2.row * CELL_SIZE + CELL_SIZE / 2, 180, function () { done++; if (done === 2 && cb) { swapping = false; 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); // Allow swapping in all four directions (up, down, left, right) return dr === 1 && dc === 0 || dr === 0 && dc === 1; } // Find all matches on the board, including special candy creation function findMatches() { var matches = []; var matchGroups = []; // For special candy creation // Horizontal for (var row = 0; row < BOARD_ROWS; ++row) { var count = 1; var startCol = 0; 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) { var group = []; for (var k = 0; k < count; ++k) { group.push(board[row][col - 1 - k]); } matches = matches.concat(group); matchGroups.push(group); } count = 1; startCol = col; } } if (count >= 3) { var group = []; for (var k = 0; k < count; ++k) { group.push(board[row][BOARD_COLS - 1 - k]); } matches = matches.concat(group); matchGroups.push(group); } } // Vertical for (var col = 0; col < BOARD_COLS; ++col) { var count = 1; var startRow = 0; 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) { var group = []; for (var k = 0; k < count; ++k) { group.push(board[row - 1 - k][col]); } matches = matches.concat(group); matchGroups.push(group); } count = 1; startRow = row; } } if (count >= 3) { var group = []; for (var k = 0; k < count; ++k) { group.push(board[BOARD_ROWS - 1 - k][col]); } matches = matches.concat(group); matchGroups.push(group); } } // Detect 2x2 square matches and mark for bomb for (var row = 0; row < BOARD_ROWS - 1; ++row) { for (var col = 0; col < BOARD_COLS - 1; ++col) { var c1 = board[row][col]; var c2 = board[row][col + 1]; var c3 = board[row + 1][col]; var c4 = board[row + 1][col + 1]; if (c1.type === c2.type && c1.type === c3.type && c1.type === c4.type && c1.blocker === BLOCKER_NONE && c2.blocker === BLOCKER_NONE && c3.blocker === BLOCKER_NONE && c4.blocker === BLOCKER_NONE) { // Only add if not already in a match group for this square var alreadyMatched = false; for (var mg = 0; mg < matchGroups.length; ++mg) { var g = matchGroups[mg]; if (g.indexOf(c1) !== -1 && g.indexOf(c2) !== -1 && g.indexOf(c3) !== -1 && g.indexOf(c4) !== -1) { alreadyMatched = true; break; } } if (!alreadyMatched) { // Mark the top-left as bomb, others as normal c1.special = SPECIAL_BOMB; c1.setType(c1.type, SPECIAL_BOMB, c1.blocker); // Remove c1 from matches so it is not destroyed, but destroy the other 3 var group = [c2, c3, c4]; matches = matches.concat(group); matchGroups.push([c1, c2, c3, c4]); // Store a flag for c1 to trigger a special 2x2 bomb explosion in removeMatches c1._pendingSquareBomb = true; } } } } // Remove duplicates var unique = []; for (var i = 0; i < matches.length; ++i) { if (unique.indexOf(matches[i]) === -1) unique.push(matches[i]); } // Mark special candy creation (striped, bomb, rainbow) for (var g = 0; g < matchGroups.length; ++g) { var group = matchGroups[g]; if (group.length === 4) { // Striped candy: horizontal or vertical var isHorizontal = group[0].row === group[1].row; var specialCandy = group[1]; // Place special at second in group if (specialCandy.special === SPECIAL_NONE) { specialCandy.special = SPECIAL_STRIPED; specialCandy.setType(specialCandy.type, SPECIAL_STRIPED, specialCandy.blocker); // Mark orientation for correct effect specialCandy._stripedOrientation = isHorizontal ? "horizontal" : "vertical"; } // Remove the special candy from the group so it is not destroyed for (var i = 0; i < group.length; ++i) { if (group[i] === specialCandy) { group.splice(i, 1); break; } } } if (group.length === 5) { // Color bomb (rainbow) only for exactly 5-in-a-row var specialCandy = group[Math.floor(group.length / 2)]; // Place in the middle if (specialCandy.special === SPECIAL_NONE) { specialCandy.special = SPECIAL_RAINBOW; specialCandy.setType(specialCandy.type, SPECIAL_RAINBOW, specialCandy.blocker); } // Remove the special candy from the group so it is not destroyed for (var i = 0; i < group.length; ++i) { if (group[i] === specialCandy) { group.splice(i, 1); break; } } } } // Bomb candy for T or L shape // Find intersections of horizontal and vertical matches for (var i = 0; i < matchGroups.length; ++i) { var groupA = matchGroups[i]; if (groupA.length !== 3) continue; for (var j = i + 1; j < matchGroups.length; ++j) { var groupB = matchGroups[j]; if (groupB.length !== 3) continue; // Check for intersection for (var a = 0; a < 3; ++a) { for (var b = 0; b < 3; ++b) { if (groupA[a] === groupB[b]) { // Place bomb at intersection var bombCandy = groupA[a]; if (bombCandy.special === SPECIAL_NONE) { bombCandy.special = SPECIAL_BOMB; bombCandy.setType(bombCandy.type, SPECIAL_BOMB, bombCandy.blocker); } } } } } } return unique; } // Remove matched candies and animate, including special candy activation and blockers function removeMatches(matches, cb) { if (!matches || matches.length === 0) { if (cb) cb(); return; } LK.getSound('match').play(); var done = 0; var toRemove = []; // Activate special candies in matches for (var i = 0; i < matches.length; ++i) { var candy = matches[i]; if (candy.special === SPECIAL_STRIPED) { // Striped candy: clear row if created from horizontal match, column if from vertical // Determine orientation by checking if the special was created from a horizontal or vertical match // We'll use a property set during match detection, or fallback to random if not set (legacy) var isHorizontal = false; if (typeof candy._stripedOrientation !== "undefined") { isHorizontal = candy._stripedOrientation === "horizontal"; } else { // Fallback: random (legacy, but should not happen with new match logic) isHorizontal = Math.random() < 0.5; } if (isHorizontal) { // Clear row for (var c = 0; c < BOARD_COLS; ++c) { var target = board[candy.row][c]; if (toRemove.indexOf(target) === -1) toRemove.push(target); } } else { // Clear column for (var r = 0; r < BOARD_ROWS; ++r) { var target = board[r][candy.col]; if (toRemove.indexOf(target) === -1) toRemove.push(target); } } } else if (candy.special === SPECIAL_RAINBOW) { // Clear all candies of a random type on board var colorType = getRandomCandyType(); for (var r = 0; r < BOARD_ROWS; ++r) { for (var c = 0; c < BOARD_COLS; ++c) { var target = board[r][c]; if (target.type === colorType && toRemove.indexOf(target) === -1) toRemove.push(target); } } } else if (candy.special === SPECIAL_BOMB) { // If this bomb was created by a 2x2 square match, do a shine and destroy 8 neighbors if (candy._pendingSquareBomb) { // Shine effect: flash the bomb candy LK.effects.flashObject(candy, 0xffff00, 400); // Destroy 8 neighbors (not self) for (var dr = -1; dr <= 1; ++dr) { for (var dc = -1; dc <= 1; ++dc) { var rr = candy.row + dr, cc = candy.col + dc; if (rr >= 0 && rr < BOARD_ROWS && cc >= 0 && cc < BOARD_COLS) { if (rr === candy.row && cc === candy.col) continue; // skip self var target = board[rr][cc]; if (toRemove.indexOf(target) === -1) toRemove.push(target); } } } // Remove the flag so it doesn't trigger again delete candy._pendingSquareBomb; } else { // Default bomb: Clear 3x3 area (including self) for (var dr = -1; dr <= 1; ++dr) { for (var dc = -1; dc <= 1; ++dc) { var rr = candy.row + dr, cc = candy.col + dc; if (rr >= 0 && rr < BOARD_ROWS && cc >= 0 && cc < BOARD_COLS) { var target = board[rr][cc]; if (toRemove.indexOf(target) === -1) toRemove.push(target); } } } } } else { if (toRemove.indexOf(candy) === -1) toRemove.push(candy); } } // Remove blockers if matched for (var i = 0; i < toRemove.length; ++i) { var candy = toRemove[i]; if (candy.blocker === BLOCKER_CHOCOLATE) { // Remove chocolate in one match candy.blocker = BLOCKER_NONE; candy.setType(candy.type, candy.special, BLOCKER_NONE); continue; } if (candy.blocker === BLOCKER_ICE) { // Remove ice in two matches: first match cracks, second removes if (!candy._iceCracked) { candy._iceCracked = true; // Tint or visually indicate cracked ice (optional) candy.setType(candy.type, candy.special, BLOCKER_ICE); // Do not mark as matched, so it remains for the next match continue; } else { candy.blocker = BLOCKER_NONE; delete candy._iceCracked; candy.setType(candy.type, candy.special, BLOCKER_NONE); // Now allow to be matched and removed continue; } } 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 === toRemove.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) { // Only move if the target cell is not already occupied by a visible, non-matched candy if (!board[row][col].visible || board[row][col].isMatched) { // Only allow candies to fall if there is a candy above (do not close the top of the column) 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; } } // Do not close the top of the column: if there is no candy above, leave the cell empty for refillBoard } } 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; // Only create a new candy if the cell is not already occupied by a visible, non-matched candy if (!board[row][col].visible || board[row][col].isMatched) { // 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; // Always add new candies to the top of the boardContainer to ensure correct z-ordering boardContainer.addChild(newCandy); if (boardContainer.children && boardContainer.children.length > 1) { // Move the newly added candy to the top (end of children array) boardContainer.removeChild(newCandy); 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) { if (candy._iceCracked) { delete candy._iceCracked; } 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; // Always add dummy candies to the bottom of the boardContainer so they don't cover visible candies boardContainer.addChildAt(dummy, 0); } } } } // 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, ensuring correct offset and boundaries // Remove any offset miscalculations: boardContainer.x/y is already set, so local is relative to grid origin // For touch/click, always use the event's x/y relative to the game, then subtract boardContainer.x/y to get local grid coordinates var localX = x - boardContainer.x; var localY = y - boardContainer.y; // Clamp localX/localY to be within the grid area if (localX < 0 || localY < 0) return; var col = Math.floor(localX / CELL_SIZE); var row = Math.floor(localY / CELL_SIZE); // Clamp to grid bounds // Boundary check if (col < 0 || col >= BOARD_COLS || row < 0 || row >= BOARD_ROWS) return; var candy = board[row][col]; // Debug: Draw a highlight rectangle over the selected cell if (typeof handleTap._debugRect !== "undefined" && handleTap._debugRect) { boardContainer.removeChild(handleTap._debugRect); handleTap._debugRect.destroy(); handleTap._debugRect = null; } var debugRect = LK.getAsset('blocker_ice', { anchorX: 0, anchorY: 0, x: col * CELL_SIZE, y: row * CELL_SIZE, width: CELL_SIZE, height: CELL_SIZE, alpha: 0.25 }); // Insert debugRect at the bottom of boardContainer's children so it doesn't block candy input if (boardContainer.children && boardContainer.children.length > 0) { boardContainer.addChildAt(debugRect, 0); } else { boardContainer.addChild(debugRect); } handleTap._debugRect = debugRect; if (!candy || candy.blocker !== BLOCKER_NONE) return; // --- Bonus: Bomb usage --- if (selectedBonus === "bomb") { // Use bomb on this cell: destroy up to 12 tiles in a cross pattern (center, 4 orthogonal, 4 diagonal, and 3 more in a star/cross) var toRemove = []; var bombPattern = [[0, 0], // center [-1, 0], [1, 0], [0, -1], [0, 1], // orthogonal [-1, -1], [-1, 1], [1, -1], [1, 1], // diagonal [-2, 0], [2, 0], [0, -2], [0, 2] // extended cross ]; for (var i = 0; i < bombPattern.length; ++i) { var dr = bombPattern[i][0]; var dc = bombPattern[i][1]; var rr = row + dr; var cc = col + dc; if (rr >= 0 && rr < BOARD_ROWS && cc >= 0 && cc < BOARD_COLS) { var target = board[rr][cc]; if (target && target.blocker === BLOCKER_NONE && toRemove.indexOf(target) === -1) { toRemove.push(target); } } } removeMatches(toRemove, function () { clearMatchedCandies(); dropCandies(function () { refillBoard(function () { processMatches(); }); }); }); movesLeft--; // Using a bonus still costs a move updateGUI(); selectedBonus = null; bombBtnBg.scaleX = bombBtnBg.scaleY = 1.0; deselectAll(); return; } // If nothing selected, select this candy if (!selectedCandy) { deselectAll(); selectedCandy = candy; candy.setSelected(true); return; } // If clicking the same candy, deselect if (selectedCandy === candy) { deselectAll(); return; } // If adjacent, try to swap if (areAdjacent(selectedCandy, candy)) { swapping = true; selectedCandy.setSelected(false); candy.setSelected(false); // Lock input during animation var c1 = selectedCandy; var c2 = candy; deselectAll(); swapCandies(c1, c2, function () { // Special candy swap logic var specialActivated = false; // Rainbow (color bomb) swap if (c1.special === SPECIAL_RAINBOW || c2.special === SPECIAL_RAINBOW) { var colorType = c1.special === SPECIAL_RAINBOW ? c2.type : c1.type; var toRemove = []; for (var r = 0; r < BOARD_ROWS; ++r) { for (var c = 0; c < BOARD_COLS; ++c) { var target = board[r][c]; if (target.type === colorType) toRemove.push(target); } } removeMatches(toRemove, function () { clearMatchedCandies(); dropCandies(function () { refillBoard(function () { processMatches(); }); }); }); movesLeft--; updateGUI(); swapping = false; specialActivated = true; } // Striped + striped: clear row and col else if (c1.special === SPECIAL_STRIPED && c2.special === SPECIAL_STRIPED) { var toRemove = []; for (var c = 0; c < BOARD_COLS; ++c) { var t1 = board[c1.row][c]; if (toRemove.indexOf(t1) === -1) toRemove.push(t1); } for (var r = 0; r < BOARD_ROWS; ++r) { var t2 = board[r][c2.col]; if (toRemove.indexOf(t2) === -1) toRemove.push(t2); } removeMatches(toRemove, function () { clearMatchedCandies(); dropCandies(function () { refillBoard(function () { processMatches(); }); }); }); movesLeft--; updateGUI(); swapping = false; specialActivated = true; } // Bomb + any: clear 3x3 around both else if (c1.special === SPECIAL_BOMB || c2.special === SPECIAL_BOMB) { var toRemove = []; var bombCandies = [c1, c2]; for (var b = 0; b < 2; ++b) { if (bombCandies[b].special === SPECIAL_BOMB) { for (var dr = -1; dr <= 1; ++dr) { for (var dc = -1; dc <= 1; ++dc) { var rr = bombCandies[b].row + dr, cc = bombCandies[b].col + dc; if (rr >= 0 && rr < BOARD_ROWS && cc >= 0 && cc < BOARD_COLS) { var target = board[rr][cc]; if (toRemove.indexOf(target) === -1) toRemove.push(target); } } } } } removeMatches(toRemove, function () { clearMatchedCandies(); dropCandies(function () { refillBoard(function () { processMatches(); }); }); }); movesLeft--; updateGUI(); swapping = false; specialActivated = true; } if (!specialActivated) { // After swap, check for matches var matches = findMatches(); if (matches.length > 0) { processMatches(); movesLeft--; updateGUI(); swapping = false; } else { // No match, swap back LK.getSound('fail').play(); // Animate a quick shake for both candies var origX1 = c1.col * CELL_SIZE + CELL_SIZE / 2; var origY1 = c1.row * CELL_SIZE + CELL_SIZE / 2; var origX2 = c2.col * CELL_SIZE + CELL_SIZE / 2; var origY2 = c2.row * CELL_SIZE + CELL_SIZE / 2; tween(c1, { x: origX1 + 20 }, { duration: 60, onFinish: function onFinish() { tween(c1, { x: origX1 }, { duration: 60 }); } }); tween(c2, { x: origX2 - 20 }, { duration: 60, onFinish: function onFinish() { tween(c2, { x: origX2 }, { duration: 60 }); } }); // Actually swap back the candies to their original positions in the board and update their row/col swapCandies(c1, c2, function () { swapping = false; deselectAll(); // Re-select the original candy for user feedback selectedCandy = c1; c1.setSelected(true); }); movesLeft--; updateGUI(); } } }); return; } // Not adjacent, select new candy 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; deselectAll(); // Save progress after move storage.currentLevel = currentLevel; storage.score = score; storage.movesLeft = movesLeft; checkGameEnd(); return; } removeMatches(matches, function () { clearMatchedCandies(); dropCandies(function () { refillBoard(function () { // --- Update objective progress after all matches/refills --- for (var i = 0; i < currentObjectives.length; ++i) { var obj = currentObjectives[i]; if (obj.type === OBJECTIVE_COLLECT) { // Count how many of this color were matched (destroyed) this turn var matched = 0; var assetId = OBJECTIVE_ICONS[obj.color]; for (var j = 0; j < matches.length; ++j) { // For blue, count both 'blue_gear' and 'candy_blue' as valid if (obj.color === 'blue') { if ((matches[j].type === 'blue_gear' || matches[j].type === 'candy_blue') && matches[j].isMatched) matched++; } else { if (matches[j].type === assetId && matches[j].isMatched) matched++; } } currentObjectiveProgress[i] += matched; } else if (obj.type === OBJECTIVE_CLEAR) { // Count blockers destroyed var cleared = 0; for (var j = 0; j < matches.length; ++j) { if (matches[j].blocker === obj.blocker && matches[j].isMatched) cleared++; } currentObjectiveProgress[i] += cleared; } else if (obj.type === OBJECTIVE_RESCUE) { // Rescue: count mechanics (special candies) that reached bottom row and were matched var rescued = 0; for (var j = 0; j < matches.length; ++j) { if (matches[j].special === SPECIAL_BOMB && matches[j].row === BOARD_ROWS - 1 && matches[j].isMatched) rescued++; } currentObjectiveProgress[i] += rescued; } // Clamp to max if (currentObjectiveProgress[i] > obj.count) currentObjectiveProgress[i] = obj.count; } updateProgressPanel(); updateStarPanel(); // --- Timed Bombs: decrement and check for explosion --- for (var t = timedBombs.length - 1; t >= 0; --t) { timedBombs[t].movesLeft--; if (timedBombs[t].movesLeft <= 0) { // Bomb explodes: game over LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); return; } } // --- Conveyor: shift candies if active --- if (conveyorActive) { shiftConveyor(); } // Chocolate spread: after all moves, if any chocolate exists, spread to adjacent var chocolateList = []; for (var row = 0; row < BOARD_ROWS; ++row) { for (var col = 0; col < BOARD_COLS; ++col) { var c = board[row][col]; if (c.blocker === BLOCKER_CHOCOLATE) chocolateList.push(c); } } if (chocolateList.length > 0) { // Try to spread chocolate to adjacent non-blocker, non-special, non-chocolate for (var i = 0; i < chocolateList.length; ++i) { var c = chocolateList[i]; var dirs = [[0, 1], [1, 0], [0, -1], [-1, 0]]; for (var d = 0; d < dirs.length; ++d) { var rr = c.row + dirs[d][0], cc = c.col + dirs[d][1]; if (rr >= 0 && rr < BOARD_ROWS && cc >= 0 && cc < BOARD_COLS) { var target = board[rr][cc]; if (target.blocker === BLOCKER_NONE && target.special === SPECIAL_NONE) { target.blocker = BLOCKER_CHOCOLATE; target.setType(target.type, target.special, BLOCKER_CHOCOLATE); break; } } } } } // After all refills, check for new matches var newMatches = findMatches(); if (newMatches.length > 0) { processMatches(); } else { swapping = false; isProcessing = false; deselectAll(); // Save progress after move storage.currentLevel = currentLevel; storage.score = score; storage.movesLeft = movesLeft; // --- Check for win/lose conditions --- if (allObjectivesComplete()) { // Celebrate: flash screen, animate stars, etc. for (var s = 0; s < 3; ++s) { if (starIcons[s].alpha === 1) LK.effects.flashObject(starIcons[s], 0xffff00, 800); } LK.effects.flashScreen(0x00ff00, 1000); LK.showYouWin(); return; } checkGameEnd(); } }); }); }); } // Update GUI function updateGUI() { scoreTxt.setText('Score: ' + score); movesTxt.setText('Moves: ' + movesLeft); targetTxt.setText('Target: ' + targetScore); // Show "Boss" for every 5th level (level 5, 10, 15, ...) var levelNum = currentLevel + 1; if (levelNum % 5 === 0) { levelTxt.setText('Level: ' + levelNum + ' (Boss!)'); if (typeof levelTopRightTxt !== "undefined") { levelTopRightTxt.setText('Level: ' + levelNum + ' (Boss!)'); } } else { levelTxt.setText('Level: ' + levelNum); if (typeof levelTopRightTxt !== "undefined") { levelTopRightTxt.setText('Level: ' + levelNum); } } // Show level description and pass requirement at the bottom center if (!window.levelDescTxt) { window.levelDescTxt = new Text2('', { size: 60, fill: "#fff" }); window.levelDescTxt.anchor.set(0.5, 1); LK.gui.bottom.addChild(window.levelDescTxt); window.levelDescTxt.y = -100; window.levelDescTxt.visible = false; } // Format description to be 3 lines below each other var desc = levels[currentLevel].description || ''; var descLines = desc.split('\n'); while (descLines.length < 3) descLines.push(''); // Show objectives as requirements for (var i = 0; i < currentObjectives.length; ++i) { var obj = currentObjectives[i]; if (obj.type === OBJECTIVE_COLLECT) { // Use asset-linked name for blue gears if (obj.color === 'blue') { descLines.push('Collect ' + obj.count + ' blue gears'); } else { descLines.push('Collect ' + obj.count + ' ' + obj.color + ' gears'); } } if (obj.type === OBJECTIVE_CLEAR) descLines.push('Clear ' + obj.count + ' ' + (obj.blocker === BLOCKER_CHOCOLATE ? 'locked pipes' : 'ice')); if (obj.type === OBJECTIVE_RESCUE) descLines.push('Rescue ' + obj.count + ' mechanics'); } window.levelDescTxt.setText(descLines.join('\n')); updateProgressPanel(); updateStarPanel(); } // Check for win/lose function checkGameEnd() { // Game over if out of moves and not all objectives complete if (movesLeft <= 0) { if (allObjectivesComplete()) { LK.showYouWin(); } else { 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); }; // Drag-to-swap logic var dragStartCandy = null; var dragCurrentCandy = null; var dragActive = false; // Helper: get candy at pixel position (returns null if not valid) function getCandyAtPixel(x, y) { var localX = x - boardContainer.x; var localY = y - boardContainer.y; if (localX < 0 || localY < 0) return null; var col = Math.floor(localX / CELL_SIZE); var row = Math.floor(localY / CELL_SIZE); if (col < 0 || col >= BOARD_COLS || row < 0 || row >= BOARD_ROWS) return null; return board[row][col]; } // Override game.down for drag start game.down = function (x, y, obj) { // Don't allow tap in top left 100x100 if (x < 100 && y < 100) return; // If bomb bonus is selected, allow placing bomb anywhere on the board if (selectedBonus === "bomb") { // Convert to board coordinates var localX = x - boardContainer.x; var localY = y - boardContainer.y; if (localX < 0 || localY < 0) return; var col = Math.floor(localX / CELL_SIZE); var row = Math.floor(localY / CELL_SIZE); if (col < 0 || col >= BOARD_COLS || row < 0 || row >= BOARD_ROWS) return; var candy = board[row][col]; // Only allow bomb on non-blocker if (!candy || candy.blocker !== BLOCKER_NONE) return; // Use bomb on this cell: destroy up to 12 tiles in a cross pattern (center, 4 orthogonal, 4 diagonal, and 3 more in a star/cross) var toRemove = []; var bombPattern = [[0, 0], [-1, 0], [1, 0], [0, -1], [0, 1], [-1, -1], [-1, 1], [1, -1], [1, 1], [-2, 0], [2, 0], [0, -2], [0, 2]]; for (var i = 0; i < bombPattern.length; ++i) { var dr = bombPattern[i][0]; var dc = bombPattern[i][1]; var rr = row + dr; var cc = col + dc; if (rr >= 0 && rr < BOARD_ROWS && cc >= 0 && cc < BOARD_COLS) { var target = board[rr][cc]; if (target && target.blocker === BLOCKER_NONE && toRemove.indexOf(target) === -1) { toRemove.push(target); } } } removeMatches(toRemove, function () { clearMatchedCandies(); dropCandies(function () { refillBoard(function () { processMatches(); }); }); }); movesLeft--; // Using a bonus still costs a move updateGUI(); selectedBonus = null; bombBtnBg.scaleX = bombBtnBg.scaleY = 1.0; deselectAll(); return; } if (swapping || animating || isProcessing) return; var candy = getCandyAtPixel(x, y); if (!candy || candy.blocker !== BLOCKER_NONE) return; dragStartCandy = candy; dragCurrentCandy = candy; dragActive = true; deselectAll(); candy.setSelected(true); selectedCandy = candy; }; // Drag move handler game.move = function (x, y, obj) { if (!dragActive || swapping || animating || isProcessing) return; var candy = getCandyAtPixel(x, y); if (!candy || candy.blocker !== BLOCKER_NONE) return; if (candy === dragStartCandy) return; // Only allow swap with adjacent if (areAdjacent(dragStartCandy, candy)) { // Lock drag dragActive = false; dragStartCandy.setSelected(false); candy.setSelected(false); var c1 = dragStartCandy; var c2 = candy; deselectAll(); swapCandies(c1, c2, function () { // Special candy swap logic (same as tap) var specialActivated = false; if (c1.special === SPECIAL_RAINBOW || c2.special === SPECIAL_RAINBOW) { var colorType = c1.special === SPECIAL_RAINBOW ? c2.type : c1.type; var toRemove = []; for (var r = 0; r < BOARD_ROWS; ++r) { for (var c = 0; c < BOARD_COLS; ++c) { var target = board[r][c]; if (target.type === colorType) toRemove.push(target); } } removeMatches(toRemove, function () { clearMatchedCandies(); dropCandies(function () { refillBoard(function () { processMatches(); }); }); }); movesLeft--; updateGUI(); swapping = false; specialActivated = true; } else if (c1.special === SPECIAL_STRIPED && c2.special === SPECIAL_STRIPED) { var toRemove = []; for (var c = 0; c < BOARD_COLS; ++c) { var t1 = board[c1.row][c]; if (toRemove.indexOf(t1) === -1) toRemove.push(t1); } for (var r = 0; r < BOARD_ROWS; ++r) { var t2 = board[r][c2.col]; if (toRemove.indexOf(t2) === -1) toRemove.push(t2); } removeMatches(toRemove, function () { clearMatchedCandies(); dropCandies(function () { refillBoard(function () { processMatches(); }); }); }); movesLeft--; updateGUI(); swapping = false; specialActivated = true; } else if (c1.special === SPECIAL_BOMB || c2.special === SPECIAL_BOMB) { var toRemove = []; var bombCandies = [c1, c2]; for (var b = 0; b < 2; ++b) { if (bombCandies[b].special === SPECIAL_BOMB) { for (var dr = -1; dr <= 1; ++dr) { for (var dc = -1; dc <= 1; ++dc) { var rr = bombCandies[b].row + dr, cc = bombCandies[b].col + dc; if (rr >= 0 && rr < BOARD_ROWS && cc >= 0 && cc < BOARD_COLS) { var target = board[rr][cc]; if (toRemove.indexOf(target) === -1) toRemove.push(target); } } } } } removeMatches(toRemove, function () { clearMatchedCandies(); dropCandies(function () { refillBoard(function () { processMatches(); }); }); }); movesLeft--; updateGUI(); swapping = false; specialActivated = true; } if (!specialActivated) { var matches = findMatches(); if (matches.length > 0) { processMatches(); movesLeft--; updateGUI(); swapping = false; } else { // No match, swap back LK.getSound('fail').play(); var origX1 = c1.col * CELL_SIZE + CELL_SIZE / 2; var origY1 = c1.row * CELL_SIZE + CELL_SIZE / 2; var origX2 = c2.col * CELL_SIZE + CELL_SIZE / 2; var origY2 = c2.row * CELL_SIZE + CELL_SIZE / 2; tween(c1, { x: origX1 + 20 }, { duration: 60, onFinish: function onFinish() { tween(c1, { x: origX1 }, { duration: 60 }); } }); tween(c2, { x: origX2 - 20 }, { duration: 60, onFinish: function onFinish() { tween(c2, { x: origX2 }, { duration: 60 }); } }); // Actually swap back the candies to their original positions in the board and update their row/col swapCandies(c1, c2, function () { swapping = false; deselectAll(); // Re-select the original candy for user feedback selectedCandy = c1; c1.setSelected(true); }); movesLeft--; updateGUI(); } } }); } }; // Drag end handler game.up = function (x, y, obj) { dragActive = false; dragStartCandy = null; dragCurrentCandy = null; // Deselect all if not swapping if (!swapping && !animating && !isProcessing) { deselectAll(); } }; // 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 } }); // Level label at bottom left var levelTxt = new Text2('Level: 1', { size: 70, fill: "#fff" }); levelTxt.anchor.set(0, 1); LK.gui.bottomLeft.addChild(levelTxt); levelTxt.x = 0; levelTxt.y = 0; levelTxt.visible = false; // Add previous level button at the bottom left (but not in the top left 100x100 area) var prevLevelBtn = new Text2('Prev', { size: 90, fill: "#fff" }); prevLevelBtn.anchor.set(0, 1); LK.gui.bottomLeft.addChild(prevLevelBtn); prevLevelBtn.x = 120; // avoid top left menu area prevLevelBtn.y = 0; prevLevelBtn.visible = false; prevLevelBtn.interactive = true; prevLevelBtn.buttonMode = true; prevLevelBtn.down = function (x, y, obj) { if (currentLevel > 0) { currentLevel--; score = 0; movesLeft = levels[currentLevel].moves; targetScore = levels[currentLevel].target; // Save progress storage.currentLevel = currentLevel; storage.score = score; storage.movesLeft = movesLeft; updateGUI(); initBoard(); } }; // Add reset button at the bottom center var resetBtn = new Text2('Reset', { size: 90, fill: "#fff" }); resetBtn.anchor.set(0.5, 1); LK.gui.bottom.addChild(resetBtn); resetBtn.y = 0; // flush to bottom resetBtn.visible = false; resetBtn.interactive = true; resetBtn.buttonMode = true; resetBtn.down = function (x, y, obj) { // Reset current level: reset score, moves, board score = 0; movesLeft = levels[currentLevel].moves; targetScore = levels[currentLevel].target; // Save progress storage.currentLevel = currentLevel; storage.score = score; storage.movesLeft = movesLeft; updateGUI(); initBoard(); }; // Start game initBoard(); updateGUI(); // If the start screen is hidden (game started), show UI if (typeof startScreen !== "undefined" && !startScreen.visible) { if (scoreTxt) scoreTxt.visible = true; if (movesTxt) movesTxt.visible = true; if (targetTxt) targetTxt.visible = true; if (levelTopRightTxt) levelTopRightTxt.visible = true; if (bonusPanel) bonusPanel.visible = true; if (boardContainer) boardContainer.visible = true; if (levelTxt) levelTxt.visible = true; if (prevLevelBtn) prevLevelBtn.visible = true; if (resetBtn) resetBtn.visible = true; // Removed reference to nextLevelBtn which is not defined if (window.levelDescTxt) window.levelDescTxt.visible = true; }
===================================================================
--- original.js
+++ change.js
@@ -142,8 +142,9 @@
var OBJECTIVE_RESCUE = 3;
// Example icons for objectives (use existing assets for now)
var OBJECTIVE_ICONS = {
blue: 'blue_gear',
+ // blue gear asset
green: 'candy_green',
red: 'candy_red',
yellow: 'candy_yellow',
purple: 'candy_purple',
@@ -1788,10 +1789,16 @@
var obj = currentObjectives[i];
if (obj.type === OBJECTIVE_COLLECT) {
// Count how many of this color were matched (destroyed) this turn
var matched = 0;
+ var assetId = OBJECTIVE_ICONS[obj.color];
for (var j = 0; j < matches.length; ++j) {
- if (matches[j].type === 'candy_' + obj.color && matches[j].isMatched) matched++;
+ // For blue, count both 'blue_gear' and 'candy_blue' as valid
+ if (obj.color === 'blue') {
+ if ((matches[j].type === 'blue_gear' || matches[j].type === 'candy_blue') && matches[j].isMatched) matched++;
+ } else {
+ if (matches[j].type === assetId && matches[j].isMatched) matched++;
+ }
}
currentObjectiveProgress[i] += matched;
} else if (obj.type === OBJECTIVE_CLEAR) {
// Count blockers destroyed
@@ -1917,9 +1924,16 @@
while (descLines.length < 3) descLines.push('');
// Show objectives as requirements
for (var i = 0; i < currentObjectives.length; ++i) {
var obj = currentObjectives[i];
- if (obj.type === OBJECTIVE_COLLECT) descLines.push('Collect ' + obj.count + ' ' + (obj.color === 'blue' ? 'blue gears' : obj.color + ' gears'));
+ if (obj.type === OBJECTIVE_COLLECT) {
+ // Use asset-linked name for blue gears
+ if (obj.color === 'blue') {
+ descLines.push('Collect ' + obj.count + ' blue gears');
+ } else {
+ descLines.push('Collect ' + obj.count + ' ' + obj.color + ' gears');
+ }
+ }
if (obj.type === OBJECTIVE_CLEAR) descLines.push('Clear ' + obj.count + ' ' + (obj.blocker === BLOCKER_CHOCOLATE ? 'locked pipes' : 'ice'));
if (obj.type === OBJECTIVE_RESCUE) descLines.push('Rescue ' + obj.count + ' mechanics');
}
window.levelDescTxt.setText(descLines.join('\n'));