User prompt
Add a persistent save system that stores the player’s current level, score, move count, and target progress whenever they exit the game. When the game is reopened, automatically load this saved data and resume the game from where the player left off. Store the data in local storage or any Upit-supported persistent memory so that it survives app restarts. ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
When checking for level completion, add a safeguard so the level completion routine only triggers once per level. After targets are fulfilled, disable further completion checks until the next level starts. Even if multiple matches or bombs clear targets in the same frame, only advance the level a single time.
User prompt
When advancing to a new level, completely clear the entire gem grid, removing all gem objects and references before spawning the new level’s gems. Reset all grid arrays, object references, and animations to empty states. Then generate a fresh grid based on the next level’s targets. Ensure no gems from the previous level remain on screen or in memory.
User prompt
Increase the size of the target panel gem icons and their count text by 30% to improve visibility. Keep the panel layout and background as is, but make sure the larger icons and text do not overlap or break the panel. Maintain clear spacing between each target gem and its count.
User prompt
Implement swipe gesture controls for gem swapping, similar to Candy Crush. Allow the player to press and drag a gem in any of the four directions (up, down, left, right). When released, if the adjacent gem can swap and form a valid match, perform the swap and resolve the match. If no valid match exists, return the gem to its original position with a smooth animation. Disable tap-to-select controls, enabling swipe movement only. Ensure swipe movement feels responsive and intuitive on both touch and mouse devices. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Display the current level’s target gem requirements in a horizontal panel fixed below the game grid. Show each target gem icon alongside the remaining count, using clear bold text. Place this target panel with a dark semi-transparent background for readability, and add margin from the bottom edge. Keep the panel visible across level transitions, updating it with the new level targets automatically.
User prompt
Place the target gem indicators inside a horizontal flexible container anchored below the top HUD bar. The container should automatically arrange all gem icons and counts in a row, with consistent horizontal padding, without overlapping the moves or level text. Use justify-content: space-evenly style logic to distribute them evenly. Keep the container responsive so that on different screen sizes, icons resize proportionally, never overlapping other HUD elements.
User prompt
When advancing to a new level, fully clear and destroy the previous gem grid and all gem objects before creating the new grid. Ensure no leftover gems remain from the previous level. Re-initialize a fresh gem grid based on the current level targets. Reset all active effects, timers, and animations. Confirm the game board is empty before spawning new gems. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Place the target gem indicators inside a horizontal flexible container anchored below the top HUD bar. The container should automatically arrange all gem icons and counts in a row, with consistent horizontal padding, without overlapping the moves or level text. Use justify-content: space-evenly style logic to distribute them evenly. Keep the container responsive so that on different screen sizes, icons resize proportionally, never overlapping other HUD elements.
User prompt
Place the target gem indicators in the top HUD bar, aligned horizontally after the “Moves” counter with consistent padding. Display them as small icons with their counts (e.g. 🔴 x9) in one clear horizontal row, avoiding any overlap with text. Use a horizontal flexbox style layout with uniform spacing and center-aligned vertically. Ensure it scales properly on all screen sizes.
User prompt
Please fix the bug: 'undefined is not an object (evaluating 'targetGemsContainer.anchor.set')' in or related to this line: 'targetGemsContainer.anchor.set(1, 0);' Line Number: 299
User prompt
Move the target gem indicators into the same top HUD bar as level, moves, and score. Display them as inline icons with counts (e.g. 🔴 x9) aligned to the right of the moves counter. Remove the separate lower panel entirely. Keep everything in a single consistent top HUD container to avoid overlap and maintain clarity on all screen sizes.
User prompt
Reduce the target panel background opacity slightly (for example 0.7), and add rounded corners with padding. Increase the target gem icon size by 20%, and use a bold font for the count text (e.g. x9). Keep the panel below the grid with at least 20px margin, but make it slightly narrower to look balanced. Ensure the panel stays fixed in position on all levels.
User prompt
Adjust the target panel background so it has a solid background color without any transparency, and fix its vertical position to stay at least 30 pixels below the gem grid. Ensure that the target panel does not overlap or cover any part of the gem grid. Lock the panel in a separate UI container with a fixed high z-index, so it stays on top of other UI but below the game board. Keep the gem icons and target counts centered horizontally inside this panel with enough padding.
User prompt
Fix the target panel position to always stay below the gem grid, with a minimum margin of 20 pixels, and never overlap with the gems. Use a fixed coordinate anchor system (for example, Y = gridBottom + 20 pixels) to guarantee consistent placement regardless of screen size or level. Keep the panel in a separate UI container with the highest z-index to stay visible but outside the game grid. Make sure the panel’s background does not overlap or hide any gem elements.
User prompt
After every level change or restart, re-calculate the layout positions of all UI elements to prevent overlapping. Keep the target panel locked at a fixed Y coordinate below the grid (for example Y = 650px on phone size), separate from the gem grid. Ensure that the score, level, and moves display stays fixed at the top, without moving. Maintain consistent padding and spacing between UI elements across levels.
User prompt
Modify the level completion logic so that when the player reaches all target gem goals, show a brief “Level Complete!” message with score summary, then automatically load the next level after a short delay (for example 2 seconds), without showing a full “Game Over” popup. Remove any “You win!” or “Play again” button from normal level transitions, and only show them on the final level 100. Ensure that moves and targets reset correctly on the next level load. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Completely remove any existing target panel references. Create a new fresh fixed-position panel below the gem grid. Add the gem target icons with remaining amounts in a horizontal row, centered. Use a semi-transparent dark background for the panel. Use bold large font for the counts to make them readable. Anchor the new panel in a separate container so it will not conflict with other UI layers, and stays visible across all levels.
User prompt
Add a new fixed target panel anchored to the bottom of the screen, with a dark semi-transparent background. Inside this panel, show clearly the target gem colors with remaining required amounts (e.g., a red gem icon and “5 left”). Keep the target panel fully visible and never hidden behind the grid. Place it at the bottom with a fixed position so it stays visible no matter the level or grid size. Refresh the counts live as the player collects gems.
User prompt
Create a new fixed UI container at the bottom of the screen, separate from the game grid. Inside this container, show the target gem icons with their remaining amount, for example “🔴 x5” with a small icon. Keep this container visible at all times and do not allow it to overlap the game grid or disappear on level change. Use a solid or semi-transparent background for readability. Place it at a fixed Y coordinate below the grid area to avoid layout conflicts.
User prompt
Adjust the target panel position so it is displayed at the bottom of the screen, below the game grid. Leave enough padding to separate it from the game grid and other UI elements. Ensure that the score, level, and moves indicators remain at the top of the screen without overlapping the target panel. Keep the target panel always visible and clearly readable with a semi-transparent background if needed.
User prompt
Add a visible target panel below the score or near the moves display. In this panel, show icons for each required gem color along with the remaining amount to collect, for example “🔴 x5”, “🟠 x10”. Update this panel live as the player collects the target gems. Keep the panel always visible during the level. Make sure the target panel does not overlap the game grid or other HUD elements.
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Gem = Container.expand(function (color, isSpecial, specialType) {
var self = Container.call(this);
self.gemType = color;
self.isSpecial = isSpecial || false;
self.specialType = specialType || null; // 'striped-horizontal', 'striped-vertical', or 'color-bomb'
var gemAssetIds = ['redGem', 'blueGem', 'greenGem', 'yellowGem', 'purpleGem', 'orangeGem'];
var assetId = self.isSpecial && self.specialType === 'super-color-bomb' ? 'superColorBomb' : self.isSpecial && self.specialType === 'color-bomb' ? 'colorBomb' : self.isSpecial && self.specialType === 'cross-bomb' ? 'crossBomb' : self.isSpecial ? 'stripedGem' : gemAssetIds[color];
var gemGraphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Add visual effects for special gems
if (self.isSpecial) {
if (self.specialType === 'super-color-bomb') {
// Super color bomb with dark core and intense rainbow sparkles
var superRainbowColors = [0x330000, 0x660000, 0x990000, 0xcc0000, 0xff0000, 0xff3300, 0xff6600, 0xff9900, 0xffcc00, 0xffff00, 0xccff00, 0x99ff00, 0x66ff00, 0x33ff00, 0x00ff00];
var superColorIndex = 0;
LK.setInterval(function () {
gemGraphics.tint = superRainbowColors[superColorIndex];
superColorIndex = (superColorIndex + 1) % superRainbowColors.length;
}, 100);
// Intense pulsing animation
tween(gemGraphics, {
scaleX: 1.5,
scaleY: 1.5,
rotation: Math.PI / 4
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(gemGraphics, {
scaleX: 1,
scaleY: 1,
rotation: 0
}, {
duration: 400,
easing: tween.easeInOut
});
}
});
} else if (self.specialType === 'color-bomb') {
// Color bomb with rainbow sparkle effect
var rainbowColors = [0xff0000, 0xff8800, 0xffff00, 0x00ff00, 0x0088ff, 0x8800ff];
var colorIndex = 0;
LK.setInterval(function () {
gemGraphics.tint = rainbowColors[colorIndex];
colorIndex = (colorIndex + 1) % rainbowColors.length;
}, 200);
// Pulsing animation
tween(gemGraphics, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(gemGraphics, {
scaleX: 1,
scaleY: 1
}, {
duration: 600,
easing: tween.easeInOut
});
}
});
} else if (self.specialType === 'cross-bomb') {
// Cross bomb with golden glow and plus symbol
gemGraphics.tint = 0xffd700;
// Rotating animation
tween(gemGraphics, {
rotation: Math.PI * 2
}, {
duration: 2000,
easing: tween.linear,
onFinish: function onFinish() {
gemGraphics.rotation = 0;
}
});
// Pulsing glow effect
tween(gemGraphics, {
scaleX: 1.4,
scaleY: 1.4
}, {
duration: 700,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(gemGraphics, {
scaleX: 1,
scaleY: 1
}, {
duration: 700,
easing: tween.easeInOut
});
}
});
} else {
// Striped gem effects
gemGraphics.tint = [0xff4444, 0x4444ff, 0x44ff44, 0xffff44, 0xff44ff, 0xff8844][color];
// Add sparkle animation
tween(gemGraphics, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(gemGraphics, {
scaleX: 1,
scaleY: 1
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
}
}
self.gridX = 0;
self.gridY = 0;
self.isAnimating = false;
self.setGridPosition = function (gridX, gridY) {
// Clear old grid position if it exists
if (self.gridX !== undefined && self.gridY !== undefined && grid[self.gridY] && grid[self.gridY][self.gridX] === self) {
grid[self.gridY][self.gridX] = null;
}
self.gridX = gridX;
self.gridY = gridY;
// Update screen position to match grid position
self.x = GRID_OFFSET_X + gridX * GEM_SIZE + GEM_SIZE / 2;
self.y = GRID_OFFSET_Y + gridY * GEM_SIZE + GEM_SIZE / 2;
// Update grid array to point to this gem
if (grid[gridY]) {
grid[gridY][gridX] = self;
}
};
self.animateToPosition = function (targetX, targetY, onComplete) {
self.isAnimating = true;
tween(self, {
x: targetX,
y: targetY
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
self.isAnimating = false;
if (onComplete) onComplete();
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2a1810
});
/****
* Game Code
****/
var GRID_SIZE = 8;
var GEM_SIZE = 200;
var GRID_OFFSET_X = (2048 - GRID_SIZE * GEM_SIZE) / 2;
var GRID_OFFSET_Y = 400;
var grid = [];
var selectedGem = null;
var movesLeft = 0;
var isProcessing = false;
var targetGemTypes = [];
var targetAmounts = [];
var collectedAmounts = [];
var currentLevel = storage.currentLevel || 1;
var levelData = [];
var targetPanel = null;
var targetPanelContainer = null;
// Initialize level data for 100 levels
function initializeLevelData() {
for (var i = 1; i <= 100; i++) {
var level = {
level: i,
moves: Math.max(15, 25 - Math.floor(i / 10)),
// Decrease moves as levels progress
targets: []
};
// Number of target colors increases every 20 levels
var numTargets = Math.min(3, 1 + Math.floor((i - 1) / 20));
// Select random gem types as targets
var availableTypes = [0, 1, 2, 3, 4, 5];
for (var j = 0; j < numTargets; j++) {
var randomIndex = Math.floor(Math.random() * availableTypes.length);
var selectedType = availableTypes[randomIndex];
availableTypes.splice(randomIndex, 1);
// Target amounts increase with level
var baseAmount = 8 + Math.floor(i / 5);
var targetAmount = baseAmount + Math.floor(Math.random() * 5);
level.targets.push({
gemType: selectedType,
amount: targetAmount
});
}
levelData.push(level);
}
}
// Initialize score display
var scoreTxt = new Text2('Score: 0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Initialize moves display
var movesTxt = new Text2('Moves: 0', {
size: 100,
fill: 0xFFFF44
});
movesTxt.anchor.set(1, 0);
movesTxt.x = -50;
movesTxt.y = 150;
LK.gui.topRight.addChild(movesTxt);
// Initialize level display
var levelTxt = new Text2('Level: 1', {
size: 100,
fill: 0x44FF44
});
levelTxt.anchor.set(0, 0);
levelTxt.x = 50;
levelTxt.y = 150;
LK.gui.topLeft.addChild(levelTxt);
function getRandomGemType() {
return Math.floor(Math.random() * 6);
}
function initializeLevelObjectives() {
var levelInfo = levelData[currentLevel - 1];
if (!levelInfo) {
// If no level data exists, create a default level
levelInfo = {
level: currentLevel,
moves: 20,
targets: [{
gemType: 0,
amount: 10
}, {
gemType: 1,
amount: 10
}]
};
}
targetGemTypes = [];
targetAmounts = [];
collectedAmounts = [];
movesLeft = levelInfo.moves;
for (var i = 0; i < levelInfo.targets.length; i++) {
var target = levelInfo.targets[i];
targetGemTypes.push(target.gemType);
targetAmounts.push(target.amount);
collectedAmounts.push(0);
}
// Update display texts
movesTxt.setText('Moves: ' + movesLeft);
levelTxt.setText('Level: ' + currentLevel);
}
function createTargetPanel() {
// Create separate container for target panel
targetPanelContainer = new Container();
targetPanelContainer.x = 2048 / 2;
targetPanelContainer.y = 2500; // Fixed position below game grid
game.addChild(targetPanelContainer);
// Create dark semi-transparent background
var panelBackground = LK.getAsset('blueGem', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 14,
scaleY: 2.5,
alpha: 0.8
});
panelBackground.tint = 0x1a1a1a; // Dark background
targetPanelContainer.addChild(panelBackground);
// Create horizontal row of target gem displays
var numTargets = targetGemTypes.length;
var gemAssetIds = ['redGem', 'blueGem', 'greenGem', 'yellowGem', 'purpleGem', 'orangeGem'];
var spacing = 400; // Fixed spacing between targets
for (var i = 0; i < numTargets; i++) {
// Position gems horizontally centered
var xOffset = (i - (numTargets - 1) / 2) * spacing;
// Create gem icon
var gemIcon = LK.getAsset(gemAssetIds[targetGemTypes[i]], {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
gemIcon.x = xOffset;
gemIcon.y = -40;
targetPanelContainer.addChild(gemIcon);
// Create count text with bold large font
var remainingAmount = targetAmounts[i] - collectedAmounts[i];
var countText = new Text2('x' + remainingAmount, {
size: 120,
fill: 0xFFFFFF
});
countText.anchor.set(0.5, 0);
countText.x = xOffset;
countText.y = 30;
targetPanelContainer.addChild(countText);
}
}
function updateTargetPanel() {
if (!targetPanelContainer || targetPanelContainer.children.length === 0) return;
var numTargets = targetGemTypes.length;
var spacing = 400;
// Update count texts (skip background at index 0)
for (var i = 0; i < numTargets; i++) {
var textIndex = 1 + i * 2 + 1; // background(0) + gem(1) + text(2) pattern
var countTextChild = targetPanelContainer.children[textIndex];
if (countTextChild && countTextChild.setText) {
var remainingAmount = Math.max(0, targetAmounts[i] - collectedAmounts[i]);
if (remainingAmount === 0) {
countTextChild.setText('DONE!');
countTextChild.fill = 0x00FF00; // Green when complete
} else {
countTextChild.setText('x' + remainingAmount);
countTextChild.fill = 0xFFFFFF; // White when incomplete
}
}
}
}
function checkLevelComplete() {
for (var i = 0; i < targetGemTypes.length; i++) {
if (collectedAmounts[i] < targetAmounts[i]) {
return false;
}
}
return true;
}
function createGem(gridX, gridY, isSpecial, specialType) {
var gemType = getRandomGemType();
var gem = new Gem(gemType, isSpecial, specialType);
gem.setGridPosition(gridX, gridY);
gem.x = GRID_OFFSET_X + gridX * GEM_SIZE + GEM_SIZE / 2;
gem.y = GRID_OFFSET_Y + gridY * GEM_SIZE + GEM_SIZE / 2;
return gem;
}
function initializeGrid() {
for (var y = 0; y < GRID_SIZE; y++) {
grid[y] = [];
for (var x = 0; x < GRID_SIZE; x++) {
var gem = createGem(x, y);
grid[y][x] = gem;
game.addChild(gem);
}
}
}
function getGridPosition(screenX, screenY) {
var gridX = Math.floor((screenX - GRID_OFFSET_X) / GEM_SIZE);
var gridY = Math.floor((screenY - GRID_OFFSET_Y) / GEM_SIZE);
if (gridX >= 0 && gridX < GRID_SIZE && gridY >= 0 && gridY < GRID_SIZE) {
return {
x: gridX,
y: gridY
};
}
return null;
}
function areAdjacent(gem1, gem2) {
var dx = Math.abs(gem1.gridX - gem2.gridX);
var dy = Math.abs(gem1.gridY - gem2.gridY);
// Only horizontal OR vertical adjacency allowed, not diagonal
return dx === 1 && dy === 0 || dx === 0 && dy === 1;
}
function swapGems(gem1, gem2) {
if (isProcessing) return;
// Check if either gem is a special gem (striped, color bomb, super color bomb, or cross bomb)
var hasStripedGem = gem1.isSpecial && (gem1.specialType === 'striped-horizontal' || gem1.specialType === 'striped-vertical') || gem2.isSpecial && (gem2.specialType === 'striped-horizontal' || gem2.specialType === 'striped-vertical');
var hasColorBomb = gem1.isSpecial && gem1.specialType === 'color-bomb' || gem2.isSpecial && gem2.specialType === 'color-bomb';
var hasSuperColorBomb = gem1.isSpecial && gem1.specialType === 'super-color-bomb' || gem2.isSpecial && gem2.specialType === 'super-color-bomb';
var hasCrossBomb = gem1.isSpecial && gem1.specialType === 'cross-bomb' || gem2.isSpecial && gem2.specialType === 'cross-bomb';
// Store original positions
var originalX1 = gem1.gridX;
var originalY1 = gem1.gridY;
var originalX2 = gem2.gridX;
var originalY2 = gem2.gridY;
var originalScreenX1 = gem1.x;
var originalScreenY1 = gem1.y;
var originalScreenX2 = gem2.x;
var originalScreenY2 = gem2.y;
// Calculate target positions for the swap animation
var targetX1 = GRID_OFFSET_X + gem2.gridX * GEM_SIZE + GEM_SIZE / 2;
var targetY1 = GRID_OFFSET_Y + gem2.gridY * GEM_SIZE + GEM_SIZE / 2;
var targetX2 = GRID_OFFSET_X + gem1.gridX * GEM_SIZE + GEM_SIZE / 2;
var targetY2 = GRID_OFFSET_Y + gem1.gridY * GEM_SIZE + GEM_SIZE / 2;
isProcessing = true;
LK.getSound('swap').play();
// Animate the visual swap first
gem1.animateToPosition(targetX1, targetY1);
gem2.animateToPosition(targetX2, targetY2, function () {
// After animation completes, handle the swap
// Temporarily swap positions
var tempGridX = gem1.gridX;
var tempGridY = gem1.gridY;
gem1.setGridPosition(gem2.gridX, gem2.gridY);
gem2.setGridPosition(tempGridX, tempGridY);
grid[gem1.gridY][gem1.gridX] = gem1;
grid[gem2.gridY][gem2.gridX] = gem2;
if (hasSuperColorBomb || hasColorBomb || hasStripedGem || hasCrossBomb) {
// If either gem is special, activate it immediately or handle special combinations
var additionalMatches = [];
// Handle special gem combinations
var gem1IsColorBomb = gem1.isSpecial && gem1.specialType === 'color-bomb';
var gem2IsColorBomb = gem2.isSpecial && gem2.specialType === 'color-bomb';
var gem1IsStriped = gem1.isSpecial && (gem1.specialType === 'striped-horizontal' || gem1.specialType === 'striped-vertical');
var gem2IsStriped = gem2.isSpecial && (gem2.specialType === 'striped-horizontal' || gem2.specialType === 'striped-vertical');
var gem1IsCrossBomb = gem1.isSpecial && gem1.specialType === 'cross-bomb';
var gem2IsCrossBomb = gem2.isSpecial && gem2.specialType === 'cross-bomb';
// Color bomb + striped gem combination
if (gem1IsColorBomb && gem2IsStriped || gem2IsColorBomb && gem1IsStriped) {
var stripedGem = gem1IsStriped ? gem1 : gem2;
var stripedColor = stripedGem.gemType;
// Transform all gems of striped gem's color into striped gems, then remove them
for (var y = 0; y < GRID_SIZE; y++) {
for (var x = 0; x < GRID_SIZE; x++) {
if (grid[y][x] && grid[y][x].gemType === stripedColor) {
var gem = grid[y][x];
// Create striped effect
tween(gem, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
if (gem.destroy) gem.destroy();
}
});
additionalMatches.push({
x: x,
y: y
});
}
}
}
}
// Striped gem + striped gem combination
else if (gem1IsStriped && gem2IsStriped) {
// Clear both row and column of both gems
var matches1 = activateStripedGem(gem1);
var matches2 = activateStripedGem(gem2);
additionalMatches = additionalMatches.concat(matches1);
additionalMatches = additionalMatches.concat(matches2);
}
// Color bomb + cross bomb combination
else if (gem1IsColorBomb && gem2IsCrossBomb || gem2IsColorBomb && gem1IsCrossBomb) {
var crossBomb = gem1IsCrossBomb ? gem1 : gem2;
var crossBombColor = crossBomb.gemType;
// First clear all gems of cross bomb's color
for (var y = 0; y < GRID_SIZE; y++) {
for (var x = 0; x < GRID_SIZE; x++) {
if (grid[y][x] && grid[y][x].gemType === crossBombColor) {
additionalMatches.push({
x: x,
y: y
});
}
}
}
// Then clear cross bomb's row and column
var crossMatches = activateCrossBomb(crossBomb);
additionalMatches = additionalMatches.concat(crossMatches);
}
// Single special gem activations (original logic)
else {
if (gem1.isSpecial && gem1.specialType === 'super-color-bomb') {
var superColorBombMatches = activateSuperColorBomb(gem1, gem2.gemType);
additionalMatches = additionalMatches.concat(superColorBombMatches);
} else if (gem1.isSpecial && gem1.specialType === 'color-bomb') {
var colorBombMatches = activateColorBomb(gem1, gem2.gemType);
additionalMatches = additionalMatches.concat(colorBombMatches);
} else if (gem1.isSpecial && gem1.specialType === 'cross-bomb') {
var crossBombMatches = activateCrossBomb(gem1);
additionalMatches = additionalMatches.concat(crossBombMatches);
} else if (gem1.isSpecial && (gem1.specialType === 'striped-horizontal' || gem1.specialType === 'striped-vertical')) {
var stripedMatches = activateStripedGem(gem1);
additionalMatches = additionalMatches.concat(stripedMatches);
}
if (gem2.isSpecial && gem2.specialType === 'super-color-bomb') {
var superColorBombMatches = activateSuperColorBomb(gem2, gem1.gemType);
additionalMatches = additionalMatches.concat(superColorBombMatches);
} else if (gem2.isSpecial && gem2.specialType === 'color-bomb') {
var colorBombMatches = activateColorBomb(gem2, gem1.gemType);
additionalMatches = additionalMatches.concat(colorBombMatches);
} else if (gem2.isSpecial && gem2.specialType === 'cross-bomb') {
var crossBombMatches = activateCrossBomb(gem2);
additionalMatches = additionalMatches.concat(crossBombMatches);
} else if (gem2.isSpecial && (gem2.specialType === 'striped-horizontal' || gem2.specialType === 'striped-vertical')) {
var stripedMatches = activateStripedGem(gem2);
additionalMatches = additionalMatches.concat(stripedMatches);
}
}
// Process the striped gem effects
if (additionalMatches.length > 0) {
// Award bonus points for striped activation
LK.setScore(LK.getScore() + additionalMatches.length * 20);
scoreTxt.setText('Score: ' + LK.getScore());
// Remove the matched gems
for (var i = 0; i < additionalMatches.length; i++) {
var match = additionalMatches[i];
var gem = grid[match.y][match.x];
if (gem) {
// Check if this gem type is a target
for (var t = 0; t < targetGemTypes.length; t++) {
if (gem.gemType === targetGemTypes[t]) {
collectedAmounts[t]++;
}
}
gem.destroy();
grid[match.y][match.x] = null;
}
}
// Update target panel display
updateTargetPanel();
// Check if level is complete
if (checkLevelComplete()) {
advanceToNextLevel();
LK.showYouWin();
}
}
isProcessing = false;
applyGravity();
} else {
// Check if this swap creates any matches (for non-striped gems)
var testMatchData = findMatches();
if (testMatchData.matches.length === 0 && testMatchData.specialGems.length === 0) {
// No matches found - animate back to original positions after a delay
LK.setTimeout(function () {
// Revert grid positions
gem1.setGridPosition(originalX1, originalY1);
gem2.setGridPosition(originalX2, originalY2);
grid[originalY1][originalX1] = gem1;
grid[originalY2][originalX2] = gem2;
// Animate gems back to their original positions
gem1.animateToPosition(originalScreenX1, originalScreenY1);
gem2.animateToPosition(originalScreenX2, originalScreenY2, function () {
isProcessing = false;
});
}, 200); // 0.2 second delay before reverting
} else {
// Valid swap - continue with match processing
isProcessing = false;
processMatches();
}
}
});
}
function findMatches() {
var matches = [];
var specialGems = [];
// Check horizontal matches
for (var y = 0; y < GRID_SIZE; y++) {
var count = 1;
var currentType = grid[y][0].gemType;
for (var x = 1; x < GRID_SIZE; x++) {
if (grid[y][x].gemType === currentType) {
count++;
} else {
if (count >= 6) {
// Create super color bomb for 6+ gem matches
var centerX = Math.floor((x - count + x - 1) / 2);
specialGems.push({
x: centerX,
y: y,
type: 'super-color-bomb',
color: currentType
});
for (var i = x - count; i < x; i++) {
if (i !== centerX) {
// Don't remove the center gem that becomes super color bomb
matches.push({
x: i,
y: y
});
}
}
} else if (count >= 5) {
// Create color bomb for 5+ gem matches
var centerX = Math.floor((x - count + x - 1) / 2);
specialGems.push({
x: centerX,
y: y,
type: 'color-bomb',
color: currentType
});
for (var i = x - count; i < x; i++) {
if (i !== centerX) {
// Don't remove the center gem that becomes color bomb
matches.push({
x: i,
y: y
});
}
}
} else if (count >= 4) {
// Create striped gem (horizontal)
var centerX = Math.floor((x - count + x - 1) / 2);
specialGems.push({
x: centerX,
y: y,
type: 'striped-horizontal',
color: currentType
});
for (var i = x - count; i < x; i++) {
if (i !== centerX) {
// Don't remove the center gem that becomes striped
matches.push({
x: i,
y: y
});
}
}
} else if (count >= 3) {
for (var i = x - count; i < x; i++) {
matches.push({
x: i,
y: y
});
}
}
count = 1;
currentType = grid[y][x].gemType;
}
}
if (count >= 6) {
// Create super color bomb for 6+ gem matches
var centerX = Math.floor((GRID_SIZE - count + GRID_SIZE - 1) / 2);
specialGems.push({
x: centerX,
y: y,
type: 'super-color-bomb',
color: currentType
});
for (var i = GRID_SIZE - count; i < GRID_SIZE; i++) {
if (i !== centerX) {
// Don't remove the center gem that becomes super color bomb
matches.push({
x: i,
y: y
});
}
}
} else if (count >= 5) {
// Create color bomb for 5+ gem matches
var centerX = Math.floor((GRID_SIZE - count + GRID_SIZE - 1) / 2);
specialGems.push({
x: centerX,
y: y,
type: 'color-bomb',
color: currentType
});
for (var i = GRID_SIZE - count; i < GRID_SIZE; i++) {
if (i !== centerX) {
// Don't remove the center gem that becomes color bomb
matches.push({
x: i,
y: y
});
}
}
} else if (count >= 4) {
// Create striped gem (horizontal)
var centerX = Math.floor((GRID_SIZE - count + GRID_SIZE - 1) / 2);
specialGems.push({
x: centerX,
y: y,
type: 'striped-horizontal',
color: currentType
});
for (var i = GRID_SIZE - count; i < GRID_SIZE; i++) {
if (i !== centerX) {
// Don't remove the center gem that becomes striped
matches.push({
x: i,
y: y
});
}
}
} else if (count >= 3) {
for (var i = GRID_SIZE - count; i < GRID_SIZE; i++) {
matches.push({
x: i,
y: y
});
}
}
}
// Check vertical matches
for (var x = 0; x < GRID_SIZE; x++) {
var count = 1;
var currentType = grid[0][x].gemType;
for (var y = 1; y < GRID_SIZE; y++) {
if (grid[y][x].gemType === currentType) {
count++;
} else {
if (count >= 6) {
// Create super color bomb for 6+ gem matches
var centerY = Math.floor((y - count + y - 1) / 2);
specialGems.push({
x: x,
y: centerY,
type: 'super-color-bomb',
color: currentType
});
for (var i = y - count; i < y; i++) {
if (i !== centerY) {
// Don't remove the center gem that becomes super color bomb
matches.push({
x: x,
y: i
});
}
}
} else if (count >= 5) {
// Create color bomb for 5+ gem matches
var centerY = Math.floor((y - count + y - 1) / 2);
specialGems.push({
x: x,
y: centerY,
type: 'color-bomb',
color: currentType
});
for (var i = y - count; i < y; i++) {
if (i !== centerY) {
// Don't remove the center gem that becomes color bomb
matches.push({
x: x,
y: i
});
}
}
} else if (count >= 4) {
// Create striped gem (vertical)
var centerY = Math.floor((y - count + y - 1) / 2);
specialGems.push({
x: x,
y: centerY,
type: 'striped-vertical',
color: currentType
});
for (var i = y - count; i < y; i++) {
if (i !== centerY) {
// Don't remove the center gem that becomes striped
matches.push({
x: x,
y: i
});
}
}
} else if (count >= 3) {
for (var i = y - count; i < y; i++) {
matches.push({
x: x,
y: i
});
}
}
count = 1;
currentType = grid[y][x].gemType;
}
}
if (count >= 6) {
// Create super color bomb for 6+ gem matches
var centerY = Math.floor((GRID_SIZE - count + GRID_SIZE - 1) / 2);
specialGems.push({
x: x,
y: centerY,
type: 'super-color-bomb',
color: currentType
});
for (var i = GRID_SIZE - count; i < GRID_SIZE; i++) {
if (i !== centerY) {
// Don't remove the center gem that becomes super color bomb
matches.push({
x: x,
y: i
});
}
}
} else if (count >= 5) {
// Create color bomb for 5+ gem matches
var centerY = Math.floor((GRID_SIZE - count + GRID_SIZE - 1) / 2);
specialGems.push({
x: x,
y: centerY,
type: 'color-bomb',
color: currentType
});
for (var i = GRID_SIZE - count; i < GRID_SIZE; i++) {
if (i !== centerY) {
// Don't remove the center gem that becomes color bomb
matches.push({
x: x,
y: i
});
}
}
} else if (count >= 4) {
// Create striped gem (vertical)
var centerY = Math.floor((GRID_SIZE - count + GRID_SIZE - 1) / 2);
specialGems.push({
x: x,
y: centerY,
type: 'striped-vertical',
color: currentType
});
for (var i = GRID_SIZE - count; i < GRID_SIZE; i++) {
if (i !== centerY) {
// Don't remove the center gem that becomes striped
matches.push({
x: x,
y: i
});
}
}
} else if (count >= 3) {
for (var i = GRID_SIZE - count; i < GRID_SIZE; i++) {
matches.push({
x: x,
y: i
});
}
}
}
// Check for T and L shapes (5 gems in cross pattern)
for (var y = 1; y < GRID_SIZE - 1; y++) {
for (var x = 1; x < GRID_SIZE - 1; x++) {
var centerType = grid[y][x].gemType;
// Check T-shape (horizontal line with vertical extension up)
if (x >= 2 && x < GRID_SIZE - 2 && y >= 2 && grid[y][x - 2].gemType === centerType && grid[y][x - 1].gemType === centerType && grid[y][x + 1].gemType === centerType && grid[y][x + 2].gemType === centerType && grid[y - 1].gemType === centerType && grid[y - 2].gemType === centerType) {
specialGems.push({
x: x,
y: y,
type: 'cross-bomb',
color: centerType
});
// Add surrounding gems to matches (excluding center)
matches.push({
x: x - 2,
y: y
});
matches.push({
x: x - 1,
y: y
});
matches.push({
x: x + 1,
y: y
});
matches.push({
x: x + 2,
y: y
});
matches.push({
x: x,
y: y - 1
});
matches.push({
x: x,
y: y - 2
});
}
// Check T-shape (horizontal line with vertical extension down)
if (x >= 2 && x < GRID_SIZE - 2 && y < GRID_SIZE - 2 && grid[y][x - 2].gemType === centerType && grid[y][x - 1].gemType === centerType && grid[y][x + 1].gemType === centerType && grid[y][x + 2].gemType === centerType && grid[y + 1][x].gemType === centerType && grid[y + 2][x].gemType === centerType) {
specialGems.push({
x: x,
y: y,
type: 'cross-bomb',
color: centerType
});
// Add surrounding gems to matches (excluding center)
matches.push({
x: x - 2,
y: y
});
matches.push({
x: x - 1,
y: y
});
matches.push({
x: x + 1,
y: y
});
matches.push({
x: x + 2,
y: y
});
matches.push({
x: x,
y: y + 1
});
matches.push({
x: x,
y: y + 2
});
}
// Check T-shape (vertical line with horizontal extension left)
if (y >= 2 && y < GRID_SIZE - 2 && x >= 2 && grid[y - 2][x].gemType === centerType && grid[y - 1][x].gemType === centerType && grid[y + 1][x].gemType === centerType && grid[y + 2][x].gemType === centerType && grid[y][x - 1].gemType === centerType && grid[y][x - 2].gemType === centerType) {
specialGems.push({
x: x,
y: y,
type: 'cross-bomb',
color: centerType
});
// Add surrounding gems to matches (excluding center)
matches.push({
x: x,
y: y - 2
});
matches.push({
x: x,
y: y - 1
});
matches.push({
x: x,
y: y + 1
});
matches.push({
x: x,
y: y + 2
});
matches.push({
x: x - 1,
y: y
});
matches.push({
x: x - 2,
y: y
});
}
// Check T-shape (vertical line with horizontal extension right)
if (y >= 2 && y < GRID_SIZE - 2 && x < GRID_SIZE - 2 && grid[y - 2][x].gemType === centerType && grid[y - 1][x].gemType === centerType && grid[y + 1][x].gemType === centerType && grid[y + 2][x].gemType === centerType && grid[y][x + 1].gemType === centerType && grid[y][x + 2].gemType === centerType) {
specialGems.push({
x: x,
y: y,
type: 'cross-bomb',
color: centerType
});
// Add surrounding gems to matches (excluding center)
matches.push({
x: x,
y: y - 2
});
matches.push({
x: x,
y: y - 1
});
matches.push({
x: x,
y: y + 1
});
matches.push({
x: x,
y: y + 2
});
matches.push({
x: x + 1,
y: y
});
matches.push({
x: x + 2,
y: y
});
}
}
}
return {
matches: matches,
specialGems: specialGems
};
}
function activateColorBomb(gem, targetColor) {
var matches = [];
LK.getSound('explosion').play();
// Find all gems of the target color
for (var y = 0; y < GRID_SIZE; y++) {
for (var x = 0; x < GRID_SIZE; x++) {
if (grid[y][x] && grid[y][x].gemType === targetColor) {
matches.push({
x: x,
y: y
});
}
}
}
// Create explosion effect animation
var explosionEffect = LK.getAsset('colorBomb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 5,
scaleY: 5,
alpha: 0.8
});
explosionEffect.x = GRID_OFFSET_X + gem.gridX * GEM_SIZE + GEM_SIZE / 2;
explosionEffect.y = GRID_OFFSET_Y + gem.gridY * GEM_SIZE + GEM_SIZE / 2;
explosionEffect.tint = 0xffff00;
game.addChild(explosionEffect);
tween(explosionEffect, {
scaleX: 15,
scaleY: 15,
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
explosionEffect.destroy();
}
});
return matches;
}
function activateSuperColorBomb(gem, targetColor) {
var matches = [];
LK.getSound('explosion').play();
// Find all gems of the target color
for (var y = 0; y < GRID_SIZE; y++) {
for (var x = 0; x < GRID_SIZE; x++) {
if (grid[y][x] && grid[y][x].gemType === targetColor) {
matches.push({
x: x,
y: y
});
}
}
}
// Create massive screen-wide explosion effect
for (var i = 0; i < 5; i++) {
var explosionEffect = LK.getAsset('superColorBomb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3 + i,
scaleY: 3 + i,
alpha: 0.6
});
explosionEffect.x = GRID_OFFSET_X + gem.gridX * GEM_SIZE + GEM_SIZE / 2;
explosionEffect.y = GRID_OFFSET_Y + gem.gridY * GEM_SIZE + GEM_SIZE / 2;
explosionEffect.tint = [0xff0000, 0xff8800, 0xffff00, 0x00ff00, 0x0088ff][i];
game.addChild(explosionEffect);
tween(explosionEffect, {
scaleX: 20 + i * 5,
scaleY: 20 + i * 5,
alpha: 0,
rotation: Math.PI * 2
}, {
duration: 1500 + i * 200,
easing: tween.easeOut,
onFinish: function onFinish() {
explosionEffect.destroy();
}
});
}
// Create screen flash effect
LK.effects.flashScreen(0xffffff, 800);
return matches;
}
function activateCrossBomb(gem) {
var matches = [];
LK.getSound('explosion').play();
// Clear entire row
for (var x = 0; x < GRID_SIZE; x++) {
if (grid[gem.gridY][x]) {
matches.push({
x: x,
y: gem.gridY
});
}
}
// Clear entire column
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[y][gem.gridX]) {
matches.push({
x: gem.gridX,
y: y
});
}
}
// Create cross explosion effect
var crossEffectH = LK.getAsset('crossBomb', {
anchorX: 0,
anchorY: 0.5,
scaleX: 10,
scaleY: 0.3,
alpha: 0.9
});
crossEffectH.x = GRID_OFFSET_X;
crossEffectH.y = GRID_OFFSET_Y + gem.gridY * GEM_SIZE + GEM_SIZE / 2;
crossEffectH.tint = 0xffd700;
game.addChild(crossEffectH);
var crossEffectV = LK.getAsset('crossBomb', {
anchorX: 0.5,
anchorY: 0,
scaleX: 0.3,
scaleY: 10,
alpha: 0.9
});
crossEffectV.x = GRID_OFFSET_X + gem.gridX * GEM_SIZE + GEM_SIZE / 2;
crossEffectV.y = GRID_OFFSET_Y;
crossEffectV.tint = 0xffd700;
game.addChild(crossEffectV);
// Animate both effects
tween(crossEffectH, {
alpha: 0,
scaleY: 1.5
}, {
duration: 800,
onFinish: function onFinish() {
crossEffectH.destroy();
}
});
tween(crossEffectV, {
alpha: 0,
scaleX: 1.5
}, {
duration: 800,
onFinish: function onFinish() {
crossEffectV.destroy();
}
});
return matches;
}
function activateStripedGem(gem) {
var matches = [];
LK.getSound('lineBlast').play();
if (gem.specialType === 'striped-horizontal') {
// Clear entire row
for (var x = 0; x < GRID_SIZE; x++) {
if (grid[gem.gridY][x]) {
matches.push({
x: x,
y: gem.gridY
});
}
}
// Create line effect animation
var lineEffect = LK.getAsset('stripedGem', {
anchorX: 0,
anchorY: 0.5,
scaleX: 10,
scaleY: 0.2,
alpha: 0.8
});
lineEffect.x = GRID_OFFSET_X;
lineEffect.y = GRID_OFFSET_Y + gem.gridY * GEM_SIZE + GEM_SIZE / 2;
lineEffect.tint = 0xffff00;
game.addChild(lineEffect);
tween(lineEffect, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
lineEffect.destroy();
}
});
} else if (gem.specialType === 'striped-vertical') {
// Clear entire column
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[y][gem.gridX]) {
matches.push({
x: gem.gridX,
y: y
});
}
}
// Create line effect animation
var lineEffect = LK.getAsset('stripedGem', {
anchorX: 0.5,
anchorY: 0,
scaleX: 0.2,
scaleY: 10,
alpha: 0.8
});
lineEffect.x = GRID_OFFSET_X + gem.gridX * GEM_SIZE + GEM_SIZE / 2;
lineEffect.y = GRID_OFFSET_Y;
lineEffect.tint = 0xffff00;
game.addChild(lineEffect);
tween(lineEffect, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
lineEffect.destroy();
}
});
}
return matches;
}
function removeMatches(matchData) {
var matches = matchData.matches || matchData;
var specialGems = matchData.specialGems || [];
if (matches.length === 0 && specialGems.length === 0) return;
LK.getSound('match').play();
var points = matches.length * 10;
// Bonus points for special gem creation
if (specialGems.length > 0) {
for (var i = 0; i < specialGems.length; i++) {
if (specialGems[i].type === 'super-color-bomb') {
points += 200; // Highest bonus for super color bomb
} else if (specialGems[i].type === 'color-bomb') {
points += 100; // Higher bonus for color bomb
} else if (specialGems[i].type === 'cross-bomb') {
points += 80; // High bonus for cross bomb
} else {
points += 50; // Regular bonus for striped gems
}
}
}
LK.setScore(LK.getScore() + points);
scoreTxt.setText('Score: ' + LK.getScore());
// Check for special gems being matched and activate their effects
var additionalMatches = [];
for (var i = 0; i < matches.length; i++) {
var match = matches[i];
var gem = grid[match.y][match.x];
if (gem && gem.isSpecial) {
if (gem.specialType === 'super-color-bomb') {
// Super color bomb clears all gems of its own color with massive effect
var superColorBombMatches = activateSuperColorBomb(gem, gem.gemType);
additionalMatches = additionalMatches.concat(superColorBombMatches);
} else if (gem.specialType === 'color-bomb') {
// Color bomb clears all gems of its own color
var colorBombMatches = activateColorBomb(gem, gem.gemType);
additionalMatches = additionalMatches.concat(colorBombMatches);
} else if (gem.specialType === 'cross-bomb') {
// Cross bomb clears its row and column
var crossBombMatches = activateCrossBomb(gem);
additionalMatches = additionalMatches.concat(crossBombMatches);
} else if (gem.specialType === 'striped-horizontal' || gem.specialType === 'striped-vertical') {
var stripedMatches = activateStripedGem(gem);
additionalMatches = additionalMatches.concat(stripedMatches);
}
}
}
// Track collected target gems
for (var i = 0; i < matches.length; i++) {
var match = matches[i];
var gem = grid[match.y][match.x];
if (gem) {
// Check if this gem type is a target
for (var t = 0; t < targetGemTypes.length; t++) {
if (gem.gemType === targetGemTypes[t]) {
collectedAmounts[t]++;
}
}
gem.destroy();
grid[match.y][match.x] = null;
}
}
// Create special gems (striped or color bomb)
for (var i = 0; i < specialGems.length; i++) {
var special = specialGems[i];
// Remove existing gem completely if it exists
var existingGem = grid[special.y][special.x];
if (existingGem) {
existingGem.destroy();
grid[special.y][special.x] = null;
}
// Create new special gem with correct color and type
var specialGem = new Gem(special.color, true, special.type);
specialGem.setGridPosition(special.x, special.y);
game.addChild(specialGem);
}
// Process additional matches from striped gem activations
if (additionalMatches.length > 0) {
for (var i = 0; i < additionalMatches.length; i++) {
var match = additionalMatches[i];
var gem = grid[match.y][match.x];
if (gem) {
// Check if this gem type is a target
for (var t = 0; t < targetGemTypes.length; t++) {
if (gem.gemType === targetGemTypes[t]) {
collectedAmounts[t]++;
}
}
gem.destroy();
grid[match.y][match.x] = null;
}
}
// Add bonus points for special gem activation (highest for super color bomb effects)
var bonusMultiplier = additionalMatches.length > 20 ? 100 : additionalMatches.length > 10 ? 50 : 20; // Highest bonus for super color bomb effects
LK.setScore(LK.getScore() + additionalMatches.length * bonusMultiplier);
scoreTxt.setText('Score: ' + LK.getScore());
}
// Update target panel display
updateTargetPanel();
// Check if level is complete
if (checkLevelComplete()) {
advanceToNextLevel();
LK.showYouWin();
}
}
function applyGravity() {
// First pass: clear all null positions and compact gems downward
for (var x = 0; x < GRID_SIZE; x++) {
var compactedGems = [];
// Collect all non-null gems from this column
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[y][x] !== null) {
compactedGems.push(grid[y][x]);
}
// Clear the grid position
grid[y][x] = null;
}
// Place compacted gems at the bottom of the column using setGridPosition
var startY = GRID_SIZE - compactedGems.length;
for (var i = 0; i < compactedGems.length; i++) {
var newY = startY + i;
var gem = compactedGems[i];
// Use setGridPosition to ensure proper synchronization
gem.gridX = x;
gem.gridY = newY;
gem.x = GRID_OFFSET_X + x * GEM_SIZE + GEM_SIZE / 2;
gem.y = GRID_OFFSET_Y + newY * GEM_SIZE + GEM_SIZE / 2;
grid[newY][x] = gem;
}
}
// Second pass: create new gems for empty positions at top
var gemsToAnimate = [];
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[y][x] === null) {
var newGem = createGem(x, y);
// Start new gems above the visible area
newGem.y = GRID_OFFSET_Y - (GRID_SIZE - y) * GEM_SIZE + GEM_SIZE / 2;
// Set proper grid position
newGem.gridX = x;
newGem.gridY = y;
grid[y][x] = newGem;
game.addChild(newGem);
var targetY = GRID_OFFSET_Y + y * GEM_SIZE + GEM_SIZE / 2;
gemsToAnimate.push({
gem: newGem,
targetY: targetY
});
}
}
}
// Third pass: animate all existing gems to their correct positions
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
var gem = grid[y][x];
if (gem && !gem.isAnimating) {
var correctY = GRID_OFFSET_Y + y * GEM_SIZE + GEM_SIZE / 2;
if (Math.abs(gem.y - correctY) > 5) {
// Only animate if position is significantly different
gemsToAnimate.push({
gem: gem,
targetY: correctY
});
}
}
}
}
// Animate all gems that need repositioning
if (gemsToAnimate.length > 0) {
isProcessing = true;
var animationsComplete = 0;
for (var i = 0; i < gemsToAnimate.length; i++) {
var animData = gemsToAnimate[i];
tween(animData.gem, {
y: animData.targetY
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
animationsComplete++;
if (animationsComplete === gemsToAnimate.length) {
isProcessing = false;
// Validate grid sync before checking matches
validateGridSync();
processMatches();
}
}
});
}
} else {
// No animations needed, validate and check for matches immediately
validateGridSync();
processMatches();
}
}
function validateGridSync() {
// Verify that each grid position contains exactly one gem with correct coordinates
for (var y = 0; y < GRID_SIZE; y++) {
for (var x = 0; x < GRID_SIZE; x++) {
var gem = grid[y][x];
if (!gem) {
console.error("Missing gem at grid position", x, y);
return false;
}
if (gem.gridX !== x || gem.gridY !== y) {
console.error("Gem position mismatch at", x, y, "gem thinks it's at", gem.gridX, gem.gridY);
gem.setGridPosition(x, y); // Fix the mismatch
}
}
}
return true;
}
function processMatches() {
// Validate grid synchronization before processing matches
validateGridSync();
var matchData = findMatches();
if (matchData.matches.length > 0 || matchData.specialGems.length > 0) {
removeMatches(matchData);
applyGravity();
}
}
function advanceToNextLevel() {
currentLevel++;
if (currentLevel > 100) {
currentLevel = 100; // Cap at level 100
}
storage.currentLevel = currentLevel;
}
function resetCurrentLevel() {
// Reset to current level data
initializeLevelObjectives();
initializeGrid();
removeInitialMatches();
if (targetPanelContainer) {
targetPanelContainer.destroy();
targetPanelContainer = null;
}
createTargetPanel();
scoreTxt.setText('Score: ' + LK.getScore());
}
function decrementMoves() {
movesLeft--;
movesTxt.setText('Moves: ' + movesLeft);
if (movesLeft <= 0) {
if (checkLevelComplete()) {
advanceToNextLevel();
LK.showYouWin();
} else {
LK.showGameOver();
}
}
}
var dragStartGem = null;
game.down = function (x, y, obj) {
if (isProcessing) return;
var gridPos = getGridPosition(x, y);
if (!gridPos) return;
var clickedGem = grid[gridPos.y][gridPos.x];
if (!clickedGem) return;
// Store the gem being dragged
dragStartGem = clickedGem;
if (selectedGem === null) {
selectedGem = clickedGem;
tween(selectedGem, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 150
});
} else if (selectedGem === clickedGem) {
tween(selectedGem, {
scaleX: 1,
scaleY: 1
}, {
duration: 150
});
selectedGem = null;
} else if (areAdjacent(selectedGem, clickedGem)) {
tween(selectedGem, {
scaleX: 1,
scaleY: 1
}, {
duration: 150
});
swapGems(selectedGem, clickedGem);
decrementMoves();
selectedGem = null;
} else {
tween(selectedGem, {
scaleX: 1,
scaleY: 1
}, {
duration: 150
});
selectedGem = clickedGem;
tween(selectedGem, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 150
});
}
};
game.up = function (x, y, obj) {
if (isProcessing || !dragStartGem) return;
var gridPos = getGridPosition(x, y);
if (!gridPos) {
dragStartGem = null;
return;
}
var targetGem = grid[gridPos.y][gridPos.x];
if (!targetGem || targetGem === dragStartGem) {
dragStartGem = null;
return;
}
// Only allow swap if gems are adjacent (horizontal or vertical only)
if (areAdjacent(dragStartGem, targetGem)) {
// Clear any existing selection
if (selectedGem) {
tween(selectedGem, {
scaleX: 1,
scaleY: 1
}, {
duration: 150
});
selectedGem = null;
}
swapGems(dragStartGem, targetGem);
decrementMoves();
} else {
// If not adjacent, do nothing - invalid move
}
dragStartGem = null;
};
function removeInitialMatches() {
var hasMatches = true;
var maxIterations = 10; // Prevent infinite loops
var iterations = 0;
while (hasMatches && iterations < maxIterations) {
hasMatches = false;
iterations++;
var matchData = findMatches();
if (matchData.matches.length > 0 || matchData.specialGems.length > 0) {
hasMatches = true;
// Remove matched gems without awarding points or creating special gems
for (var i = 0; i < matchData.matches.length; i++) {
var match = matchData.matches[i];
var gem = grid[match.y][match.x];
if (gem) {
gem.destroy();
grid[match.y][match.x] = null;
}
}
// Apply gravity to drop gems down
for (var x = 0; x < GRID_SIZE; x++) {
var compactedGems = [];
// Collect all non-null gems from this column
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[y][x] !== null) {
compactedGems.push(grid[y][x]);
}
// Clear the grid position
grid[y][x] = null;
}
// Place compacted gems at the bottom of the column
var startY = GRID_SIZE - compactedGems.length;
for (var j = 0; j < compactedGems.length; j++) {
var newY = startY + j;
var gem = compactedGems[j];
grid[newY][x] = gem;
gem.setGridPosition(x, newY);
}
}
// Create new gems for empty positions at top
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[y][x] === null) {
var newGem = createGem(x, y);
grid[y][x] = newGem;
game.addChild(newGem);
}
}
}
}
}
// Final validation
validateGridSync();
}
// Initialize the game
initializeLevelData();
initializeLevelObjectives();
initializeGrid();
removeInitialMatches();
createTargetPanel();
scoreTxt.setText('Score: ' + LK.getScore()); ===================================================================
--- original.js
+++ change.js
@@ -179,12 +179,12 @@
var isProcessing = false;
var targetGemTypes = [];
var targetAmounts = [];
var collectedAmounts = [];
-var objectivePanel = null;
-var objectiveTxt = null;
var currentLevel = storage.currentLevel || 1;
var levelData = [];
+var targetPanel = null;
+var targetPanelContainer = null;
// Initialize level data for 100 levels
function initializeLevelData() {
for (var i = 1; i <= 100; i++) {
var level = {
@@ -269,105 +269,71 @@
// Update display texts
movesTxt.setText('Moves: ' + movesLeft);
levelTxt.setText('Level: ' + currentLevel);
}
-function createObjectivePanel() {
- // Create fixed UI container at bottom of screen below game grid
- objectivePanel = new Container();
- objectivePanel.x = 2048 / 2;
- objectivePanel.y = 2600; // Fixed position at bottom with more padding
- game.addChild(objectivePanel); // Add to game instead of LK.gui for proper positioning
- // Create dark semi-transparent background panel for better readability
- var panelBg = LK.getAsset('blueGem', {
+function createTargetPanel() {
+ // Create separate container for target panel
+ targetPanelContainer = new Container();
+ targetPanelContainer.x = 2048 / 2;
+ targetPanelContainer.y = 2500; // Fixed position below game grid
+ game.addChild(targetPanelContainer);
+ // Create dark semi-transparent background
+ var panelBackground = LK.getAsset('blueGem', {
anchorX: 0.5,
anchorY: 0.5,
- scaleX: 12,
- scaleY: 3,
- alpha: 0.9
+ scaleX: 14,
+ scaleY: 2.5,
+ alpha: 0.8
});
- panelBg.tint = 0x000000; // Dark black background
- panelBg.x = 0;
- panelBg.y = 0;
- objectivePanel.addChild(panelBg);
- // Create objective title with enhanced styling
- objectiveTxt = new Text2('TARGETS:', {
- size: 90,
- fill: 0xFFFFFF
- });
- objectiveTxt.anchor.set(0.5, 0);
- objectiveTxt.x = 0;
- objectiveTxt.y = -90;
- objectivePanel.addChild(objectiveTxt);
- // Create target gem displays with clear icons and amounts
+ panelBackground.tint = 0x1a1a1a; // Dark background
+ targetPanelContainer.addChild(panelBackground);
+ // Create horizontal row of target gem displays
var numTargets = targetGemTypes.length;
- var spacing = numTargets === 1 ? 0 : numTargets === 2 ? 400 : 350;
+ var gemAssetIds = ['redGem', 'blueGem', 'greenGem', 'yellowGem', 'purpleGem', 'orangeGem'];
+ var spacing = 400; // Fixed spacing between targets
for (var i = 0; i < numTargets; i++) {
- var gemAssetIds = ['redGem', 'blueGem', 'greenGem', 'yellowGem', 'purpleGem', 'orangeGem'];
- // Create gem icon with clear, visible size
- var targetGem = LK.getAsset(gemAssetIds[targetGemTypes[i]], {
+ // Position gems horizontally centered
+ var xOffset = (i - (numTargets - 1) / 2) * spacing;
+ // Create gem icon
+ var gemIcon = LK.getAsset(gemAssetIds[targetGemTypes[i]], {
anchorX: 0.5,
anchorY: 0.5,
- scaleX: 0.8,
- scaleY: 0.8
+ scaleX: 0.6,
+ scaleY: 0.6
});
- targetGem.x = (i - (numTargets - 1) / 2) * spacing;
- targetGem.y = -10;
- objectivePanel.addChild(targetGem);
- // Create text showing remaining amount with x prefix
+ gemIcon.x = xOffset;
+ gemIcon.y = -40;
+ targetPanelContainer.addChild(gemIcon);
+ // Create count text with bold large font
var remainingAmount = targetAmounts[i] - collectedAmounts[i];
- var targetText = new Text2('x' + remainingAmount, {
- size: 100,
+ var countText = new Text2('x' + remainingAmount, {
+ size: 120,
fill: 0xFFFFFF
});
- targetText.anchor.set(0.5, 0);
- targetText.x = (i - (numTargets - 1) / 2) * spacing;
- targetText.y = 50;
- objectivePanel.addChild(targetText);
- // Add smaller progress indicator below
- var progressText = new Text2(collectedAmounts[i] + '/' + targetAmounts[i], {
- size: 65,
- fill: 0xAAAAAAA
- });
- progressText.anchor.set(0.5, 0);
- progressText.x = (i - (numTargets - 1) / 2) * spacing;
- progressText.y = 120;
- objectivePanel.addChild(progressText);
+ countText.anchor.set(0.5, 0);
+ countText.x = xOffset;
+ countText.y = 30;
+ targetPanelContainer.addChild(countText);
}
}
-function updateObjectiveDisplay() {
- // Update the text for each target with enhanced display
- for (var i = 0; i < targetGemTypes.length; i++) {
- var baseIndex = 2; // Skip background(0) and title(1)
- var gemIndex = baseIndex + i * 3; // gem, remaining text, progress text per target
- var remainingTextIndex = baseIndex + i * 3 + 1;
- var progressTextIndex = baseIndex + i * 3 + 2;
- // Calculate remaining amount
- var remainingAmount = Math.max(0, targetAmounts[i] - collectedAmounts[i]);
- // Update remaining amount text with x# format
- var remainingTextChild = objectivePanel.children[remainingTextIndex];
- if (remainingTextChild && remainingTextChild.setText) {
+function updateTargetPanel() {
+ if (!targetPanelContainer || targetPanelContainer.children.length === 0) return;
+ var numTargets = targetGemTypes.length;
+ var spacing = 400;
+ // Update count texts (skip background at index 0)
+ for (var i = 0; i < numTargets; i++) {
+ var textIndex = 1 + i * 2 + 1; // background(0) + gem(1) + text(2) pattern
+ var countTextChild = targetPanelContainer.children[textIndex];
+ if (countTextChild && countTextChild.setText) {
+ var remainingAmount = Math.max(0, targetAmounts[i] - collectedAmounts[i]);
if (remainingAmount === 0) {
- remainingTextChild.setText('COMPLETE!');
- remainingTextChild.fill = 0x00FF00; // Green when complete
+ countTextChild.setText('DONE!');
+ countTextChild.fill = 0x00FF00; // Green when complete
} else {
- remainingTextChild.setText('x' + remainingAmount);
- remainingTextChild.fill = 0xFFFFFF; // White when incomplete
+ countTextChild.setText('x' + remainingAmount);
+ countTextChild.fill = 0xFFFFFF; // White when incomplete
}
}
- // Update progress text
- var progressTextChild = objectivePanel.children[progressTextIndex];
- if (progressTextChild && progressTextChild.setText) {
- progressTextChild.setText(collectedAmounts[i] + '/' + targetAmounts[i]);
- // Change color based on progress
- var progress = collectedAmounts[i] / targetAmounts[i];
- if (progress >= 1.0) {
- progressTextChild.fill = 0x00FF00; // Green when complete
- } else if (progress >= 0.5) {
- progressTextChild.fill = 0xFFFF00; // Yellow when halfway
- } else {
- progressTextChild.fill = 0xCCCCCC; // Gray when starting
- }
- }
}
}
function checkLevelComplete() {
for (var i = 0; i < targetGemTypes.length; i++) {
@@ -557,10 +523,10 @@
gem.destroy();
grid[match.y][match.x] = null;
}
}
- // Update objective display
- updateObjectiveDisplay();
+ // Update target panel display
+ updateTargetPanel();
// Check if level is complete
if (checkLevelComplete()) {
advanceToNextLevel();
LK.showYouWin();
@@ -1325,10 +1291,10 @@
var bonusMultiplier = additionalMatches.length > 20 ? 100 : additionalMatches.length > 10 ? 50 : 20; // Highest bonus for super color bomb effects
LK.setScore(LK.getScore() + additionalMatches.length * bonusMultiplier);
scoreTxt.setText('Score: ' + LK.getScore());
}
- // Update objective display
- updateObjectiveDisplay();
+ // Update target panel display
+ updateTargetPanel();
// Check if level is complete
if (checkLevelComplete()) {
advanceToNextLevel();
LK.showYouWin();
@@ -1461,13 +1427,13 @@
// Reset to current level data
initializeLevelObjectives();
initializeGrid();
removeInitialMatches();
- if (objectivePanel) {
- objectivePanel.destroy();
- objectivePanel = null;
+ if (targetPanelContainer) {
+ targetPanelContainer.destroy();
+ targetPanelContainer = null;
}
- createObjectivePanel();
+ createTargetPanel();
scoreTxt.setText('Score: ' + LK.getScore());
}
function decrementMoves() {
movesLeft--;
@@ -1621,6 +1587,6 @@
initializeLevelData();
initializeLevelObjectives();
initializeGrid();
removeInitialMatches();
-createObjectivePanel();
+createTargetPanel();
scoreTxt.setText('Score: ' + LK.getScore());
\ No newline at end of file
Blue gem square. In-Game asset. 2d. High contrast. No shadows
Green gem circle. In-Game asset. 2d. High contrast. No shadows
Orange gem circle. In-Game asset. 2d. High contrast. No shadows
Red gem circle. In-Game asset. 2d. High contrast. No shadows
Yellow gem pyramid. In-Game asset. 2d. High contrast. No shadows
Purple gem cube. In-Game asset. 2d. High contrast. No shadows
A 2D top-down icon for a match-3 special gem with a bright colored jewel (blue or red) and a bold white plus (+) sign on top. The plus sign should be centered, thick, and clearly visible. The gem should have a shiny, cartoon-style look with soft gradients and a glowing outline. No background, transparent, game-ready.
Color bomb gem. In-Game asset. 2d. High contrast. No shadows
Crossbomb gem. In-Game asset. 2d. High contrast. No shadows
Super colorbomb gem. In-Game asset. 2d. High contrast. No shadows