User prompt
Go optimum for game
User prompt
Add levels if player beat first level target must be rise by 600 ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
You must fix the bugs
User prompt
Crystal must be slideable ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
Crystal Crush Blast
Initial prompt
Create a candy crush game with powerfull add ons
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
level: 1,
targetScore: 5000
});
/****
* Classes
****/
var Crystal = Container.expand(function (crystalType) {
var self = Container.call(this);
self.crystalType = crystalType;
self.gridX = 0;
self.gridY = 0;
self.isSelected = false;
self.isAnimating = false;
var assetName = 'crystal_' + crystalType;
if (crystalType === 'bomb') assetName = 'bomb_crystal';else if (crystalType === 'lightning') assetName = 'lightning_crystal';else if (crystalType === 'rainbow') assetName = 'rainbow_crystal';
var crystalGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.setSelected = function (selected) {
self.isSelected = selected;
if (selected) {
crystalGraphics.scaleX = 1.2;
crystalGraphics.scaleY = 1.2;
} else {
crystalGraphics.scaleX = 1.0;
crystalGraphics.scaleY = 1.0;
}
};
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = GRID_START_X + gridX * CELL_SIZE + CELL_SIZE / 2;
self.y = GRID_START_Y + gridY * CELL_SIZE + CELL_SIZE / 2;
};
self.animateToPosition = function (newX, newY, callback) {
self.isAnimating = true;
tween(self, {
x: newX,
y: newY
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
self.isAnimating = false;
if (callback) callback();
}
});
};
self.down = function (x, y, obj) {
if (self.isAnimating || gameState !== 'playing') return;
handleCrystalClick(self);
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
var GRID_SIZE = 8;
var CELL_SIZE = 240;
var GRID_START_X = (2048 - GRID_SIZE * CELL_SIZE) / 2;
var GRID_START_Y = (2732 - GRID_SIZE * CELL_SIZE) / 2;
var crystalTypes = ['red', 'blue', 'green', 'yellow', 'purple', 'orange'];
var grid = [];
var selectedCrystal = null;
var gameState = 'playing';
var score = 0;
var movesLeft = 30;
var currentLevel = storage.level || 1;
var targetScore = storage.targetScore || 5000;
var frameCount = 0;
var performanceMode = false;
// Performance optimization flags
var ENABLE_PARTICLE_EFFECTS = true;
var ENABLE_SMOOTH_ANIMATIONS = true;
// UI Elements
var scoreText = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreText.anchor.set(0, 0);
scoreText.x = 50;
scoreText.y = 100;
game.addChild(scoreText);
var movesText = new Text2('Moves: 30', {
size: 80,
fill: 0xFFFFFF
});
movesText.anchor.set(1, 0);
movesText.x = 1998;
movesText.y = 100;
game.addChild(movesText);
var targetText = new Text2('Target: ' + targetScore, {
size: 60,
fill: 0xFFFF00
});
targetText.anchor.set(0.5, 0);
targetText.x = 1024;
targetText.y = 50;
game.addChild(targetText);
var levelText = new Text2('Level: ' + currentLevel, {
size: 60,
fill: 0x00FF00
});
levelText.anchor.set(0.5, 0);
levelText.x = 1024;
levelText.y = 120;
game.addChild(levelText);
// Initialize grid background
function createGridBackground() {
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
var cell = LK.getAsset('grid_cell', {
anchorX: 0.5,
anchorY: 0.5
});
cell.x = GRID_START_X + x * CELL_SIZE + CELL_SIZE / 2;
cell.y = GRID_START_Y + y * CELL_SIZE + CELL_SIZE / 2;
cell.alpha = 0.3;
game.addChild(cell);
}
}
}
// Initialize game grid
function initializeGrid() {
grid = [];
for (var x = 0; x < GRID_SIZE; x++) {
grid[x] = [];
for (var y = 0; y < GRID_SIZE; y++) {
var crystalType = crystalTypes[Math.floor(Math.random() * crystalTypes.length)];
var crystal = new Crystal(crystalType);
crystal.setGridPosition(x, y);
grid[x][y] = crystal;
game.addChild(crystal);
}
}
// Remove initial matches
removeInitialMatches();
}
function removeInitialMatches() {
var hasMatches = true;
while (hasMatches) {
hasMatches = false;
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (checkMatchAt(x, y).length >= 3) {
hasMatches = true;
var newType = crystalTypes[Math.floor(Math.random() * crystalTypes.length)];
// Remove old crystal and create new one with different type
var oldCrystal = grid[x][y];
game.removeChild(oldCrystal);
var newCrystal = new Crystal(newType);
newCrystal.setGridPosition(x, y);
grid[x][y] = newCrystal;
game.addChild(newCrystal);
}
}
}
}
}
function handleCrystalClick(crystal) {
if (!selectedCrystal) {
selectedCrystal = crystal;
crystal.setSelected(true);
} else if (selectedCrystal === crystal) {
selectedCrystal.setSelected(false);
selectedCrystal = null;
} else {
// Check if crystals are adjacent
var dx = Math.abs(selectedCrystal.gridX - crystal.gridX);
var dy = Math.abs(selectedCrystal.gridY - crystal.gridY);
if (dx === 1 && dy === 0 || dx === 0 && dy === 1) {
swapCrystals(selectedCrystal, crystal);
}
selectedCrystal.setSelected(false);
selectedCrystal = null;
}
}
function swapCrystals(crystal1, crystal2) {
if (movesLeft <= 0) return;
// Store original positions
var crystal1OriginalX = crystal1.x;
var crystal1OriginalY = crystal1.y;
var crystal2OriginalX = crystal2.x;
var crystal2OriginalY = crystal2.y;
// Store original grid positions
var crystal1GridX = crystal1.gridX;
var crystal1GridY = crystal1.gridY;
var crystal2GridX = crystal2.gridX;
var crystal2GridY = crystal2.gridY;
// Temporarily swap grid positions
crystal1.gridX = crystal2GridX;
crystal1.gridY = crystal2GridY;
crystal2.gridX = crystal1GridX;
crystal2.gridY = crystal1GridY;
grid[crystal1GridX][crystal1GridY] = crystal2;
grid[crystal2GridX][crystal2GridY] = crystal1;
// Animate crystals sliding to each other's positions
var animationsCompleted = 0;
crystal1.animateToPosition(crystal2OriginalX, crystal2OriginalY, function () {
animationsCompleted++;
if (animationsCompleted === 2) {
checkSwapResult();
}
});
crystal2.animateToPosition(crystal1OriginalX, crystal1OriginalY, function () {
animationsCompleted++;
if (animationsCompleted === 2) {
checkSwapResult();
}
});
function checkSwapResult() {
// Check for matches after swap
var matches1 = checkMatchAt(crystal1.gridX, crystal1.gridY);
var matches2 = checkMatchAt(crystal2.gridX, crystal2.gridY);
if (matches1.length >= 3 || matches2.length >= 3) {
movesLeft--;
updateUI();
processMatches();
LK.getSound('match').play();
} else {
// Swap back if no matches - animate back to original positions
crystal1.gridX = crystal1GridX;
crystal1.gridY = crystal1GridY;
crystal2.gridX = crystal2GridX;
crystal2.gridY = crystal2GridY;
grid[crystal1GridX][crystal1GridY] = crystal1;
grid[crystal2GridX][crystal2GridY] = crystal2;
crystal1.animateToPosition(crystal1OriginalX, crystal1OriginalY, null);
crystal2.animateToPosition(crystal2OriginalX, crystal2OriginalY, null);
}
}
}
function checkMatchAt(x, y) {
if (x < 0 || x >= GRID_SIZE || y < 0 || y >= GRID_SIZE || !grid[x] || !grid[x][y]) return [];
var crystal = grid[x][y];
if (!crystal || crystal.isAnimating) return []; // Skip animating crystals
var matches = [crystal];
var type = crystal.crystalType;
// Check horizontal matches
var left = x - 1;
while (left >= 0 && grid[left][y] && grid[left][y].crystalType === type) {
matches.push(grid[left][y]);
left--;
}
var right = x + 1;
while (right < GRID_SIZE && grid[right][y] && grid[right][y].crystalType === type) {
matches.push(grid[right][y]);
right++;
}
if (matches.length >= 3) return matches;
// Check vertical matches
matches = [crystal];
var up = y - 1;
while (up >= 0 && grid[x][up] && grid[x][up].crystalType === type) {
matches.push(grid[x][up]);
up--;
}
var down = y + 1;
while (down < GRID_SIZE && grid[x][down] && grid[x][down].crystalType === type) {
matches.push(grid[x][down]);
down++;
}
return matches.length >= 3 ? matches : [];
}
function processMatches() {
var allMatches = [];
var matchedCrystals = {};
// Find all matches - optimized with early detection
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (!grid[x] || !grid[x][y]) continue;
var crystalKey = x + '_' + y;
if (matchedCrystals[crystalKey]) continue; // Skip already processed crystals
var matches = checkMatchAt(x, y);
if (matches.length >= 3) {
for (var i = 0; i < matches.length; i++) {
var crystal = matches[i];
var key = crystal.gridX + '_' + crystal.gridY;
if (!matchedCrystals[key]) {
allMatches.push(crystal);
matchedCrystals[key] = true;
}
}
}
}
}
if (allMatches.length > 0) {
// Calculate score with combo bonus
var baseScore = allMatches.length * 100;
var comboBonus = allMatches.length >= 5 ? 500 : allMatches.length >= 4 ? 200 : 0;
score += baseScore + comboBonus;
// Visual feedback for large matches
if (allMatches.length >= 4) {
LK.effects.flashScreen(0xFFD700, 300);
}
// Remove matched crystals with fade effect
for (var i = 0; i < allMatches.length; i++) {
var crystal = allMatches[i];
tween(crystal, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 200,
onFinish: function onFinish() {
game.removeChild(crystal);
}
});
grid[crystal.gridX][crystal.gridY] = null;
}
// Drop crystals down
dropCrystals();
updateUI();
checkWinCondition();
}
}
function dropCrystals() {
var animationsInProgress = 0;
var animationsCompleted = 0;
var animationCallback = function animationCallback() {
animationsCompleted++;
if (animationsCompleted === animationsInProgress) {
checkForMatches();
}
};
for (var x = 0; x < GRID_SIZE; x++) {
var writeIndex = GRID_SIZE - 1;
// Move existing crystals down
for (var y = GRID_SIZE - 1; y >= 0; y--) {
if (grid[x][y] !== null) {
if (y !== writeIndex) {
grid[x][writeIndex] = grid[x][y];
grid[x][y] = null;
grid[x][writeIndex].gridY = writeIndex;
// Animate crystal falling to new position
var targetX = GRID_START_X + x * CELL_SIZE + CELL_SIZE / 2;
var targetY = GRID_START_Y + writeIndex * CELL_SIZE + CELL_SIZE / 2;
animationsInProgress++;
grid[x][writeIndex].animateToPosition(targetX, targetY, animationCallback);
}
writeIndex--;
}
}
// Fill empty spaces with new crystals
for (var emptyY = writeIndex; emptyY >= 0; emptyY--) {
var crystalType = crystalTypes[Math.floor(Math.random() * crystalTypes.length)];
var newCrystal = new Crystal(crystalType);
// Start new crystals above the grid and animate them falling down
newCrystal.x = GRID_START_X + x * CELL_SIZE + CELL_SIZE / 2;
newCrystal.y = GRID_START_Y - (writeIndex - emptyY + 1) * CELL_SIZE + CELL_SIZE / 2;
newCrystal.gridX = x;
newCrystal.gridY = emptyY;
grid[x][emptyY] = newCrystal;
game.addChild(newCrystal);
var targetX = GRID_START_X + x * CELL_SIZE + CELL_SIZE / 2;
var targetY = GRID_START_Y + emptyY * CELL_SIZE + CELL_SIZE / 2;
animationsInProgress++;
newCrystal.animateToPosition(targetX, targetY, animationCallback);
}
}
// If no animations are in progress, check immediately
if (animationsInProgress === 0) {
LK.setTimeout(function () {
processMatches();
}, 100);
}
}
function checkForMatches() {
// Check for new matches after dropping
LK.setTimeout(function () {
processMatches();
}, 100);
}
var lastScore = -1;
var lastMoves = -1;
var lastTarget = -1;
var lastLevel = -1;
function updateUI() {
if (score !== lastScore) {
scoreText.setText('Score: ' + score);
lastScore = score;
}
if (movesLeft !== lastMoves) {
movesText.setText('Moves: ' + movesLeft);
lastMoves = movesLeft;
}
if (targetScore !== lastTarget) {
targetText.setText('Target: ' + targetScore);
lastTarget = targetScore;
}
if (currentLevel !== lastLevel) {
levelText.setText('Level: ' + currentLevel);
lastLevel = currentLevel;
}
}
function checkWinCondition() {
if (score >= targetScore) {
// Level completed - advance to next level
currentLevel++;
targetScore += 600;
// Save progress to storage
storage.level = currentLevel;
storage.targetScore = targetScore;
// Reset for next level
score = 0;
movesLeft = 30;
gameState = 'playing';
// Clear and reinitialize the grid
clearGrid();
initializeGrid();
updateUI();
// Show level completion message
LK.effects.flashScreen(0x00FF00, 500);
} else if (movesLeft <= 0) {
gameState = 'lost';
LK.showGameOver();
}
}
function clearGrid() {
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[x] && grid[x][y]) {
game.removeChild(grid[x][y]);
grid[x][y] = null;
}
}
}
}
// Initialize game
createGridBackground();
initializeGrid();
updateUI();
game.update = function () {
// Game loop - matches are processed through event system
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
level: 1,
targetScore: 5000
});
/****
* Classes
****/
var Crystal = Container.expand(function (crystalType) {
var self = Container.call(this);
self.crystalType = crystalType;
self.gridX = 0;
self.gridY = 0;
self.isSelected = false;
self.isAnimating = false;
var assetName = 'crystal_' + crystalType;
if (crystalType === 'bomb') assetName = 'bomb_crystal';else if (crystalType === 'lightning') assetName = 'lightning_crystal';else if (crystalType === 'rainbow') assetName = 'rainbow_crystal';
var crystalGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.setSelected = function (selected) {
self.isSelected = selected;
if (selected) {
crystalGraphics.scaleX = 1.2;
crystalGraphics.scaleY = 1.2;
} else {
crystalGraphics.scaleX = 1.0;
crystalGraphics.scaleY = 1.0;
}
};
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = GRID_START_X + gridX * CELL_SIZE + CELL_SIZE / 2;
self.y = GRID_START_Y + gridY * CELL_SIZE + CELL_SIZE / 2;
};
self.animateToPosition = function (newX, newY, callback) {
self.isAnimating = true;
tween(self, {
x: newX,
y: newY
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
self.isAnimating = false;
if (callback) callback();
}
});
};
self.down = function (x, y, obj) {
if (self.isAnimating || gameState !== 'playing') return;
handleCrystalClick(self);
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
var GRID_SIZE = 8;
var CELL_SIZE = 240;
var GRID_START_X = (2048 - GRID_SIZE * CELL_SIZE) / 2;
var GRID_START_Y = (2732 - GRID_SIZE * CELL_SIZE) / 2;
var crystalTypes = ['red', 'blue', 'green', 'yellow', 'purple', 'orange'];
var grid = [];
var selectedCrystal = null;
var gameState = 'playing';
var score = 0;
var movesLeft = 30;
var currentLevel = storage.level || 1;
var targetScore = storage.targetScore || 5000;
var frameCount = 0;
var performanceMode = false;
// Performance optimization flags
var ENABLE_PARTICLE_EFFECTS = true;
var ENABLE_SMOOTH_ANIMATIONS = true;
// UI Elements
var scoreText = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreText.anchor.set(0, 0);
scoreText.x = 50;
scoreText.y = 100;
game.addChild(scoreText);
var movesText = new Text2('Moves: 30', {
size: 80,
fill: 0xFFFFFF
});
movesText.anchor.set(1, 0);
movesText.x = 1998;
movesText.y = 100;
game.addChild(movesText);
var targetText = new Text2('Target: ' + targetScore, {
size: 60,
fill: 0xFFFF00
});
targetText.anchor.set(0.5, 0);
targetText.x = 1024;
targetText.y = 50;
game.addChild(targetText);
var levelText = new Text2('Level: ' + currentLevel, {
size: 60,
fill: 0x00FF00
});
levelText.anchor.set(0.5, 0);
levelText.x = 1024;
levelText.y = 120;
game.addChild(levelText);
// Initialize grid background
function createGridBackground() {
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
var cell = LK.getAsset('grid_cell', {
anchorX: 0.5,
anchorY: 0.5
});
cell.x = GRID_START_X + x * CELL_SIZE + CELL_SIZE / 2;
cell.y = GRID_START_Y + y * CELL_SIZE + CELL_SIZE / 2;
cell.alpha = 0.3;
game.addChild(cell);
}
}
}
// Initialize game grid
function initializeGrid() {
grid = [];
for (var x = 0; x < GRID_SIZE; x++) {
grid[x] = [];
for (var y = 0; y < GRID_SIZE; y++) {
var crystalType = crystalTypes[Math.floor(Math.random() * crystalTypes.length)];
var crystal = new Crystal(crystalType);
crystal.setGridPosition(x, y);
grid[x][y] = crystal;
game.addChild(crystal);
}
}
// Remove initial matches
removeInitialMatches();
}
function removeInitialMatches() {
var hasMatches = true;
while (hasMatches) {
hasMatches = false;
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (checkMatchAt(x, y).length >= 3) {
hasMatches = true;
var newType = crystalTypes[Math.floor(Math.random() * crystalTypes.length)];
// Remove old crystal and create new one with different type
var oldCrystal = grid[x][y];
game.removeChild(oldCrystal);
var newCrystal = new Crystal(newType);
newCrystal.setGridPosition(x, y);
grid[x][y] = newCrystal;
game.addChild(newCrystal);
}
}
}
}
}
function handleCrystalClick(crystal) {
if (!selectedCrystal) {
selectedCrystal = crystal;
crystal.setSelected(true);
} else if (selectedCrystal === crystal) {
selectedCrystal.setSelected(false);
selectedCrystal = null;
} else {
// Check if crystals are adjacent
var dx = Math.abs(selectedCrystal.gridX - crystal.gridX);
var dy = Math.abs(selectedCrystal.gridY - crystal.gridY);
if (dx === 1 && dy === 0 || dx === 0 && dy === 1) {
swapCrystals(selectedCrystal, crystal);
}
selectedCrystal.setSelected(false);
selectedCrystal = null;
}
}
function swapCrystals(crystal1, crystal2) {
if (movesLeft <= 0) return;
// Store original positions
var crystal1OriginalX = crystal1.x;
var crystal1OriginalY = crystal1.y;
var crystal2OriginalX = crystal2.x;
var crystal2OriginalY = crystal2.y;
// Store original grid positions
var crystal1GridX = crystal1.gridX;
var crystal1GridY = crystal1.gridY;
var crystal2GridX = crystal2.gridX;
var crystal2GridY = crystal2.gridY;
// Temporarily swap grid positions
crystal1.gridX = crystal2GridX;
crystal1.gridY = crystal2GridY;
crystal2.gridX = crystal1GridX;
crystal2.gridY = crystal1GridY;
grid[crystal1GridX][crystal1GridY] = crystal2;
grid[crystal2GridX][crystal2GridY] = crystal1;
// Animate crystals sliding to each other's positions
var animationsCompleted = 0;
crystal1.animateToPosition(crystal2OriginalX, crystal2OriginalY, function () {
animationsCompleted++;
if (animationsCompleted === 2) {
checkSwapResult();
}
});
crystal2.animateToPosition(crystal1OriginalX, crystal1OriginalY, function () {
animationsCompleted++;
if (animationsCompleted === 2) {
checkSwapResult();
}
});
function checkSwapResult() {
// Check for matches after swap
var matches1 = checkMatchAt(crystal1.gridX, crystal1.gridY);
var matches2 = checkMatchAt(crystal2.gridX, crystal2.gridY);
if (matches1.length >= 3 || matches2.length >= 3) {
movesLeft--;
updateUI();
processMatches();
LK.getSound('match').play();
} else {
// Swap back if no matches - animate back to original positions
crystal1.gridX = crystal1GridX;
crystal1.gridY = crystal1GridY;
crystal2.gridX = crystal2GridX;
crystal2.gridY = crystal2GridY;
grid[crystal1GridX][crystal1GridY] = crystal1;
grid[crystal2GridX][crystal2GridY] = crystal2;
crystal1.animateToPosition(crystal1OriginalX, crystal1OriginalY, null);
crystal2.animateToPosition(crystal2OriginalX, crystal2OriginalY, null);
}
}
}
function checkMatchAt(x, y) {
if (x < 0 || x >= GRID_SIZE || y < 0 || y >= GRID_SIZE || !grid[x] || !grid[x][y]) return [];
var crystal = grid[x][y];
if (!crystal || crystal.isAnimating) return []; // Skip animating crystals
var matches = [crystal];
var type = crystal.crystalType;
// Check horizontal matches
var left = x - 1;
while (left >= 0 && grid[left][y] && grid[left][y].crystalType === type) {
matches.push(grid[left][y]);
left--;
}
var right = x + 1;
while (right < GRID_SIZE && grid[right][y] && grid[right][y].crystalType === type) {
matches.push(grid[right][y]);
right++;
}
if (matches.length >= 3) return matches;
// Check vertical matches
matches = [crystal];
var up = y - 1;
while (up >= 0 && grid[x][up] && grid[x][up].crystalType === type) {
matches.push(grid[x][up]);
up--;
}
var down = y + 1;
while (down < GRID_SIZE && grid[x][down] && grid[x][down].crystalType === type) {
matches.push(grid[x][down]);
down++;
}
return matches.length >= 3 ? matches : [];
}
function processMatches() {
var allMatches = [];
var matchedCrystals = {};
// Find all matches - optimized with early detection
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (!grid[x] || !grid[x][y]) continue;
var crystalKey = x + '_' + y;
if (matchedCrystals[crystalKey]) continue; // Skip already processed crystals
var matches = checkMatchAt(x, y);
if (matches.length >= 3) {
for (var i = 0; i < matches.length; i++) {
var crystal = matches[i];
var key = crystal.gridX + '_' + crystal.gridY;
if (!matchedCrystals[key]) {
allMatches.push(crystal);
matchedCrystals[key] = true;
}
}
}
}
}
if (allMatches.length > 0) {
// Calculate score with combo bonus
var baseScore = allMatches.length * 100;
var comboBonus = allMatches.length >= 5 ? 500 : allMatches.length >= 4 ? 200 : 0;
score += baseScore + comboBonus;
// Visual feedback for large matches
if (allMatches.length >= 4) {
LK.effects.flashScreen(0xFFD700, 300);
}
// Remove matched crystals with fade effect
for (var i = 0; i < allMatches.length; i++) {
var crystal = allMatches[i];
tween(crystal, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 200,
onFinish: function onFinish() {
game.removeChild(crystal);
}
});
grid[crystal.gridX][crystal.gridY] = null;
}
// Drop crystals down
dropCrystals();
updateUI();
checkWinCondition();
}
}
function dropCrystals() {
var animationsInProgress = 0;
var animationsCompleted = 0;
var animationCallback = function animationCallback() {
animationsCompleted++;
if (animationsCompleted === animationsInProgress) {
checkForMatches();
}
};
for (var x = 0; x < GRID_SIZE; x++) {
var writeIndex = GRID_SIZE - 1;
// Move existing crystals down
for (var y = GRID_SIZE - 1; y >= 0; y--) {
if (grid[x][y] !== null) {
if (y !== writeIndex) {
grid[x][writeIndex] = grid[x][y];
grid[x][y] = null;
grid[x][writeIndex].gridY = writeIndex;
// Animate crystal falling to new position
var targetX = GRID_START_X + x * CELL_SIZE + CELL_SIZE / 2;
var targetY = GRID_START_Y + writeIndex * CELL_SIZE + CELL_SIZE / 2;
animationsInProgress++;
grid[x][writeIndex].animateToPosition(targetX, targetY, animationCallback);
}
writeIndex--;
}
}
// Fill empty spaces with new crystals
for (var emptyY = writeIndex; emptyY >= 0; emptyY--) {
var crystalType = crystalTypes[Math.floor(Math.random() * crystalTypes.length)];
var newCrystal = new Crystal(crystalType);
// Start new crystals above the grid and animate them falling down
newCrystal.x = GRID_START_X + x * CELL_SIZE + CELL_SIZE / 2;
newCrystal.y = GRID_START_Y - (writeIndex - emptyY + 1) * CELL_SIZE + CELL_SIZE / 2;
newCrystal.gridX = x;
newCrystal.gridY = emptyY;
grid[x][emptyY] = newCrystal;
game.addChild(newCrystal);
var targetX = GRID_START_X + x * CELL_SIZE + CELL_SIZE / 2;
var targetY = GRID_START_Y + emptyY * CELL_SIZE + CELL_SIZE / 2;
animationsInProgress++;
newCrystal.animateToPosition(targetX, targetY, animationCallback);
}
}
// If no animations are in progress, check immediately
if (animationsInProgress === 0) {
LK.setTimeout(function () {
processMatches();
}, 100);
}
}
function checkForMatches() {
// Check for new matches after dropping
LK.setTimeout(function () {
processMatches();
}, 100);
}
var lastScore = -1;
var lastMoves = -1;
var lastTarget = -1;
var lastLevel = -1;
function updateUI() {
if (score !== lastScore) {
scoreText.setText('Score: ' + score);
lastScore = score;
}
if (movesLeft !== lastMoves) {
movesText.setText('Moves: ' + movesLeft);
lastMoves = movesLeft;
}
if (targetScore !== lastTarget) {
targetText.setText('Target: ' + targetScore);
lastTarget = targetScore;
}
if (currentLevel !== lastLevel) {
levelText.setText('Level: ' + currentLevel);
lastLevel = currentLevel;
}
}
function checkWinCondition() {
if (score >= targetScore) {
// Level completed - advance to next level
currentLevel++;
targetScore += 600;
// Save progress to storage
storage.level = currentLevel;
storage.targetScore = targetScore;
// Reset for next level
score = 0;
movesLeft = 30;
gameState = 'playing';
// Clear and reinitialize the grid
clearGrid();
initializeGrid();
updateUI();
// Show level completion message
LK.effects.flashScreen(0x00FF00, 500);
} else if (movesLeft <= 0) {
gameState = 'lost';
LK.showGameOver();
}
}
function clearGrid() {
for (var x = 0; x < GRID_SIZE; x++) {
for (var y = 0; y < GRID_SIZE; y++) {
if (grid[x] && grid[x][y]) {
game.removeChild(grid[x][y]);
grid[x][y] = null;
}
}
}
}
// Initialize game
createGridBackground();
initializeGrid();
updateUI();
game.update = function () {
// Game loop - matches are processed through event system
};