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 savedScore = storage.savedScore || 0;
var savedMoves = storage.savedMoves || 0;
var savedTargetProgress = storage.savedTargetProgress || [];
var levelData = [];
var targetPanel = null;
var targetPanelContainer = null;
var levelCompleted = false;
// 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;
levelCompleted = false;
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);
}
// Load saved progress if available for current level
if (savedMoves > 0 && savedTargetProgress.length === targetGemTypes.length) {
movesLeft = savedMoves;
for (var i = 0; i < savedTargetProgress.length; i++) {
if (i < collectedAmounts.length) {
collectedAmounts[i] = savedTargetProgress[i] || 0;
}
}
}
// Update display texts
movesTxt.setText('Moves: ' + movesLeft);
levelTxt.setText('Level: ' + currentLevel);
}
var targetGemsContainer = null;
var targetGemIcons = [];
var targetCountTexts = [];
function createTargetHUD() {
// Create main horizontal panel container below game grid
targetGemsContainer = new Container();
// Position container below the game grid with margin from bottom edge
var gridBottomY = GRID_OFFSET_Y + GRID_SIZE * GEM_SIZE;
targetGemsContainer.x = 2048 / 2; // Center horizontally on screen
targetGemsContainer.y = gridBottomY + 100; // Position below grid with margin
game.addChild(targetGemsContainer); // Add to game for consistent positioning
// Create dark semi-transparent background for readability
var backgroundWidth = 1600;
var backgroundHeight = 150;
var backgroundPanel = LK.getAsset('blueGem', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: backgroundWidth / 200,
scaleY: backgroundHeight / 200,
alpha: 0.7
});
backgroundPanel.tint = 0x000000; // Dark background
backgroundPanel.x = 0;
backgroundPanel.y = 50;
targetGemsContainer.addChild(backgroundPanel);
// Create target gem displays with horizontal layout
var gemAssetIds = ['redGem', 'blueGem', 'greenGem', 'yellowGem', 'purpleGem', 'orangeGem'];
// Calculate spacing for even distribution
var containerWidth = backgroundWidth - 100; // Leave padding on sides
var numTargets = targetGemTypes.length;
var sectionWidth = containerWidth / numTargets; // Equal sections for each target
var iconSize = 104; // Increased icon size by 30% (80 * 1.3 = 104)
targetGemIcons = [];
targetCountTexts = [];
for (var i = 0; i < targetGemTypes.length; i++) {
// Calculate position using even distribution
var sectionCenter = (i + 0.5) * sectionWidth - containerWidth / 2; // Center within section
// Create gem icon
var iconScale = iconSize / 200; // Scale based on icon size
var gemIcon = LK.getAsset(gemAssetIds[targetGemTypes[i]], {
anchorX: 0.5,
anchorY: 0.5,
scaleX: iconScale,
scaleY: iconScale
});
gemIcon.x = sectionCenter;
gemIcon.y = 30; // Position within background panel
targetGemsContainer.addChild(gemIcon);
targetGemIcons.push(gemIcon);
// Create count text with clear bold styling - increased size by 30%
var remainingAmount = targetAmounts[i] - collectedAmounts[i];
var countText = new Text2('x' + remainingAmount, {
size: 78,
fill: 0xFFFFFF,
font: "'Arial Black', 'Impact', bold"
});
countText.anchor.set(0.5, 0);
countText.x = sectionCenter;
countText.y = 75; // Slightly adjusted position to accommodate larger text
targetGemsContainer.addChild(countText);
targetCountTexts.push(countText);
}
}
function updateTargetHUD() {
if (!targetGemsContainer || targetCountTexts.length === 0) return;
for (var i = 0; i < targetGemTypes.length; i++) {
if (targetCountTexts[i]) {
var remainingAmount = Math.max(0, targetAmounts[i] - collectedAmounts[i]);
if (remainingAmount === 0) {
targetCountTexts[i].setText('DONE!');
targetCountTexts[i].fill = 0x00FF00; // Green when complete
} else {
targetCountTexts[i].setText('x' + remainingAmount);
targetCountTexts[i].fill = 0xFFFFFF; // White when incomplete
}
}
}
// Save updated target progress
storage.savedTargetProgress = collectedAmounts;
}
function checkLevelComplete() {
if (levelCompleted) return false; // Prevent multiple completions
for (var i = 0; i < targetGemTypes.length; i++) {
if (collectedAmounts[i] < targetAmounts[i]) {
return false;
}
}
return true;
}
function showLevelCompleteMessage() {
// Create level complete message container
var messageContainer = new Container();
messageContainer.x = 2048 / 2;
messageContainer.y = 2732 / 2;
game.addChild(messageContainer);
// Create semi-transparent background
var background = LK.getAsset('blueGem', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 16,
scaleY: 8,
alpha: 0.9
});
background.tint = 0x000000;
messageContainer.addChild(background);
// Create "Level Complete!" text
var completeText = new Text2('Level Complete!', {
size: 200,
fill: 0x00FF00
});
completeText.anchor.set(0.5, 0.5);
completeText.y = -100;
messageContainer.addChild(completeText);
// Create score summary text
var scoreText = new Text2('Score: ' + LK.getScore(), {
size: 120,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0.5);
scoreText.y = 50;
messageContainer.addChild(scoreText);
// Create next level text
var nextLevelText = new Text2('Level ' + (currentLevel + 1) + ' starting...', {
size: 100,
fill: 0xFFFF44
});
nextLevelText.anchor.set(0.5, 0.5);
nextLevelText.y = 150;
messageContainer.addChild(nextLevelText);
// Animate the message in
tween(messageContainer, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(messageContainer, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeInOut
});
}
});
// Auto-advance to next level after 2 seconds
LK.setTimeout(function () {
messageContainer.destroy();
levelCompleted = true; // Mark level as completed
advanceToNextLevel();
resetCurrentLevel();
}, 2000);
}
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 HUD display
updateTargetHUD();
// Check if level is complete
if (checkLevelComplete()) {
levelCompleted = true; // Mark level as completed
if (currentLevel >= 100) {
LK.showYouWin(); // Show final win screen only on level 100
} else {
showLevelCompleteMessage(); // Show brief message and auto-advance
}
}
}
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 with bounce effect
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 with easing
gem1.animateToPosition(originalScreenX1, originalScreenY1);
gem2.animateToPosition(originalScreenX2, originalScreenY2, function () {
isProcessing = false;
});
// Add a subtle shake effect to indicate invalid move
tween(gem1, {
rotation: 0.1
}, {
duration: 100,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(gem1, {
rotation: -0.1
}, {
duration: 100,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(gem1, {
rotation: 0
}, {
duration: 100,
easing: tween.easeInOut
});
}
});
}
});
}, 150); // Shorter delay for better responsiveness
} 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());
// Save updated score
storage.savedScore = 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 HUD display
updateTargetHUD();
// Check if level is complete
if (checkLevelComplete()) {
levelCompleted = true; // Mark level as completed
if (currentLevel >= 100) {
LK.showYouWin(); // Show final win screen only on level 100
} else {
showLevelCompleteMessage(); // Show brief message and auto-advance
}
}
}
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;
// Reset saved progress for new level
storage.savedMoves = 0;
storage.savedTargetProgress = [];
}
function resetCurrentLevel() {
// First, completely clear the entire gem grid and all references
for (var y = 0; y < GRID_SIZE; y++) {
for (var x = 0; x < GRID_SIZE; x++) {
var gem = grid[y][x];
if (gem) {
gem.destroy();
grid[y][x] = null;
}
}
}
// Clear all gem arrays and references
grid = [];
selectedGem = null;
dragStartGem = null;
dragStartPos = null;
isDragging = false;
// Reset processing state
isProcessing = false;
// Initialize fresh empty grid array
for (var y = 0; y < GRID_SIZE; y++) {
grid[y] = [];
for (var x = 0; x < GRID_SIZE; x++) {
grid[y][x] = null;
}
}
// Reset to current level data
initializeLevelObjectives();
initializeGrid();
removeInitialMatches();
// Destroy existing target HUD and create new one
if (targetGemsContainer) {
targetGemsContainer.destroy();
targetGemsContainer = null;
targetGemIcons = [];
targetCountTexts = [];
}
createTargetHUD();
scoreTxt.setText('Score: ' + LK.getScore());
// Save current game state
storage.savedScore = LK.getScore();
storage.savedMoves = movesLeft;
storage.savedTargetProgress = collectedAmounts;
}
function decrementMoves() {
movesLeft--;
movesTxt.setText('Moves: ' + movesLeft);
// Save updated moves and target progress
storage.savedMoves = movesLeft;
storage.savedTargetProgress = collectedAmounts;
if (movesLeft <= 0) {
if (checkLevelComplete()) {
levelCompleted = true; // Mark level as completed
if (currentLevel >= 100) {
LK.showYouWin(); // Show final win screen only on level 100
} else {
showLevelCompleteMessage(); // Show brief message and auto-advance
}
} else {
LK.showGameOver();
}
}
}
var dragStartGem = null;
var dragStartPos = null;
var isDragging = false;
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 and initial position
dragStartGem = clickedGem;
dragStartPos = {
x: x,
y: y
};
isDragging = false;
// Clear any existing selection visual
if (selectedGem) {
tween(selectedGem, {
scaleX: 1,
scaleY: 1
}, {
duration: 150
});
selectedGem = null;
}
// Highlight the touched gem slightly
tween(clickedGem, {
scaleX: 1.05,
scaleY: 1.05
}, {
duration: 100
});
};
game.move = function (x, y, obj) {
if (!dragStartGem || !dragStartPos || isProcessing) return;
var deltaX = x - dragStartPos.x;
var deltaY = y - dragStartPos.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// Start considering it a drag after minimum distance
if (!isDragging && distance > 30) {
isDragging = true;
}
};
game.up = function (x, y, obj) {
if (isProcessing || !dragStartGem) {
// Reset highlight on any gem that was touched
if (dragStartGem) {
tween(dragStartGem, {
scaleX: 1,
scaleY: 1
}, {
duration: 150
});
}
dragStartGem = null;
dragStartPos = null;
isDragging = false;
return;
}
// Reset highlight
tween(dragStartGem, {
scaleX: 1,
scaleY: 1
}, {
duration: 150
});
if (!isDragging || !dragStartPos) {
dragStartGem = null;
dragStartPos = null;
isDragging = false;
return;
}
var deltaX = x - dragStartPos.x;
var deltaY = y - dragStartPos.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// Must drag minimum distance to register as swipe
if (distance < 50) {
dragStartGem = null;
dragStartPos = null;
isDragging = false;
return;
}
// Determine swipe direction (prioritize larger delta)
var targetGridX = dragStartGem.gridX;
var targetGridY = dragStartGem.gridY;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// Horizontal swipe
if (deltaX > 0) {
targetGridX++; // Swipe right
} else {
targetGridX--; // Swipe left
}
} else {
// Vertical swipe
if (deltaY > 0) {
targetGridY++; // Swipe down
} else {
targetGridY--; // Swipe up
}
}
// Check if target position is valid
if (targetGridX >= 0 && targetGridX < GRID_SIZE && targetGridY >= 0 && targetGridY < GRID_SIZE) {
var targetGem = grid[targetGridY][targetGridX];
if (targetGem && areAdjacent(dragStartGem, targetGem)) {
swapGems(dragStartGem, targetGem);
decrementMoves();
}
}
dragStartGem = null;
dragStartPos = null;
isDragging = false;
};
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();
// Load saved score if available
if (savedScore > 0) {
LK.setScore(savedScore);
}
initializeLevelObjectives();
initializeGrid();
removeInitialMatches();
createTargetHUD();
scoreTxt.setText('Score: ' + LK.getScore()); ===================================================================
--- original.js
+++ change.js
@@ -180,8 +180,11 @@
var targetGemTypes = [];
var targetAmounts = [];
var collectedAmounts = [];
var currentLevel = storage.currentLevel || 1;
+var savedScore = storage.savedScore || 0;
+var savedMoves = storage.savedMoves || 0;
+var savedTargetProgress = storage.savedTargetProgress || [];
var levelData = [];
var targetPanel = null;
var targetPanelContainer = null;
var levelCompleted = false;
@@ -267,8 +270,17 @@
targetGemTypes.push(target.gemType);
targetAmounts.push(target.amount);
collectedAmounts.push(0);
}
+ // Load saved progress if available for current level
+ if (savedMoves > 0 && savedTargetProgress.length === targetGemTypes.length) {
+ movesLeft = savedMoves;
+ for (var i = 0; i < savedTargetProgress.length; i++) {
+ if (i < collectedAmounts.length) {
+ collectedAmounts[i] = savedTargetProgress[i] || 0;
+ }
+ }
+ }
// Update display texts
movesTxt.setText('Moves: ' + movesLeft);
levelTxt.setText('Level: ' + currentLevel);
}
@@ -348,8 +360,10 @@
targetCountTexts[i].fill = 0xFFFFFF; // White when incomplete
}
}
}
+ // Save updated target progress
+ storage.savedTargetProgress = collectedAmounts;
}
function checkLevelComplete() {
if (levelCompleted) return false; // Prevent multiple completions
for (var i = 0; i < targetGemTypes.length; i++) {
@@ -1325,8 +1339,10 @@
}
}
LK.setScore(LK.getScore() + points);
scoreTxt.setText('Score: ' + LK.getScore());
+ // Save updated score
+ storage.savedScore = 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];
@@ -1533,8 +1549,11 @@
if (currentLevel > 100) {
currentLevel = 100; // Cap at level 100
}
storage.currentLevel = currentLevel;
+ // Reset saved progress for new level
+ storage.savedMoves = 0;
+ storage.savedTargetProgress = [];
}
function resetCurrentLevel() {
// First, completely clear the entire gem grid and all references
for (var y = 0; y < GRID_SIZE; y++) {
@@ -1573,12 +1592,19 @@
targetCountTexts = [];
}
createTargetHUD();
scoreTxt.setText('Score: ' + LK.getScore());
+ // Save current game state
+ storage.savedScore = LK.getScore();
+ storage.savedMoves = movesLeft;
+ storage.savedTargetProgress = collectedAmounts;
}
function decrementMoves() {
movesLeft--;
movesTxt.setText('Moves: ' + movesLeft);
+ // Save updated moves and target progress
+ storage.savedMoves = movesLeft;
+ storage.savedTargetProgress = collectedAmounts;
if (movesLeft <= 0) {
if (checkLevelComplete()) {
levelCompleted = true; // Mark level as completed
if (currentLevel >= 100) {
@@ -1759,8 +1785,12 @@
validateGridSync();
}
// Initialize the game
initializeLevelData();
+// Load saved score if available
+if (savedScore > 0) {
+ LK.setScore(savedScore);
+}
initializeLevelObjectives();
initializeGrid();
removeInitialMatches();
createTargetHUD();
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