/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Block class: represents a block on the grid or in the draggable area var Block = Container.expand(function () { var self = Container.call(this); // Block color index: 0=red, 1=green, 2=blue, 3=yellow, 4=purple, 5=white self.colorIndex = 0; self.gridX = -1; // grid position, -1 if not on grid self.gridY = -1; self.isOnGrid = false; // Attach asset based on colorIndex self.setColor = function (idx) { self.colorIndex = idx; if (self.blockAsset) { self.removeChild(self.blockAsset); } if (self.faceAsset) { self.removeChild(self.faceAsset); } var colorNames = ['red', 'green', 'blue', 'yellow', 'purple', 'white']; var assetId = 'block_' + colorNames[idx]; self.blockAsset = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); // --- Add 3D-like shading and highlights --- // Main highlight (top left) var highlight = LK.getAsset('block_white', { width: 60, height: 60, anchorX: 0.2, anchorY: 0.2, x: -36, y: -36, alpha: 0.18 }); self.addChild(highlight); // Side shadow (bottom right) var shadow = LK.getAsset('block_black', { width: 80, height: 80, anchorX: 0.8, anchorY: 0.8, x: 36, y: 36, alpha: 0.10 }); self.addChild(shadow); // Edge highlight (top edge) var edgeHighlight = LK.getAsset('block_white', { width: 80, height: 16, anchorX: 0.5, anchorY: 0.5, x: 0, y: -44, alpha: 0.10 }); self.addChild(edgeHighlight); // Edge shadow (bottom edge) var edgeShadow = LK.getAsset('block_black', { width: 80, height: 16, anchorX: 0.5, anchorY: 0.5, x: 0, y: 44, alpha: 0.08 }); self.addChild(edgeShadow); // Add a cute manga face overlay (big manga eyes, highlights, and a bigger smile) self.faceAsset = new Container(); // Manga left eye (big white) var eyeL = LK.getAsset('block_white', { width: 36, height: 36, anchorX: 0.5, anchorY: 0.5, x: -36, y: -22, alpha: 0.98 }); // Manga right eye (big white) var eyeR = LK.getAsset('block_white', { width: 36, height: 36, anchorX: 0.5, anchorY: 0.5, x: 36, y: -22, alpha: 0.98 }); // Left eye pupil (black) var pupilL = LK.getAsset('block_black', { width: 16, height: 16, anchorX: 0.5, anchorY: 0.5, x: -36, y: -22, alpha: 0.95 }); // Right eye pupil (black) var pupilR = LK.getAsset('block_black', { width: 16, height: 16, anchorX: 0.5, anchorY: 0.5, x: 36, y: -22, alpha: 0.95 }); // Left eye highlight (white) var eyeHL = LK.getAsset('block_white', { width: 7, height: 7, anchorX: 0.5, anchorY: 0.5, x: -40, y: -28, alpha: 0.85 }); // Right eye highlight (white) var eyeHR = LK.getAsset('block_white', { width: 7, height: 7, anchorX: 0.5, anchorY: 0.5, x: 32, y: -28, alpha: 0.85 }); // Cheeks var cheekL = LK.getAsset('block_red', { width: 18, height: 10, anchorX: 0.5, anchorY: 0.5, x: -38, y: 18, alpha: 0.18 }); var cheekR = LK.getAsset('block_red', { width: 18, height: 10, anchorX: 0.5, anchorY: 0.5, x: 38, y: 18, alpha: 0.18 }); // Big smile var smileAssetId = typeof LK.assets !== "undefined" && LK.assets && typeof LK.assets['block_black'] !== "undefined" ? 'block_black' : 'block_blue'; var smile = LK.getAsset(smileAssetId, { width: 60, height: 16, anchorX: 0.5, anchorY: 0.5, x: 0, y: 22, alpha: 0.8, rotation: 0.08 }); // Smile highlight (white, for manga shine) var smileHL = LK.getAsset('block_white', { width: 18, height: 4, anchorX: 0.5, anchorY: 0.5, x: 0, y: 18, alpha: 0.18, rotation: 0.08 }); self.faceAsset.addChild(eyeL); self.faceAsset.addChild(eyeR); self.faceAsset.addChild(pupilL); self.faceAsset.addChild(pupilR); self.faceAsset.addChild(eyeHL); self.faceAsset.addChild(eyeHR); self.faceAsset.addChild(cheekL); self.faceAsset.addChild(cheekR); self.faceAsset.addChild(smile); self.faceAsset.addChild(smileHL); self.addChild(self.faceAsset); }; self.setColor(0); // Animate merge self.animateMerge = function (_onFinish) { tween(self, { scaleX: 1.3, scaleY: 1.3 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeIn, onFinish: _onFinish }); } }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222233 }); /**** * Game Code ****/ // Sound for merge // Merged block (final color) // We'll use 5 colors: red, green, blue, yellow, purple // Define block shapes for each color and merged color // --- Game Constants --- var GRID_COLS = 6; var GRID_ROWS = 7; var CELL_SIZE = 200; // px var GRID_OFFSET_X = Math.floor((2048 - GRID_COLS * CELL_SIZE) / 2); var GRID_OFFSET_Y = 350; var COLORS = [0, 1, 2, 3, 4]; // 0=red, 1=green, 2=blue, 3=yellow, 4=purple var COLOR_NAMES = ['red', 'green', 'blue', 'yellow', 'purple', 'white']; var MAX_COLOR_INDEX = 5; // 0-4 normal, 5=white (final, can't merge further) 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 // --- GUI Elements --- scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // --- Draw Happy Skybox Background Gradient --- var skybox = LK.getAsset('block_blue', { width: 2048, height: 2732, anchorX: 0, anchorY: 0, x: 0, y: 0, alpha: 1 }); game.addChild(skybox); // Add a white "sun" circle for extra cuteness var sun = LK.getAsset('block_white', { width: 420, height: 420, anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 180, alpha: 0.18 }); game.addChild(sun); // Add some "clouds" using white blocks with low alpha for (var i = 0; i < 4; i++) { var cloud = LK.getAsset('block_white', { width: 320 + Math.random() * 120, height: 90 + Math.random() * 40, anchorX: 0.5, anchorY: 0.5, x: 300 + i * 420 + Math.random() * 80, y: 120 + Math.random() * 60, alpha: 0.10 + Math.random() * 0.08 }); game.addChild(cloud); } // --- 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); } } // --- 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() { while (nextBlocks.length < 3) { var idx = randomColorIndex(); var block = createNextBlock(idx); nextBlocks.push(block); } // 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) { var cell = getGridCellFromPos(x, y); if (!cell) return false; var col = cell.col, row = cell.row; if (grid[col][row]) return false; // occupied // 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) { isProcessing = false; if (onFinish) onFinish(); return; } var colorIdx = block.colorIndex; if (colorIdx >= MAX_COLOR_INDEX) { 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 and upgrade LK.getSound('merge').play(); // Merge! for (var i = 0; i < connected.length; i++) { var b = connected[i].block; if (b !== block) { // Animate and remove (function (bref) { tween(bref, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 120, easing: tween.easeIn, onFinish: function onFinish() { bref.destroy(); } }); })(b); removeBlockFromGrid(b.gridX, b.gridY); } } // Upgrade block color var newColor = colorIdx + 1; if (newColor > MAX_COLOR_INDEX) newColor = MAX_COLOR_INDEX; block.setColor(newColor); block.animateMerge(function () { // After animation, check for further merges (cascade) // We need to check if the upgraded block can merge again LK.setTimeout(function () { // Find new connected blocks after upgrade var newConnected = findConnectedBlocks(col, row, block.colorIndex, {}); if (newConnected.length >= 3) { // Chain reaction: process again processMerges(col, row, onFinish); } else { // No more merges, finish isProcessing = false; if (onFinish) onFinish(); } }, 180); }); // Update score var points = 10 * connected.length * (colorIdx + 1); score += points; scoreTxt.setText(score); } 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) { 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]; if (block && block.y > BLOCK_DROP_Y) { block.destroy(); grid[c][r] = null; } } } }; // --- Game Start --- score = 0; scoreTxt.setText(score); refillNextBlocks(); // --- Game Over / Reset handled by LK engine ---
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Block class: represents a block on the grid or in the draggable area
var Block = Container.expand(function () {
var self = Container.call(this);
// Block color index: 0=red, 1=green, 2=blue, 3=yellow, 4=purple, 5=white
self.colorIndex = 0;
self.gridX = -1; // grid position, -1 if not on grid
self.gridY = -1;
self.isOnGrid = false;
// Attach asset based on colorIndex
self.setColor = function (idx) {
self.colorIndex = idx;
if (self.blockAsset) {
self.removeChild(self.blockAsset);
}
if (self.faceAsset) {
self.removeChild(self.faceAsset);
}
var colorNames = ['red', 'green', 'blue', 'yellow', 'purple', 'white'];
var assetId = 'block_' + colorNames[idx];
self.blockAsset = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// --- Add 3D-like shading and highlights ---
// Main highlight (top left)
var highlight = LK.getAsset('block_white', {
width: 60,
height: 60,
anchorX: 0.2,
anchorY: 0.2,
x: -36,
y: -36,
alpha: 0.18
});
self.addChild(highlight);
// Side shadow (bottom right)
var shadow = LK.getAsset('block_black', {
width: 80,
height: 80,
anchorX: 0.8,
anchorY: 0.8,
x: 36,
y: 36,
alpha: 0.10
});
self.addChild(shadow);
// Edge highlight (top edge)
var edgeHighlight = LK.getAsset('block_white', {
width: 80,
height: 16,
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -44,
alpha: 0.10
});
self.addChild(edgeHighlight);
// Edge shadow (bottom edge)
var edgeShadow = LK.getAsset('block_black', {
width: 80,
height: 16,
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 44,
alpha: 0.08
});
self.addChild(edgeShadow);
// Add a cute manga face overlay (big manga eyes, highlights, and a bigger smile)
self.faceAsset = new Container();
// Manga left eye (big white)
var eyeL = LK.getAsset('block_white', {
width: 36,
height: 36,
anchorX: 0.5,
anchorY: 0.5,
x: -36,
y: -22,
alpha: 0.98
});
// Manga right eye (big white)
var eyeR = LK.getAsset('block_white', {
width: 36,
height: 36,
anchorX: 0.5,
anchorY: 0.5,
x: 36,
y: -22,
alpha: 0.98
});
// Left eye pupil (black)
var pupilL = LK.getAsset('block_black', {
width: 16,
height: 16,
anchorX: 0.5,
anchorY: 0.5,
x: -36,
y: -22,
alpha: 0.95
});
// Right eye pupil (black)
var pupilR = LK.getAsset('block_black', {
width: 16,
height: 16,
anchorX: 0.5,
anchorY: 0.5,
x: 36,
y: -22,
alpha: 0.95
});
// Left eye highlight (white)
var eyeHL = LK.getAsset('block_white', {
width: 7,
height: 7,
anchorX: 0.5,
anchorY: 0.5,
x: -40,
y: -28,
alpha: 0.85
});
// Right eye highlight (white)
var eyeHR = LK.getAsset('block_white', {
width: 7,
height: 7,
anchorX: 0.5,
anchorY: 0.5,
x: 32,
y: -28,
alpha: 0.85
});
// Cheeks
var cheekL = LK.getAsset('block_red', {
width: 18,
height: 10,
anchorX: 0.5,
anchorY: 0.5,
x: -38,
y: 18,
alpha: 0.18
});
var cheekR = LK.getAsset('block_red', {
width: 18,
height: 10,
anchorX: 0.5,
anchorY: 0.5,
x: 38,
y: 18,
alpha: 0.18
});
// Big smile
var smileAssetId = typeof LK.assets !== "undefined" && LK.assets && typeof LK.assets['block_black'] !== "undefined" ? 'block_black' : 'block_blue';
var smile = LK.getAsset(smileAssetId, {
width: 60,
height: 16,
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 22,
alpha: 0.8,
rotation: 0.08
});
// Smile highlight (white, for manga shine)
var smileHL = LK.getAsset('block_white', {
width: 18,
height: 4,
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 18,
alpha: 0.18,
rotation: 0.08
});
self.faceAsset.addChild(eyeL);
self.faceAsset.addChild(eyeR);
self.faceAsset.addChild(pupilL);
self.faceAsset.addChild(pupilR);
self.faceAsset.addChild(eyeHL);
self.faceAsset.addChild(eyeHR);
self.faceAsset.addChild(cheekL);
self.faceAsset.addChild(cheekR);
self.faceAsset.addChild(smile);
self.faceAsset.addChild(smileHL);
self.addChild(self.faceAsset);
};
self.setColor(0);
// Animate merge
self.animateMerge = function (_onFinish) {
tween(self, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeIn,
onFinish: _onFinish
});
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222233
});
/****
* Game Code
****/
// Sound for merge
// Merged block (final color)
// We'll use 5 colors: red, green, blue, yellow, purple
// Define block shapes for each color and merged color
// --- Game Constants ---
var GRID_COLS = 6;
var GRID_ROWS = 7;
var CELL_SIZE = 200; // px
var GRID_OFFSET_X = Math.floor((2048 - GRID_COLS * CELL_SIZE) / 2);
var GRID_OFFSET_Y = 350;
var COLORS = [0, 1, 2, 3, 4]; // 0=red, 1=green, 2=blue, 3=yellow, 4=purple
var COLOR_NAMES = ['red', 'green', 'blue', 'yellow', 'purple', 'white'];
var MAX_COLOR_INDEX = 5; // 0-4 normal, 5=white (final, can't merge further)
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
// --- GUI Elements ---
scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// --- Draw Happy Skybox Background Gradient ---
var skybox = LK.getAsset('block_blue', {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
alpha: 1
});
game.addChild(skybox);
// Add a white "sun" circle for extra cuteness
var sun = LK.getAsset('block_white', {
width: 420,
height: 420,
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 180,
alpha: 0.18
});
game.addChild(sun);
// Add some "clouds" using white blocks with low alpha
for (var i = 0; i < 4; i++) {
var cloud = LK.getAsset('block_white', {
width: 320 + Math.random() * 120,
height: 90 + Math.random() * 40,
anchorX: 0.5,
anchorY: 0.5,
x: 300 + i * 420 + Math.random() * 80,
y: 120 + Math.random() * 60,
alpha: 0.10 + Math.random() * 0.08
});
game.addChild(cloud);
}
// --- 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);
}
}
// --- 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() {
while (nextBlocks.length < 3) {
var idx = randomColorIndex();
var block = createNextBlock(idx);
nextBlocks.push(block);
}
// 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) {
var cell = getGridCellFromPos(x, y);
if (!cell) return false;
var col = cell.col,
row = cell.row;
if (grid[col][row]) return false; // occupied
// 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) {
isProcessing = false;
if (onFinish) onFinish();
return;
}
var colorIdx = block.colorIndex;
if (colorIdx >= MAX_COLOR_INDEX) {
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 and upgrade
LK.getSound('merge').play();
// Merge!
for (var i = 0; i < connected.length; i++) {
var b = connected[i].block;
if (b !== block) {
// Animate and remove
(function (bref) {
tween(bref, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 120,
easing: tween.easeIn,
onFinish: function onFinish() {
bref.destroy();
}
});
})(b);
removeBlockFromGrid(b.gridX, b.gridY);
}
}
// Upgrade block color
var newColor = colorIdx + 1;
if (newColor > MAX_COLOR_INDEX) newColor = MAX_COLOR_INDEX;
block.setColor(newColor);
block.animateMerge(function () {
// After animation, check for further merges (cascade)
// We need to check if the upgraded block can merge again
LK.setTimeout(function () {
// Find new connected blocks after upgrade
var newConnected = findConnectedBlocks(col, row, block.colorIndex, {});
if (newConnected.length >= 3) {
// Chain reaction: process again
processMerges(col, row, onFinish);
} else {
// No more merges, finish
isProcessing = false;
if (onFinish) onFinish();
}
}, 180);
});
// Update score
var points = 10 * connected.length * (colorIdx + 1);
score += points;
scoreTxt.setText(score);
} 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) {
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];
if (block && block.y > BLOCK_DROP_Y) {
block.destroy();
grid[c][r] = null;
}
}
}
};
// --- Game Start ---
score = 0;
scoreTxt.setText(score);
refillNextBlocks();
// --- Game Over / Reset handled by LK engine ---