User prompt
Design a level progression system for the match-3 game where each level has specific objectives that must be completed within a limited number of moves. Implement three core objective types: 1) Collect Targets (e.g., gather 20 blue gears), 2) Clear Obstacles (e.g., remove 15 locked pipes), and 3) Rescue Items (e.g., free 5 trapped mechanics). Include a move counter that decrements after every swap and ends the level if reaches zero. Add progress trackers for each objective with real-time updates. For bonus systems, award stars based on remaining moves (3 stars >50% moves left, 2 stars >25%, 1 star if passed). Ensure levels gradually introduce new mechanics like timed bombs (explode after 3 moves) and conveyor belts (shift tiles every move). Make objectives visually distinct with icons/animations and add celebratory effects when completed.
User prompt
UPDATE GAME LEVEL PASSING CONDITIONS AND MAKE THEM MORE CONVENIENT AND EASIER.
User prompt
THE ONLY CONDITION FOR THE GAME TO BE GAME OVER IS THE END OF THE NUMBER OF MOVES. ORGANIZE ALL THE RULES ACCORDINGLY
User prompt
SET MOVES NUMBERS.
User prompt
RECREATE ENDGAME CONDITIONS. GO FROM EASY TO HARD, LEVEL 1 IS THE EASIEST, LEVEL 100 IS THE HARDEST. THE NUMBER OF MOVES FOR EACH LEVEL MUST BE CERTAIN
User prompt
EACH PERSON ENTERING THE GAME MUST START AT LEVEL 1
User prompt
THERE IS A BUG IN THE ENDGAME CONDITION. HOW TO CLEAR BLOCKERS
User prompt
THE PASS REQUIREMENT FOR THE SECTION MUST BE SPECIFIED BELOW
User prompt
THERE ARE BUGS IN THE GAME. WHEN A NEW GAME IS CREATED, THERE SHOULD NEVER BE 3 OF THE SAME BOXES NEXT TO EACH OTHER. THAT IS, THE FACTORS THAT PROVIDE THE EXPLOSION CONDITION CREATE BUGS.
User prompt
CHAPTER PASSING CONDITIONS SHOULD NOT BE POINTS. EACH ONE SHOULD HAVE DIFFERENT CONDITIONS. FOR EXAMPLE, EXPLODING ALL THE BOXES IN THE FIELD. EXPLODING ALL THE BOXES THAT DISAPPEAR WHEN AN EXPLOSION OCCURS NEXT TO THEM. THERE SHOULD BE VARIOUS FUN FEATURES SUCH AS BIRDS THAT START IN RANDOM PLACES IN THE BOXES COMING TO THE BOTTOM AND STAYING FREE.
User prompt
WE NEED 100 DİFFERENT LVL.
User prompt
BONUS BOMB CAN PLACE WİTH WHERE İ CLİCK
User prompt
İ SHOULD USE BOMB BONUS WHERE İ WANT. İ CAN CHOOSE THAT AND İ NEED TO PLACED THAT WHERE İ WANT. BOMB WİLL DESTROY 12 TİLE
User prompt
REMOVE NEXT LEVEL WRİTE ON NEXT LEVEL PAGE. AND YOU NEED TO WAİT FOR CLİCK NEXT LEVEL İN THAT SCREEN
User prompt
Please fix the bug: 'Uncaught ReferenceError: nextLevelBtn is not defined' in or related to this line: 'if (nextLevelBtn) {' Line Number: 824
User prompt
REMOVE NEXT LEVEL WRİTE
User prompt
REMOVE START WRİTE
User prompt
START SCREEN CHANGE ASSET WİTH START ASSETS
User prompt
add asset for start button
User prompt
add start button to start screen
User prompt
add a start screen to game
User prompt
add some bonus can choosable in a game. like bomb
User prompt
Remove the ability for the tops of the boxes to close as matches occur.
User prompt
We need to add a purpose to the game
User prompt
The game must be at least level 100
/**** * 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.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 ****/ // 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', '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; // Level system // Level design: early levels are easy, later levels are harder and require more skill // Unique level designs for variety and gameplay changes // 15 unique levels, then repeat with increased difficulty to reach 100+ var baseLevels = [ // Level 1: Classic match-3, no blockers, easy intro { moves: 20, target: 400, blockers: [], description: "Classic mode!\nNo blockers, just match candies.\nEnjoy a sweet start!", solutions: [[{ row: 8, col: 3 }, { row: 7, col: 3 }], [{ row: 5, col: 2 }, { row: 5, col: 3 }]] }, // Level 2: Chocolate row in the middle, must clear to progress { moves: 18, target: 600, blockers: [{ row: 4, type: BLOCKER_CHOCOLATE }], description: "Chocolate row blocks the center.\nClear it to keep matching!\nCan you break through?", solutions: [[{ row: 5, col: 3 }, { row: 4, col: 3 }], [{ row: 3, col: 2 }, { row: 4, col: 2 }]] }, // Level 3: Ice on the bottom, candies drop slower (simulate by more blockers) { moves: 16, target: 800, blockers: [{ row: 7, type: BLOCKER_ICE }, { row: 8, type: BLOCKER_ICE }], description: "Frozen bottom rows!\nBreak the ice to win.\nWatch your moves.", solutions: [[{ row: 6, col: 4 }, { row: 7, col: 4 }], [{ row: 8, col: 1 }, { row: 7, col: 1 }]] }, // Level 4: Alternating chocolate and ice, zig-zag pattern { moves: 15, target: 1000, blockers: [{ row: 2, type: BLOCKER_CHOCOLATE }, { row: 3, type: BLOCKER_ICE }, { row: 4, type: BLOCKER_CHOCOLATE }, { row: 5, type: BLOCKER_ICE }], description: "Zig-zag blockers ahead!\nPlan your matches carefully.\nClear all to win.", solutions: [[{ row: 1, col: 3 }, { row: 2, col: 3 }], [{ row: 5, col: 5 }, { row: 4, col: 5 }]] }, // Level 5: Boss - chocolate and ice checkerboard, higher target { moves: 14, target: 1400, 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 }], description: "Boss: Checkerboard blockers!\nBig challenge awaits you.\nBreak through for victory!", solutions: [[{ row: 1, col: 2 }, { row: 2, col: 2 }], [{ row: 5, col: 4 }, { row: 4, col: 4 }]] }, // Level 6: Cross and diamond of ice, with chocolate corners { moves: 13, target: 1700, blockers: [{ row: 0, type: BLOCKER_CHOCOLATE }, { row: 4, type: BLOCKER_ICE }, { row: 8, type: BLOCKER_CHOCOLATE }], description: "Cross & diamond of ice.\nChocolate corners block you.\nBreak through the patterns!", solutions: [[{ row: 3, col: 3 }, { row: 4, col: 3 }], [{ row: 8, col: 0 }, { row: 7, col: 0 }]] }, // Level 7: Chocolate on the sides, ice in the center, U-shape at bottom { moves: 12, target: 1900, blockers: [{ row: 0, type: BLOCKER_CHOCOLATE }, { row: 8, type: BLOCKER_CHOCOLATE }, { row: 4, type: BLOCKER_ICE }, { row: 7, type: BLOCKER_ICE }], description: "Sides blocked by chocolate.\nCenter blocked by ice.\nU-shape at the bottom!", solutions: [[{ row: 4, col: 3 }, { row: 5, col: 3 }], [{ row: 7, col: 1 }, { row: 6, col: 1 }]] }, // Level 8: Alternating rows of blockers, vertical stripe in center, random chocolate { moves: 12, target: 2100, blockers: [{ row: 1, type: BLOCKER_ICE }, { row: 3, type: BLOCKER_CHOCOLATE }, { row: 5, type: BLOCKER_ICE }, { row: 7, type: BLOCKER_CHOCOLATE }], description: "Alternating blockers appear!\nVertical stripe in the center.\nRandom chocolate surprises.", solutions: [[{ row: 2, col: 3 }, { row: 3, col: 3 }], [{ row: 5, col: 5 }, { row: 4, col: 5 }]] }, // Level 9: Spiral blockers, spiral arm from bottom right, random ice { moves: 11, target: 2400, 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 }], description: "Spiral blockers challenge you!\nCan you break out?\nWatch for spiral arms.", solutions: [[{ row: 4, col: 2 }, { row: 5, col: 2 }], [{ row: 7, col: 6 }, { row: 6, col: 6 }]] }, // Level 10: Boss - nearly every row blocked, zig-zag path open, random blockers { moves: 10, target: 3000, 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 }], description: "Boss: Nearly full blockers!\nSurvive the gauntlet.\nZig-zag path is open!", solutions: [[{ row: 1, col: 1 }, { row: 2, col: 1 }], [{ row: 7, col: 5 }, { row: 6, col: 5 }]] }, // Level 11: Hollow square of chocolate, ice in the center { moves: 13, target: 3500, blockers: [{ row: 0, type: BLOCKER_CHOCOLATE }, { row: 1, type: BLOCKER_CHOCOLATE }, { row: 2, type: BLOCKER_CHOCOLATE }, { row: 6, type: BLOCKER_CHOCOLATE }, { row: 7, type: BLOCKER_CHOCOLATE }, { row: 8, type: BLOCKER_CHOCOLATE }, { row: 4, type: BLOCKER_ICE }], description: "Hollow chocolate square.\nIce in the center.\nBreak the fortress!", solutions: [[{ row: 4, col: 3 }, { row: 3, col: 3 }], [{ row: 6, col: 1 }, { row: 5, col: 1 }]] }, // Level 12: Diagonal chocolate, anti-diagonal ice { moves: 12, target: 4000, 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 }], description: "Diagonal chocolate blocks.\nAnti-diagonal ice appears.\nDiagonal thinking required!", solutions: [[{ row: 2, col: 2 }, { row: 3, col: 2 }], [{ row: 7, col: 6 }, { row: 6, col: 6 }]] }, // Level 13: Center cross of chocolate, outer ring of ice { moves: 11, target: 4500, blockers: [{ row: 0, type: BLOCKER_ICE }, { row: 1, type: BLOCKER_ICE }, { row: 2, type: BLOCKER_ICE }, { row: 3, type: BLOCKER_ICE }, { row: 4, type: BLOCKER_CHOCOLATE }, { row: 5, type: BLOCKER_ICE }, { row: 6, type: BLOCKER_ICE }, { row: 7, type: BLOCKER_ICE }, { row: 8, type: BLOCKER_ICE }], description: "Center cross of chocolate.\nOuter ring of ice surrounds.\nBreak the cross to win!", solutions: [[{ row: 4, col: 3 }, { row: 3, col: 3 }], [{ row: 8, col: 0 }, { row: 7, col: 0 }]] }, // Level 14: Random blockers, every other column { moves: 10, target: 5000, blockers: [{ row: 2, type: BLOCKER_CHOCOLATE }, { row: 4, type: BLOCKER_ICE }, { row: 6, type: BLOCKER_CHOCOLATE }], description: "Random blockers appear!\nEvery other column is tricky.\nUnpredictable fun!", solutions: [[{ row: 2, col: 2 }, { row: 3, col: 2 }], [{ row: 6, col: 4 }, { row: 5, col: 4 }]] }, // Level 15: Boss - alternating chocolate and ice, spiral and cross { moves: 9, target: 6000, 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 }], description: "Boss: Alternating blockers!\nSpiral and cross patterns.\nThe ultimate challenge!", solutions: [[{ row: 1, col: 1 }, { row: 2, col: 1 }], [{ row: 7, col: 5 }, { row: 6, col: 5 }]] }]; // Now, repeat and vary the base levels to reach at least 100 levels var levels = []; for (var i = 0; i < 100; ++i) { // Copy base level, but increase target and decrease moves for higher levels var base = baseLevels[i % baseLevels.length]; // Deep copy to avoid mutation var newLevel = { moves: Math.max(5, base.moves - Math.floor(i / baseLevels.length)), // decrease moves every cycle target: base.target + 400 * i, // increase target each level blockers: [], description: base.description, solutions: base.solutions }; // Copy blockers, but for higher levels, add more blockers for (var b = 0; b < base.blockers.length; ++b) { newLevel.blockers.push({ row: base.blockers[b].row, type: base.blockers[b].type }); } // For every 10th level, add extra blockers for more challenge if (i > 0 && i % 10 === 0) { for (var r = 0; r < 3; ++r) { newLevel.blockers.push({ row: r, type: i % 20 === 0 ? BLOCKER_CHOCOLATE : BLOCKER_ICE }); } newLevel.description = "Super challenge!\nExtra blockers added.\nCan you win?"; } // For every 5th level, make it a "Boss" with higher target if (i > 0 && i % 5 === 0) { newLevel.target += 1000; newLevel.description = "Boss Level!\nBlockers everywhere.\nShow your skills!"; } // For every 25th level, make it a "Mega Boss" if (i > 0 && i % 25 === 0) { newLevel.target += 2000; newLevel.moves = Math.max(5, newLevel.moves - 2); newLevel.description = "Mega Boss!\nThe ultimate test.\nGood luck!"; } levels.push(newLevel); } var currentLevel = typeof storage.currentLevel !== "undefined" ? storage.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 = []; 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; } } } } 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 = 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); continue; } else { candy.blocker = BLOCKER_NONE; candy.setType(candy.type, candy.special, BLOCKER_NONE); 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; 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, 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 3x3 area centered on (row, col) var toRemove = []; for (var dr = -1; dr <= 1; ++dr) { for (var dc = -1; dc <= 1; ++dc) { var rr = row + dr, 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 () { // 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; 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 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(''); window.levelDescTxt.setText(descLines.join('\n')); } // Check for win/lose function checkGameEnd() { // Win condition: player reaches or exceeds the target score within the allowed moves if (score >= targetScore) { // Next level if available, else show win if (currentLevel < levels.length - 1) { // Flash the episode score LK.effects.flashScreen(0xffff00, 800); // Show a big score text in the center if (!window.episodeScoreTxt) { window.episodeScoreTxt = new Text2('', { size: 180, fill: "#fff" }); window.episodeScoreTxt.anchor.set(0.5, 0.5); LK.gui.center.addChild(window.episodeScoreTxt); } window.episodeScoreTxt.setText('Score: ' + score); window.episodeScoreTxt.visible = true; // Hide any previous next level frame and score text to prevent overlap if (window.episodeScoreTxt) window.episodeScoreTxt.visible = false; if (window.nextLevelBtnBig) window.nextLevelBtnBig.visible = false; // Show next level button in the center below the score, as a button with asset background and text label if (!window.nextLevelBtnBig) { // Create a container for the button window.nextLevelBtnBig = new Container(); // Add a button background asset (replace 'prism_button' with your asset id) var btnBg = LK.getAsset('prism_button', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, width: 500, height: 160 }); window.nextLevelBtnBig.addChild(btnBg); // Add the text label on top var btnLabel = new Text2('Next Level', { size: 120, fill: 0xFFFFFF, font: "Impact, 'Arial Black', Tahoma" }); btnLabel.anchor.set(0.5, 0.5); btnLabel.x = 0; btnLabel.y = 0; window.nextLevelBtnBig.addChild(btnLabel); window.nextLevelBtnBig.anchorX = 0.5; window.nextLevelBtnBig.anchorY = 0.5; window.nextLevelBtnBig.x = 0; window.nextLevelBtnBig.y = 200; LK.gui.center.addChild(window.nextLevelBtnBig); window.nextLevelBtnBig.interactive = true; window.nextLevelBtnBig.buttonMode = true; window.nextLevelBtnBig.down = function (x, y, obj) { if (window.episodeScoreTxt) window.episodeScoreTxt.visible = false; if (window.nextLevelBtnBig) window.nextLevelBtnBig.visible = false; currentLevel++; score = 0; movesLeft = levels[currentLevel].moves; targetScore = levels[currentLevel].target; // Save progress storage.currentLevel = currentLevel; storage.score = score; storage.movesLeft = movesLeft; updateGUI(); initBoard(); }; } window.nextLevelBtnBig.visible = true; if (window.episodeScoreTxt) window.episodeScoreTxt.visible = true; // Hide after 2 seconds if user doesn't tap LK.setTimeout(function () { if (window.episodeScoreTxt) window.episodeScoreTxt.visible = false; if (window.nextLevelBtnBig) window.nextLevelBtnBig.visible = false; if (window.nextLevelBtnBig && window.nextLevelBtnBig.visible) { // If still visible, auto-advance window.nextLevelBtnBig.down(); } }, 4000); } else { LK.showYouWin(); } } else if (movesLeft <= 0) { // Lose condition: player runs out of moves before reaching the target score 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 (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
@@ -783,9 +783,9 @@
boardContainer.visible = true;
if (levelTxt) levelTxt.visible = true;
if (prevLevelBtn) prevLevelBtn.visible = true;
if (resetBtn) resetBtn.visible = true;
- if (nextLevelBtn) nextLevelBtn.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
@@ -2091,7 +2091,7 @@
if (boardContainer) boardContainer.visible = true;
if (levelTxt) levelTxt.visible = true;
if (prevLevelBtn) prevLevelBtn.visible = true;
if (resetBtn) resetBtn.visible = true;
- if (nextLevelBtn) nextLevelBtn.visible = true;
+ // Removed reference to nextLevelBtn which is not defined
if (window.levelDescTxt) window.levelDescTxt.visible = true;
}
\ No newline at end of file