Code edit (1 edits merged)
Please save this source code
User prompt
in the bottom center of the playing field it should say “Glaud” in small orange. you will add this text.
User prompt
small orange “Glaud” in the top center of the playing field.
User prompt
small orange “Glaud” in the top center of the playing field.
User prompt
In the top right corner of the screen it should say little orange “Glaud”.
User prompt
make a background that completely covers the back.
User prompt
make a background that completely covers the back.
User prompt
Please fix the bug: 'Timeout.tick error: popPromises is not defined' in or related to this line: 'popPromises.push({' Line Number: 772
User prompt
Please fix the bug: 'Timeout.tick error: popPromises is not defined' in or related to this line: 'popPromises.push({' Line Number: 771
User prompt
Please fix the bug: 'Timeout.tick error: popPromises is not defined' in or related to this line: 'popPromises.push({' Line Number: 772
User prompt
Please fix the bug: 'Timeout.tick error: popPromises is not defined' in or related to this line: 'popPromises.push({' Line Number: 773
User prompt
Please fix the bug: 'Timeout.tick error: Cannot read properties of undefined (reading 'push')' in or related to this line: 'popPromises.push({' Line Number: 742
User prompt
Please fix the bug: 'Timeout.tick error: handleCompletion is not defined' in or related to this line: 'handleCompletion(actions, function () {' Line Number: 266
User prompt
slide the game background 200 to the right
User prompt
slide the playground 200 to the right
User prompt
Make sure that the Level text and the playing field are in the same place horizontally.
User prompt
Problem: The "Level: X" text is currently positioned automatically by the GUI system (LK.gui.top), likely near the very top of the screen, not aligned with the game board which is shifted down. Goal: Align the top of the "Level: X" text horizontally with the top edge of the gameBoardContainer. Solution Explanation: Remove from GUI Control: Stop adding the levelTxt to the automatic GUI container (LK.gui.top.addChild(levelTxt)). The GUI system's automatic placement prevents precise alignment with the board. Add to Main Stage: Instead, add the levelTxt directly to the main game stage using game.addChild(levelTxt). This allows you to manually control its exact x and y coordinates. Set Horizontal Position (X): Calculate the desired horizontal position. To center it on the screen, set levelTxt.x = screenWidth / 2; (e.g., levelTxt.x = 2048 / 2;). Ensure the text's horizontal anchor is set to 0.5 (levelTxt.anchor.set(0.5, 0);) so it centers correctly around this point. Set Vertical Position (Y): This is the key step. Set the levelTxt.y coordinate to be exactly the same as the calculated top y coordinate of the gameBoardContainer. In your setupGame function, this value is stored in the containerY variable (const containerY = (2732 - boardPixelHeight) / 2 + 200;). So, set levelTxt.y = containerY;. Anchor: Make sure the text's vertical anchor is 0 (levelTxt.anchor.set(0.5, 0);). This ensures that the top of the text aligns with the containerY value. (Optional) Add Padding: If you don't want the text touching the board, subtract a small amount from the y coordinate after setting it: levelTxt.y = containerY - 80; (adjust the 80 value for desired spacing). By adding the text directly to the game stage and manually setting its y coordinate to match the gameBoardContainer's calculated y coordinate, you achieve the desired horizontal alignment.
User prompt
align horizontally with the level.
User prompt
Method 1: Increase the Size of Each Cell and Candy Concept: Instead of an 8x8 grid using, say, 100x100 pixel cells, you change the fundamental unit to be 200x200 pixels. Everything is drawn larger from the start. Graphics: Your images for candies and cell backgrounds should ideally be designed for this larger size to avoid looking pixelated when drawn bigger. If using shapes drawn by code, their dimensions should be doubled. Positioning: Calculations that place candies based on their row, column, and the cell size will automatically adjust because the cell size value itself has increased. Centering the whole board on the screen should also still work correctly using the board's new, larger total dimensions. Pros: Results in the sharpest visuals with no blurriness. Interaction (clicking) usually works without extra adjustments. Cons: May require you to have or create larger, higher-resolution graphics for your candies and cells. Method 2: Scale the Entire Game Board Container Concept: Keep the original size for each cell and candy (e.g., 100x100 pixels). After the entire 8x8 grid is put together in its container, you tell the game engine to simply stretch that whole container to be twice as wide and twice as tall. Graphics: You don't need to change your original candy or cell graphics. However, because they are being stretched like a photograph being enlarged, they will likely look blurry or pixelated. Positioning: This is trickier. Just scaling the container usually makes it expand from its top-left corner. To keep the enlarged board centered (or correctly shifted), you must calculate its starting position based on its final, scaled-up size, not its original size. This requires more careful calculation. Pros: Quicker to implement as you don't need to change assets or fundamental size variables. Cons: Visual quality suffers (blurriness/pixelation). Positioning math is more complex to get right. Click detection might need careful testing to ensure it still aligns correctly with the scaled graphics. Recommendation: For the best visual result, Method 1 (increasing cell size) is generally preferred, assuming you have or can create suitable graphics. If visual quality is less critical or you need a faster change, Method 2 (scaling the container) works, but pay close attention to correctly calculating the final position to keep it centered or shifted as intended. 14.5s
User prompt
double your playground
User prompt
Please fix the bug: 'ReferenceError: resolveDropsAndFills is not defined' in or related to this line: 'resolveDropsAndFills();' Line Number: 323
User prompt
Implementing Special Candy Swap Combinations Here's a breakdown of how to implement two common special candy swap effects, focusing on the logic, code steps, and crucial points to avoid errors: Mechanic 1: Color Bomb + Regular Candy Swap Goal: Swapping a Color Bomb (from a 5-match) with an adjacent normal candy destroys the bomb itself and all other candies on the board matching the normal candy's color. Implementation Steps: Modify swapCandies: Inside the callback after the swap animation finishes (e.g., within handleCompletion). Check if one candy is type === 'colorBomb' and the other !isSpecial (and a normal color type). If true: Store the normal candy's type on the bomb object (e.g., bombCandy.swappedWithType = normalCandy.type;). Set isSwapping = false;. Skip the regular findCompleteMatches() check. Call a new function: executeColorBombEffect(bombCandy, bombCandy.swappedWithType);. Create executeColorBombEffect(bombCandy, targetType): Set isProcessing = true;. Create an empty list: let candiesToDestroy = [];. Add bombCandy to candiesToDestroy. Set board[bombCandy.row][bombCandy.col] = null;. If targetType is valid (not special, not null): Iterate through all candies on the board. If a candy's type === targetType and !c.isSpecial, add it to candiesToDestroy (if not already added) and set its board position to null. If candiesToDestroy contains more than just the bomb: Play sound (LK.getSound('special').play()). Add score. updateUI(). Create an actions array for handleCompletion. For each candy in candiesToDestroy: Add a pop action: actions.push({ then: (resolve) => candy.pop(delay, resolve) });. Use a small, potentially staggered delay. Remove the candy from the global candies array. Use handleCompletion(actions, () => { ... }); to wait for all pops. Inside the handleCompletion callback: candiesToDestroy.forEach(c => c.destroy()); (Cleanup). Call resolveDropsAndFills(); to refill the board. If candiesToDestroy only contains the bomb (no targets found): Handle just popping the bomb itself using the same actions/handleCompletion structure, then call resolveDropsAndFills(). Specific Considerations (Color Bomb): Targeting: Ensure targetType is correctly identified and passed. Avoid Targeting Specials: The effect typically doesn't destroy other special candies of the target color. Add !c.isSpecial check. Self-Destruct: The Color Bomb must destroy itself. Chain Reactions: Activating other specials destroyed by the bomb is complex; avoid implementing this initially for simplicity. Bomb + Bomb: Requires a separate if check in swapCandies and a different effect function (e.g., executeBoardClearEffect). Mechanic 2: Striped Candy + Striped Candy Swap Goal: Swapping any two Striped Candies (type 'stripedH' or 'stripedV', from 4-matches) destroys all candies in both the entire row AND the entire column where the swap occurred. Implementation Steps: Modify swapCandies: Inside the animation completion callback, after the Color Bomb check. Add an else if block: else if ((candy1.type === 'stripedH' || candy1.type === 'stripedV') && (candy2.type === 'stripedH' || candy2.type === 'stripedV')). If true: Set isSwapping = false;. Skip the regular findCompleteMatches() check. Call a new function: executeDoubleStripedEffect(candy1); (Pass one candy to get the final position). Create executeDoubleStripedEffect(centerCandy): Set isProcessing = true;. Get the activation position: const targetRow = centerCandy.row;, const targetCol = centerCandy.col;. Create an empty list: let candiesToDestroy = [];. Scan Row: Iterate c from 0 to BOARD_SIZE-1. Add board[targetRow]?.[c] to candiesToDestroy if it exists and isn't already added. Scan Column: Iterate r from 0 to BOARD_SIZE-1. Add board[r]?.[targetCol] to candiesToDestroy if it exists and isn't already added. Set the board position to null for all candies in candiesToDestroy. Play a powerful sound. Add significant score. updateUI(). Create an actions array for handleCompletion. For each candy in candiesToDestroy: Calculate delay based on Manhattan distance from the center: const delay = (Math.abs(candy.row - targetRow) + Math.abs(candy.col - targetCol)) * 35;. Add a pop action: actions.push({ then: (resolve) => candy.pop(delay, resolve) });. Remove the candy from the global candies array. Use handleCompletion(actions, () => { ... }); to wait for all pops. Inside the handleCompletion callback: candiesToDestroy.forEach(c => c.destroy()); (Cleanup). Call resolveDropsAndFills(); to refill the board. Specific Considerations (Striped + Striped): Trigger: Ensure the if condition correctly identifies two striped types. No Duplicates: Use indexOf check when adding to candiesToDestroy to avoid adding the center candy twice. Self-Destruct: Both initial striped candies must be included in the destruction. Chain Reactions: Typically, other special candies caught in the crossfire are just destroyed, not activated. Keep it simple initially. Visuals: The distance-based delay creates a nice radiating explosion effect. General Critical Considerations (Apply to BOTH Mechanics): TIMING / SYNCHRONIZATION IS PARAMOUNT: This is the most common failure point. The entire sequence for the special effect (finding targets, updating the model, playing animations, waiting for animations, cleaning up) must complete before resolveDropsAndFills is called. The isProcessing flag must lock the game during this entire time. Using a reliable waiting mechanism (handleCompletion or Promises if available) is essential. Simple setTimeout delays for activation will not work correctly and will break the game state. Update Model First: Always set board[r][c] = null for candies being destroyed before their pop animation starts. Cleanup: Remember to destroy() the candy objects and remove them from the candies array after their animations finish. Other Combinations: Plan for other swaps (Striped+Bomb, Wrapped+Striped, etc.). Each needs its own else if in swapCandies and a dedicated execute...Effect function. Clear Code: Keep the effect logic separate and well-commented. Remove unused variables (isDropping). Implementing these special swaps requires careful attention to the sequence of events and ensuring the game waits appropriately at each stage. Get the timing right, and these effects will greatly enhance the game!
User prompt
Understanding the Logic: Trigger: Swapping a colorBomb type candy with an adjacent normal (non-special) candy. Target: The type (e.g., 'redCandy', 'blueCandy') of the normal candy it was swapped with. Action: Destroy the Color Bomb and all candies on the board that match the targetType. Result: Initiate the standard board resolution process (drops and fills) for the cleared spaces. 2. How to Implement in Code (Steps): Update swapCandies Function: When two candies (candy1, candy2) are swapped, check their types. If one is colorBomb and the other is a normal candy (check !candyX.isSpecial and ensure its type is a standard color), do not proceed to the regular findMatches check after the swap animation. Instead, identify which candy is the bomb and which is the target. Store the targetType (e.g., bombCandy.swappedWithType = targetCandy.type;). After the swap animation completes (using handleCompletion or similar), call a new dedicated function, like executeColorBombEffect(bombCandy, targetType). Crucially: Ensure the swap animation fully completes before calling the effect function. Create executeColorBombEffect(bombCandy, targetType) Function: This function takes the bombCandy object and the targetType string as arguments. Lock: Set isProcessing = true; immediately to prevent other actions. Find Targets: Iterate through the candies array or the board grid. Identify the bombCandy itself. Identify all other candies where candy.type === targetType. Important: Usually, you don't destroy other special candies of the target color, so add a check like && !c.isSpecial. Collect all these identified candies (the bomb + targets) into a temporary list (e.g., candiesToDestroyByBomb). Update Model: Immediately set the board grid positions for all candies in candiesToDestroyByBomb to null. This is vital for the subsequent drop/fill logic. Prepare Animations: Create a list of "action" objects for your handleCompletion function. For each candy in candiesToDestroyByBomb, add an action that calls its pop() method. Include the bombCandy itself in this list. Stagger the delay parameter in pop() animations for a better visual effect (e.g., based on distance from the bomb, or just index). Sound & Score: Play a specific sound effect (LK.getSound('special').play() or a unique "zap" sound). Add score based on the number of candies destroyed. Wait: Use your handleCompletion mechanism to wait for all the pop() animations in your actions list to finish. Cleanup: Inside the handleCompletion callback (once all pops are done), physically destroy() all the popped candy objects and remove them from the main candies array. Next Step: After the cleanup is complete, call resolveDropsAndFills() to trigger the normal falling and refilling process for the spaces cleared by the bomb. 3. Important Considerations & Avoiding Pitfalls: Timing & Synchronization (Most Critical!): The entire Color Bomb effect (finding targets, popping animations, cleanup) MUST complete before the game attempts to handle drops (resolveDropsAndFills). Executing the effect asynchronously (like with a simple setTimeout) without proper waiting will break the game logic, leading to freezes, crashes, or unpredictable behavior. The isProcessing flag must protect this whole sequence, and a robust waiting mechanism (handleCompletion or Promises if supported) is essential. Target Color Identification: Reliably passing the correct targetType from swapCandies to executeColorBombEffect is key. Using a temporary property on the bomb object (like swappedWithType) set during the swap is a reasonable approach. Update Model First: Remove the targeted candies from the board array before starting their pop animations. This ensures handleDrops sees the correct empty spaces later. Self-Destruction: Ensure the Color Bomb itself is included in the list of candies to be destroyed. Targeting Other Specials: Decide if a Color Bomb should destroy other special candies that happen to be the target color. Typically, they don't. Add an !c.isSpecial check when identifying targets. Chain Reactions (Advanced): What happens if a Color Bomb destroys a striped candy? Should the striped candy also activate? This requires careful design. Implementing chains significantly increases complexity, as executeColorBombEffect would need to detect destroyed specials, trigger their effects recursively, and wait for everything to finish. It's easier to not implement chains initially. Color Bomb + Color Bomb Swap: Swapping two Color Bombs usually clears the entire screen. This requires a specific check in swapCandies and likely a separate function (e.g., executeBoardClearEffect). Visual Effect: Instead of all target candies popping simultaneously, consider staggering the pop() delays to create an effect that radiates outwards from the bomb or appears more random and impactful. Code Clarity: Keep the code organized. Separate the effect logic clearly. Remove unused variables like isDropping. By carefully managing the sequence of operations and ensuring the game waits correctly for animations and effects to complete, you can implement this powerful and fun Color Bomb mechanic successfully.
User prompt
Please fix the bug: 'Uncaught ReferenceError: isDropping is not defined' in or related to this line: 'if (game && typeof game.handleCandyClick === 'function' && !isSwapping && !isDropping) {' Line Number: 181
User prompt
Please fix the bug: 'Uncaught ReferenceError: isDropping is not defined' in or related to this line: 'if (game && typeof game.handleCandyClick === 'function' && !isSwapping && !isDropping) {' Line Number: 181
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { level: 1, score: 0, highScore: 0 }); /**** * Classes ****/ var Candy = Container.expand(function (type, row, col) { var self = Container.call(this); self.type = type || 'redCandy'; self.row = row || 0; self.col = col || 0; self.isSpecial = false; // Flag indicating if this candy is special (e.g., striped, wrapped) // Visual representation var visual = self.attachAsset(self.type, { anchorX: 0.5, anchorY: 0.5, alpha: 1 }); // Selection overlay (hidden by default) var overlay = self.attachAsset('selectedOverlay', { anchorX: 0.5, anchorY: 0.5, alpha: 0, visible: false // Start invisible }); // Marker for special candies (optional, initially hidden) var specialMarker = null; self.setSpecial = function (special) { self.isSpecial = special; // Add visual distinction for special candies if needed if (special) { if (!specialMarker) { // Example: Add a marker instead of scaling/pulsing the base visual specialMarker = self.attachAsset('specialMarkerCandy', { anchorX: 0.5, anchorY: 0.5, alpha: 1 // Make it visible }); } // Optional: Pulse animation for special marker tween.stop(specialMarker); // Stop previous tweens on marker specialMarker.alpha = 0.6; tween(specialMarker, { alpha: 1.0 }, { duration: 800, easing: tween.easeInOut, yoyo: true, // Go back and forth loop: true // Repeat indefinitely }); } else { if (specialMarker) { tween.stop(specialMarker); // Stop animation specialMarker.destroy(); specialMarker = null; } } }; self.select = function () { overlay.visible = true; tween.stop(overlay, { alpha: true }); // Stop previous animation tween(overlay, { alpha: 0.4 }, { duration: 150 }); }; self.deselect = function () { tween.stop(overlay, { alpha: true }); // Stop previous animation tween(overlay, { alpha: 0 }, { duration: 150, onFinish: function onFinish() { overlay.visible = false; // Hide after fade out } }); }; self.moveTo = function (newRow, newCol, animate, delay, onComplete) { self.row = newRow; self.col = newCol; var newX = CELL_SIZE * self.col + CELL_SIZE / 2; var newY = CELL_SIZE * self.row + CELL_SIZE / 2; if (animate) { tween.stop(self, { x: true, y: true }); // Stop previous movement tween(self, { x: newX, y: newY }, { duration: 300, // Consistent duration delay: delay || 0, easing: tween.easeOutQuad, // Smooth easing onFinish: onComplete }); } else { tween.stop(self, { x: true, y: true }); // Ensure any running tween is stopped self.x = newX; self.y = newY; if (onComplete) { onComplete(); } } }; // Pop animation - Returns the tween instance self.pop = function (delay, onComplete) { tween.stop(self, { alpha: true, scaleX: true, scaleY: true }); // Stop other tweens return tween(self, { alpha: 0, scaleX: 0.1, // Shrink down scaleY: 0.1 }, { duration: 250, delay: delay || 0, easing: tween.easeInQuad, // Ease in for disappearance onFinish: function onFinish() { // Note: Actual destruction and removal from arrays happen elsewhere if (onComplete) { onComplete(); } } }); }; // Override destroy to clean up marker tween var baseDestroy = self.destroy; self.destroy = function () { if (specialMarker) { tween.stop(specialMarker); } baseDestroy.call(self); // Call original destroy }; self.down = function (x, y, obj) { // Delegate to the main game click handler if (game && typeof game.handleCandyClick === 'function' && !isSwapping && !isDropping) { game.handleCandyClick(self); } }; return self; }); var GameBoard = Container.expand(function (rows, cols) { var self = Container.call(this); self.rows = rows || 8; self.cols = cols || 8; // Background board var boardVisual = self.attachAsset('gameBoard', { anchorX: 0.5, anchorY: 0.5 }); boardVisual.width = CELL_SIZE * self.cols; // Match width to grid boardVisual.height = CELL_SIZE * self.rows; // Match height to grid // Cell backgrounds self.cells = []; for (var row = 0; row < self.rows; row++) { self.cells[row] = []; // Initialize row array for (var col = 0; col < self.cols; col++) { var cell = self.attachAsset('cellBackground', { anchorX: 0.5, anchorY: 0.5, x: col * CELL_SIZE + CELL_SIZE / 2, y: row * CELL_SIZE + CELL_SIZE / 2 }); self.cells[row][col] = cell; // Store cell reference if needed later } } return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x5D94FB // A pleasant blue }); /**** * Game Code ****/ function handleCompletion(actions, callback) { var remaining = actions.length; if (remaining === 0) { callback(); } actions.forEach(function (action) { action.then(function () { remaining--; if (remaining === 0) { callback(); } }); }); } function resolveDropsAndFills() { handleDrops(); } function executeDoubleStripedEffect(centerCandy) { isProcessing = true; var targetRow = centerCandy.row; var targetCol = centerCandy.col; var candiesToDestroy = []; for (var c = 0; c < BOARD_SIZE; c++) { var _board$targetRow; var candy = (_board$targetRow = board[targetRow]) === null || _board$targetRow === void 0 ? void 0 : _board$targetRow[c]; if (candy && candiesToDestroy.indexOf(candy) === -1) { candiesToDestroy.push(candy); board[targetRow][c] = null; } } // Game state variables var popPromises = []; // Initialize popPromises array to store promises for animations for (var r = 0; r < BOARD_SIZE; r++) { var _board$r; var candy = (_board$r = board[r]) === null || _board$r === void 0 ? void 0 : _board$r[targetCol]; if (candy && candiesToDestroy.indexOf(candy) === -1) { candiesToDestroy.push(candy); board[r][targetCol] = null; } } // Play sound and update score LK.getSound('special').play(); score += candiesToDestroy.length * 20; updateUI(); // Prepare animations var actions = []; candiesToDestroy.forEach(function (candy) { var delay = (Math.abs(candy.row - targetRow) + Math.abs(candy.col - targetCol)) * 35; actions.push({ then: function then(resolve) { return candy.pop(delay, resolve); } }); }); // Wait for animations to complete handleCompletion(actions, function () { candiesToDestroy.forEach(function (c) { return c.destroy(); }); resolveDropsAndFills(); }); } function executeColorBombEffect(bombCandy, targetType) { isProcessing = true; // Lock the board // Find all candies of the targetType var candiesToDestroyByBomb = []; candies.forEach(function (c) { if (c.type === targetType && !c.isSpecial) { candiesToDestroyByBomb.push(c); } }); // Include the bombCandy itself candiesToDestroyByBomb.push(bombCandy); // Update board model candiesToDestroyByBomb.forEach(function (candy) { board[candy.row][candy.col] = null; }); // Prepare animations candiesToDestroyByBomb.forEach(function (candy, index) { popPromises.push({ then: function then(resolve) { candy.pop(index * 30, function () { candy.destroy(); resolve(); }); } }); }); // Play sound and update score LK.getSound('special').play(); score += candiesToDestroyByBomb.length * 10; updateUI(); // Wait for animations to complete (function (promises, callback) { var remaining = promises.length; if (remaining === 0) { callback(); } promises.forEach(function (promise) { promise.then(function () { remaining--; if (remaining === 0) { callback(); } }); }); })(popPromises, function () { // Cleanup and proceed to drop/fill candiesToDestroyByBomb.forEach(function (candy) { var index = candies.indexOf(candy); if (index > -1) { candies.splice(index, 1); } }); resolveDropsAndFills(); }); } function activateSpecialCandy(candy) { switch (candy.type) { case 'colorBomb': // Clear all candies of the color it was swapped with var targetType = candy.swappedWithType; // Assume swappedWithType is set during swap candies.forEach(function (c) { if (c.type === targetType) { popPromises.push({ then: function then(resolve) { c.pop(0, function () { c.destroy(); resolve(); }); } }); } }); break; case 'stripedH': // Clear entire row for (var col = 0; col < BOARD_SIZE; col++) { var rowCandy = board[candy.row][col]; if (rowCandy) { popPromises.push({ then: function then(resolve) { rowCandy.pop(col * 50, function () { // Add delay based on column index rowCandy.destroy(); resolve(); }); } }); } } break; case 'stripedV': // Clear entire column for (var row = 0; row < BOARD_SIZE; row++) { var colCandy = board[row][candy.col]; if (colCandy) { colCandy.pop(row * 50, function () { // Add delay based on row index colCandy.destroy(); }); } } break; default: break; } } // Sound for special candy creation/activation // Keep as is for selection // Example: Could be a different shape/texture // Renamed specialCandy shape slightly for clarity // Assets definition looks good, assuming LK.init.shape/sound works as expected. // Constants var BOARD_SIZE = 8; var CELL_SIZE = 200; // Increase size of each cell for better visibility var CANDY_TYPES = ['redCandy', 'yellowCandy', 'greenCandy', 'blueCandy', 'purpleCandy', 'orangeCandy']; var NUM_CANDY_TYPES = CANDY_TYPES.length; // Game state variables var board; // 2D array holding Candy objects or null var candies = []; // Flat list of all active Candy objects on the board var selectedCandy = null; var isSwapping = false; // True during the swap animation var isProcessing = false; // General flag for when board is resolving (matches, drops, fills) var isDropping = false; // Flag to indicate if candies are currently dropping var moves = 0; var score = 0; var targetScore = 0; var level = storage.level || 1; var highScore = storage.highScore || 0; // Game elements var gameBoardContainer; // Holds the visual board and candies var scoreTxt, movesTxt, levelTxt; // --- Core Game Logic Functions --- function setupGame() { // Create and center the game board container var background = LK.getAsset('gameBoard', { anchorX: 0.5, anchorY: 0.5, x: (2048 - CELL_SIZE * BOARD_SIZE) / 2 + 400, y: (2732 - CELL_SIZE * BOARD_SIZE) / 2 + 200 }); game.addChild(background); gameBoardContainer = new GameBoard(BOARD_SIZE, BOARD_SIZE); // Center the board (assuming 2048x2732 is the stage size) gameBoardContainer.x = (2048 - gameBoardContainer.width) / 2 + 400; // Shift right by 400 pixels gameBoardContainer.y = (2732 - gameBoardContainer.height) / 2 + 200; // Shift down by 200 pixels game.addChild(gameBoardContainer); // Setup UI elements scoreTxt = new Text2('Score: 0', { size: 70, fill: 0xFFFFFF, align: 'right' }); scoreTxt.anchor.set(1, 0); // Anchor top-right LK.gui.topRight.addChild(scoreTxt); scoreTxt.x = -20; // Add some padding from the edge movesTxt = new Text2('Moves: 0', { size: 70, fill: 0xFFFFFF, align: 'left' }); movesTxt.anchor.set(0, 0); // Anchor top-left LK.gui.topLeft.addChild(movesTxt); movesTxt.x = 20; // Add some padding from the edge levelTxt = new Text2('Level: 1', { size: 70, fill: 0xFFFFFF }); levelTxt.anchor.set(0.5, 0); // Anchor top-center LK.gui.top.addChild(levelTxt); // Initialize the first level initializeLevel(level); // Start background music LK.playMusic('bgmusic', { loop: true, fade: { start: 0, end: 0.5, duration: 1000 } }); // Attach global click listener (if Candy.down isn't sufficient) // game.down = handleGameClick; // Use this if clicking empty cells should deselect } function initializeLevel(levelNum) { level = levelNum; score = 0; moves = 20 + (level - 1) * 2; // Example: Increase moves per level targetScore = 1000 * level; // Example: Increase target score per level // Add additional objectives like clearing jellies or bringing ingredients down var objectives = { clearJellies: Math.floor(level / 2), bringIngredients: Math.floor(level / 3) }; selectedCandy = null; isSwapping = false; isProcessing = false; updateUI(); initializeBoard(); } function initializeBoard() { // Clear existing candies visually and from arrays for (var i = 0; i < candies.length; i++) { candies[i].destroy(); } candies = []; board = []; // Create the board array and fill with candies for (var row = 0; row < BOARD_SIZE; row++) { board[row] = []; for (var col = 0; col < BOARD_SIZE; col++) { board[row][col] = null; // Ensure initialized to null // Generate candy ensuring no initial matches var attempts = 0; do { var type = getRandomCandyType(); // Temporarily place to check for matches var tempCandy = { type: type }; // Lightweight check board[row][col] = tempCandy; attempts++; if (attempts > 50) { // Safety break for rare impossible scenarios console.warn("Struggling to place initial candy without match at", row, col); break; } } while (checkMatchAt(row, col)); // If check passed or safety break, create the real candy var candy = new Candy(type, row, col); candy.moveTo(row, col, false); // Place instantly gameBoardContainer.addChild(candy); board[row][col] = candy; // Place real candy in board candies.push(candy); } } // Final check: Ensure there are possible moves if (!hasPossibleMoves()) { console.log("No initial moves, reshuffling..."); // Use LK.setTimeout to allow the initial draw frame to happen first LK.setTimeout(reshuffleBoard, 100); } } // Helper to check for matches *only* at a specific new candy's location function checkMatchAt(row, col) { var candy = board[row][col]; if (!candy) { return false; } var type = candy.type; // Check Horizontal var count = 1; if (col > 0 && board[row][col - 1] && board[row][col - 1].type === type) { count++; } if (col > 1 && board[row][col - 2] && board[row][col - 2].type === type) { count++; } if (count >= 3) { return true; } // Check Vertical count = 1; if (row > 0 && board[row - 1][col] && board[row - 1][col].type === type) { count++; } if (row > 1 && board[row - 2][col] && board[row - 2][col].type === type) { count++; } if (count >= 3) { return true; } return false; } function getRandomCandyType() { var typesAvailable = Math.min(NUM_CANDY_TYPES, 3 + Math.floor(level / 3)); // Slower difficulty ramp return CANDY_TYPES[Math.floor(Math.random() * typesAvailable)]; } function updateUI() { scoreTxt.setText('Score: ' + score); movesTxt.setText('Moves: ' + moves); levelTxt.setText('Level: ' + level); } // Renamed from handleCandyClick to be clearer game.handleCandyClick = function (candy) { if (isProcessing || isSwapping || isDropping) { return; } // Ignore clicks during processing or animations if (selectedCandy === null) { selectedCandy = candy; // Highlight the cell background gameBoardContainer.cells[candy.row][candy.col].tint = 0xffff00; // Highlight with yellow } else { // Second selection if (selectedCandy === candy) { // Deselect if same candy clicked // Revert cell background tint gameBoardContainer.cells[selectedCandy.row][selectedCandy.col].tint = 0x704f3d; // Original color gameBoardContainer.cells[selectedCandy.row][selectedCandy.col].tint = 0x704f3d; // Revert tint selectedCandy = null; } else { // Check if candies are adjacent var rowDiff = Math.abs(selectedCandy.row - candy.row); var colDiff = Math.abs(selectedCandy.col - candy.col); if (rowDiff === 1 && colDiff === 0 || rowDiff === 0 && colDiff === 1) { // Adjacent: Initiate swap selectedCandy.deselect(); // Deselect the first one visually swapCandies(selectedCandy, candy); selectedCandy = null; // Clear selection after initiating swap } else { // Not adjacent: Change selection to the new candy // Revert previous selection's cell background tint gameBoardContainer.cells[selectedCandy.row][selectedCandy.col].tint = 0x704f3d; // Original color selectedCandy = candy; // Highlight new selection's cell background gameBoardContainer.cells[candy.row][candy.col].tint = 0xffff00; // Highlight with yellow LK.getSound('invalid').play(); // Sound for invalid selection change } } } }; function swapCandies(candy1, candy2) { if (isSwapping || isProcessing) { return; } // Prevent overlapping swaps isSwapping = true; // Store original positions for potential revert var candy1Row = candy1.row; var candy1Col = candy1.col; var candy2Row = candy2.row; var candy2Col = candy2.col; // Track the type of candy a color bomb is swapped with if (candy1.type === 'colorBomb') { candy1.swappedWithType = candy2.type; } else if (candy2.type === 'colorBomb') { candy2.swappedWithType = candy1.type; } // Update board model immediately board[candy1Row][candy1Col] = candy2; board[candy2Row][candy2Col] = candy1; // Update candy objects' internal row/col candy1.row = candy2Row; candy1.col = candy2Col; candy2.row = candy1Row; candy2.col = candy1Col; // Animate the swap visually LK.getSound('swap').play(); var tween1 = candy1.moveTo(candy2Row, candy2Col, true); var tween2 = candy2.moveTo(candy1Row, candy1Col, true); // After animation finishes (wait for the longer of the two, though they should be same duration) // Using a small timeout is simpler than tracking both tweens LK.setTimeout(function () { // Check if the swap resulted in a match var matchFound = checkMatchAt(candy1.row, candy1.col) || checkMatchAt(candy2.row, candy2.col) || findMatches().length > 0; // More robust check // Check for color bomb swap if (candy1.type === 'colorBomb' && !candy2.isSpecial) { executeColorBombEffect(candy1, candy2.type); isSwapping = false; return; } else if (candy2.type === 'colorBomb' && !candy1.isSpecial) { executeColorBombEffect(candy2, candy1.type); isSwapping = false; return; } if (matchFound) { // Valid swap, deduct move and process matches moves--; updateUI(); isSwapping = false; // Allow processing now processBoard(); // Start the match -> drop -> fill cycle // Check game state later, after processing finishes } else if ((candy1.type === 'stripedH' || candy1.type === 'stripedV') && (candy2.type === 'stripedH' || candy2.type === 'stripedV')) { executeDoubleStripedEffect(candy1); isSwapping = false; return; } else { // Invalid swap, animate back LK.getSound('invalid').play(); // Update board model back board[candy1Row][candy1Col] = candy1; board[candy2Row][candy2Col] = candy2; // Update candy objects' internal row/col back candy1.row = candy1Row; candy1.col = candy1Col; candy2.row = candy2Row; candy2.col = candy2Col; // Animate back candy1.moveTo(candy1Row, candy1Col, true); candy2.moveTo(candy2Row, candy2Col, true); // After swap back animation finishes LK.setTimeout(function () { isSwapping = false; // Ready for next input gameBoardContainer.cells[candy1.row][candy1.col].tint = 0x704f3d; // Revert tint gameBoardContainer.cells[candy2.row][candy2.col].tint = 0x704f3d; // Revert tint selectedCandy = null; }, 310); // Slightly longer than moveTo duration } }, 310); // Wait for swap animation to mostly complete } // Main processing loop trigger function processBoard() { if (isProcessing) { return; } // Avoid re-entry isProcessing = true; handleMatches(); } // Step 1: Find and handle matches function handleMatches() { var matches = findMatches(); if (matches.length > 0) { LK.getSound('match').play(); var pointsEarned = 0; var specialCandiesToCreate = []; // Store {row, col, type} for special creation // Mark candies for removal and calculate score/special var candiesToRemove = []; matches.forEach(function (match) { var matchSize = match.length; var pointsPerCandy = 10 * matchSize; // Bonus for longer matches // Decide if a special candy should be created from this match var createSpecial = null; // { type: 'stripedH', row: r, col: c } etc. if (matchSize >= 5) { // 5-in-a-row -> Color Bomb (example) createSpecial = { type: 'colorBomb', row: match.row, col: match.col + (match.horizontal ? Math.floor(matchSize / 2) : 0) }; if (!match.horizontal) { createSpecial.col = match.col; } if (!match.horizontal) { createSpecial.row = match.row + Math.floor(matchSize / 2); } } else if (matchSize === 4) { // 4-in-a-row -> Striped (example) createSpecial = { type: match.horizontal ? 'stripedV' : 'stripedH', row: match.row, col: match.col + (match.horizontal ? Math.floor(matchSize / 2) : 0) }; if (!match.horizontal) { createSpecial.col = match.col; } if (!match.horizontal) { createSpecial.row = match.row + Math.floor(matchSize / 2); } } // Add logic for L/T shapes for Wrapped candies if needed var specialCreated = false; for (var j = 0; j < matchSize; j++) { var row = match.horizontal ? match.row : match.row + j; var col = match.horizontal ? match.col + j : match.col; var candy = board[row][col]; if (candy && candiesToRemove.indexOf(candy) === -1) { if (candy.isSpecial) { popPromises.push({ then: function then(resolve) { setTimeout(function () { activateSpecialCandy(candy); resolve(); }, 300); // Delay special candy activation to allow normal match processing } }); } // Ensure not already marked candiesToRemove.push(candy); pointsEarned += pointsPerCandy * (candy.isSpecial ? 2 : 1); // More points for popping special candies // If this is the spot for special creation, mark it if (createSpecial && row === createSpecial.row && col === createSpecial.col) { specialCandiesToCreate.push(createSpecial); specialCreated = true; } } } // If special should be created but wasn't assigned to a specific candy (e.g., user swipe location matters), handle here // if (createSpecial && !specialCreated) { ... } }); score += pointsEarned; updateUI(); // Animate removal candiesToRemove.forEach(function (candy, index) { // Remove from board model immediately if (board[candy.row][candy.col] === candy) { board[candy.row][candy.col] = null; } // Animate pop popPromises.push({ then: function then(resolve) { candy.pop(index * 30, resolve); // Pass resolve directly to pop as onComplete } }); // Remove from the master list var listIndex = candies.indexOf(candy); if (listIndex > -1) { candies.splice(listIndex, 1); } }); // After all pops are visually done, proceed to drop/fill (function (promises, callback) { var remaining = promises.length; if (remaining === 0) { callback(); } promises.forEach(function (promise) { promise.then(function () { remaining--; if (remaining === 0) { callback(); } }); }); })(popPromises, function () { candiesToRemove.forEach(function (candy) { return candy.destroy(); }); // Final cleanup // Now create the special candies *before* dropping specialCandiesToCreate.forEach(function (spec) { var newType = spec.type; // Use the specified special type // In a real game, 'spec.type' would determine the *kind* of special candy // For now, we just make it visually 'special' var specialCandy = new Candy(spec.type, spec.row, spec.col); specialCandy.setSpecial(true); // Make it special specialCandy.moveTo(spec.row, spec.col, false); // Place instantly gameBoardContainer.addChild(specialCandy); board[spec.row][spec.col] = specialCandy; candies.push(specialCandy); LK.getSound('special').play(); }); handleDrops(); // Proceed to next step }); } else { // No matches found, board is stable isProcessing = false; selectedCandy = null; // Clear selection // Check for possible moves ONLY if the board is stable if (!hasPossibleMoves()) { reshuffleBoard(); // This will set isProcessing again briefly } else { checkGameState(); // Check win/loss only when stable and moves exist } } } // Step 2: Handle candies dropping down function handleDrops() { var dropsOccurred = false; var dropPromises = []; for (var col = 0; col < BOARD_SIZE; col++) { var emptyRow = BOARD_SIZE - 1; // Start checking from bottom // Find the lowest empty spot for (var row = BOARD_SIZE - 1; row >= 0; row--) { if (board[row][col] === null) { emptyRow = row; // Found the lowest empty row in this column break; // No need to check further up for the initial empty spot } } // Now check candies above this empty spot for (var row = BOARD_SIZE - 1; row >= 0; row--) { if (board[row][col] !== null) { // Check if there's a gap below this candy var targetRow = row; while (targetRow + 1 < BOARD_SIZE && board[targetRow + 1][col] === null) { targetRow++; } if (targetRow !== row) { // Move in model var candy = board[row][col]; board[targetRow][col] = candy; board[row][col] = null; // Animate move var dropTween = candy.moveTo(targetRow, col, true, 0); // Drop animation dropPromises.push({ then: function then(resolve) { if (dropTween && dropTween.vars) { dropTween.vars.onFinish = resolve; } else { resolve(); // Resolve immediately if dropTween or vars is undefined } } }); dropsOccurred = true; } } } } if (dropsOccurred) { (function (promises, callback) { var remaining = promises.length; if (remaining === 0) { callback(); } promises.forEach(function (promise) { promise.then(function () { remaining--; if (remaining === 0) { callback(); } }); }); })(dropPromises, handleFills); // Wait for drops, then fill } else { handleFills(); // No drops, proceed directly to filling } } // Step 3: Fill empty spaces from the top function handleFills() { var fillsOccurred = false; var fillPromises = []; for (var col = 0; col < BOARD_SIZE; col++) { var fillRow = -1; // Start generating candies above the board for (var row = BOARD_SIZE - 1; row >= 0; row--) { if (board[row][col] === null) { // Found an empty spot var type = getRandomCandyType(); var candy = new Candy(type, fillRow, col); // Create above the board candy.moveTo(fillRow, col, false); // Place instantly above gameBoardContainer.addChild(candy); board[row][col] = candy; // Assign to target cell in model candies.push(candy); // Animate move into place var delay = (-fillRow - 1) * 50; // Stagger based on how high it starts var fillTween = candy.moveTo(row, col, true, delay); fillPromises.push({ then: function then(resolve) { if (fillTween && fillTween.vars) { fillTween.vars.onFinish = resolve; } else { resolve(); // Resolve immediately if fillTween or vars is undefined } } }); fillsOccurred = true; fillRow--; // Next candy starts even higher } } } if (fillsOccurred) { if (typeof Promise !== 'undefined' && Promise.all) { Promise.all(fillPromises).then(handleMatches); // Wait for fills, then check for new matches (cascade) } else { console.error("Promise is not supported in this environment."); handleMatches(); // Fallback to directly calling handleMatches } } else { // No fills needed, implies no matches occurred in the previous step either. Board is stable. handleMatches(); // Final check (will find no matches and trigger end processing) } } function findMatches() { var matches = []; var checked = []; // Keep track of candies already part of a found match function markChecked(candy) { if (candy && checked.indexOf(candy) === -1) { checked.push(candy); } } // Check horizontal matches for (var row = 0; row < BOARD_SIZE; row++) { for (var col = 0; col < BOARD_SIZE - 2; col++) { var candy1 = board[row][col]; if (!candy1 || checked.indexOf(candy1) > -1) { continue; } // Skip nulls or already checked var candy2 = board[row][col + 1]; var candy3 = board[row][col + 2]; if (candy2 && candy3 && candy1.type === candy2.type && candy1.type === candy3.type) { // Found a horizontal match of at least 3 var currentMatch = [candy1, candy2, candy3]; var matchLength = 3; // Check for longer match for (var k = col + 3; k < BOARD_SIZE; k++) { var nextCandy = board[row][k]; if (nextCandy && nextCandy.type === candy1.type) { currentMatch.push(nextCandy); matchLength++; } else { break; // End of match } } // Store match details matches.push({ candies: currentMatch, // Store the actual candy objects row: row, col: col, length: matchLength, horizontal: true }); // Mark all candies in this match as checked currentMatch.forEach(markChecked); col += matchLength - 1; // Skip checked candies in the outer loop } } } // Check vertical matches for (var col = 0; col < BOARD_SIZE; col++) { for (var row = 0; row < BOARD_SIZE - 2; row++) { var candy1 = board[row][col]; // Skip nulls or candies already part of a horizontal match if (!candy1 || checked.indexOf(candy1) > -1) { continue; } var candy2 = board[row + 1][col]; var candy3 = board[row + 2][col]; if (candy2 && candy3 && candy1.type === candy2.type && candy1.type === candy3.type) { // Found a vertical match of at least 3 var currentMatch = [candy1, candy2, candy3]; var matchLength = 3; // Check for longer match for (var k = row + 3; k < BOARD_SIZE; k++) { var nextCandy = board[k][col]; if (nextCandy && nextCandy.type === candy1.type) { // Avoid adding if already part of a horizontal match found earlier if (checked.indexOf(nextCandy) === -1) { currentMatch.push(nextCandy); } else { // If it was part of a horizontal match, just increase length but don't re-add } matchLength++; } else { break; // End of match } } // Store match details matches.push({ candies: currentMatch, // Store the actual candy objects row: row, col: col, length: matchLength, horizontal: false }); // Mark all candies in this match as checked currentMatch.forEach(markChecked); row += matchLength - 1; // Skip checked candies in the outer loop } } } return matches; } function hasPossibleMoves() { // Check horizontal swaps that could create a match for (var row = 0; row < BOARD_SIZE; row++) { for (var col = 0; col < BOARD_SIZE - 1; col++) { if (canSwapCreateMatch(row, col, row, col + 1)) { return true; } } } // Check vertical swaps that could create a match for (var row = 0; row < BOARD_SIZE - 1; row++) { for (var col = 0; col < BOARD_SIZE; col++) { if (canSwapCreateMatch(row, col, row + 1, col)) { return true; } } } return false; // No possible moves found } // Helper function for hasPossibleMoves function canSwapCreateMatch(r1, c1, r2, c2) { var candy1 = board[r1][c1]; var candy2 = board[r2][c2]; if (!candy1 || !candy2) { return false; } // Cannot swap with empty space // Temporarily swap types in the model (don't need full object swap) board[r1][c1] = candy2; board[r2][c2] = candy1; // Check if this swap creates a match around either position var createsMatch = checkMatchAt(r1, c1) || checkMatchAt(r2, c2) || findMatches().length > 0; // Simplified check // Swap back board[r1][c1] = candy1; board[r2][c2] = candy2; return createsMatch; } function reshuffleBoard() { if (isProcessing) { // Don't reshuffle if already processing LK.setTimeout(reshuffleBoard, 500); // Try again later return; } isProcessing = true; // Block input during reshuffle // Display reshuffling message var reshuffleTxt = new Text2('Reshuffling...', { size: 100, fill: 0xFFFFFF }); reshuffleTxt.anchor.set(0.5, 0.5); LK.gui.center.addChild(reshuffleTxt); // 1. Collect all current candy types and special status var currentCandiesInfo = []; candies.forEach(function (candy) { currentCandiesInfo.push({ type: candy.type, isSpecial: candy.isSpecial // Retain special status during reshuffle }); candy.destroy(); // Remove old visuals immediately }); candies = []; // Clear the list board = []; // Clear the board model // 2. Shuffle the collected info for (var i = currentCandiesInfo.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var _ref = [currentCandiesInfo[j], currentCandiesInfo[i]]; currentCandiesInfo[i] = _ref[0]; currentCandiesInfo[j] = _ref[1]; } // 3. Rebuild the board ensuring no initial matches AND possible moves var attempts = 0; do { attempts++; if (attempts > 10) { // Safety break if it struggles too much console.error("Failed to reshuffle into a valid state after 10 attempts."); // Could force a simple known-good layout here or reset level isProcessing = false; // Unlock reshuffleTxt.destroy(); // Maybe show a "No more moves" game over? checkGameState(true); // Force check, likely leading to game over if moves are 0 return; } // Clear board for re-attempt candies.forEach(function (c) { return c.destroy(); }); candies = []; board = []; var infoIndex = 0; for (var row = 0; row < BOARD_SIZE; row++) { board[row] = []; for (var col = 0; col < BOARD_SIZE; col++) { var info = currentCandiesInfo[infoIndex++]; board[row][col] = null; // Temp null for checkMatchAt // Ensure no immediate matches with shuffled type var placeAttempts = 0; do { var currentInfo = info; // Use the shuffled info board[row][col] = { type: currentInfo.type, isSpecial: currentInfo.isSpecial }; // Temp place placeAttempts++; if (placeAttempts > currentCandiesInfo.length) { // Avoid infinite loop if types are very limited // Swap with a later random type if stuck var swapIdx = infoIndex + Math.floor(Math.random() * (currentCandiesInfo.length - infoIndex)); if (swapIdx < currentCandiesInfo.length) { var _ref2 = [currentCandiesInfo[swapIdx], currentCandiesInfo[infoIndex - 1]]; currentCandiesInfo[infoIndex - 1] = _ref2[0]; currentCandiesInfo[swapIdx] = _ref2[1]; info = currentCandiesInfo[infoIndex - 1]; // Get the newly swapped info } board[row][col] = { type: info.type, isSpecial: info.isSpecial }; // Try again with swapped break; // Exit do-while for this cell after swap } } while (checkMatchAt(row, col)); // Check if placing this type creates a match // Create the actual candy var candy = new Candy(board[row][col].type, row, col); candy.setSpecial(board[row][col].isSpecial); candy.alpha = 0; // Start invisible candy.moveTo(row, col, false); gameBoardContainer.addChild(candy); board[row][col] = candy; // Replace temp object with real candy candies.push(candy); // Animate fade-in tween(candy, { alpha: 1 }, { duration: 300, delay: (row + col) * 20 }); } } // Loop condition: Keep reshuffling if the new board has no possible moves } while (!hasPossibleMoves() && attempts < 10); // Reshuffle successful LK.setTimeout(function () { reshuffleTxt.destroy(); isProcessing = false; // Unlock the board }, 500); // Keep message visible briefly } function checkGameState() { var forceCheck = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; // Don't check state while the board is actively processing, unless forced (e.g., reshuffle failed) if (isProcessing && !forceCheck) { return; } // Check Win Condition if (score >= targetScore) { console.log("Level " + level + " Complete!"); isProcessing = true; // Prevent further input // Update storage level++; storage.level = level; if (score > highScore) { highScore = score; storage.highScore = highScore; } storage.score = score; // Save current score too, maybe for win screen display LK.setTimeout(function () { LK.showYouWin({ level: level - 1, score: score, highScore: highScore }); }, 1000); // Delay for effect } // Check Lose Condition else if (moves <= 0) { console.log("Game Over - Out of Moves!"); isProcessing = true; // Prevent further input // Update high score if needed if (score > highScore) { highScore = score; storage.highScore = highScore; } storage.score = score; // Save final score LK.setTimeout(function () { LK.showGameOver({ level: level, score: score, highScore: highScore }); }, 1000); // Delay for effect } // Note: The "no possible moves" scenario is now handled by reshuffleBoard, // but if reshuffling fails repeatedly, it might also lead to a game over state here. } // --- Game Start --- setupGame();
===================================================================
--- original.js
+++ change.js
@@ -229,9 +229,10 @@
candiesToDestroy.push(candy);
board[targetRow][c] = null;
}
}
- // Scan Column
+ // Game state variables
+ var popPromises = []; // Initialize popPromises array to store promises for animations
for (var r = 0; r < BOARD_SIZE; r++) {
var _board$r;
var candy = (_board$r = board[r]) === null || _board$r === void 0 ? void 0 : _board$r[targetCol];
if (candy && candiesToDestroy.indexOf(candy) === -1) {
Generate a high-quality icon asset of the specific **blue candy** shown in the provided Candy Crush screenshot. * **Shape:** A smooth, **round, slightly flattened sphere or thick disc** shape with perfectly curved edges. It should look plump and solid. * **Color:** A bright, **vibrant, medium blue**. Clear and saturated, avoiding overly dark (navy) or light (sky blue) tones. * **Surface & Finish:** **Highly glossy** and reflective, like polished hard candy or a glass marble. The surface should look perfectly smooth. * **Lighting & Highlights:** Features a **prominent, distinct, curved white specular highlight** positioned near the **top-left edge**, following the candy's spherical contour. Additional subtle, broader highlights should be visible across the top surface, giving it dimension. Clear shading should be present on the bottom and right sides to emphasize its **3D, spherical volume**. * **Style:** Clean, **stylized 3D render**, matching the cheerful, polished, and sli. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Basic Prompt: Bright green candy, emerald green tones, crystallized texture, covered in small sugar granules, looking sweet and delicious. More Detailed Prompt (Optional): Bright green candy, with hints of emerald green and light lime green sparkles, crystallized texture, covered in small sugar granules, looking fresh and delicious as if it was just made. There is a faint halo of light around the candy, and the background is blurred. Additional Details to Add to the Prompt: Shape: Round, square, heart-shaped, etc. Material: Glass, frosted glass, sugar crystal, etc. Lighting: Soft, hard, dramatic, etc. Background: Solid color, patterned, blurred, themed (e.g., candy store), etc. Additional Objects: Other candies, paper packaging, ribbon, etc. Example Combined Prompt: Round, bright green candy with emerald green and lime green tones, crystallized texture, covered in small sugar granules, looking fresh and delicious. Soft lighting, blurred background.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Basic Prompt: Bright orange candy, in shades of orange, with a smooth and glossy surface, sweet and appealing looking confection. More Detailed Prompt (Optional): Vibrant orange candy, luminous as if lit by sunlight, with hints of orange and light tangerine tones, possessing a smooth and flawless surface, high-quality candy. The candy has light reflections and the background is softly blurred. Additional Details to Add to the Prompt: Shape: Sphere, cube, star, etc. Texture: Smooth, matte, slightly grainy, etc. Lighting: Natural light, studio light, warm light, cool light, etc. Background: White, colored, patterned, candy store, kitchen counter, etc. Additional Objects: Candy wrapper, glass jar, candy stand, etc. Example Combined Prompt: Sphere-shaped, bright orange candy, luminous as if lit by sunlight, with hints of orange and light tangerine tones, possessing a smooth and flawless surface, high-quality candy. There are distinct light reflections on the candy. The background is white. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Basic Prompt: Bright purple candy, in shades of lavender and violet, with a smooth surface, sweet and enticing looking confection. More Detailed Prompt (Optional): Deep purple in color, with hints of lavender and light lilac where the light hits it, possessing a smooth and glossy surface, crystal clear like glass, high-quality purple candy. There's a subtle halo of light around the candy, and the background is softly blurred. Additional Details to Add to the Prompt: Shape: Crystal, drop, heart, round, etc. Texture: Glossy, matte, frosted, slightly rough, etc. Lighting: Soft, dramatic, natural light, artificial light, etc. Background: Solid color, patterned, candy store, countertop, etc. Additional Objects: Other candies, transparent packaging, ribbon, etc. Basic Prompt: Bright purple candy, in shades of lavender and violet, with a smooth surface, sweet and enticing looking confection. More Detailed Prompt (Optional): Deep purple in color, with hints of lavender and light lilac where th. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Basic Prompt: Bright yellow candy, lemon yellow color, smooth surface, sweet and tempting looking treat. More Detailed Prompt (Optional): Vibrant, sunny yellow candy, with subtle hints of lemon and gold. It has a smooth, glossy surface, almost like glass. The light catches it just right, creating a small sparkle. The background is slightly blurred. More Variations to add (Optional): Shape: Sphere, star, gumball, square, etc. Texture: Gummy, hard candy, crystalline, etc. Lighting: Soft, harsh, natural, artificial, etc. Background: Plain, gradient, candy store, etc. Additions: Candy wrapper, other candies, etc. Example Combination Prompt: Round, bright yellow candy like a gumball. Has a smooth, glossy surface with soft lighting. Hints of lemon and gold colors, p. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Basic Prompt: Bright red candy, vibrant crimson color, smooth surface, sweet and appealing treat. More Detailed Prompt (Optional): A glistening, ruby-red candy, with a smooth, reflective surface. The color is a rich, deep crimson, almost like a precious gem. The light catches it beautifully, creating highlights and shadows. The background is soft and blurred. Possible Variations to Add (Optional): Shape: Heart, sphere, cube, star, twisted, etc. Texture: Glossy, matte, gummy, hard, crystalline, etc. Lighting: Soft, harsh, natural, artificial, dramatic, etc. Background: Solid color, patterned, candy store, blurred, bokeh, etc. Additions: Candy wrapper, other candies, sprinkles, sugar coating, etc. Example Combination Prompt: A heart-shaped, glistening, ruby-red candy with a smooth, reflective surface. Rich, deep crimson color. Soft lighting, blurred background.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Generate a high-quality asset representing a **single square background tile** for a match-3 game cell, inspired by the cell interiors in the provided Candy Crush screenshot, but made **significantly more transparent**. * **Shape:** A perfect **square**. * **Base Appearance:** * **Color:** A **muted, desaturated, cool grey-blue** or **dark teal-grey**. * **Texture:** Contains an **extremely subtle, fine-grained texture** (like faint diagonal lines or uniform digital noise) integrated into the color. * **Transparency:** The key feature is **increased translucency**. The tile should be **moderately to significantly see-through**, allowing layers placed underneath it to be clearly visible. It should *not* be fully opaque like the original screenshot implies, nor fully transparent (invisible). Aim for roughly **40-60% opacity**. * **Lighting:** Maintain **soft, even, ambient lighting** across the surface of the square. No internal highlights or shadows within the tile. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows