User prompt
Fix bugs
User prompt
- Players receive a random colored block to place on a 7x9 grid. - Drag and drop the block onto any empty cell. - When three or more adjacent blocks of the same color connect, they merge into a new color and clear, scoring points. - Merges can trigger chain reactions for bonus points. - The game ends when the grid is full and no moves remain. - Aim for high scores by planning merges and creating cascades.
User prompt
Can not see the game, fix it
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of null (reading 'grid')' in or related to this line: 'var bdata = prevState.grid[c] && prevState.grid[c][r] ? prevState.grid[c][r] : null;' Line Number: 252
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of null (reading 'grid')' in or related to this line: 'var bdata = prevState.grid[c][r];' Line Number: 249
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught ReferenceError: copyNextBlocksState is not defined' in or related to this line: 'return arr;' Line Number: 210
User prompt
Please fix the bug: 'Uncaught ReferenceError: copyNextBlocksState is not defined' in or related to this line: 'return arr;' Line Number: 210
User prompt
Please fix the bug: 'Uncaught ReferenceError: copyNextBlocksState is not defined' in or related to this line: 'return arr;' Line Number: 210
User prompt
Please fix the bug: 'Uncaught ReferenceError: copyNextBlocksState is not defined' in or related to this line: 'return arr;' Line Number: 210
User prompt
Please fix the bug: 'Uncaught ReferenceError: copyNextBlocksState is not defined' in or related to this line: 'return arr;' Line Number: 210
User prompt
Please fix the bug: 'Uncaught ReferenceError: copyNextBlocksState is not defined' in or related to this line: 'return arr;' Line Number: 210
User prompt
Please fix the bug: 'Uncaught ReferenceError: copyNextBlocksState is not defined' in or related to this line: 'return arr;' Line Number: 210
User prompt
Please fix the bug: 'Uncaught ReferenceError: copyNextBlocksState is not defined' in or related to this line: 'return arr;' Line Number: 210
User prompt
Please fix the bug: 'Uncaught ReferenceError: copyNextBlocksState is not defined' in or related to this line: 'return arr;' Line Number: 210
User prompt
Fix the game
User prompt
Please fix the bug: 'Uncaught ReferenceError: copyNextBlocksState is not defined' in or related to this line: 'return arr;' Line Number: 210
User prompt
Please fix the bug: 'Uncaught ReferenceError: copyNextBlocksState is not defined' in or related to this line: 'return arr;' Line Number: 210
User prompt
Please fix the bug: 'Uncaught ReferenceError: copyNextBlocksState is not defined' in or related to this line: 'return arr;' Line Number: 210
User prompt
Reset game codes to its functional
User prompt
Fix errors
User prompt
Undo deleted asset
User prompt
Please fix the bug: 'Uncaught ReferenceError: copyNextBlocksState is not defined' in or related to this line: 'return arr;' Line Number: 209
User prompt
Please fix the bug: 'Uncaught ReferenceError: copyNextBlocksState is not defined' in or related to this line: 'return arr;' Line Number: 209
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // --- Game Over / Reset handled by LK engine --- // --- Block Class --- var Block = Container.expand(function () { var self = Container.call(this); // Default color index (0=red, 1=orange, 2=yellow, 3=green, 4=blue, 5=purple, 6=pink, 7=white, 8=rainbow) self.colorIndex = 0; self.isLocked = false; self.isOnGrid = false; self.gridX = -1; self.gridY = -1; // Attach block asset (default, will be set by setColor) self.blockAsset = self.attachAsset('Hd', { anchorX: 0.5, anchorY: 0.5 }); self.scaleX = 1; self.scaleY = 1; // Set color and locked state self.setColor = function (colorIdx) { self.colorIndex = colorIdx; self.isLocked = false; // No more locked blocks // Remove old asset if exists if (self.blockAsset) { self.removeChild(self.blockAsset); } self.blockAsset = self.attachAsset('Hd', { anchorX: 0.5, anchorY: 0.5 }); self.scaleX = 1; self.scaleY = 1; // Assign each block a unique, visually distinct color using the Hd asset for all // 0=yellow, 1=red, 2=blue, 3=green, 4=purple, 5=rainbow/white, 6=pink, 7=black, 8=orange var tints = [0xFFF600, // 0 Yellow 0xFF6666, // 1 Red 0x3ec1ff, // 2 Blue 0x7cff6b, // 3 Green (matches face_green) 0xd08cff, // 4 Purple 0xffffff, // 5 Rainbow/white (special) 0xffb7e5, // 6 Pink 0x222222, // 7 Black 0xffa500 // 8 Orange ]; // Map colorIndex to correct tint if (colorIdx === 0) { self.blockAsset.tint = tints[0]; // yellow } else if (colorIdx === 1) { self.blockAsset.tint = tints[1]; // red } else if (colorIdx === 2) { self.blockAsset.tint = tints[2]; // blue } else if (colorIdx === 3) { self.blockAsset.tint = tints[3]; // green } else if (colorIdx === 4) { self.blockAsset.tint = tints[4]; // purple } else if (colorIdx === 5) { self.blockAsset.tint = tints[5]; // rainbow/white } else if (colorIdx === 6) { self.blockAsset.tint = tints[6]; // pink } else if (colorIdx === 7) { self.blockAsset.tint = tints[7]; // black } else if (colorIdx === 8) { self.blockAsset.tint = tints[8]; // orange } else { self.blockAsset.tint = tints[0]; // fallback to yellow } // Add a lightful flash effect when color is set LK.effects.flashObject(self.blockAsset, 0xffffff, 220); }; // Animate merge (scale up and back) self.animateMerge = function (_onFinish) { tween(self, { scaleX: 1.25, scaleY: 1.25 }, { duration: 80, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 100, easing: tween.easeIn, onFinish: function onFinish() { if (_onFinish) _onFinish(); } }); } }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222233 }); /**** * Game Code ****/ // Flower and honey jar assets // Face assets in different colors // Removed block_gray asset initialization (no more grey squares) // locked block // --- Game Constants --- // Define block shapes for each color and merged color // We'll use 4 colors: red, blue, yellow, purple (no green) // Merged block (final color) // Sound for merge // visually white, but will be rainbow in logic // More defined, fun, and clear block shapes for each color // Cheerful blue // Cheerful green // Cheerful purple // Bright yellow for rainbow // Cheerful red // Pure white // Cheerful yellow // Cheerful pink // Fill nextBlocks with random colors (original mechanic) var GRID_COLS = 6; var GRID_ROWS = 7; var CELL_SIZE = 260; // px (was 200, now bigger for larger squares) var GRID_OFFSET_X = Math.floor((2048 - GRID_COLS * CELL_SIZE) / 2); var GRID_OFFSET_Y = 350; var COLORS = [0, 1, 2, 3, 4, 6, 7, 8]; // 0=yellow, 1=red, 2=blue, 3=green, 4=purple, 6=pink, 7=black, 8=orange var COLOR_NAMES = ['yellow', 'red', 'blue', 'green', 'purple', 'rainbow', 'pink', 'black', 'orange']; var MAX_COLOR_INDEX = 8; // 0-8 normal, 5=rainbow (special, can't merge further) var RAINBOW_COLOR_INDEX = 5; // special rainbow block (if needed) var BLOCK_DROP_Y = 2200; // y position for blocks that fall off // --- Game State --- var grid = []; // 2D array [col][row] of Block or null for (var c = 0; c < GRID_COLS; c++) { grid[c] = []; for (var r = 0; r < GRID_ROWS; r++) { grid[c][r] = null; } } var draggingBlock = null; var dragOffsetX = 0; var dragOffsetY = 0; var nextBlocks = []; // Array of next blocks to place var score = 0; var scoreTxt = null; var isProcessing = false; // Prevent input during merges // --- Undo/Föregående State --- var prevState = null; // Helper: Deep copy grid state (only colorIndex, isLocked, isOnGrid, gridX, gridY) function copyGridState(srcGrid) { var arr = []; for (var c = 0; c < GRID_COLS; c++) { arr[c] = []; for (var r = 0; r < GRID_ROWS; r++) { var b = srcGrid[c][r]; if (b) { // Remove pink and violet cubes if present if (b.colorIndex === 4 || b.colorIndex === 6) { arr[c][r] = { colorIndex: b.colorIndex, isLocked: b.isLocked, isOnGrid: b.isOnGrid, gridX: b.gridX, gridY: b.gridY }; } else { arr[c][r] = null; } } } return arr; } // Helper: Deep copy nextBlocks state (only colorIndex) function copyNextBlocksState(srcNextBlocks) { var arr = []; for (var i = 0; i < srcNextBlocks.length; i++) { // Defensive: handle both Block objects and plain objects var colorIndex = srcNextBlocks[i] && typeof srcNextBlocks[i].colorIndex !== "undefined" ? srcNextBlocks[i].colorIndex : null; arr.push({ colorIndex: colorIndex }); } return arr; } // Save current state for undo function savePrevState() { prevState = { grid: copyGridState(grid), nextBlocks: copyNextBlocksState(nextBlocks), score: score }; } // Restore previous state (undo) function restorePrevState() { if (!prevState) return; // Remove all blocks from game for (var c = 0; c < GRID_COLS; c++) { for (var r = 0; r < GRID_ROWS; r++) { if (grid[c][r]) { grid[c][r].destroy(); grid[c][r] = null; } } } } // Restore grid for (var c = 0; c < GRID_COLS; c++) { for (var r = 0; r < GRID_ROWS; r++) { var bdata = prevState.grid[c][r]; if (bdata) { var b = new Block(); b.setColor(bdata.colorIndex); b.isOnGrid = bdata.isOnGrid; b.gridX = bdata.gridX; b.gridY = bdata.gridY; var pos = getPosForCell(c, r); b.x = pos.x; b.y = pos.y; grid[c][r] = b; game.addChild(b); } else { grid[c][r] = null; } } } // Remove all nextBlocks from game for (var i = 0; i < nextBlocks.length; i++) { if (nextBlocks[i]) nextBlocks[i].destroy(); } nextBlocks = []; // Restore nextBlocks for (var i = 0; i < prevState.nextBlocks.length; i++) { var nb = new Block(); nb.setColor(prevState.nextBlocks[i].colorIndex); nb.x = 2048 / 2 + (nextBlocks.length - 1) * 220; nb.y = 220; nb.scaleX = nb.scaleY = 1; nb.isOnGrid = false; nb.gridX = -1; nb.gridY = -1; game.addChild(nb); nextBlocks.push(nb); } // Reposition nextBlocks for (var i = 0; i < nextBlocks.length; i++) { var bx = 2048 / 2 + (i - 1) * 220; nextBlocks[i].x = bx; nextBlocks[i].y = 220; } // Restore score score = prevState.score; scoreTxt.setText(score); prevState = null; } // Add a button for undo/föregående var undoBtn = new Text2('⟲', { size: 110, fill: "#fff" }); undoBtn.anchor.set(0.5, 0.5); undoBtn.x = 2048 - 120; undoBtn.y = 120; undoBtn.interactive = true; undoBtn.buttonMode = true; undoBtn.down = function () { restorePrevState(); }; LK.gui.top.addChild(undoBtn); // --- GUI Elements --- scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // --- Draw Background Image --- var background = LK.getAsset('Background', { width: 2048, height: 2732, anchorX: 0, anchorY: 0, x: 0, y: 0, alpha: 1 }); game.addChild(background); // --- Draw Grid Background --- for (var c = 0; c < GRID_COLS; c++) { for (var r = 0; r < GRID_ROWS; r++) { var cell = LK.getAsset('block_white', { anchorX: 0.5, anchorY: 0.5, x: GRID_OFFSET_X + c * CELL_SIZE + CELL_SIZE / 2, y: GRID_OFFSET_Y + r * CELL_SIZE + CELL_SIZE / 2, alpha: 0.08 }); game.addChild(cell); } } // --- Draw Grid Lines --- for (var c = 0; c <= GRID_COLS; c++) { var x = GRID_OFFSET_X + c * CELL_SIZE; var y1 = GRID_OFFSET_Y; var y2 = GRID_OFFSET_Y + GRID_ROWS * CELL_SIZE; // Draw vertical line as a thick, high-contrast rectangle var vline = LK.getAsset('block_black', { width: 14, height: GRID_ROWS * CELL_SIZE, anchorX: 0.5, anchorY: 0, x: x, y: y1, alpha: 0.38 }); game.addChild(vline); } for (var r = 0; r <= GRID_ROWS; r++) { var y = GRID_OFFSET_Y + r * CELL_SIZE; var x1 = GRID_OFFSET_X; var x2 = GRID_OFFSET_X + GRID_COLS * CELL_SIZE; // Draw horizontal line as a thick, high-contrast rectangle var hline = LK.getAsset('block_black', { width: GRID_COLS * CELL_SIZE, height: 14, anchorX: 0, anchorY: 0.5, x: x1, y: y, alpha: 0.38 }); game.addChild(hline); } // --- Helper Functions --- // Get grid cell from x,y (game coordinates) function getGridCellFromPos(x, y) { var gx = Math.floor((x - GRID_OFFSET_X) / CELL_SIZE); var gy = Math.floor((y - GRID_OFFSET_Y) / CELL_SIZE); if (gx < 0 || gx >= GRID_COLS || gy < 0 || gy >= GRID_ROWS) return null; return { col: gx, row: gy }; } // Get position (x,y) for grid cell function getPosForCell(col, row) { return { x: GRID_OFFSET_X + col * CELL_SIZE + CELL_SIZE / 2, y: GRID_OFFSET_Y + row * CELL_SIZE + CELL_SIZE / 2 }; } // Generate a random color index (0-4) function randomColorIndex() { return COLORS[Math.floor(Math.random() * COLORS.length)]; } // Create a new block for the "next" area function createNextBlock(idx) { var block = new Block(); block.setColor(idx); block.x = 2048 / 2 + (nextBlocks.length - 1) * 220; block.y = 220; block.scaleX = block.scaleY = 1; block.isOnGrid = false; block.gridX = -1; block.gridY = -1; game.addChild(block); return block; } // Fill nextBlocks to always have 3 blocks function refillNextBlocks() { // Limit: Only allow new blocks if there is at least one empty cell on the grid var emptyCells = 0; for (var c = 0; c < GRID_COLS; c++) { for (var r = 0; r < GRID_ROWS; r++) { if (!grid[c][r]) emptyCells++; } } // Only allow up to 3 next blocks, but never more than empty cells var maxNext = Math.min(3, emptyCells); // Fill nextBlocks with random colors (original mechanic) while (nextBlocks.length < maxNext) { // 5% chance to spawn a rainbow block, otherwise random color var idx; var rand = Math.random(); if (rand < 0.05) { idx = RAINBOW_COLOR_INDEX; // rainbow } else { // Allow all defined COLORS except rainbow (5) as next blocks var allowedColors = COLORS.filter(function (cidx) { return cidx !== RAINBOW_COLOR_INDEX; }); idx = allowedColors[Math.floor(Math.random() * allowedColors.length)]; } var block = createNextBlock(idx); nextBlocks.push(block); } // Remove excess nextBlocks if grid is almost full while (nextBlocks.length > maxNext) { var b = nextBlocks.pop(); if (b) b.destroy(); } // Position them nicely for (var i = 0; i < nextBlocks.length; i++) { var bx = 2048 / 2 + (i - 1) * 220; tween(nextBlocks[i], { x: bx, y: 220 }, { duration: 180, easing: tween.easeInOut }); } } // Remove a block from nextBlocks and shift others function popNextBlock(block) { var idx = nextBlocks.indexOf(block); if (idx >= 0) { nextBlocks.splice(idx, 1); } refillNextBlocks(); } // Place block on grid function placeBlockOnGrid(block, col, row) { block.isOnGrid = true; block.gridX = col; block.gridY = row; grid[col][row] = block; var pos = getPosForCell(col, row); tween(block, { x: pos.x, y: pos.y, scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeOut }); } // Remove block from grid function removeBlockFromGrid(col, row) { var block = grid[col][row]; if (block) { grid[col][row] = null; } } // Find all connected blocks of the same color (DFS) function findConnectedBlocks(col, row, colorIndex, visited) { if (col < 0 || col >= GRID_COLS || row < 0 || row >= GRID_ROWS) return []; if (visited[col + "," + row]) return []; var block = grid[col][row]; if (!block || block.colorIndex !== colorIndex) return []; visited[col + "," + row] = true; var result = [{ col: col, row: row, block: block }]; // 4 directions var dirs = [[1, 0], [-1, 0], [0, 1], [0, -1]]; for (var i = 0; i < dirs.length; i++) { var nc = col + dirs[i][0]; var nr = row + dirs[i][1]; result = result.concat(findConnectedBlocks(nc, nr, colorIndex, visited)); } return result; } // Check if any moves are possible (empty cell exists) function hasMoves() { for (var c = 0; c < GRID_COLS; c++) { for (var r = 0; r < GRID_ROWS; r++) { if (!grid[c][r]) return true; } } return false; } // --- Game Logic --- // Handle block drop onto grid function tryPlaceBlock(block, x, y) { if (isProcessing) return false; var cell = getGridCellFromPos(x, y); if (!cell) return false; var col = cell.col, row = cell.row; if (grid[col][row]) return false; // occupied // No locked blocks, so no need to check if (grid[col][row]) return false; // cell is occupied if (block.colorIndex === RAINBOW_COLOR_INDEX) { // Rainbow block special checks } // Save state for undo before placing function savePrevState() { prevState = { grid: copyGridState(grid), nextBlocks: copyNextBlocksState(nextBlocks), score: score }; } savePrevState(); if (block.colorIndex === RAINBOW_COLOR_INDEX) { // Place rainbow block and trigger effect placeBlockOnGrid(block, col, row); block.isOnGrid = true; popNextBlock(block); // Pick a random color present on the grid (not locked, not white, not rainbow) var presentColors = {}; for (var c2 = 0; c2 < GRID_COLS; c2++) { for (var r2 = 0; r2 < GRID_ROWS; r2++) { var b2 = grid[c2][r2]; if (b2 && (b2.colorIndex === 0 || b2.colorIndex === 1 || b2.colorIndex === 2 || b2.colorIndex === 4)) { presentColors[b2.colorIndex] = true; } } } var colorList = []; for (var k in presentColors) { if (presentColors.hasOwnProperty(k)) colorList.push(parseInt(k)); } if (colorList.length > 0) { var colorToClear = colorList[Math.floor(Math.random() * colorList.length)]; // Animate and remove all blocks of that color for (var c2 = 0; c2 < GRID_COLS; c2++) { for (var r2 = 0; r2 < GRID_ROWS; r2++) { var b2 = grid[c2][r2]; if (b2 && b2.colorIndex === colorToClear) { var tmpBlock = b2; // Store reference before nulling the grid position grid[c2][r2] = null; (function (bref) { tween(bref, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 180, easing: tween.easeIn, onFinish: function onFinish() { bref.destroy(); } }); })(tmpBlock); } } } // Add bonus score for rainbow clear score += 200; scoreTxt.setText(score); } // Play a sound for rainbow effect LK.getSound('merge').play(); // After effect, check for game over LK.setTimeout(function () { if (!hasMoves()) { LK.showGameOver(); } }, 220); return true; } // Place block placeBlockOnGrid(block, col, row); popNextBlock(block); // After placement, check for merges processMerges(col, row, function () { // After all merges and cascades, check for game over if (!hasMoves()) { LK.showGameOver(); } }); return true; } // Process merges and cascades starting from (col,row) function processMerges(col, row, onFinish) { if (isProcessing) return; isProcessing = true; var block = grid[col][row]; if (!block || block.isLocked) { isProcessing = false; if (onFinish) onFinish(); return; } var colorIdx = block.colorIndex; if (colorIdx >= MAX_COLOR_INDEX) { isProcessing = false; if (onFinish) onFinish(); return; } // Remove pink (6) and violet (4) cubes immediately if present at this position if (block.colorIndex === 4 || block.colorIndex === 6) { removeBlockFromGrid(col, row); tween(block, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 120, easing: tween.easeIn, onFinish: function onFinish() { block.destroy(); isProcessing = false; if (onFinish) onFinish(); } }); return; } // Find all connected blocks of same color var connected = findConnectedBlocks(col, row, colorIdx, {}); if (connected.length >= 3) { // Play happy merge sound only on actual merge LK.getSound('merge').play(); // Find center position for merged block var sumX = 0, sumY = 0; for (var i = 0; i < connected.length; i++) { sumX += connected[i].col; sumY += connected[i].row; } var centerCol = Math.round(sumX / connected.length); var centerRow = Math.round(sumY / connected.length); // Pick a new color for the merged block: must be a color not present in the merged group var presentColors = {}; for (var i = 0; i < connected.length; i++) { presentColors[connected[i].block.colorIndex] = true; } // Try to find a color not present in the merged group var allPossibleColors = [0, 1, 2, 3, 4, 6, 7, 8, 5]; // all colors, rainbow last var newColorIdx = -1; for (var i = 0; i < allPossibleColors.length; i++) { if (!presentColors[allPossibleColors[i]]) { newColorIdx = allPossibleColors[i]; break; } } // If all colors are present, fallback to rainbow/white if (newColorIdx === -1) { newColorIdx = RAINBOW_COLOR_INDEX; } // Remove all blocks in connected, but delay the destruction until after the merged block is created var blocksToRemove = []; for (var i = 0; i < connected.length; i++) { var b = connected[i].block; blocksToRemove.push(b); removeBlockFromGrid(b.gridX, b.gridY); } // Animate and destroy the old blocks, then create the merged block after all are gone var destroyCount = 0; for (var i = 0; i < blocksToRemove.length; i++) { (function (bref) { tween(bref, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 120, easing: tween.easeIn, onFinish: function onFinish() { bref.destroy(); destroyCount++; // When all blocks are destroyed, create the merged block and animate it if (destroyCount === blocksToRemove.length) { // Add delay for purple (4) and pink (6) merges var doTransform = function doTransform() { // Create merged block at center position var mergedBlock = new Block(); mergedBlock.setColor(newColorIdx); mergedBlock.isOnGrid = true; mergedBlock.gridX = centerCol; mergedBlock.gridY = centerRow; var pos = getPosForCell(centerCol, centerRow); mergedBlock.x = pos.x; mergedBlock.y = pos.y; grid[centerCol][centerRow] = mergedBlock; game.addChild(mergedBlock); mergedBlock.animateMerge(function () { // Update score var points = 10 * connected.length * (colorIdx + 1); score += points; scoreTxt.setText(score); // After animation, finish processing isProcessing = false; // After merge, check if the new merged block itself can merge again (chain reaction) processMerges(centerCol, centerRow, onFinish); }); }; // If merging to purple (4) or pink (6), add a delay before transform if (newColorIdx === 4 || newColorIdx === 6) { LK.setTimeout(doTransform, 320); } else { doTransform(); } } } }); })(blocksToRemove[i]); } // If no blocks to remove (should not happen), just finish if (blocksToRemove.length === 0) { mergedBlock.animateMerge(function () { var points = 10 * connected.length * (colorIdx + 1); score += points; scoreTxt.setText(score); isProcessing = false; processMerges(centerCol, centerRow, onFinish); }); } } else { isProcessing = false; if (onFinish) onFinish(); } } // --- Input Handling --- // Only allow dragging from nextBlocks game.down = function (x, y, obj) { if (isProcessing) return; for (var i = 0; i < nextBlocks.length; i++) { var block = nextBlocks[i]; // Check if touch is inside block var dx = x - block.x; var dy = y - block.y; if (Math.abs(dx) < 90 && Math.abs(dy) < 90 && !block.isLocked && !block.isOnGrid) { draggingBlock = block; dragOffsetX = dx; dragOffsetY = dy; // Play a sound when picking up a block LK.getSound('merge').play(); // Bring to front game.addChild(block); tween(block, { scaleX: 1.15, scaleY: 1.15 }, { duration: 80, easing: tween.easeOut }); break; } } }; game.move = function (x, y, obj) { if (!draggingBlock) return; draggingBlock.x = x - dragOffsetX; draggingBlock.y = y - dragOffsetY; }; game.up = function (x, y, obj) { if (!draggingBlock) return; // Try to place on grid var placed = tryPlaceBlock(draggingBlock, draggingBlock.x, draggingBlock.y); if (!placed) { // Snap back to next area var idx = nextBlocks.indexOf(draggingBlock); var bx = 2048 / 2 + (idx - 1) * 220; tween(draggingBlock, { x: bx, y: 220, scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeInOut }); } else { // Play a sound when block is placed on the grid LK.getSound('merge').play(); // Block is now on grid, don't need to do anything } draggingBlock = null; }; // --- Game Update Loop --- game.update = function () { // Animate blocks falling off (if any) for (var c = 0; c < GRID_COLS; c++) { for (var r = 0; r < GRID_ROWS; r++) { var block = grid[c][r]; // Remove pink and violet cubes if present if (block && (block.colorIndex === 4 || block.colorIndex === 6)) { block.destroy(); grid[c][r] = null; } else if (block && block.y > BLOCK_DROP_Y) { block.destroy(); grid[c][r] = null; } } } // Occasionally spawn a locked block in a random empty cell (every 10 turns) if (typeof game.turnsSinceLock === "undefined") game.turnsSinceLock = 0; if (typeof game.lastBlocksOnGrid === "undefined") game.lastBlocksOnGrid = 0; var blocksOnGrid = 0; for (var c = 0; c < GRID_COLS; c++) { for (var r = 0; r < GRID_ROWS; r++) { if (grid[c][r]) blocksOnGrid++; } } // Removed logic for spawning gray/locked blocks every 10 turns (no more grey squares) if (blocksOnGrid > game.lastBlocksOnGrid) { game.turnsSinceLock++; game.lastBlocksOnGrid = blocksOnGrid; // No locked block spawn } }; // --- Game Start --- score = 0; scoreTxt.setText(score); refillNextBlocks();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// --- Game Over / Reset handled by LK engine ---
// --- Block Class ---
var Block = Container.expand(function () {
var self = Container.call(this);
// Default color index (0=red, 1=orange, 2=yellow, 3=green, 4=blue, 5=purple, 6=pink, 7=white, 8=rainbow)
self.colorIndex = 0;
self.isLocked = false;
self.isOnGrid = false;
self.gridX = -1;
self.gridY = -1;
// Attach block asset (default, will be set by setColor)
self.blockAsset = self.attachAsset('Hd', {
anchorX: 0.5,
anchorY: 0.5
});
self.scaleX = 1;
self.scaleY = 1;
// Set color and locked state
self.setColor = function (colorIdx) {
self.colorIndex = colorIdx;
self.isLocked = false; // No more locked blocks
// Remove old asset if exists
if (self.blockAsset) {
self.removeChild(self.blockAsset);
}
self.blockAsset = self.attachAsset('Hd', {
anchorX: 0.5,
anchorY: 0.5
});
self.scaleX = 1;
self.scaleY = 1;
// Assign each block a unique, visually distinct color using the Hd asset for all
// 0=yellow, 1=red, 2=blue, 3=green, 4=purple, 5=rainbow/white, 6=pink, 7=black, 8=orange
var tints = [0xFFF600,
// 0 Yellow
0xFF6666,
// 1 Red
0x3ec1ff,
// 2 Blue
0x7cff6b,
// 3 Green (matches face_green)
0xd08cff,
// 4 Purple
0xffffff,
// 5 Rainbow/white (special)
0xffb7e5,
// 6 Pink
0x222222,
// 7 Black
0xffa500 // 8 Orange
];
// Map colorIndex to correct tint
if (colorIdx === 0) {
self.blockAsset.tint = tints[0]; // yellow
} else if (colorIdx === 1) {
self.blockAsset.tint = tints[1]; // red
} else if (colorIdx === 2) {
self.blockAsset.tint = tints[2]; // blue
} else if (colorIdx === 3) {
self.blockAsset.tint = tints[3]; // green
} else if (colorIdx === 4) {
self.blockAsset.tint = tints[4]; // purple
} else if (colorIdx === 5) {
self.blockAsset.tint = tints[5]; // rainbow/white
} else if (colorIdx === 6) {
self.blockAsset.tint = tints[6]; // pink
} else if (colorIdx === 7) {
self.blockAsset.tint = tints[7]; // black
} else if (colorIdx === 8) {
self.blockAsset.tint = tints[8]; // orange
} else {
self.blockAsset.tint = tints[0]; // fallback to yellow
}
// Add a lightful flash effect when color is set
LK.effects.flashObject(self.blockAsset, 0xffffff, 220);
};
// Animate merge (scale up and back)
self.animateMerge = function (_onFinish) {
tween(self, {
scaleX: 1.25,
scaleY: 1.25
}, {
duration: 80,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
easing: tween.easeIn,
onFinish: function onFinish() {
if (_onFinish) _onFinish();
}
});
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222233
});
/****
* Game Code
****/
// Flower and honey jar assets
// Face assets in different colors
// Removed block_gray asset initialization (no more grey squares)
// locked block
// --- Game Constants ---
// Define block shapes for each color and merged color
// We'll use 4 colors: red, blue, yellow, purple (no green)
// Merged block (final color)
// Sound for merge
// visually white, but will be rainbow in logic
// More defined, fun, and clear block shapes for each color
// Cheerful blue
// Cheerful green
// Cheerful purple
// Bright yellow for rainbow
// Cheerful red
// Pure white
// Cheerful yellow
// Cheerful pink
// Fill nextBlocks with random colors (original mechanic)
var GRID_COLS = 6;
var GRID_ROWS = 7;
var CELL_SIZE = 260; // px (was 200, now bigger for larger squares)
var GRID_OFFSET_X = Math.floor((2048 - GRID_COLS * CELL_SIZE) / 2);
var GRID_OFFSET_Y = 350;
var COLORS = [0, 1, 2, 3, 4, 6, 7, 8]; // 0=yellow, 1=red, 2=blue, 3=green, 4=purple, 6=pink, 7=black, 8=orange
var COLOR_NAMES = ['yellow', 'red', 'blue', 'green', 'purple', 'rainbow', 'pink', 'black', 'orange'];
var MAX_COLOR_INDEX = 8; // 0-8 normal, 5=rainbow (special, can't merge further)
var RAINBOW_COLOR_INDEX = 5; // special rainbow block (if needed)
var BLOCK_DROP_Y = 2200; // y position for blocks that fall off
// --- Game State ---
var grid = []; // 2D array [col][row] of Block or null
for (var c = 0; c < GRID_COLS; c++) {
grid[c] = [];
for (var r = 0; r < GRID_ROWS; r++) {
grid[c][r] = null;
}
}
var draggingBlock = null;
var dragOffsetX = 0;
var dragOffsetY = 0;
var nextBlocks = []; // Array of next blocks to place
var score = 0;
var scoreTxt = null;
var isProcessing = false; // Prevent input during merges
// --- Undo/Föregående State ---
var prevState = null;
// Helper: Deep copy grid state (only colorIndex, isLocked, isOnGrid, gridX, gridY)
function copyGridState(srcGrid) {
var arr = [];
for (var c = 0; c < GRID_COLS; c++) {
arr[c] = [];
for (var r = 0; r < GRID_ROWS; r++) {
var b = srcGrid[c][r];
if (b) {
// Remove pink and violet cubes if present
if (b.colorIndex === 4 || b.colorIndex === 6) {
arr[c][r] = {
colorIndex: b.colorIndex,
isLocked: b.isLocked,
isOnGrid: b.isOnGrid,
gridX: b.gridX,
gridY: b.gridY
};
} else {
arr[c][r] = null;
}
}
}
return arr;
}
// Helper: Deep copy nextBlocks state (only colorIndex)
function copyNextBlocksState(srcNextBlocks) {
var arr = [];
for (var i = 0; i < srcNextBlocks.length; i++) {
// Defensive: handle both Block objects and plain objects
var colorIndex = srcNextBlocks[i] && typeof srcNextBlocks[i].colorIndex !== "undefined" ? srcNextBlocks[i].colorIndex : null;
arr.push({
colorIndex: colorIndex
});
}
return arr;
}
// Save current state for undo
function savePrevState() {
prevState = {
grid: copyGridState(grid),
nextBlocks: copyNextBlocksState(nextBlocks),
score: score
};
}
// Restore previous state (undo)
function restorePrevState() {
if (!prevState) return;
// Remove all blocks from game
for (var c = 0; c < GRID_COLS; c++) {
for (var r = 0; r < GRID_ROWS; r++) {
if (grid[c][r]) {
grid[c][r].destroy();
grid[c][r] = null;
}
}
}
}
// Restore grid
for (var c = 0; c < GRID_COLS; c++) {
for (var r = 0; r < GRID_ROWS; r++) {
var bdata = prevState.grid[c][r];
if (bdata) {
var b = new Block();
b.setColor(bdata.colorIndex);
b.isOnGrid = bdata.isOnGrid;
b.gridX = bdata.gridX;
b.gridY = bdata.gridY;
var pos = getPosForCell(c, r);
b.x = pos.x;
b.y = pos.y;
grid[c][r] = b;
game.addChild(b);
} else {
grid[c][r] = null;
}
}
}
// Remove all nextBlocks from game
for (var i = 0; i < nextBlocks.length; i++) {
if (nextBlocks[i]) nextBlocks[i].destroy();
}
nextBlocks = [];
// Restore nextBlocks
for (var i = 0; i < prevState.nextBlocks.length; i++) {
var nb = new Block();
nb.setColor(prevState.nextBlocks[i].colorIndex);
nb.x = 2048 / 2 + (nextBlocks.length - 1) * 220;
nb.y = 220;
nb.scaleX = nb.scaleY = 1;
nb.isOnGrid = false;
nb.gridX = -1;
nb.gridY = -1;
game.addChild(nb);
nextBlocks.push(nb);
}
// Reposition nextBlocks
for (var i = 0; i < nextBlocks.length; i++) {
var bx = 2048 / 2 + (i - 1) * 220;
nextBlocks[i].x = bx;
nextBlocks[i].y = 220;
}
// Restore score
score = prevState.score;
scoreTxt.setText(score);
prevState = null;
}
// Add a button for undo/föregående
var undoBtn = new Text2('⟲', {
size: 110,
fill: "#fff"
});
undoBtn.anchor.set(0.5, 0.5);
undoBtn.x = 2048 - 120;
undoBtn.y = 120;
undoBtn.interactive = true;
undoBtn.buttonMode = true;
undoBtn.down = function () {
restorePrevState();
};
LK.gui.top.addChild(undoBtn);
// --- GUI Elements ---
scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// --- Draw Background Image ---
var background = LK.getAsset('Background', {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
alpha: 1
});
game.addChild(background);
// --- Draw Grid Background ---
for (var c = 0; c < GRID_COLS; c++) {
for (var r = 0; r < GRID_ROWS; r++) {
var cell = LK.getAsset('block_white', {
anchorX: 0.5,
anchorY: 0.5,
x: GRID_OFFSET_X + c * CELL_SIZE + CELL_SIZE / 2,
y: GRID_OFFSET_Y + r * CELL_SIZE + CELL_SIZE / 2,
alpha: 0.08
});
game.addChild(cell);
}
}
// --- Draw Grid Lines ---
for (var c = 0; c <= GRID_COLS; c++) {
var x = GRID_OFFSET_X + c * CELL_SIZE;
var y1 = GRID_OFFSET_Y;
var y2 = GRID_OFFSET_Y + GRID_ROWS * CELL_SIZE;
// Draw vertical line as a thick, high-contrast rectangle
var vline = LK.getAsset('block_black', {
width: 14,
height: GRID_ROWS * CELL_SIZE,
anchorX: 0.5,
anchorY: 0,
x: x,
y: y1,
alpha: 0.38
});
game.addChild(vline);
}
for (var r = 0; r <= GRID_ROWS; r++) {
var y = GRID_OFFSET_Y + r * CELL_SIZE;
var x1 = GRID_OFFSET_X;
var x2 = GRID_OFFSET_X + GRID_COLS * CELL_SIZE;
// Draw horizontal line as a thick, high-contrast rectangle
var hline = LK.getAsset('block_black', {
width: GRID_COLS * CELL_SIZE,
height: 14,
anchorX: 0,
anchorY: 0.5,
x: x1,
y: y,
alpha: 0.38
});
game.addChild(hline);
}
// --- Helper Functions ---
// Get grid cell from x,y (game coordinates)
function getGridCellFromPos(x, y) {
var gx = Math.floor((x - GRID_OFFSET_X) / CELL_SIZE);
var gy = Math.floor((y - GRID_OFFSET_Y) / CELL_SIZE);
if (gx < 0 || gx >= GRID_COLS || gy < 0 || gy >= GRID_ROWS) return null;
return {
col: gx,
row: gy
};
}
// Get position (x,y) for grid cell
function getPosForCell(col, row) {
return {
x: GRID_OFFSET_X + col * CELL_SIZE + CELL_SIZE / 2,
y: GRID_OFFSET_Y + row * CELL_SIZE + CELL_SIZE / 2
};
}
// Generate a random color index (0-4)
function randomColorIndex() {
return COLORS[Math.floor(Math.random() * COLORS.length)];
}
// Create a new block for the "next" area
function createNextBlock(idx) {
var block = new Block();
block.setColor(idx);
block.x = 2048 / 2 + (nextBlocks.length - 1) * 220;
block.y = 220;
block.scaleX = block.scaleY = 1;
block.isOnGrid = false;
block.gridX = -1;
block.gridY = -1;
game.addChild(block);
return block;
}
// Fill nextBlocks to always have 3 blocks
function refillNextBlocks() {
// Limit: Only allow new blocks if there is at least one empty cell on the grid
var emptyCells = 0;
for (var c = 0; c < GRID_COLS; c++) {
for (var r = 0; r < GRID_ROWS; r++) {
if (!grid[c][r]) emptyCells++;
}
}
// Only allow up to 3 next blocks, but never more than empty cells
var maxNext = Math.min(3, emptyCells);
// Fill nextBlocks with random colors (original mechanic)
while (nextBlocks.length < maxNext) {
// 5% chance to spawn a rainbow block, otherwise random color
var idx;
var rand = Math.random();
if (rand < 0.05) {
idx = RAINBOW_COLOR_INDEX; // rainbow
} else {
// Allow all defined COLORS except rainbow (5) as next blocks
var allowedColors = COLORS.filter(function (cidx) {
return cidx !== RAINBOW_COLOR_INDEX;
});
idx = allowedColors[Math.floor(Math.random() * allowedColors.length)];
}
var block = createNextBlock(idx);
nextBlocks.push(block);
}
// Remove excess nextBlocks if grid is almost full
while (nextBlocks.length > maxNext) {
var b = nextBlocks.pop();
if (b) b.destroy();
}
// Position them nicely
for (var i = 0; i < nextBlocks.length; i++) {
var bx = 2048 / 2 + (i - 1) * 220;
tween(nextBlocks[i], {
x: bx,
y: 220
}, {
duration: 180,
easing: tween.easeInOut
});
}
}
// Remove a block from nextBlocks and shift others
function popNextBlock(block) {
var idx = nextBlocks.indexOf(block);
if (idx >= 0) {
nextBlocks.splice(idx, 1);
}
refillNextBlocks();
}
// Place block on grid
function placeBlockOnGrid(block, col, row) {
block.isOnGrid = true;
block.gridX = col;
block.gridY = row;
grid[col][row] = block;
var pos = getPosForCell(col, row);
tween(block, {
x: pos.x,
y: pos.y,
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeOut
});
}
// Remove block from grid
function removeBlockFromGrid(col, row) {
var block = grid[col][row];
if (block) {
grid[col][row] = null;
}
}
// Find all connected blocks of the same color (DFS)
function findConnectedBlocks(col, row, colorIndex, visited) {
if (col < 0 || col >= GRID_COLS || row < 0 || row >= GRID_ROWS) return [];
if (visited[col + "," + row]) return [];
var block = grid[col][row];
if (!block || block.colorIndex !== colorIndex) return [];
visited[col + "," + row] = true;
var result = [{
col: col,
row: row,
block: block
}];
// 4 directions
var dirs = [[1, 0], [-1, 0], [0, 1], [0, -1]];
for (var i = 0; i < dirs.length; i++) {
var nc = col + dirs[i][0];
var nr = row + dirs[i][1];
result = result.concat(findConnectedBlocks(nc, nr, colorIndex, visited));
}
return result;
}
// Check if any moves are possible (empty cell exists)
function hasMoves() {
for (var c = 0; c < GRID_COLS; c++) {
for (var r = 0; r < GRID_ROWS; r++) {
if (!grid[c][r]) return true;
}
}
return false;
}
// --- Game Logic ---
// Handle block drop onto grid
function tryPlaceBlock(block, x, y) {
if (isProcessing) return false;
var cell = getGridCellFromPos(x, y);
if (!cell) return false;
var col = cell.col,
row = cell.row;
if (grid[col][row]) return false; // occupied
// No locked blocks, so no need to check
if (grid[col][row]) return false; // cell is occupied
if (block.colorIndex === RAINBOW_COLOR_INDEX) {
// Rainbow block special checks
}
// Save state for undo before placing
function savePrevState() {
prevState = {
grid: copyGridState(grid),
nextBlocks: copyNextBlocksState(nextBlocks),
score: score
};
}
savePrevState();
if (block.colorIndex === RAINBOW_COLOR_INDEX) {
// Place rainbow block and trigger effect
placeBlockOnGrid(block, col, row);
block.isOnGrid = true;
popNextBlock(block);
// Pick a random color present on the grid (not locked, not white, not rainbow)
var presentColors = {};
for (var c2 = 0; c2 < GRID_COLS; c2++) {
for (var r2 = 0; r2 < GRID_ROWS; r2++) {
var b2 = grid[c2][r2];
if (b2 && (b2.colorIndex === 0 || b2.colorIndex === 1 || b2.colorIndex === 2 || b2.colorIndex === 4)) {
presentColors[b2.colorIndex] = true;
}
}
}
var colorList = [];
for (var k in presentColors) {
if (presentColors.hasOwnProperty(k)) colorList.push(parseInt(k));
}
if (colorList.length > 0) {
var colorToClear = colorList[Math.floor(Math.random() * colorList.length)];
// Animate and remove all blocks of that color
for (var c2 = 0; c2 < GRID_COLS; c2++) {
for (var r2 = 0; r2 < GRID_ROWS; r2++) {
var b2 = grid[c2][r2];
if (b2 && b2.colorIndex === colorToClear) {
var tmpBlock = b2; // Store reference before nulling the grid position
grid[c2][r2] = null;
(function (bref) {
tween(bref, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 180,
easing: tween.easeIn,
onFinish: function onFinish() {
bref.destroy();
}
});
})(tmpBlock);
}
}
}
// Add bonus score for rainbow clear
score += 200;
scoreTxt.setText(score);
}
// Play a sound for rainbow effect
LK.getSound('merge').play();
// After effect, check for game over
LK.setTimeout(function () {
if (!hasMoves()) {
LK.showGameOver();
}
}, 220);
return true;
}
// Place block
placeBlockOnGrid(block, col, row);
popNextBlock(block);
// After placement, check for merges
processMerges(col, row, function () {
// After all merges and cascades, check for game over
if (!hasMoves()) {
LK.showGameOver();
}
});
return true;
}
// Process merges and cascades starting from (col,row)
function processMerges(col, row, onFinish) {
if (isProcessing) return;
isProcessing = true;
var block = grid[col][row];
if (!block || block.isLocked) {
isProcessing = false;
if (onFinish) onFinish();
return;
}
var colorIdx = block.colorIndex;
if (colorIdx >= MAX_COLOR_INDEX) {
isProcessing = false;
if (onFinish) onFinish();
return;
}
// Remove pink (6) and violet (4) cubes immediately if present at this position
if (block.colorIndex === 4 || block.colorIndex === 6) {
removeBlockFromGrid(col, row);
tween(block, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 120,
easing: tween.easeIn,
onFinish: function onFinish() {
block.destroy();
isProcessing = false;
if (onFinish) onFinish();
}
});
return;
}
// Find all connected blocks of same color
var connected = findConnectedBlocks(col, row, colorIdx, {});
if (connected.length >= 3) {
// Play happy merge sound only on actual merge
LK.getSound('merge').play();
// Find center position for merged block
var sumX = 0,
sumY = 0;
for (var i = 0; i < connected.length; i++) {
sumX += connected[i].col;
sumY += connected[i].row;
}
var centerCol = Math.round(sumX / connected.length);
var centerRow = Math.round(sumY / connected.length);
// Pick a new color for the merged block: must be a color not present in the merged group
var presentColors = {};
for (var i = 0; i < connected.length; i++) {
presentColors[connected[i].block.colorIndex] = true;
}
// Try to find a color not present in the merged group
var allPossibleColors = [0, 1, 2, 3, 4, 6, 7, 8, 5]; // all colors, rainbow last
var newColorIdx = -1;
for (var i = 0; i < allPossibleColors.length; i++) {
if (!presentColors[allPossibleColors[i]]) {
newColorIdx = allPossibleColors[i];
break;
}
}
// If all colors are present, fallback to rainbow/white
if (newColorIdx === -1) {
newColorIdx = RAINBOW_COLOR_INDEX;
}
// Remove all blocks in connected, but delay the destruction until after the merged block is created
var blocksToRemove = [];
for (var i = 0; i < connected.length; i++) {
var b = connected[i].block;
blocksToRemove.push(b);
removeBlockFromGrid(b.gridX, b.gridY);
}
// Animate and destroy the old blocks, then create the merged block after all are gone
var destroyCount = 0;
for (var i = 0; i < blocksToRemove.length; i++) {
(function (bref) {
tween(bref, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 120,
easing: tween.easeIn,
onFinish: function onFinish() {
bref.destroy();
destroyCount++;
// When all blocks are destroyed, create the merged block and animate it
if (destroyCount === blocksToRemove.length) {
// Add delay for purple (4) and pink (6) merges
var doTransform = function doTransform() {
// Create merged block at center position
var mergedBlock = new Block();
mergedBlock.setColor(newColorIdx);
mergedBlock.isOnGrid = true;
mergedBlock.gridX = centerCol;
mergedBlock.gridY = centerRow;
var pos = getPosForCell(centerCol, centerRow);
mergedBlock.x = pos.x;
mergedBlock.y = pos.y;
grid[centerCol][centerRow] = mergedBlock;
game.addChild(mergedBlock);
mergedBlock.animateMerge(function () {
// Update score
var points = 10 * connected.length * (colorIdx + 1);
score += points;
scoreTxt.setText(score);
// After animation, finish processing
isProcessing = false;
// After merge, check if the new merged block itself can merge again (chain reaction)
processMerges(centerCol, centerRow, onFinish);
});
};
// If merging to purple (4) or pink (6), add a delay before transform
if (newColorIdx === 4 || newColorIdx === 6) {
LK.setTimeout(doTransform, 320);
} else {
doTransform();
}
}
}
});
})(blocksToRemove[i]);
}
// If no blocks to remove (should not happen), just finish
if (blocksToRemove.length === 0) {
mergedBlock.animateMerge(function () {
var points = 10 * connected.length * (colorIdx + 1);
score += points;
scoreTxt.setText(score);
isProcessing = false;
processMerges(centerCol, centerRow, onFinish);
});
}
} else {
isProcessing = false;
if (onFinish) onFinish();
}
}
// --- Input Handling ---
// Only allow dragging from nextBlocks
game.down = function (x, y, obj) {
if (isProcessing) return;
for (var i = 0; i < nextBlocks.length; i++) {
var block = nextBlocks[i];
// Check if touch is inside block
var dx = x - block.x;
var dy = y - block.y;
if (Math.abs(dx) < 90 && Math.abs(dy) < 90 && !block.isLocked && !block.isOnGrid) {
draggingBlock = block;
dragOffsetX = dx;
dragOffsetY = dy;
// Play a sound when picking up a block
LK.getSound('merge').play();
// Bring to front
game.addChild(block);
tween(block, {
scaleX: 1.15,
scaleY: 1.15
}, {
duration: 80,
easing: tween.easeOut
});
break;
}
}
};
game.move = function (x, y, obj) {
if (!draggingBlock) return;
draggingBlock.x = x - dragOffsetX;
draggingBlock.y = y - dragOffsetY;
};
game.up = function (x, y, obj) {
if (!draggingBlock) return;
// Try to place on grid
var placed = tryPlaceBlock(draggingBlock, draggingBlock.x, draggingBlock.y);
if (!placed) {
// Snap back to next area
var idx = nextBlocks.indexOf(draggingBlock);
var bx = 2048 / 2 + (idx - 1) * 220;
tween(draggingBlock, {
x: bx,
y: 220,
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeInOut
});
} else {
// Play a sound when block is placed on the grid
LK.getSound('merge').play();
// Block is now on grid, don't need to do anything
}
draggingBlock = null;
};
// --- Game Update Loop ---
game.update = function () {
// Animate blocks falling off (if any)
for (var c = 0; c < GRID_COLS; c++) {
for (var r = 0; r < GRID_ROWS; r++) {
var block = grid[c][r];
// Remove pink and violet cubes if present
if (block && (block.colorIndex === 4 || block.colorIndex === 6)) {
block.destroy();
grid[c][r] = null;
} else if (block && block.y > BLOCK_DROP_Y) {
block.destroy();
grid[c][r] = null;
}
}
}
// Occasionally spawn a locked block in a random empty cell (every 10 turns)
if (typeof game.turnsSinceLock === "undefined") game.turnsSinceLock = 0;
if (typeof game.lastBlocksOnGrid === "undefined") game.lastBlocksOnGrid = 0;
var blocksOnGrid = 0;
for (var c = 0; c < GRID_COLS; c++) {
for (var r = 0; r < GRID_ROWS; r++) {
if (grid[c][r]) blocksOnGrid++;
}
}
// Removed logic for spawning gray/locked blocks every 10 turns (no more grey squares)
if (blocksOnGrid > game.lastBlocksOnGrid) {
game.turnsSinceLock++;
game.lastBlocksOnGrid = blocksOnGrid;
// No locked block spawn
}
};
// --- Game Start ---
score = 0;
scoreTxt.setText(score);
refillNextBlocks();