/****
* 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()); /****
* 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());
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