/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
unlockedLevel: 1
});
/****
* Classes
****/
var Gem = Container.expand(function (gemType, gridX, gridY, isBlocked) {
var self = Container.call(this);
self.gemType = gemType;
self.gridX = gridX;
self.gridY = gridY;
self.isSpecial = false;
self.specialType = 'normal'; // 'normal', 'striped_horizontal', 'striped_vertical', 'rainbow'
self.isAnimating = false;
self.isSelected = false;
self.isBlocked = isBlocked || false;
var gemAsset = self.attachAsset(gemType, {
anchorX: 0.5,
anchorY: 0.5
});
// Apply blocked visual directly to the gem
if (self.isBlocked) {
self.removeChild(gemAsset);
gemAsset = self.attachAsset('blocked_gem', {
anchorX: 0.5,
anchorY: 0.5
});
}
self.setSpecial = function (type) {
self.isSpecial = true;
self.specialType = type;
if (type === 'striped_horizontal' || type === 'striped_vertical') {
self.removeChild(gemAsset);
gemAsset = self.attachAsset('striped_gem', {
anchorX: 0.5,
anchorY: 0.5
});
if (type === 'striped_horizontal') {
gemAsset.rotation = Math.PI / 2;
}
} else if (type === 'rainbow') {
self.removeChild(gemAsset);
gemAsset = self.attachAsset('rainbow_gem', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'bomb') {
self.removeChild(gemAsset);
gemAsset = self.attachAsset('bomb_gem', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'target') {
self.removeChild(gemAsset);
gemAsset = self.attachAsset('target_gem', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'line_cleaner') {
self.removeChild(gemAsset);
gemAsset = self.attachAsset('line_cleaner', {
anchorX: 0.5,
anchorY: 0.5
});
}
};
self.highlight = function () {
self.isSelected = true;
gemAsset.alpha = 0.7;
};
self.unhighlight = function () {
self.isSelected = false;
gemAsset.alpha = 1.0;
};
self.unblock = function () {
if (self.isBlocked) {
self.isBlocked = false;
// Completely remove the blocked gem asset
self.removeChild(gemAsset);
// Create new gem asset with original gem type
gemAsset = self.attachAsset(self.gemType, {
anchorX: 0.5,
anchorY: 0.5
});
// Reset any visual properties to ensure clean state
gemAsset.alpha = 1.0;
gemAsset.tint = 0xffffff;
gemAsset.scaleX = 1.0;
gemAsset.scaleY = 1.0;
// Animate restoration of gem's original appearance
tween(gemAsset, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(gemAsset, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 150,
easing: tween.easeIn
});
}
});
}
};
self.down = function (x, y, obj) {
if (!self.isAnimating && !self.isBlocked) {
handleGemSelect(self);
}
};
return self;
});
var MenuState = Container.expand(function () {
var self = Container.call(this);
// Create menu background
var menuBg = self.attachAsset('board_bg', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 1.3,
scaleY: 1.7
});
menuBg.tint = 0x2c2c54;
// Title text
var titleText = new Text2('MATCH-3 PUZZLE', {
size: 120,
fill: '#ffffff'
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 400;
self.addChild(titleText);
// Subtitle
var subtitleText = new Text2('Select Level', {
size: 80,
fill: '#ffaa00'
});
subtitleText.anchor.set(0.5, 0.5);
subtitleText.x = 1024;
subtitleText.y = 550;
self.addChild(subtitleText);
// Level buttons array
self.levelButtons = [];
// Create level buttons (1-10)
for (var i = 1; i <= 10; i++) {
var unlockedLevel = storage.unlockedLevel || 1;
var isUnlocked = i <= unlockedLevel;
var buttonBg = LK.getAsset(isUnlocked ? 'gem_blue' : 'gem_red', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
var levelText = new Text2(i.toString(), {
size: 60,
fill: isUnlocked ? '#ffffff' : '#888888'
});
levelText.anchor.set(0.5, 0.5);
var button = new Container();
button.addChild(buttonBg);
button.addChild(levelText);
// Position buttons in 2 rows of 5
var col = (i - 1) % 5;
var row = Math.floor((i - 1) / 5);
button.x = 400 + col * 250;
button.y = 800 + row * 200;
button.levelNumber = i;
button.buttonBg = buttonBg;
button.isUnlocked = isUnlocked;
// Button interaction
button.down = function (x, y, obj) {
// Use 'this' to reference the button that was clicked
if (this.isUnlocked) {
var level = this.levelNumber;
startGame(level);
}
};
self.levelButtons.push(button);
self.addChild(button);
}
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
var gameState = 'menu'; // 'menu' or 'playing'
var currentLevel = 1;
var menuScreen = null;
function startGame(level) {
gameState = 'playing';
currentLevel = level;
// Remove menu screen
if (menuScreen) {
menuScreen.destroy();
menuScreen = null;
}
// Reset game variables based on level
if (level === 1) {
movesLeft = 19;
targetBlueGems = 30;
targetGreenGems = 0;
} else if (level === 2) {
movesLeft = 23;
targetBlueGems = 0;
targetGreenGems = 35;
} else if (level === 3) {
movesLeft = 10;
targetBlueGems = 40;
targetGreenGems = 0;
} else {
movesLeft = Math.max(10, 20 - level);
targetBlueGems = 25 + level * 5;
targetGreenGems = 0;
}
currentScore = 0;
cascadeDepth = 0;
blueGemsCollected = 0;
greenGemsCollected = 0;
selectedGem = null;
isProcessing = false;
// Update UI
movesText.setText('Moves: ' + movesLeft);
scoreText.setText('Score: ' + currentScore);
levelText.setText('Level: ' + currentLevel);
blueGemsText.setText('Blue Gems: ' + blueGemsCollected + '/' + targetBlueGems);
greenGemsText.setText('Green Gems: ' + greenGemsCollected + '/' + targetGreenGems);
// Show game UI
scoreText.visible = true;
movesText.visible = true;
levelText.visible = true;
blueGemsText.visible = currentLevel === 1 || currentLevel > 3;
greenGemsText.visible = currentLevel === 2;
// For level 3, show blocked gems objective
if (currentLevel === 3) {
blueGemsText.setText('Unblock All Gems!');
blueGemsText.visible = true;
}
boardBg.visible = true;
// Initialize grid for gameplay
initializeGrid();
}
function showMenu() {
gameState = 'menu';
// Hide game UI
scoreText.visible = false;
movesText.visible = false;
levelText.visible = false;
blueGemsText.visible = false;
greenGemsText.visible = false;
boardBg.visible = false;
// Clear any existing gems
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[x] && grid[x][y]) {
if (grid[x][y].parent) {
grid[x][y].destroy();
}
grid[x][y] = null;
}
}
}
// Show menu
menuScreen = new MenuState();
game.addChild(menuScreen);
}
var GRID_SIZE = 6;
var GEM_SIZE = 200;
var BOARD_SIZE = GRID_SIZE * GEM_SIZE;
var BOARD_OFFSET_X = (2048 - BOARD_SIZE) / 2;
var BOARD_OFFSET_Y = (2732 - BOARD_SIZE) / 2 - 200;
var gemTypes = ['gem_red', 'gem_blue', 'gem_green', 'gem_yellow', 'gem_purple', 'gem_orange'];
var grid = [];
var selectedGem = null;
var isProcessing = false;
var movesLeft = 30;
var currentScore = 0;
var cascadeDepth = 0;
var blueGemsCollected = 0;
var targetBlueGems = 30;
var greenGemsCollected = 0;
var targetGreenGems = 0;
// Initialize UI
var scoreText = new Text2('Score: 0', {
size: 60,
fill: '#ffffff'
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
var movesText = new Text2('Moves: 30', {
size: 60,
fill: '#ffffff'
});
movesText.anchor.set(0.5, 0);
movesText.y = 80;
LK.gui.top.addChild(movesText);
var levelText = new Text2('Level: 1', {
size: 60,
fill: '#ffffff'
});
levelText.anchor.set(0.5, 0);
levelText.y = 160;
LK.gui.top.addChild(levelText);
var blueGemsText = new Text2('Blue Gems: 0/30', {
size: 60,
fill: '#4da6ff'
});
blueGemsText.anchor.set(0.5, 0);
blueGemsText.y = 240;
LK.gui.top.addChild(blueGemsText);
var greenGemsText = new Text2('Green Gems: 0/0', {
size: 60,
fill: '#44dd44'
});
greenGemsText.anchor.set(0.5, 0);
greenGemsText.y = 300;
LK.gui.top.addChild(greenGemsText);
// Create board background
var boardBg = game.attachAsset('board_bg', {
anchorX: 0.5,
anchorY: 0.5,
x: BOARD_OFFSET_X + BOARD_SIZE / 2,
y: BOARD_OFFSET_Y + BOARD_SIZE / 2
});
// Initialize grid
function initializeGrid() {
grid = [];
for (var x = 0; x < GRID_SIZE; x++) {
grid[x] = [];
for (var y = 0; y < GRID_SIZE; y++) {
grid[x][y] = null;
}
}
// Fill grid with gems, ensuring no initial matches
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
var validTypes = gemTypes.slice();
// Remove types that would create horizontal matches
if (x >= 2 && grid[x - 1][y] && grid[x - 2][y] && grid[x - 1][y].gemType === grid[x - 2][y].gemType) {
var typeToRemove = grid[x - 1][y].gemType;
var index = validTypes.indexOf(typeToRemove);
if (index > -1) validTypes.splice(index, 1);
}
// Remove types that would create vertical matches
if (y >= 2 && grid[x][y - 1] && grid[x][y - 2] && grid[x][y - 1].gemType === grid[x][y - 2].gemType) {
var typeToRemove = grid[x][y - 1].gemType;
var index = validTypes.indexOf(typeToRemove);
if (index > -1) validTypes.splice(index, 1);
}
// Use different gem types for each level to adjust difficulty
var availableTypes;
if (currentLevel === 1) {
availableTypes = gemTypes.slice(0, 4); // 4 types for level 1
} else if (currentLevel === 2) {
availableTypes = gemTypes.slice(0, 5); // 5 types for level 2
} else if (currentLevel === 3) {
availableTypes = gemTypes.slice(0, 6); // All 6 types for level 3
} else {
availableTypes = gemTypes; // All types for higher levels
}
var filteredValidTypes = validTypes.filter(function (type) {
return availableTypes.indexOf(type) !== -1;
});
if (filteredValidTypes.length === 0) filteredValidTypes = availableTypes.slice(0, 2);
var randomType = filteredValidTypes[Math.floor(Math.random() * filteredValidTypes.length)];
// For level 3, add some blocked gems
var isBlocked = false;
if (currentLevel === 3) {
// Add blocked gems in bottom 2 rows (rows 4 and 5)
if (y === 4 || y === 5) {
isBlocked = true;
}
}
var gem = new Gem(randomType, x, y, isBlocked);
gem.x = BOARD_OFFSET_X + x * GEM_SIZE + GEM_SIZE / 2;
gem.y = BOARD_OFFSET_Y + y * GEM_SIZE + GEM_SIZE / 2;
grid[x][y] = gem;
game.addChild(gem);
}
}
}
function handleGemSelect(gem) {
if (isProcessing) return;
if (selectedGem === null) {
selectedGem = gem;
gem.highlight();
} else if (selectedGem === gem) {
selectedGem.unhighlight();
selectedGem = null;
} else {
// Check if either gem is blocked - cannot swap with blocked gems
if (selectedGem.isBlocked || gem.isBlocked) {
selectedGem.unhighlight();
selectedGem = null;
return;
}
// Check if gems are adjacent
var dx = Math.abs(selectedGem.gridX - gem.gridX);
var dy = Math.abs(selectedGem.gridY - gem.gridY);
if (dx === 1 && dy === 0 || dx === 0 && dy === 1) {
// Swap gems
swapGems(selectedGem, gem);
}
selectedGem.unhighlight();
selectedGem = null;
}
}
function swapGems(gem1, gem2) {
if (movesLeft <= 0) return;
// Don't allow swapping with blocked gems
if (gem1.isBlocked || gem2.isBlocked) {
return;
}
isProcessing = true;
// Check if both gems are special - create super combo
if (gem1.isSpecial && gem2.isSpecial) {
movesLeft--;
movesText.setText('Moves: ' + movesLeft);
LK.setTimeout(function () {
activateSpecialCombo(gem1, gem2);
LK.setTimeout(function () {
applyGravity();
}, 500);
}, 100);
return;
}
// Check if either gem is special and activate it
var specialActivated = false;
if (gem1.isSpecial) {
specialActivated = true;
movesLeft--;
movesText.setText('Moves: ' + movesLeft);
LK.setTimeout(function () {
activateSpecialGem(gem1);
LK.setTimeout(function () {
applyGravity();
}, 500);
}, 100);
return;
}
if (gem2.isSpecial) {
specialActivated = true;
movesLeft--;
movesText.setText('Moves: ' + movesLeft);
LK.setTimeout(function () {
activateSpecialGem(gem2);
LK.setTimeout(function () {
applyGravity();
}, 500);
}, 100);
return;
}
// Store original positions
var tempX = gem1.gridX;
var tempY = gem1.gridY;
// Temporarily swap gems to check for matches
gem1.gridX = gem2.gridX;
gem1.gridY = gem2.gridY;
gem2.gridX = tempX;
gem2.gridY = tempY;
grid[gem1.gridX][gem1.gridY] = gem1;
grid[gem2.gridX][gem2.gridY] = gem2;
// Check if this swap creates any matches
var testMatches = findMatches();
// If no matches, revert the swap and don't continue
if (testMatches.length === 0) {
// Revert grid positions to original
gem1.gridX = tempX;
gem1.gridY = tempY;
gem2.gridX = gem2.gridX;
gem2.gridY = gem2.gridY;
grid[gem1.gridX][gem1.gridY] = gem1;
grid[gem2.gridX][gem2.gridY] = gem2;
isProcessing = false;
return;
}
// Update grid positions (already done above for testing)
// Animate swap
var targetX1 = BOARD_OFFSET_X + gem1.gridX * GEM_SIZE + GEM_SIZE / 2;
var targetY1 = BOARD_OFFSET_Y + gem1.gridY * GEM_SIZE + GEM_SIZE / 2;
var targetX2 = BOARD_OFFSET_X + gem2.gridX * GEM_SIZE + GEM_SIZE / 2;
var targetY2 = BOARD_OFFSET_Y + gem2.gridY * GEM_SIZE + GEM_SIZE / 2;
gem1.isAnimating = true;
gem2.isAnimating = true;
tween(gem1, {
x: targetX1,
y: targetY1
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
gem1.isAnimating = false;
checkSwapComplete();
}
});
tween(gem2, {
x: targetX2,
y: targetY2
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
gem2.isAnimating = false;
checkSwapComplete();
}
});
}
var swapCompleteCount = 0;
function checkSwapComplete() {
swapCompleteCount++;
if (swapCompleteCount === 2) {
swapCompleteCount = 0;
// Check for matches
var matches = findMatches();
if (matches.length > 0) {
movesLeft--;
movesText.setText('Moves: ' + movesLeft);
processMatches(matches);
} else {
// No matches, just end processing since swap was invalid
isProcessing = false;
}
}
}
function findMatches() {
var matches = [];
var visited = [];
// Initialize visited array
for (var x = 0; x < GRID_SIZE; x++) {
visited[x] = [];
for (var y = 0; y < GRID_SIZE; y++) {
visited[x][y] = false;
}
}
// Find horizontal matches
for (var y = 0; y < GRID_SIZE; y++) {
for (var x = 0; x < GRID_SIZE - 2; x++) {
if (grid[x][y] && grid[x + 1][y] && grid[x + 2][y] && !grid[x][y].isBlocked && !grid[x + 1][y].isBlocked && !grid[x + 2][y].isBlocked && grid[x][y].gemType === grid[x + 1][y].gemType && grid[x + 1][y].gemType === grid[x + 2][y].gemType) {
var matchGroup = [];
var currentX = x;
while (currentX < GRID_SIZE && grid[currentX][y] && grid[currentX][y].gemType === grid[x][y].gemType) {
if (!visited[currentX][y]) {
matchGroup.push(grid[currentX][y]);
visited[currentX][y] = true;
}
currentX++;
}
if (matchGroup.length >= 3) {
matches.push(matchGroup);
}
}
}
}
// Find vertical matches
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE - 2; y++) {
if (grid[x][y] && grid[x][y + 1] && grid[x][y + 2] && !grid[x][y].isBlocked && !grid[x][y + 1].isBlocked && !grid[x][y + 2].isBlocked && grid[x][y].gemType === grid[x][y + 1].gemType && grid[x][y + 1].gemType === grid[x][y + 2].gemType) {
var matchGroup = [];
var currentY = y;
while (currentY < GRID_SIZE && grid[x][currentY] && grid[x][currentY].gemType === grid[x][y].gemType) {
if (!visited[x][currentY]) {
matchGroup.push(grid[x][currentY]);
visited[x][currentY] = true;
}
currentY++;
}
if (matchGroup.length >= 3) {
matches.push(matchGroup);
}
}
}
}
// Find 2x2 square matches
for (var x = 0; x < GRID_SIZE - 1; x++) {
for (var y = 0; y < GRID_SIZE - 1; y++) {
if (grid[x][y] && grid[x + 1][y] && grid[x][y + 1] && grid[x + 1][y + 1]) {
var gemType = grid[x][y].gemType;
if (grid[x + 1][y].gemType === gemType && grid[x][y + 1].gemType === gemType && grid[x + 1][y + 1].gemType === gemType && !visited[x][y] && !visited[x + 1][y] && !visited[x][y + 1] && !visited[x + 1][y + 1]) {
var squareMatch = [grid[x][y], grid[x + 1][y], grid[x][y + 1], grid[x + 1][y + 1]];
matches.push(squareMatch);
for (var i = 0; i < squareMatch.length; i++) {
visited[squareMatch[i].gridX][squareMatch[i].gridY] = true;
}
}
}
}
}
// Find L and T shaped matches
for (var x = 1; x < GRID_SIZE - 1; x++) {
for (var y = 1; y < GRID_SIZE - 1; y++) {
if (grid[x][y]) {
var centerGem = grid[x][y];
var gemType = centerGem.gemType;
// Check L shapes (4 possible orientations)
// L shape: top-left
if (grid[x - 1][y] && grid[x][y - 1] && grid[x - 1][y - 1] && grid[x - 1][y].gemType === gemType && grid[x][y - 1].gemType === gemType && grid[x - 1][y - 1].gemType === gemType && !visited[x][y] && !visited[x - 1][y] && !visited[x][y - 1] && !visited[x - 1][y - 1]) {
var lMatch = [centerGem, grid[x - 1][y], grid[x][y - 1], grid[x - 1][y - 1]];
matches.push(lMatch);
for (var i = 0; i < lMatch.length; i++) {
visited[lMatch[i].gridX][lMatch[i].gridY] = true;
}
}
// L shape: top-right
if (grid[x + 1][y] && grid[x][y - 1] && grid[x + 1][y - 1] && grid[x + 1][y].gemType === gemType && grid[x][y - 1].gemType === gemType && grid[x + 1][y - 1].gemType === gemType && !visited[x][y] && !visited[x + 1][y] && !visited[x][y - 1] && !visited[x + 1][y - 1]) {
var lMatch = [centerGem, grid[x + 1][y], grid[x][y - 1], grid[x + 1][y - 1]];
matches.push(lMatch);
for (var i = 0; i < lMatch.length; i++) {
visited[lMatch[i].gridX][lMatch[i].gridY] = true;
}
}
// L shape: bottom-left
if (grid[x - 1][y] && grid[x][y + 1] && grid[x - 1][y + 1] && grid[x - 1][y].gemType === gemType && grid[x][y + 1].gemType === gemType && grid[x - 1][y + 1].gemType === gemType && !visited[x][y] && !visited[x - 1][y] && !visited[x][y + 1] && !visited[x - 1][y + 1]) {
var lMatch = [centerGem, grid[x - 1][y], grid[x][y + 1], grid[x - 1][y + 1]];
matches.push(lMatch);
for (var i = 0; i < lMatch.length; i++) {
visited[lMatch[i].gridX][lMatch[i].gridY] = true;
}
}
// L shape: bottom-right
if (grid[x + 1][y] && grid[x][y + 1] && grid[x + 1][y + 1] && grid[x + 1][y].gemType === gemType && grid[x][y + 1].gemType === gemType && grid[x + 1][y + 1].gemType === gemType && !visited[x][y] && !visited[x + 1][y] && !visited[x][y + 1] && !visited[x + 1][y + 1]) {
var lMatch = [centerGem, grid[x + 1][y], grid[x][y + 1], grid[x + 1][y + 1]];
matches.push(lMatch);
for (var i = 0; i < lMatch.length; i++) {
visited[lMatch[i].gridX][lMatch[i].gridY] = true;
}
}
// Check T shapes (4 possible orientations)
// T shape: horizontal top
if (grid[x - 1][y] && grid[x + 1][y] && grid[x][y - 1] && grid[x - 1][y].gemType === gemType && grid[x + 1][y].gemType === gemType && grid[x][y - 1].gemType === gemType && !visited[x][y] && !visited[x - 1][y] && !visited[x + 1][y] && !visited[x][y - 1]) {
var tMatch = [centerGem, grid[x - 1][y], grid[x + 1][y], grid[x][y - 1]];
matches.push(tMatch);
for (var i = 0; i < tMatch.length; i++) {
visited[tMatch[i].gridX][tMatch[i].gridY] = true;
}
}
// T shape: horizontal bottom
if (grid[x - 1][y] && grid[x + 1][y] && grid[x][y + 1] && grid[x - 1][y].gemType === gemType && grid[x + 1][y].gemType === gemType && grid[x][y + 1].gemType === gemType && !visited[x][y] && !visited[x - 1][y] && !visited[x + 1][y] && !visited[x][y + 1]) {
var tMatch = [centerGem, grid[x - 1][y], grid[x + 1][y], grid[x][y + 1]];
matches.push(tMatch);
for (var i = 0; i < tMatch.length; i++) {
visited[tMatch[i].gridX][tMatch[i].gridY] = true;
}
}
// T shape: vertical left
if (grid[x][y - 1] && grid[x][y + 1] && grid[x - 1][y] && grid[x][y - 1].gemType === gemType && grid[x][y + 1].gemType === gemType && grid[x - 1][y].gemType === gemType && !visited[x][y] && !visited[x][y - 1] && !visited[x][y + 1] && !visited[x - 1][y]) {
var tMatch = [centerGem, grid[x][y - 1], grid[x][y + 1], grid[x - 1][y]];
matches.push(tMatch);
for (var i = 0; i < tMatch.length; i++) {
visited[tMatch[i].gridX][tMatch[i].gridY] = true;
}
}
// T shape: vertical right
if (grid[x][y - 1] && grid[x][y + 1] && grid[x + 1][y] && grid[x][y - 1].gemType === gemType && grid[x][y + 1].gemType === gemType && grid[x + 1][y].gemType === gemType && !visited[x][y] && !visited[x][y - 1] && !visited[x][y + 1] && !visited[x + 1][y]) {
var tMatch = [centerGem, grid[x][y - 1], grid[x][y + 1], grid[x + 1][y]];
matches.push(tMatch);
for (var i = 0; i < tMatch.length; i++) {
visited[tMatch[i].gridX][tMatch[i].gridY] = true;
}
}
}
}
}
return matches;
}
function processMatches(matches) {
cascadeDepth++;
var totalScore = 0;
var specialGems = [];
for (var i = 0; i < matches.length; i++) {
var match = matches[i];
var matchScore = match.length * 10 * cascadeDepth;
totalScore += matchScore;
// Create special gems for larger matches
if (match.length === 4) {
// Check if it's a 2x2 square
var isSquare = false;
var isLOrT = false;
if (match.length === 4) {
// Sort gems by position to check square pattern
var sortedMatch = match.slice().sort(function (a, b) {
if (a.gridX !== b.gridX) return a.gridX - b.gridX;
return a.gridY - b.gridY;
});
// Check if it forms a 2x2 square
if (sortedMatch[0].gridX === sortedMatch[1].gridX - 1 && sortedMatch[0].gridY === sortedMatch[2].gridY - 1 && sortedMatch[1].gridX === sortedMatch[3].gridX && sortedMatch[2].gridY === sortedMatch[3].gridY && sortedMatch[0].gridX === sortedMatch[2].gridX && sortedMatch[1].gridY === sortedMatch[0].gridY) {
isSquare = true;
}
// Check if it's an L or T shape (4 gems in specific patterns)
// L/T shapes will have gems that don't form a straight line
var minX = Math.min(match[0].gridX, match[1].gridX, match[2].gridX, match[3].gridX);
var maxX = Math.max(match[0].gridX, match[1].gridX, match[2].gridX, match[3].gridX);
var minY = Math.min(match[0].gridY, match[1].gridY, match[2].gridY, match[3].gridY);
var maxY = Math.max(match[0].gridY, match[1].gridY, match[2].gridY, match[3].gridY);
// If it spans more than 1 row AND more than 1 column, it's likely L or T
if (maxX - minX > 0 && maxY - minY > 0 && !isSquare) {
isLOrT = true;
}
}
if (isSquare) {
// Create target gem for 2x2 square
var specialGem = match[0];
specialGem.setSpecial('target');
specialGems.push(specialGem);
} else if (isLOrT) {
// Create bomb gem for L and T shapes
var specialGem = match[0];
specialGem.setSpecial('bomb');
specialGems.push(specialGem);
} else {
// For 4-gem straight line matches, create line cleaner
var specialGem = match[0];
specialGem.setSpecial('line_cleaner');
specialGems.push(specialGem);
}
} else if (match.length >= 5) {
// Create rainbow gem
var specialGem = match[0];
specialGem.setSpecial('rainbow');
specialGems.push(specialGem);
}
// Remove matched gems (except special gems) with crystal crack effects
for (var j = 0; j < match.length; j++) {
var gem = match[j];
if (specialGems.indexOf(gem) === -1) {
// Track blue gem collection
if (gem.gemType === 'gem_blue') {
blueGemsCollected++;
blueGemsText.setText('Blue Gems: ' + blueGemsCollected + '/' + targetBlueGems);
}
// Track green gem collection
if (gem.gemType === 'gem_green') {
greenGemsCollected++;
greenGemsText.setText('Green Gems: ' + greenGemsCollected + '/' + targetGreenGems);
}
if (grid[gem.gridX] && grid[gem.gridX][gem.gridY]) {
grid[gem.gridX][gem.gridY] = null;
}
// Add crystal crack effect
LK.setTimeout(function (targetGem) {
return function () {
tween(targetGem, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 0.8,
tint: 0xffffff
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(targetGem, {
scaleX: 0.1,
scaleY: 0.1,
alpha: 0,
rotation: Math.PI * 0.5,
tint: 0xaaaaaa
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
if (targetGem.parent) {
targetGem.destroy();
}
}
});
}
});
};
}(gem), j * 20);
}
}
}
currentScore += totalScore;
scoreText.setText('Score: ' + currentScore);
LK.getSound('match').play();
// Apply gravity and cascade
// Unblock adjacent gems
for (var i = 0; i < matches.length; i++) {
var match = matches[i];
for (var j = 0; j < match.length; j++) {
var gem = match[j];
// Check all adjacent positions and unblock them
for (var dx = -1; dx <= 1; dx++) {
for (var dy = -1; dy <= 1; dy++) {
if (dx === 0 && dy === 0) continue;
var adjX = gem.gridX + dx;
var adjY = gem.gridY + dy;
if (adjX >= 0 && adjX < GRID_SIZE && adjY >= 0 && adjY < GRID_SIZE) {
if (grid[adjX][adjY] && grid[adjX][adjY].isBlocked) {
grid[adjX][adjY].unblock();
}
}
}
}
}
}
LK.setTimeout(function () {
applyGravity();
}, 200);
}
function applyGravity() {
var gemsToAnimate = [];
// Move gems down
for (var x = 0; x < GRID_SIZE; x++) {
var writeIndex = GRID_SIZE - 1;
for (var y = GRID_SIZE - 1; y >= 0; y--) {
if (grid[x][y] !== null) {
if (grid[x][y].isBlocked) {
// Blocked gems stay in place
writeIndex--;
} else {
if (y !== writeIndex) {
grid[x][writeIndex] = grid[x][y];
grid[x][y] = null;
grid[x][writeIndex].gridY = writeIndex;
var targetY = BOARD_OFFSET_Y + writeIndex * GEM_SIZE + GEM_SIZE / 2;
gemsToAnimate.push({
gem: grid[x][writeIndex],
targetY: targetY
});
}
writeIndex--;
}
}
}
// Check if spawning path is clear - only blocked gems at the spawn positions matter
var canSpawnNewGems = true;
// Check if any of the positions we need to fill have blocked gems
for (var checkY = 0; checkY <= writeIndex; checkY++) {
if (grid[x][checkY] && grid[x][checkY].isBlocked) {
canSpawnNewGems = false;
break;
}
}
// Only spawn new gems if the actual spawn positions are clear
if (canSpawnNewGems) {
for (var y = 0; y <= writeIndex; y++) {
var availableTypes;
if (currentLevel === 1) {
availableTypes = gemTypes.slice(0, 4); // 4 types for level 1
} else if (currentLevel === 2) {
availableTypes = gemTypes.slice(0, 5); // 5 types for level 2
} else if (currentLevel === 3) {
availableTypes = gemTypes.slice(0, 6); // All 6 types for level 3
} else {
availableTypes = gemTypes; // All types for higher levels
}
var randomType = availableTypes[Math.floor(Math.random() * availableTypes.length)];
var newGem = new Gem(randomType, x, y);
newGem.x = BOARD_OFFSET_X + x * GEM_SIZE + GEM_SIZE / 2;
newGem.y = BOARD_OFFSET_Y + y * GEM_SIZE + GEM_SIZE / 2 - (writeIndex - y + 1) * GEM_SIZE;
grid[x][y] = newGem;
game.addChild(newGem);
var targetY = BOARD_OFFSET_Y + y * GEM_SIZE + GEM_SIZE / 2;
gemsToAnimate.push({
gem: newGem,
targetY: targetY
});
}
}
}
// Animate falling gems
var animationCount = 0;
for (var i = 0; i < gemsToAnimate.length; i++) {
var animData = gemsToAnimate[i];
animData.gem.isAnimating = true;
tween(animData.gem, {
y: animData.targetY
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
animationCount++;
if (animationCount === gemsToAnimate.length) {
checkForCascade();
}
}
});
}
if (gemsToAnimate.length === 0) {
checkForCascade();
}
}
function activateSpecialGem(gem) {
if (!gem.isSpecial) return false;
var gemsToRemove = [];
if (gem.specialType === 'striped_horizontal') {
// Enhanced horizontal striped effect - sweeping motion
tween(gem, {
scaleX: 8,
scaleY: 1.5,
tint: 0xffff00,
alpha: 0.7
}, {
duration: 350,
easing: tween.easeOut
});
// Remove entire row
for (var x = 0; x < GRID_SIZE; x++) {
if (grid[x][gem.gridY] && grid[x][gem.gridY] !== gem) {
gemsToRemove.push(grid[x][gem.gridY]);
}
}
} else if (gem.specialType === 'striped_vertical') {
// Enhanced vertical striped effect - sweeping motion
tween(gem, {
scaleX: 1.5,
scaleY: 8,
tint: 0xffff00,
alpha: 0.7
}, {
duration: 350,
easing: tween.easeOut
});
// Remove entire column
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[gem.gridX][y] && grid[gem.gridX][y] !== gem) {
gemsToRemove.push(grid[gem.gridX][y]);
}
}
} else if (gem.specialType === 'rainbow') {
// Enhanced rainbow effect - color cycling shimmer
tween(gem, {
scaleX: 2.5,
scaleY: 2.5,
tint: 0xff00ff
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(gem, {
tint: 0x00ffff
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(gem, {
tint: 0xffff00
}, {
duration: 200,
easing: tween.easeInOut
});
}
});
}
});
// Remove all gems of the same type as the first regular gem found
var targetType = null;
for (var x = 0; x < GRID_SIZE && !targetType; x++) {
for (var y = 0; y < GRID_SIZE && !targetType; y++) {
if (grid[x][y] && !grid[x][y].isSpecial && grid[x][y] !== gem) {
targetType = grid[x][y].gemType;
}
}
}
if (targetType) {
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[x][y] && grid[x][y].gemType === targetType && grid[x][y] !== gem) {
gemsToRemove.push(grid[x][y]);
}
}
}
}
} else if (gem.specialType === 'bomb') {
// Add explosion flash effect for bomb
LK.effects.flashScreen(0xff8800, 600);
// Enhanced bomb visual effect - pulsing explosion
tween(gem, {
scaleX: 4,
scaleY: 4,
rotation: Math.PI * 2,
tint: 0xff4400
}, {
duration: 400,
easing: tween.easeOut
});
// Remove 3x3 area around the bomb
for (var dx = -1; dx <= 1; dx++) {
for (var dy = -1; dy <= 1; dy++) {
var targetX = gem.gridX + dx;
var targetY = gem.gridY + dy;
if (targetX >= 0 && targetX < GRID_SIZE && targetY >= 0 && targetY < GRID_SIZE) {
if (grid[targetX][targetY] && grid[targetX][targetY] !== gem) {
gemsToRemove.push(grid[targetX][targetY]);
}
}
}
}
} else if (gem.specialType === 'target') {
// Enhanced target effect - bullseye pulsing
tween(gem, {
scaleX: 2.5,
scaleY: 2.5,
tint: 0xff0000
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(gem, {
scaleX: 1.5,
scaleY: 1.5,
tint: 0xffffff
}, {
duration: 150,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(gem, {
scaleX: 2,
scaleY: 2,
tint: 0xff0000
}, {
duration: 150,
easing: tween.easeInOut
});
}
});
}
});
// Remove single target gem (the gem itself)
// Target gem destroys only itself when activated
} else if (gem.specialType === 'line_cleaner') {
// Enhanced line cleaner effect - crosshair targeting
tween(gem, {
scaleX: 3,
scaleY: 3,
tint: 0x00ff00,
rotation: Math.PI / 4
}, {
duration: 300,
easing: tween.easeOut
});
// Determine if it was a horizontal or vertical line and clear that line
// Check adjacent gems to determine orientation
var isHorizontal = false;
var isVertical = false;
// Check horizontal neighbors
if (gem.gridX > 0 && grid[gem.gridX - 1][gem.gridY] && grid[gem.gridX - 1][gem.gridY].gemType === gem.gemType || gem.gridX < GRID_SIZE - 1 && grid[gem.gridX + 1][gem.gridY] && grid[gem.gridX + 1][gem.gridY].gemType === gem.gemType) {
isHorizontal = true;
}
// Check vertical neighbors
if (gem.gridY > 0 && grid[gem.gridX][gem.gridY - 1] && grid[gem.gridX][gem.gridY - 1].gemType === gem.gemType || gem.gridY < GRID_SIZE - 1 && grid[gem.gridX][gem.gridY + 1] && grid[gem.gridX][gem.gridY + 1].gemType === gem.gemType) {
isVertical = true;
}
if (isHorizontal && !isVertical) {
// Clear entire row
for (var x = 0; x < GRID_SIZE; x++) {
if (grid[x][gem.gridY] && grid[x][gem.gridY] !== gem) {
gemsToRemove.push(grid[x][gem.gridY]);
}
}
} else if (isVertical && !isHorizontal) {
// Clear entire column
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[gem.gridX][y] && grid[gem.gridX][y] !== gem) {
gemsToRemove.push(grid[gem.gridX][y]);
}
}
} else {
// Default to clearing row if unclear
for (var x = 0; x < GRID_SIZE; x++) {
if (grid[x][gem.gridY] && grid[x][gem.gridY] !== gem) {
gemsToRemove.push(grid[x][gem.gridY]);
}
}
}
}
// Remove the special gem itself
gemsToRemove.push(gem);
// Add explosion effect
tween(gem, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut
});
// Remove all targeted gems with crystal crack effects and staggered timing
for (var i = 0; i < gemsToRemove.length; i++) {
var gemToRemove = gemsToRemove[i];
// Track blue gem collection
if (gemToRemove.gemType === 'gem_blue') {
blueGemsCollected++;
blueGemsText.setText('Blue Gems: ' + blueGemsCollected + '/' + targetBlueGems);
}
// Track green gem collection
if (gemToRemove.gemType === 'gem_green') {
greenGemsCollected++;
greenGemsText.setText('Green Gems: ' + greenGemsCollected + '/' + targetGreenGems);
}
if (grid[gemToRemove.gridX] && grid[gemToRemove.gridX][gemToRemove.gridY]) {
grid[gemToRemove.gridX][gemToRemove.gridY] = null;
}
// Add crystal crack effect with delay for cascade visual
LK.setTimeout(function (targetGem) {
return function () {
// First stage - crystal stress effect
tween(targetGem, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0.9,
tint: 0xffffff
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
// Second stage - crack and shatter
tween(targetGem, {
scaleX: 0.1,
scaleY: 0.1,
alpha: 0,
rotation: Math.PI,
tint: 0xcccccc
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
if (targetGem.parent) {
targetGem.destroy();
}
}
});
}
});
};
}(gemToRemove), i * 50);
}
// Add score for special activation
var bonusScore = gemsToRemove.length * 20;
currentScore += bonusScore;
scoreText.setText('Score: ' + currentScore);
return true;
}
function showWinScreen() {
gameState = 'win';
// Hide game UI
scoreText.visible = false;
movesText.visible = false;
levelText.visible = false;
blueGemsText.visible = false;
greenGemsText.visible = false;
boardBg.visible = false;
// Create win screen background
var winBg = game.attachAsset('board_bg', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 1.3,
scaleY: 1.7
});
winBg.tint = 0x2c5422;
// Congratulations text
var winText = new Text2('LEVEL COMPLETED!', {
size: 100,
fill: '#ffffff'
});
winText.anchor.set(0.5, 0.5);
winText.x = 1024;
winText.y = 800;
game.addChild(winText);
// Score text
var finalScoreText = new Text2('Final Score: ' + currentScore, {
size: 80,
fill: '#ffaa00'
});
finalScoreText.anchor.set(0.5, 0.5);
finalScoreText.x = 1024;
finalScoreText.y = 950;
game.addChild(finalScoreText);
// Next Level button
var nextLevelButton = new Container();
var nextLevelBg = LK.getAsset('gem_green', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
var nextLevelText = new Text2('NEXT LEVEL', {
size: 50,
fill: '#ffffff'
});
nextLevelText.anchor.set(0.5, 0.5);
nextLevelButton.addChild(nextLevelBg);
nextLevelButton.addChild(nextLevelText);
nextLevelButton.x = 750;
nextLevelButton.y = 1150;
nextLevelButton.down = function (x, y, obj) {
// Clean up win screen
winBg.destroy();
winText.destroy();
finalScoreText.destroy();
nextLevelButton.destroy();
menuButton.destroy();
// Start next level
if (currentLevel < 10) {
startGame(currentLevel + 1);
} else {
showMenu();
}
};
// Menu button
var menuButton = new Container();
var menuBg = LK.getAsset('gem_blue', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
var menuText = new Text2('MENU', {
size: 50,
fill: '#ffffff'
});
menuText.anchor.set(0.5, 0.5);
menuButton.addChild(menuBg);
menuButton.addChild(menuText);
menuButton.x = 1298;
menuButton.y = 1150;
menuButton.down = function (x, y, obj) {
// Clean up win screen
winBg.destroy();
winText.destroy();
finalScoreText.destroy();
nextLevelButton.destroy();
menuButton.destroy();
// Return to menu
showMenu();
};
game.addChild(nextLevelButton);
game.addChild(menuButton);
// Hide next level button if it's the last level
if (currentLevel >= 10) {
nextLevelText.setText('ALL LEVELS\nCOMPLETED!');
nextLevelBg.tint = 0x666666;
}
}
function checkForCascade() {
var matches = findMatches();
if (matches.length > 0) {
LK.getSound('cascade').play();
processMatches(matches);
} else {
cascadeDepth = 0;
isProcessing = false;
// Check for level 3 special win condition (all blocked gems unblocked)
if (currentLevel === 3) {
var blockedGemsRemaining = 0;
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[x] && grid[x][y] && grid[x][y].isBlocked) {
blockedGemsRemaining++;
}
}
}
if (blockedGemsRemaining === 0) {
// Level 3 completed - all blocked gems unblocked!
var unlockedLevel = storage.unlockedLevel || 1;
if (currentLevel >= unlockedLevel && currentLevel < 10) {
storage.unlockedLevel = currentLevel + 1;
}
LK.showYouWin();
return;
}
// Check for game over in level 3 - if moves exhausted but blocks remain
if (movesLeft <= 0) {
LK.showGameOver();
return;
}
} else {
// Check for blue gem completion for other levels
if (blueGemsCollected >= targetBlueGems && targetBlueGems > 0) {
// Level completed - blue gems collected!
var unlockedLevel = storage.unlockedLevel || 1;
if (currentLevel >= unlockedLevel && currentLevel < 10) {
storage.unlockedLevel = currentLevel + 1;
}
showWinScreen();
return;
}
// Check for green gem completion
if (greenGemsCollected >= targetGreenGems && targetGreenGems > 0) {
// Level completed - green gems collected!
var unlockedLevel = storage.unlockedLevel || 1;
if (currentLevel >= unlockedLevel && currentLevel < 10) {
storage.unlockedLevel = currentLevel + 1;
}
showWinScreen();
return;
}
// Check for game over in other levels
if (movesLeft <= 0) {
LK.setTimeout(function () {
showMenu();
}, 1000);
}
}
}
}
// Start with menu instead of initializing grid directly
showMenu();
game.update = function () {
// Update gem animations
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[x] && grid[x][y] && grid[x][y].isAnimating) {
grid[x][y].isAnimating = false;
}
}
}
};
function activateSpecialCombo(gem1, gem2) {
var gemsToRemove = [];
var comboScore = 0;
// Add dramatic visual effect for combo
tween(gem1, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut
});
tween(gem2, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut
});
// Determine combo effect based on gem types
var type1 = gem1.specialType;
var type2 = gem2.specialType;
if ((type1 === 'striped_horizontal' || type1 === 'striped_vertical') && (type2 === 'striped_horizontal' || type2 === 'striped_vertical')) {
// Striped + Striped = Clear entire row AND column
for (var x = 0; x < GRID_SIZE; x++) {
if (grid[x][gem1.gridY] && grid[x][gem1.gridY] !== gem1 && grid[x][gem1.gridY] !== gem2) {
gemsToRemove.push(grid[x][gem1.gridY]);
}
}
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[gem1.gridX][y] && grid[gem1.gridX][y] !== gem1 && grid[gem1.gridX][y] !== gem2) {
gemsToRemove.push(grid[gem1.gridX][y]);
}
}
comboScore = 500;
} else if (type1 === 'bomb' && type2 === 'bomb') {
// Bomb + Bomb = 5x5 explosion
for (var dx = -2; dx <= 2; dx++) {
for (var dy = -2; dy <= 2; dy++) {
var targetX = gem1.gridX + dx;
var targetY = gem1.gridY + dy;
if (targetX >= 0 && targetX < GRID_SIZE && targetY >= 0 && targetY < GRID_SIZE) {
if (grid[targetX][targetY] && grid[targetX][targetY] !== gem1 && grid[targetX][targetY] !== gem2) {
gemsToRemove.push(grid[targetX][targetY]);
}
}
}
}
comboScore = 400;
} else if ((type1 === 'striped_horizontal' || type1 === 'striped_vertical') && type2 === 'bomb') {
// Striped + Bomb = 3 rows or 3 columns
if (type1 === 'striped_horizontal') {
for (var dy = -1; dy <= 1; dy++) {
for (var x = 0; x < GRID_SIZE; x++) {
var targetY = gem1.gridY + dy;
if (targetY >= 0 && targetY < GRID_SIZE) {
if (grid[x][targetY] && grid[x][targetY] !== gem1 && grid[x][targetY] !== gem2) {
gemsToRemove.push(grid[x][targetY]);
}
}
}
}
} else {
for (var dx = -1; dx <= 1; dx++) {
for (var y = 0; y < GRID_SIZE; y++) {
var targetX = gem1.gridX + dx;
if (targetX >= 0 && targetX < GRID_SIZE) {
if (grid[targetX][y] && grid[targetX][y] !== gem1 && grid[targetX][y] !== gem2) {
gemsToRemove.push(grid[targetX][y]);
}
}
}
}
}
comboScore = 350;
} else if (type1 === 'bomb' && (type2 === 'striped_horizontal' || type2 === 'striped_vertical')) {
// Bomb + Striped = 3 rows or 3 columns
if (type2 === 'striped_horizontal') {
for (var dy = -1; dy <= 1; dy++) {
for (var x = 0; x < GRID_SIZE; x++) {
var targetY = gem2.gridY + dy;
if (targetY >= 0 && targetY < GRID_SIZE) {
if (grid[x][targetY] && grid[x][targetY] !== gem1 && grid[x][targetY] !== gem2) {
gemsToRemove.push(grid[x][targetY]);
}
}
}
}
} else {
for (var dx = -1; dx <= 1; dx++) {
for (var y = 0; y < GRID_SIZE; y++) {
var targetX = gem2.gridX + dx;
if (targetX >= 0 && targetX < GRID_SIZE) {
if (grid[targetX][y] && grid[targetX][y] !== gem1 && grid[targetX][y] !== gem2) {
gemsToRemove.push(grid[targetX][y]);
}
}
}
}
}
comboScore = 350;
} else if (type1 === 'rainbow' || type2 === 'rainbow') {
// Rainbow + Any special = Transform all gems of one type to that special type
var otherGem = type1 === 'rainbow' ? gem2 : gem1;
var targetType = gemTypes[Math.floor(Math.random() * gemTypes.length)];
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[x][y] && grid[x][y].gemType === targetType && grid[x][y] !== gem1 && grid[x][y] !== gem2) {
// Transform to special type
grid[x][y].setSpecial(otherGem.specialType);
}
}
}
comboScore = 600;
} else if (type1 === 'target' || type2 === 'target') {
// Target + Any special = Clear all gems of 2 random colors
var color1 = gemTypes[Math.floor(Math.random() * gemTypes.length)];
var color2 = gemTypes[Math.floor(Math.random() * gemTypes.length)];
while (color2 === color1) {
color2 = gemTypes[Math.floor(Math.random() * gemTypes.length)];
}
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[x][y] && (grid[x][y].gemType === color1 || grid[x][y].gemType === color2) && grid[x][y] !== gem1 && grid[x][y] !== gem2) {
gemsToRemove.push(grid[x][y]);
}
}
}
comboScore = 450;
} else if (type1 === 'line_cleaner' || type2 === 'line_cleaner') {
// Line cleaner + Any special = Clear cross pattern (both row and column)
var targetGem = type1 === 'line_cleaner' ? gem1 : gem2;
// Clear entire row
for (var x = 0; x < GRID_SIZE; x++) {
if (grid[x][targetGem.gridY] && grid[x][targetGem.gridY] !== gem1 && grid[x][targetGem.gridY] !== gem2) {
gemsToRemove.push(grid[x][targetGem.gridY]);
}
}
// Clear entire column
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[targetGem.gridX][y] && grid[targetGem.gridX][y] !== gem1 && grid[targetGem.gridX][y] !== gem2) {
gemsToRemove.push(grid[targetGem.gridX][y]);
}
}
comboScore = 400;
}
// Remove the special gems themselves
gemsToRemove.push(gem1);
gemsToRemove.push(gem2);
// Remove all targeted gems with enhanced crystal crack effects
for (var i = 0; i < gemsToRemove.length; i++) {
var gemToRemove = gemsToRemove[i];
// Track blue gem collection
if (gemToRemove.gemType === 'gem_blue') {
blueGemsCollected++;
blueGemsText.setText('Blue Gems: ' + blueGemsCollected + '/' + targetBlueGems);
}
// Track green gem collection
if (gemToRemove.gemType === 'gem_green') {
greenGemsCollected++;
greenGemsText.setText('Green Gems: ' + greenGemsCollected + '/' + targetGreenGems);
}
if (grid[gemToRemove.gridX] && grid[gemToRemove.gridX][gemToRemove.gridY]) {
grid[gemToRemove.gridX][gemToRemove.gridY] = null;
}
// Enhanced crystal crack effect for combo - dramatic shattering
tween(gemToRemove, {
scaleX: 1.4,
scaleY: 1.4,
alpha: 1.0,
tint: 0xffffff
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(gemToRemove, {
scaleX: 0,
scaleY: 0,
alpha: 0,
rotation: Math.PI * 2,
tint: 0x888888
}, {
duration: 250,
easing: tween.easeIn,
onFinish: function onFinish() {
if (gemToRemove.parent) {
gemToRemove.destroy();
}
}
});
}
});
}
// Add bonus score for special combo
currentScore += comboScore;
scoreText.setText('Score: ' + currentScore);
// Flash screen to indicate powerful combo
LK.effects.flashScreen(0xffd700, 800);
} /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
unlockedLevel: 1
});
/****
* Classes
****/
var Gem = Container.expand(function (gemType, gridX, gridY, isBlocked) {
var self = Container.call(this);
self.gemType = gemType;
self.gridX = gridX;
self.gridY = gridY;
self.isSpecial = false;
self.specialType = 'normal'; // 'normal', 'striped_horizontal', 'striped_vertical', 'rainbow'
self.isAnimating = false;
self.isSelected = false;
self.isBlocked = isBlocked || false;
var gemAsset = self.attachAsset(gemType, {
anchorX: 0.5,
anchorY: 0.5
});
// Apply blocked visual directly to the gem
if (self.isBlocked) {
self.removeChild(gemAsset);
gemAsset = self.attachAsset('blocked_gem', {
anchorX: 0.5,
anchorY: 0.5
});
}
self.setSpecial = function (type) {
self.isSpecial = true;
self.specialType = type;
if (type === 'striped_horizontal' || type === 'striped_vertical') {
self.removeChild(gemAsset);
gemAsset = self.attachAsset('striped_gem', {
anchorX: 0.5,
anchorY: 0.5
});
if (type === 'striped_horizontal') {
gemAsset.rotation = Math.PI / 2;
}
} else if (type === 'rainbow') {
self.removeChild(gemAsset);
gemAsset = self.attachAsset('rainbow_gem', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'bomb') {
self.removeChild(gemAsset);
gemAsset = self.attachAsset('bomb_gem', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'target') {
self.removeChild(gemAsset);
gemAsset = self.attachAsset('target_gem', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'line_cleaner') {
self.removeChild(gemAsset);
gemAsset = self.attachAsset('line_cleaner', {
anchorX: 0.5,
anchorY: 0.5
});
}
};
self.highlight = function () {
self.isSelected = true;
gemAsset.alpha = 0.7;
};
self.unhighlight = function () {
self.isSelected = false;
gemAsset.alpha = 1.0;
};
self.unblock = function () {
if (self.isBlocked) {
self.isBlocked = false;
// Completely remove the blocked gem asset
self.removeChild(gemAsset);
// Create new gem asset with original gem type
gemAsset = self.attachAsset(self.gemType, {
anchorX: 0.5,
anchorY: 0.5
});
// Reset any visual properties to ensure clean state
gemAsset.alpha = 1.0;
gemAsset.tint = 0xffffff;
gemAsset.scaleX = 1.0;
gemAsset.scaleY = 1.0;
// Animate restoration of gem's original appearance
tween(gemAsset, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(gemAsset, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 150,
easing: tween.easeIn
});
}
});
}
};
self.down = function (x, y, obj) {
if (!self.isAnimating && !self.isBlocked) {
handleGemSelect(self);
}
};
return self;
});
var MenuState = Container.expand(function () {
var self = Container.call(this);
// Create menu background
var menuBg = self.attachAsset('board_bg', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 1.3,
scaleY: 1.7
});
menuBg.tint = 0x2c2c54;
// Title text
var titleText = new Text2('MATCH-3 PUZZLE', {
size: 120,
fill: '#ffffff'
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 400;
self.addChild(titleText);
// Subtitle
var subtitleText = new Text2('Select Level', {
size: 80,
fill: '#ffaa00'
});
subtitleText.anchor.set(0.5, 0.5);
subtitleText.x = 1024;
subtitleText.y = 550;
self.addChild(subtitleText);
// Level buttons array
self.levelButtons = [];
// Create level buttons (1-10)
for (var i = 1; i <= 10; i++) {
var unlockedLevel = storage.unlockedLevel || 1;
var isUnlocked = i <= unlockedLevel;
var buttonBg = LK.getAsset(isUnlocked ? 'gem_blue' : 'gem_red', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
var levelText = new Text2(i.toString(), {
size: 60,
fill: isUnlocked ? '#ffffff' : '#888888'
});
levelText.anchor.set(0.5, 0.5);
var button = new Container();
button.addChild(buttonBg);
button.addChild(levelText);
// Position buttons in 2 rows of 5
var col = (i - 1) % 5;
var row = Math.floor((i - 1) / 5);
button.x = 400 + col * 250;
button.y = 800 + row * 200;
button.levelNumber = i;
button.buttonBg = buttonBg;
button.isUnlocked = isUnlocked;
// Button interaction
button.down = function (x, y, obj) {
// Use 'this' to reference the button that was clicked
if (this.isUnlocked) {
var level = this.levelNumber;
startGame(level);
}
};
self.levelButtons.push(button);
self.addChild(button);
}
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
var gameState = 'menu'; // 'menu' or 'playing'
var currentLevel = 1;
var menuScreen = null;
function startGame(level) {
gameState = 'playing';
currentLevel = level;
// Remove menu screen
if (menuScreen) {
menuScreen.destroy();
menuScreen = null;
}
// Reset game variables based on level
if (level === 1) {
movesLeft = 19;
targetBlueGems = 30;
targetGreenGems = 0;
} else if (level === 2) {
movesLeft = 23;
targetBlueGems = 0;
targetGreenGems = 35;
} else if (level === 3) {
movesLeft = 10;
targetBlueGems = 40;
targetGreenGems = 0;
} else {
movesLeft = Math.max(10, 20 - level);
targetBlueGems = 25 + level * 5;
targetGreenGems = 0;
}
currentScore = 0;
cascadeDepth = 0;
blueGemsCollected = 0;
greenGemsCollected = 0;
selectedGem = null;
isProcessing = false;
// Update UI
movesText.setText('Moves: ' + movesLeft);
scoreText.setText('Score: ' + currentScore);
levelText.setText('Level: ' + currentLevel);
blueGemsText.setText('Blue Gems: ' + blueGemsCollected + '/' + targetBlueGems);
greenGemsText.setText('Green Gems: ' + greenGemsCollected + '/' + targetGreenGems);
// Show game UI
scoreText.visible = true;
movesText.visible = true;
levelText.visible = true;
blueGemsText.visible = currentLevel === 1 || currentLevel > 3;
greenGemsText.visible = currentLevel === 2;
// For level 3, show blocked gems objective
if (currentLevel === 3) {
blueGemsText.setText('Unblock All Gems!');
blueGemsText.visible = true;
}
boardBg.visible = true;
// Initialize grid for gameplay
initializeGrid();
}
function showMenu() {
gameState = 'menu';
// Hide game UI
scoreText.visible = false;
movesText.visible = false;
levelText.visible = false;
blueGemsText.visible = false;
greenGemsText.visible = false;
boardBg.visible = false;
// Clear any existing gems
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[x] && grid[x][y]) {
if (grid[x][y].parent) {
grid[x][y].destroy();
}
grid[x][y] = null;
}
}
}
// Show menu
menuScreen = new MenuState();
game.addChild(menuScreen);
}
var GRID_SIZE = 6;
var GEM_SIZE = 200;
var BOARD_SIZE = GRID_SIZE * GEM_SIZE;
var BOARD_OFFSET_X = (2048 - BOARD_SIZE) / 2;
var BOARD_OFFSET_Y = (2732 - BOARD_SIZE) / 2 - 200;
var gemTypes = ['gem_red', 'gem_blue', 'gem_green', 'gem_yellow', 'gem_purple', 'gem_orange'];
var grid = [];
var selectedGem = null;
var isProcessing = false;
var movesLeft = 30;
var currentScore = 0;
var cascadeDepth = 0;
var blueGemsCollected = 0;
var targetBlueGems = 30;
var greenGemsCollected = 0;
var targetGreenGems = 0;
// Initialize UI
var scoreText = new Text2('Score: 0', {
size: 60,
fill: '#ffffff'
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
var movesText = new Text2('Moves: 30', {
size: 60,
fill: '#ffffff'
});
movesText.anchor.set(0.5, 0);
movesText.y = 80;
LK.gui.top.addChild(movesText);
var levelText = new Text2('Level: 1', {
size: 60,
fill: '#ffffff'
});
levelText.anchor.set(0.5, 0);
levelText.y = 160;
LK.gui.top.addChild(levelText);
var blueGemsText = new Text2('Blue Gems: 0/30', {
size: 60,
fill: '#4da6ff'
});
blueGemsText.anchor.set(0.5, 0);
blueGemsText.y = 240;
LK.gui.top.addChild(blueGemsText);
var greenGemsText = new Text2('Green Gems: 0/0', {
size: 60,
fill: '#44dd44'
});
greenGemsText.anchor.set(0.5, 0);
greenGemsText.y = 300;
LK.gui.top.addChild(greenGemsText);
// Create board background
var boardBg = game.attachAsset('board_bg', {
anchorX: 0.5,
anchorY: 0.5,
x: BOARD_OFFSET_X + BOARD_SIZE / 2,
y: BOARD_OFFSET_Y + BOARD_SIZE / 2
});
// Initialize grid
function initializeGrid() {
grid = [];
for (var x = 0; x < GRID_SIZE; x++) {
grid[x] = [];
for (var y = 0; y < GRID_SIZE; y++) {
grid[x][y] = null;
}
}
// Fill grid with gems, ensuring no initial matches
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
var validTypes = gemTypes.slice();
// Remove types that would create horizontal matches
if (x >= 2 && grid[x - 1][y] && grid[x - 2][y] && grid[x - 1][y].gemType === grid[x - 2][y].gemType) {
var typeToRemove = grid[x - 1][y].gemType;
var index = validTypes.indexOf(typeToRemove);
if (index > -1) validTypes.splice(index, 1);
}
// Remove types that would create vertical matches
if (y >= 2 && grid[x][y - 1] && grid[x][y - 2] && grid[x][y - 1].gemType === grid[x][y - 2].gemType) {
var typeToRemove = grid[x][y - 1].gemType;
var index = validTypes.indexOf(typeToRemove);
if (index > -1) validTypes.splice(index, 1);
}
// Use different gem types for each level to adjust difficulty
var availableTypes;
if (currentLevel === 1) {
availableTypes = gemTypes.slice(0, 4); // 4 types for level 1
} else if (currentLevel === 2) {
availableTypes = gemTypes.slice(0, 5); // 5 types for level 2
} else if (currentLevel === 3) {
availableTypes = gemTypes.slice(0, 6); // All 6 types for level 3
} else {
availableTypes = gemTypes; // All types for higher levels
}
var filteredValidTypes = validTypes.filter(function (type) {
return availableTypes.indexOf(type) !== -1;
});
if (filteredValidTypes.length === 0) filteredValidTypes = availableTypes.slice(0, 2);
var randomType = filteredValidTypes[Math.floor(Math.random() * filteredValidTypes.length)];
// For level 3, add some blocked gems
var isBlocked = false;
if (currentLevel === 3) {
// Add blocked gems in bottom 2 rows (rows 4 and 5)
if (y === 4 || y === 5) {
isBlocked = true;
}
}
var gem = new Gem(randomType, x, y, isBlocked);
gem.x = BOARD_OFFSET_X + x * GEM_SIZE + GEM_SIZE / 2;
gem.y = BOARD_OFFSET_Y + y * GEM_SIZE + GEM_SIZE / 2;
grid[x][y] = gem;
game.addChild(gem);
}
}
}
function handleGemSelect(gem) {
if (isProcessing) return;
if (selectedGem === null) {
selectedGem = gem;
gem.highlight();
} else if (selectedGem === gem) {
selectedGem.unhighlight();
selectedGem = null;
} else {
// Check if either gem is blocked - cannot swap with blocked gems
if (selectedGem.isBlocked || gem.isBlocked) {
selectedGem.unhighlight();
selectedGem = null;
return;
}
// Check if gems are adjacent
var dx = Math.abs(selectedGem.gridX - gem.gridX);
var dy = Math.abs(selectedGem.gridY - gem.gridY);
if (dx === 1 && dy === 0 || dx === 0 && dy === 1) {
// Swap gems
swapGems(selectedGem, gem);
}
selectedGem.unhighlight();
selectedGem = null;
}
}
function swapGems(gem1, gem2) {
if (movesLeft <= 0) return;
// Don't allow swapping with blocked gems
if (gem1.isBlocked || gem2.isBlocked) {
return;
}
isProcessing = true;
// Check if both gems are special - create super combo
if (gem1.isSpecial && gem2.isSpecial) {
movesLeft--;
movesText.setText('Moves: ' + movesLeft);
LK.setTimeout(function () {
activateSpecialCombo(gem1, gem2);
LK.setTimeout(function () {
applyGravity();
}, 500);
}, 100);
return;
}
// Check if either gem is special and activate it
var specialActivated = false;
if (gem1.isSpecial) {
specialActivated = true;
movesLeft--;
movesText.setText('Moves: ' + movesLeft);
LK.setTimeout(function () {
activateSpecialGem(gem1);
LK.setTimeout(function () {
applyGravity();
}, 500);
}, 100);
return;
}
if (gem2.isSpecial) {
specialActivated = true;
movesLeft--;
movesText.setText('Moves: ' + movesLeft);
LK.setTimeout(function () {
activateSpecialGem(gem2);
LK.setTimeout(function () {
applyGravity();
}, 500);
}, 100);
return;
}
// Store original positions
var tempX = gem1.gridX;
var tempY = gem1.gridY;
// Temporarily swap gems to check for matches
gem1.gridX = gem2.gridX;
gem1.gridY = gem2.gridY;
gem2.gridX = tempX;
gem2.gridY = tempY;
grid[gem1.gridX][gem1.gridY] = gem1;
grid[gem2.gridX][gem2.gridY] = gem2;
// Check if this swap creates any matches
var testMatches = findMatches();
// If no matches, revert the swap and don't continue
if (testMatches.length === 0) {
// Revert grid positions to original
gem1.gridX = tempX;
gem1.gridY = tempY;
gem2.gridX = gem2.gridX;
gem2.gridY = gem2.gridY;
grid[gem1.gridX][gem1.gridY] = gem1;
grid[gem2.gridX][gem2.gridY] = gem2;
isProcessing = false;
return;
}
// Update grid positions (already done above for testing)
// Animate swap
var targetX1 = BOARD_OFFSET_X + gem1.gridX * GEM_SIZE + GEM_SIZE / 2;
var targetY1 = BOARD_OFFSET_Y + gem1.gridY * GEM_SIZE + GEM_SIZE / 2;
var targetX2 = BOARD_OFFSET_X + gem2.gridX * GEM_SIZE + GEM_SIZE / 2;
var targetY2 = BOARD_OFFSET_Y + gem2.gridY * GEM_SIZE + GEM_SIZE / 2;
gem1.isAnimating = true;
gem2.isAnimating = true;
tween(gem1, {
x: targetX1,
y: targetY1
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
gem1.isAnimating = false;
checkSwapComplete();
}
});
tween(gem2, {
x: targetX2,
y: targetY2
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
gem2.isAnimating = false;
checkSwapComplete();
}
});
}
var swapCompleteCount = 0;
function checkSwapComplete() {
swapCompleteCount++;
if (swapCompleteCount === 2) {
swapCompleteCount = 0;
// Check for matches
var matches = findMatches();
if (matches.length > 0) {
movesLeft--;
movesText.setText('Moves: ' + movesLeft);
processMatches(matches);
} else {
// No matches, just end processing since swap was invalid
isProcessing = false;
}
}
}
function findMatches() {
var matches = [];
var visited = [];
// Initialize visited array
for (var x = 0; x < GRID_SIZE; x++) {
visited[x] = [];
for (var y = 0; y < GRID_SIZE; y++) {
visited[x][y] = false;
}
}
// Find horizontal matches
for (var y = 0; y < GRID_SIZE; y++) {
for (var x = 0; x < GRID_SIZE - 2; x++) {
if (grid[x][y] && grid[x + 1][y] && grid[x + 2][y] && !grid[x][y].isBlocked && !grid[x + 1][y].isBlocked && !grid[x + 2][y].isBlocked && grid[x][y].gemType === grid[x + 1][y].gemType && grid[x + 1][y].gemType === grid[x + 2][y].gemType) {
var matchGroup = [];
var currentX = x;
while (currentX < GRID_SIZE && grid[currentX][y] && grid[currentX][y].gemType === grid[x][y].gemType) {
if (!visited[currentX][y]) {
matchGroup.push(grid[currentX][y]);
visited[currentX][y] = true;
}
currentX++;
}
if (matchGroup.length >= 3) {
matches.push(matchGroup);
}
}
}
}
// Find vertical matches
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE - 2; y++) {
if (grid[x][y] && grid[x][y + 1] && grid[x][y + 2] && !grid[x][y].isBlocked && !grid[x][y + 1].isBlocked && !grid[x][y + 2].isBlocked && grid[x][y].gemType === grid[x][y + 1].gemType && grid[x][y + 1].gemType === grid[x][y + 2].gemType) {
var matchGroup = [];
var currentY = y;
while (currentY < GRID_SIZE && grid[x][currentY] && grid[x][currentY].gemType === grid[x][y].gemType) {
if (!visited[x][currentY]) {
matchGroup.push(grid[x][currentY]);
visited[x][currentY] = true;
}
currentY++;
}
if (matchGroup.length >= 3) {
matches.push(matchGroup);
}
}
}
}
// Find 2x2 square matches
for (var x = 0; x < GRID_SIZE - 1; x++) {
for (var y = 0; y < GRID_SIZE - 1; y++) {
if (grid[x][y] && grid[x + 1][y] && grid[x][y + 1] && grid[x + 1][y + 1]) {
var gemType = grid[x][y].gemType;
if (grid[x + 1][y].gemType === gemType && grid[x][y + 1].gemType === gemType && grid[x + 1][y + 1].gemType === gemType && !visited[x][y] && !visited[x + 1][y] && !visited[x][y + 1] && !visited[x + 1][y + 1]) {
var squareMatch = [grid[x][y], grid[x + 1][y], grid[x][y + 1], grid[x + 1][y + 1]];
matches.push(squareMatch);
for (var i = 0; i < squareMatch.length; i++) {
visited[squareMatch[i].gridX][squareMatch[i].gridY] = true;
}
}
}
}
}
// Find L and T shaped matches
for (var x = 1; x < GRID_SIZE - 1; x++) {
for (var y = 1; y < GRID_SIZE - 1; y++) {
if (grid[x][y]) {
var centerGem = grid[x][y];
var gemType = centerGem.gemType;
// Check L shapes (4 possible orientations)
// L shape: top-left
if (grid[x - 1][y] && grid[x][y - 1] && grid[x - 1][y - 1] && grid[x - 1][y].gemType === gemType && grid[x][y - 1].gemType === gemType && grid[x - 1][y - 1].gemType === gemType && !visited[x][y] && !visited[x - 1][y] && !visited[x][y - 1] && !visited[x - 1][y - 1]) {
var lMatch = [centerGem, grid[x - 1][y], grid[x][y - 1], grid[x - 1][y - 1]];
matches.push(lMatch);
for (var i = 0; i < lMatch.length; i++) {
visited[lMatch[i].gridX][lMatch[i].gridY] = true;
}
}
// L shape: top-right
if (grid[x + 1][y] && grid[x][y - 1] && grid[x + 1][y - 1] && grid[x + 1][y].gemType === gemType && grid[x][y - 1].gemType === gemType && grid[x + 1][y - 1].gemType === gemType && !visited[x][y] && !visited[x + 1][y] && !visited[x][y - 1] && !visited[x + 1][y - 1]) {
var lMatch = [centerGem, grid[x + 1][y], grid[x][y - 1], grid[x + 1][y - 1]];
matches.push(lMatch);
for (var i = 0; i < lMatch.length; i++) {
visited[lMatch[i].gridX][lMatch[i].gridY] = true;
}
}
// L shape: bottom-left
if (grid[x - 1][y] && grid[x][y + 1] && grid[x - 1][y + 1] && grid[x - 1][y].gemType === gemType && grid[x][y + 1].gemType === gemType && grid[x - 1][y + 1].gemType === gemType && !visited[x][y] && !visited[x - 1][y] && !visited[x][y + 1] && !visited[x - 1][y + 1]) {
var lMatch = [centerGem, grid[x - 1][y], grid[x][y + 1], grid[x - 1][y + 1]];
matches.push(lMatch);
for (var i = 0; i < lMatch.length; i++) {
visited[lMatch[i].gridX][lMatch[i].gridY] = true;
}
}
// L shape: bottom-right
if (grid[x + 1][y] && grid[x][y + 1] && grid[x + 1][y + 1] && grid[x + 1][y].gemType === gemType && grid[x][y + 1].gemType === gemType && grid[x + 1][y + 1].gemType === gemType && !visited[x][y] && !visited[x + 1][y] && !visited[x][y + 1] && !visited[x + 1][y + 1]) {
var lMatch = [centerGem, grid[x + 1][y], grid[x][y + 1], grid[x + 1][y + 1]];
matches.push(lMatch);
for (var i = 0; i < lMatch.length; i++) {
visited[lMatch[i].gridX][lMatch[i].gridY] = true;
}
}
// Check T shapes (4 possible orientations)
// T shape: horizontal top
if (grid[x - 1][y] && grid[x + 1][y] && grid[x][y - 1] && grid[x - 1][y].gemType === gemType && grid[x + 1][y].gemType === gemType && grid[x][y - 1].gemType === gemType && !visited[x][y] && !visited[x - 1][y] && !visited[x + 1][y] && !visited[x][y - 1]) {
var tMatch = [centerGem, grid[x - 1][y], grid[x + 1][y], grid[x][y - 1]];
matches.push(tMatch);
for (var i = 0; i < tMatch.length; i++) {
visited[tMatch[i].gridX][tMatch[i].gridY] = true;
}
}
// T shape: horizontal bottom
if (grid[x - 1][y] && grid[x + 1][y] && grid[x][y + 1] && grid[x - 1][y].gemType === gemType && grid[x + 1][y].gemType === gemType && grid[x][y + 1].gemType === gemType && !visited[x][y] && !visited[x - 1][y] && !visited[x + 1][y] && !visited[x][y + 1]) {
var tMatch = [centerGem, grid[x - 1][y], grid[x + 1][y], grid[x][y + 1]];
matches.push(tMatch);
for (var i = 0; i < tMatch.length; i++) {
visited[tMatch[i].gridX][tMatch[i].gridY] = true;
}
}
// T shape: vertical left
if (grid[x][y - 1] && grid[x][y + 1] && grid[x - 1][y] && grid[x][y - 1].gemType === gemType && grid[x][y + 1].gemType === gemType && grid[x - 1][y].gemType === gemType && !visited[x][y] && !visited[x][y - 1] && !visited[x][y + 1] && !visited[x - 1][y]) {
var tMatch = [centerGem, grid[x][y - 1], grid[x][y + 1], grid[x - 1][y]];
matches.push(tMatch);
for (var i = 0; i < tMatch.length; i++) {
visited[tMatch[i].gridX][tMatch[i].gridY] = true;
}
}
// T shape: vertical right
if (grid[x][y - 1] && grid[x][y + 1] && grid[x + 1][y] && grid[x][y - 1].gemType === gemType && grid[x][y + 1].gemType === gemType && grid[x + 1][y].gemType === gemType && !visited[x][y] && !visited[x][y - 1] && !visited[x][y + 1] && !visited[x + 1][y]) {
var tMatch = [centerGem, grid[x][y - 1], grid[x][y + 1], grid[x + 1][y]];
matches.push(tMatch);
for (var i = 0; i < tMatch.length; i++) {
visited[tMatch[i].gridX][tMatch[i].gridY] = true;
}
}
}
}
}
return matches;
}
function processMatches(matches) {
cascadeDepth++;
var totalScore = 0;
var specialGems = [];
for (var i = 0; i < matches.length; i++) {
var match = matches[i];
var matchScore = match.length * 10 * cascadeDepth;
totalScore += matchScore;
// Create special gems for larger matches
if (match.length === 4) {
// Check if it's a 2x2 square
var isSquare = false;
var isLOrT = false;
if (match.length === 4) {
// Sort gems by position to check square pattern
var sortedMatch = match.slice().sort(function (a, b) {
if (a.gridX !== b.gridX) return a.gridX - b.gridX;
return a.gridY - b.gridY;
});
// Check if it forms a 2x2 square
if (sortedMatch[0].gridX === sortedMatch[1].gridX - 1 && sortedMatch[0].gridY === sortedMatch[2].gridY - 1 && sortedMatch[1].gridX === sortedMatch[3].gridX && sortedMatch[2].gridY === sortedMatch[3].gridY && sortedMatch[0].gridX === sortedMatch[2].gridX && sortedMatch[1].gridY === sortedMatch[0].gridY) {
isSquare = true;
}
// Check if it's an L or T shape (4 gems in specific patterns)
// L/T shapes will have gems that don't form a straight line
var minX = Math.min(match[0].gridX, match[1].gridX, match[2].gridX, match[3].gridX);
var maxX = Math.max(match[0].gridX, match[1].gridX, match[2].gridX, match[3].gridX);
var minY = Math.min(match[0].gridY, match[1].gridY, match[2].gridY, match[3].gridY);
var maxY = Math.max(match[0].gridY, match[1].gridY, match[2].gridY, match[3].gridY);
// If it spans more than 1 row AND more than 1 column, it's likely L or T
if (maxX - minX > 0 && maxY - minY > 0 && !isSquare) {
isLOrT = true;
}
}
if (isSquare) {
// Create target gem for 2x2 square
var specialGem = match[0];
specialGem.setSpecial('target');
specialGems.push(specialGem);
} else if (isLOrT) {
// Create bomb gem for L and T shapes
var specialGem = match[0];
specialGem.setSpecial('bomb');
specialGems.push(specialGem);
} else {
// For 4-gem straight line matches, create line cleaner
var specialGem = match[0];
specialGem.setSpecial('line_cleaner');
specialGems.push(specialGem);
}
} else if (match.length >= 5) {
// Create rainbow gem
var specialGem = match[0];
specialGem.setSpecial('rainbow');
specialGems.push(specialGem);
}
// Remove matched gems (except special gems) with crystal crack effects
for (var j = 0; j < match.length; j++) {
var gem = match[j];
if (specialGems.indexOf(gem) === -1) {
// Track blue gem collection
if (gem.gemType === 'gem_blue') {
blueGemsCollected++;
blueGemsText.setText('Blue Gems: ' + blueGemsCollected + '/' + targetBlueGems);
}
// Track green gem collection
if (gem.gemType === 'gem_green') {
greenGemsCollected++;
greenGemsText.setText('Green Gems: ' + greenGemsCollected + '/' + targetGreenGems);
}
if (grid[gem.gridX] && grid[gem.gridX][gem.gridY]) {
grid[gem.gridX][gem.gridY] = null;
}
// Add crystal crack effect
LK.setTimeout(function (targetGem) {
return function () {
tween(targetGem, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 0.8,
tint: 0xffffff
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(targetGem, {
scaleX: 0.1,
scaleY: 0.1,
alpha: 0,
rotation: Math.PI * 0.5,
tint: 0xaaaaaa
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
if (targetGem.parent) {
targetGem.destroy();
}
}
});
}
});
};
}(gem), j * 20);
}
}
}
currentScore += totalScore;
scoreText.setText('Score: ' + currentScore);
LK.getSound('match').play();
// Apply gravity and cascade
// Unblock adjacent gems
for (var i = 0; i < matches.length; i++) {
var match = matches[i];
for (var j = 0; j < match.length; j++) {
var gem = match[j];
// Check all adjacent positions and unblock them
for (var dx = -1; dx <= 1; dx++) {
for (var dy = -1; dy <= 1; dy++) {
if (dx === 0 && dy === 0) continue;
var adjX = gem.gridX + dx;
var adjY = gem.gridY + dy;
if (adjX >= 0 && adjX < GRID_SIZE && adjY >= 0 && adjY < GRID_SIZE) {
if (grid[adjX][adjY] && grid[adjX][adjY].isBlocked) {
grid[adjX][adjY].unblock();
}
}
}
}
}
}
LK.setTimeout(function () {
applyGravity();
}, 200);
}
function applyGravity() {
var gemsToAnimate = [];
// Move gems down
for (var x = 0; x < GRID_SIZE; x++) {
var writeIndex = GRID_SIZE - 1;
for (var y = GRID_SIZE - 1; y >= 0; y--) {
if (grid[x][y] !== null) {
if (grid[x][y].isBlocked) {
// Blocked gems stay in place
writeIndex--;
} else {
if (y !== writeIndex) {
grid[x][writeIndex] = grid[x][y];
grid[x][y] = null;
grid[x][writeIndex].gridY = writeIndex;
var targetY = BOARD_OFFSET_Y + writeIndex * GEM_SIZE + GEM_SIZE / 2;
gemsToAnimate.push({
gem: grid[x][writeIndex],
targetY: targetY
});
}
writeIndex--;
}
}
}
// Check if spawning path is clear - only blocked gems at the spawn positions matter
var canSpawnNewGems = true;
// Check if any of the positions we need to fill have blocked gems
for (var checkY = 0; checkY <= writeIndex; checkY++) {
if (grid[x][checkY] && grid[x][checkY].isBlocked) {
canSpawnNewGems = false;
break;
}
}
// Only spawn new gems if the actual spawn positions are clear
if (canSpawnNewGems) {
for (var y = 0; y <= writeIndex; y++) {
var availableTypes;
if (currentLevel === 1) {
availableTypes = gemTypes.slice(0, 4); // 4 types for level 1
} else if (currentLevel === 2) {
availableTypes = gemTypes.slice(0, 5); // 5 types for level 2
} else if (currentLevel === 3) {
availableTypes = gemTypes.slice(0, 6); // All 6 types for level 3
} else {
availableTypes = gemTypes; // All types for higher levels
}
var randomType = availableTypes[Math.floor(Math.random() * availableTypes.length)];
var newGem = new Gem(randomType, x, y);
newGem.x = BOARD_OFFSET_X + x * GEM_SIZE + GEM_SIZE / 2;
newGem.y = BOARD_OFFSET_Y + y * GEM_SIZE + GEM_SIZE / 2 - (writeIndex - y + 1) * GEM_SIZE;
grid[x][y] = newGem;
game.addChild(newGem);
var targetY = BOARD_OFFSET_Y + y * GEM_SIZE + GEM_SIZE / 2;
gemsToAnimate.push({
gem: newGem,
targetY: targetY
});
}
}
}
// Animate falling gems
var animationCount = 0;
for (var i = 0; i < gemsToAnimate.length; i++) {
var animData = gemsToAnimate[i];
animData.gem.isAnimating = true;
tween(animData.gem, {
y: animData.targetY
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
animationCount++;
if (animationCount === gemsToAnimate.length) {
checkForCascade();
}
}
});
}
if (gemsToAnimate.length === 0) {
checkForCascade();
}
}
function activateSpecialGem(gem) {
if (!gem.isSpecial) return false;
var gemsToRemove = [];
if (gem.specialType === 'striped_horizontal') {
// Enhanced horizontal striped effect - sweeping motion
tween(gem, {
scaleX: 8,
scaleY: 1.5,
tint: 0xffff00,
alpha: 0.7
}, {
duration: 350,
easing: tween.easeOut
});
// Remove entire row
for (var x = 0; x < GRID_SIZE; x++) {
if (grid[x][gem.gridY] && grid[x][gem.gridY] !== gem) {
gemsToRemove.push(grid[x][gem.gridY]);
}
}
} else if (gem.specialType === 'striped_vertical') {
// Enhanced vertical striped effect - sweeping motion
tween(gem, {
scaleX: 1.5,
scaleY: 8,
tint: 0xffff00,
alpha: 0.7
}, {
duration: 350,
easing: tween.easeOut
});
// Remove entire column
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[gem.gridX][y] && grid[gem.gridX][y] !== gem) {
gemsToRemove.push(grid[gem.gridX][y]);
}
}
} else if (gem.specialType === 'rainbow') {
// Enhanced rainbow effect - color cycling shimmer
tween(gem, {
scaleX: 2.5,
scaleY: 2.5,
tint: 0xff00ff
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(gem, {
tint: 0x00ffff
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(gem, {
tint: 0xffff00
}, {
duration: 200,
easing: tween.easeInOut
});
}
});
}
});
// Remove all gems of the same type as the first regular gem found
var targetType = null;
for (var x = 0; x < GRID_SIZE && !targetType; x++) {
for (var y = 0; y < GRID_SIZE && !targetType; y++) {
if (grid[x][y] && !grid[x][y].isSpecial && grid[x][y] !== gem) {
targetType = grid[x][y].gemType;
}
}
}
if (targetType) {
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[x][y] && grid[x][y].gemType === targetType && grid[x][y] !== gem) {
gemsToRemove.push(grid[x][y]);
}
}
}
}
} else if (gem.specialType === 'bomb') {
// Add explosion flash effect for bomb
LK.effects.flashScreen(0xff8800, 600);
// Enhanced bomb visual effect - pulsing explosion
tween(gem, {
scaleX: 4,
scaleY: 4,
rotation: Math.PI * 2,
tint: 0xff4400
}, {
duration: 400,
easing: tween.easeOut
});
// Remove 3x3 area around the bomb
for (var dx = -1; dx <= 1; dx++) {
for (var dy = -1; dy <= 1; dy++) {
var targetX = gem.gridX + dx;
var targetY = gem.gridY + dy;
if (targetX >= 0 && targetX < GRID_SIZE && targetY >= 0 && targetY < GRID_SIZE) {
if (grid[targetX][targetY] && grid[targetX][targetY] !== gem) {
gemsToRemove.push(grid[targetX][targetY]);
}
}
}
}
} else if (gem.specialType === 'target') {
// Enhanced target effect - bullseye pulsing
tween(gem, {
scaleX: 2.5,
scaleY: 2.5,
tint: 0xff0000
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(gem, {
scaleX: 1.5,
scaleY: 1.5,
tint: 0xffffff
}, {
duration: 150,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(gem, {
scaleX: 2,
scaleY: 2,
tint: 0xff0000
}, {
duration: 150,
easing: tween.easeInOut
});
}
});
}
});
// Remove single target gem (the gem itself)
// Target gem destroys only itself when activated
} else if (gem.specialType === 'line_cleaner') {
// Enhanced line cleaner effect - crosshair targeting
tween(gem, {
scaleX: 3,
scaleY: 3,
tint: 0x00ff00,
rotation: Math.PI / 4
}, {
duration: 300,
easing: tween.easeOut
});
// Determine if it was a horizontal or vertical line and clear that line
// Check adjacent gems to determine orientation
var isHorizontal = false;
var isVertical = false;
// Check horizontal neighbors
if (gem.gridX > 0 && grid[gem.gridX - 1][gem.gridY] && grid[gem.gridX - 1][gem.gridY].gemType === gem.gemType || gem.gridX < GRID_SIZE - 1 && grid[gem.gridX + 1][gem.gridY] && grid[gem.gridX + 1][gem.gridY].gemType === gem.gemType) {
isHorizontal = true;
}
// Check vertical neighbors
if (gem.gridY > 0 && grid[gem.gridX][gem.gridY - 1] && grid[gem.gridX][gem.gridY - 1].gemType === gem.gemType || gem.gridY < GRID_SIZE - 1 && grid[gem.gridX][gem.gridY + 1] && grid[gem.gridX][gem.gridY + 1].gemType === gem.gemType) {
isVertical = true;
}
if (isHorizontal && !isVertical) {
// Clear entire row
for (var x = 0; x < GRID_SIZE; x++) {
if (grid[x][gem.gridY] && grid[x][gem.gridY] !== gem) {
gemsToRemove.push(grid[x][gem.gridY]);
}
}
} else if (isVertical && !isHorizontal) {
// Clear entire column
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[gem.gridX][y] && grid[gem.gridX][y] !== gem) {
gemsToRemove.push(grid[gem.gridX][y]);
}
}
} else {
// Default to clearing row if unclear
for (var x = 0; x < GRID_SIZE; x++) {
if (grid[x][gem.gridY] && grid[x][gem.gridY] !== gem) {
gemsToRemove.push(grid[x][gem.gridY]);
}
}
}
}
// Remove the special gem itself
gemsToRemove.push(gem);
// Add explosion effect
tween(gem, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut
});
// Remove all targeted gems with crystal crack effects and staggered timing
for (var i = 0; i < gemsToRemove.length; i++) {
var gemToRemove = gemsToRemove[i];
// Track blue gem collection
if (gemToRemove.gemType === 'gem_blue') {
blueGemsCollected++;
blueGemsText.setText('Blue Gems: ' + blueGemsCollected + '/' + targetBlueGems);
}
// Track green gem collection
if (gemToRemove.gemType === 'gem_green') {
greenGemsCollected++;
greenGemsText.setText('Green Gems: ' + greenGemsCollected + '/' + targetGreenGems);
}
if (grid[gemToRemove.gridX] && grid[gemToRemove.gridX][gemToRemove.gridY]) {
grid[gemToRemove.gridX][gemToRemove.gridY] = null;
}
// Add crystal crack effect with delay for cascade visual
LK.setTimeout(function (targetGem) {
return function () {
// First stage - crystal stress effect
tween(targetGem, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0.9,
tint: 0xffffff
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
// Second stage - crack and shatter
tween(targetGem, {
scaleX: 0.1,
scaleY: 0.1,
alpha: 0,
rotation: Math.PI,
tint: 0xcccccc
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
if (targetGem.parent) {
targetGem.destroy();
}
}
});
}
});
};
}(gemToRemove), i * 50);
}
// Add score for special activation
var bonusScore = gemsToRemove.length * 20;
currentScore += bonusScore;
scoreText.setText('Score: ' + currentScore);
return true;
}
function showWinScreen() {
gameState = 'win';
// Hide game UI
scoreText.visible = false;
movesText.visible = false;
levelText.visible = false;
blueGemsText.visible = false;
greenGemsText.visible = false;
boardBg.visible = false;
// Create win screen background
var winBg = game.attachAsset('board_bg', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 1.3,
scaleY: 1.7
});
winBg.tint = 0x2c5422;
// Congratulations text
var winText = new Text2('LEVEL COMPLETED!', {
size: 100,
fill: '#ffffff'
});
winText.anchor.set(0.5, 0.5);
winText.x = 1024;
winText.y = 800;
game.addChild(winText);
// Score text
var finalScoreText = new Text2('Final Score: ' + currentScore, {
size: 80,
fill: '#ffaa00'
});
finalScoreText.anchor.set(0.5, 0.5);
finalScoreText.x = 1024;
finalScoreText.y = 950;
game.addChild(finalScoreText);
// Next Level button
var nextLevelButton = new Container();
var nextLevelBg = LK.getAsset('gem_green', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
var nextLevelText = new Text2('NEXT LEVEL', {
size: 50,
fill: '#ffffff'
});
nextLevelText.anchor.set(0.5, 0.5);
nextLevelButton.addChild(nextLevelBg);
nextLevelButton.addChild(nextLevelText);
nextLevelButton.x = 750;
nextLevelButton.y = 1150;
nextLevelButton.down = function (x, y, obj) {
// Clean up win screen
winBg.destroy();
winText.destroy();
finalScoreText.destroy();
nextLevelButton.destroy();
menuButton.destroy();
// Start next level
if (currentLevel < 10) {
startGame(currentLevel + 1);
} else {
showMenu();
}
};
// Menu button
var menuButton = new Container();
var menuBg = LK.getAsset('gem_blue', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
var menuText = new Text2('MENU', {
size: 50,
fill: '#ffffff'
});
menuText.anchor.set(0.5, 0.5);
menuButton.addChild(menuBg);
menuButton.addChild(menuText);
menuButton.x = 1298;
menuButton.y = 1150;
menuButton.down = function (x, y, obj) {
// Clean up win screen
winBg.destroy();
winText.destroy();
finalScoreText.destroy();
nextLevelButton.destroy();
menuButton.destroy();
// Return to menu
showMenu();
};
game.addChild(nextLevelButton);
game.addChild(menuButton);
// Hide next level button if it's the last level
if (currentLevel >= 10) {
nextLevelText.setText('ALL LEVELS\nCOMPLETED!');
nextLevelBg.tint = 0x666666;
}
}
function checkForCascade() {
var matches = findMatches();
if (matches.length > 0) {
LK.getSound('cascade').play();
processMatches(matches);
} else {
cascadeDepth = 0;
isProcessing = false;
// Check for level 3 special win condition (all blocked gems unblocked)
if (currentLevel === 3) {
var blockedGemsRemaining = 0;
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[x] && grid[x][y] && grid[x][y].isBlocked) {
blockedGemsRemaining++;
}
}
}
if (blockedGemsRemaining === 0) {
// Level 3 completed - all blocked gems unblocked!
var unlockedLevel = storage.unlockedLevel || 1;
if (currentLevel >= unlockedLevel && currentLevel < 10) {
storage.unlockedLevel = currentLevel + 1;
}
LK.showYouWin();
return;
}
// Check for game over in level 3 - if moves exhausted but blocks remain
if (movesLeft <= 0) {
LK.showGameOver();
return;
}
} else {
// Check for blue gem completion for other levels
if (blueGemsCollected >= targetBlueGems && targetBlueGems > 0) {
// Level completed - blue gems collected!
var unlockedLevel = storage.unlockedLevel || 1;
if (currentLevel >= unlockedLevel && currentLevel < 10) {
storage.unlockedLevel = currentLevel + 1;
}
showWinScreen();
return;
}
// Check for green gem completion
if (greenGemsCollected >= targetGreenGems && targetGreenGems > 0) {
// Level completed - green gems collected!
var unlockedLevel = storage.unlockedLevel || 1;
if (currentLevel >= unlockedLevel && currentLevel < 10) {
storage.unlockedLevel = currentLevel + 1;
}
showWinScreen();
return;
}
// Check for game over in other levels
if (movesLeft <= 0) {
LK.setTimeout(function () {
showMenu();
}, 1000);
}
}
}
}
// Start with menu instead of initializing grid directly
showMenu();
game.update = function () {
// Update gem animations
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[x] && grid[x][y] && grid[x][y].isAnimating) {
grid[x][y].isAnimating = false;
}
}
}
};
function activateSpecialCombo(gem1, gem2) {
var gemsToRemove = [];
var comboScore = 0;
// Add dramatic visual effect for combo
tween(gem1, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut
});
tween(gem2, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut
});
// Determine combo effect based on gem types
var type1 = gem1.specialType;
var type2 = gem2.specialType;
if ((type1 === 'striped_horizontal' || type1 === 'striped_vertical') && (type2 === 'striped_horizontal' || type2 === 'striped_vertical')) {
// Striped + Striped = Clear entire row AND column
for (var x = 0; x < GRID_SIZE; x++) {
if (grid[x][gem1.gridY] && grid[x][gem1.gridY] !== gem1 && grid[x][gem1.gridY] !== gem2) {
gemsToRemove.push(grid[x][gem1.gridY]);
}
}
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[gem1.gridX][y] && grid[gem1.gridX][y] !== gem1 && grid[gem1.gridX][y] !== gem2) {
gemsToRemove.push(grid[gem1.gridX][y]);
}
}
comboScore = 500;
} else if (type1 === 'bomb' && type2 === 'bomb') {
// Bomb + Bomb = 5x5 explosion
for (var dx = -2; dx <= 2; dx++) {
for (var dy = -2; dy <= 2; dy++) {
var targetX = gem1.gridX + dx;
var targetY = gem1.gridY + dy;
if (targetX >= 0 && targetX < GRID_SIZE && targetY >= 0 && targetY < GRID_SIZE) {
if (grid[targetX][targetY] && grid[targetX][targetY] !== gem1 && grid[targetX][targetY] !== gem2) {
gemsToRemove.push(grid[targetX][targetY]);
}
}
}
}
comboScore = 400;
} else if ((type1 === 'striped_horizontal' || type1 === 'striped_vertical') && type2 === 'bomb') {
// Striped + Bomb = 3 rows or 3 columns
if (type1 === 'striped_horizontal') {
for (var dy = -1; dy <= 1; dy++) {
for (var x = 0; x < GRID_SIZE; x++) {
var targetY = gem1.gridY + dy;
if (targetY >= 0 && targetY < GRID_SIZE) {
if (grid[x][targetY] && grid[x][targetY] !== gem1 && grid[x][targetY] !== gem2) {
gemsToRemove.push(grid[x][targetY]);
}
}
}
}
} else {
for (var dx = -1; dx <= 1; dx++) {
for (var y = 0; y < GRID_SIZE; y++) {
var targetX = gem1.gridX + dx;
if (targetX >= 0 && targetX < GRID_SIZE) {
if (grid[targetX][y] && grid[targetX][y] !== gem1 && grid[targetX][y] !== gem2) {
gemsToRemove.push(grid[targetX][y]);
}
}
}
}
}
comboScore = 350;
} else if (type1 === 'bomb' && (type2 === 'striped_horizontal' || type2 === 'striped_vertical')) {
// Bomb + Striped = 3 rows or 3 columns
if (type2 === 'striped_horizontal') {
for (var dy = -1; dy <= 1; dy++) {
for (var x = 0; x < GRID_SIZE; x++) {
var targetY = gem2.gridY + dy;
if (targetY >= 0 && targetY < GRID_SIZE) {
if (grid[x][targetY] && grid[x][targetY] !== gem1 && grid[x][targetY] !== gem2) {
gemsToRemove.push(grid[x][targetY]);
}
}
}
}
} else {
for (var dx = -1; dx <= 1; dx++) {
for (var y = 0; y < GRID_SIZE; y++) {
var targetX = gem2.gridX + dx;
if (targetX >= 0 && targetX < GRID_SIZE) {
if (grid[targetX][y] && grid[targetX][y] !== gem1 && grid[targetX][y] !== gem2) {
gemsToRemove.push(grid[targetX][y]);
}
}
}
}
}
comboScore = 350;
} else if (type1 === 'rainbow' || type2 === 'rainbow') {
// Rainbow + Any special = Transform all gems of one type to that special type
var otherGem = type1 === 'rainbow' ? gem2 : gem1;
var targetType = gemTypes[Math.floor(Math.random() * gemTypes.length)];
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[x][y] && grid[x][y].gemType === targetType && grid[x][y] !== gem1 && grid[x][y] !== gem2) {
// Transform to special type
grid[x][y].setSpecial(otherGem.specialType);
}
}
}
comboScore = 600;
} else if (type1 === 'target' || type2 === 'target') {
// Target + Any special = Clear all gems of 2 random colors
var color1 = gemTypes[Math.floor(Math.random() * gemTypes.length)];
var color2 = gemTypes[Math.floor(Math.random() * gemTypes.length)];
while (color2 === color1) {
color2 = gemTypes[Math.floor(Math.random() * gemTypes.length)];
}
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[x][y] && (grid[x][y].gemType === color1 || grid[x][y].gemType === color2) && grid[x][y] !== gem1 && grid[x][y] !== gem2) {
gemsToRemove.push(grid[x][y]);
}
}
}
comboScore = 450;
} else if (type1 === 'line_cleaner' || type2 === 'line_cleaner') {
// Line cleaner + Any special = Clear cross pattern (both row and column)
var targetGem = type1 === 'line_cleaner' ? gem1 : gem2;
// Clear entire row
for (var x = 0; x < GRID_SIZE; x++) {
if (grid[x][targetGem.gridY] && grid[x][targetGem.gridY] !== gem1 && grid[x][targetGem.gridY] !== gem2) {
gemsToRemove.push(grid[x][targetGem.gridY]);
}
}
// Clear entire column
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[targetGem.gridX][y] && grid[targetGem.gridX][y] !== gem1 && grid[targetGem.gridX][y] !== gem2) {
gemsToRemove.push(grid[targetGem.gridX][y]);
}
}
comboScore = 400;
}
// Remove the special gems themselves
gemsToRemove.push(gem1);
gemsToRemove.push(gem2);
// Remove all targeted gems with enhanced crystal crack effects
for (var i = 0; i < gemsToRemove.length; i++) {
var gemToRemove = gemsToRemove[i];
// Track blue gem collection
if (gemToRemove.gemType === 'gem_blue') {
blueGemsCollected++;
blueGemsText.setText('Blue Gems: ' + blueGemsCollected + '/' + targetBlueGems);
}
// Track green gem collection
if (gemToRemove.gemType === 'gem_green') {
greenGemsCollected++;
greenGemsText.setText('Green Gems: ' + greenGemsCollected + '/' + targetGreenGems);
}
if (grid[gemToRemove.gridX] && grid[gemToRemove.gridX][gemToRemove.gridY]) {
grid[gemToRemove.gridX][gemToRemove.gridY] = null;
}
// Enhanced crystal crack effect for combo - dramatic shattering
tween(gemToRemove, {
scaleX: 1.4,
scaleY: 1.4,
alpha: 1.0,
tint: 0xffffff
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(gemToRemove, {
scaleX: 0,
scaleY: 0,
alpha: 0,
rotation: Math.PI * 2,
tint: 0x888888
}, {
duration: 250,
easing: tween.easeIn,
onFinish: function onFinish() {
if (gemToRemove.parent) {
gemToRemove.destroy();
}
}
});
}
});
}
// Add bonus score for special combo
currentScore += comboScore;
scoreText.setText('Score: ' + currentScore);
// Flash screen to indicate powerful combo
LK.effects.flashScreen(0xffd700, 800);
}
Kristal gorunumlu rainbow bonus gem'i olmasi icin turuncu, mavi, turkuaz, yesil , pembe iceren ozel bir kristal png arka plan olmasin. In-Game asset. 2d. High contrast. No shadows
Kristal olarak bi bomba bonusu icin bir gem olustur
Bu renkte bir line clearer icin kristal bonus gem olustur
make wood for blocked gem. In-Game asset. 2d. High contrast. No shadows