/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Flower = Container.expand(function (type) {
var self = Container.call(this);
self.flowerType = type;
self.gridX = 0;
self.gridY = 0;
self.destroyed = false;
var flowerGraphics = self.attachAsset(type, {
anchorX: 0.5,
anchorY: 0.5
});
// Add subtle glow effect
flowerGraphics.alpha = 0.9;
self.down = function (x, y, obj) {
if (!isSwapping && !isMatching) {
game.selectFlower(self);
}
};
self.animateMatch = function (callback) {
if (self.isAnimating) {
if (callback) callback();
return;
}
if (!flowerGraphics || !self.parent || self.destroyed) {
if (callback) callback();
return;
}
self.isAnimating = true;
var particlesCompleted = 0;
var totalParticles = Math.min(3, LK.getScore() > 5000 ? 2 : 3); // Reduce particles at high scores
var flowerAnimationDone = false;
var callbackCalled = false;
function checkAllComplete() {
if (particlesCompleted >= totalParticles && flowerAnimationDone && callback && !callbackCalled) {
callbackCalled = true;
self.isAnimating = false;
// Use timeout to prevent stack overflow during heavy explosions
LK.setTimeout(function () {
try {
callback();
} catch (e) {
console.log('Animation callback error:', e);
}
}, 16); // Faster callback timing
}
}
// Create optimized particle explosion effect with performance scaling
for (var i = 0; i < totalParticles; i++) {
try {
var particle = self.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8 + Math.random() * 0.3,
scaleY: 0.8 + Math.random() * 0.3,
alpha: 0.8
});
var angle = i / totalParticles * Math.PI * 2 + Math.random() * 0.2;
var distance = 60 + Math.random() * 40; // Reduced distance for performance
var targetX = Math.cos(angle) * distance;
var targetY = Math.sin(angle) * distance;
// Simplified initial motion
particle.x = Math.cos(angle) * 8;
particle.y = Math.sin(angle) * 8;
tween(particle, {
x: targetX,
y: targetY,
scaleX: 0.1,
scaleY: 0.1,
alpha: 0,
rotation: Math.random() * Math.PI
}, {
duration: LK.getScore() > 5000 ? 250 : 300,
// Faster animations at high scores
easing: tween.easeOut,
onFinish: function onFinish() {
if (particle && particle.destroy) {
particle.destroy();
}
particlesCompleted++;
checkAllComplete();
}
});
} catch (e) {
particlesCompleted++;
checkAllComplete();
}
}
// Add flower scaling and fade animation with optimized timing
try {
// Stop any existing tweens
tween.stop(flowerGraphics);
tween(flowerGraphics, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0,
rotation: Math.random() * Math.PI * 0.1
}, {
duration: LK.getScore() > 5000 ? 200 : 250,
easing: tween.easeOut,
onFinish: function onFinish() {
self.visible = false;
flowerAnimationDone = true;
checkAllComplete();
}
});
} catch (e) {
self.visible = false;
flowerAnimationDone = true;
checkAllComplete();
}
};
self.animateFall = function (targetY, callback) {
// Prevent multiple fall animations on same flower
if (self.isFalling || self.destroyed || !self.parent) {
if (callback) {
LK.setTimeout(callback, 8); // Faster callback for performance
}
return;
}
self.isFalling = true;
// Calculate fall distance for dynamic duration with performance scaling
var fallDistance = Math.abs(targetY - self.y);
var baseDuration = LK.getScore() > 8000 ? 80 : LK.getScore() > 5000 ? 100 : 120;
var dynamicDuration = Math.min(baseDuration + fallDistance / 600 * 40, LK.getScore() > 5000 ? 200 : 300);
// Stop all existing tweens to prevent conflicts
tween.stop(self);
// Ensure flower is in proper state
self.scaleX = 1.0;
self.scaleY = 1.0;
self.rotation = 0;
// Optimized fall animation with performance-based easing
tween(self, {
y: targetY
}, {
duration: dynamicDuration,
easing: LK.getScore() > 5000 ? tween.linear : tween.easeOut,
onFinish: function onFinish() {
self.isFalling = false;
if (callback) {
// Use faster setTimeout to prevent stack overflow while maintaining performance
LK.setTimeout(function () {
try {
callback();
} catch (e) {
console.log('Fall callback error:', e);
}
}, 8);
}
}
});
};
// Override destroy to set destroyed flag
var originalDestroy = self.destroy;
self.destroy = function () {
self.destroyed = true;
self.isFalling = false;
self.isAnimating = false;
tween.stop(self);
if (originalDestroy) originalDestroy.call(self);
};
return self;
});
var ShimmerFlower = Flower.expand(function (type) {
var self = Flower.call(this, type);
self.isShimmer = true;
// Add shimmer particles around the flower
self.shimmerParticles = [];
for (var i = 0; i < 6; i++) {
var shimmer = self.attachAsset('shimmerEffect', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
self.shimmerParticles.push(shimmer);
}
// Animate shimmer effect continuously
self.animateShimmer = function () {
for (var i = 0; i < self.shimmerParticles.length; i++) {
var particle = self.shimmerParticles[i];
var angle = i / self.shimmerParticles.length * Math.PI * 2;
var radius = 55 + Math.sin(LK.ticks * 0.05 + i) * 8;
particle.x = Math.cos(angle + LK.ticks * 0.04) * radius;
particle.y = Math.sin(angle + LK.ticks * 0.04) * radius;
particle.alpha = 0.7 + Math.sin(LK.ticks * 0.08 + i) * 0.2;
particle.scaleX = 0.9 + Math.sin(LK.ticks * 0.06 + i) * 0.15;
particle.scaleY = 0.9 + Math.sin(LK.ticks * 0.06 + i) * 0.15;
}
};
self.update = function () {
self.animateShimmer();
};
self.activateShimmerEffect = function () {
if (!grid || !grid.flowers || !self.parent) return;
// Shimmer Effect: Explodes in a cross pattern (self + adjacent flowers)
try {
var onExplosionComplete = function onExplosionComplete() {
explosionsCompleted++;
if (explosionsCompleted >= totalExplosions) {
var newScore = LK.getScore() + flowersToExplode.length * 25;
LK.setScore(newScore);
if (scoreTxt && scoreTxt.setText) scoreTxt.setText('Score: ' + newScore);
// Optimized high score update with error handling
if (game && game.updateHighScore) game.updateHighScore(newScore);
LK.setTimeout(function () {
if (grid && grid.dropFlowers) {
grid.dropFlowers(function () {
if (game && game.checkForMatches) game.checkForMatches();
});
}
}, 200);
}
}; // Explode all flowers in the list
// Create special shimmer explosion effect on self first
self.createShimmerExplosion();
var flowersToExplode = [];
// Add self to explosion list
flowersToExplode.push({
x: self.gridX,
y: self.gridY
});
// Add adjacent flowers (up, down, left, right)
var adjacentPositions = [{
x: self.gridX,
y: self.gridY - 1
},
// up
{
x: self.gridX,
y: self.gridY + 1
},
// down
{
x: self.gridX - 1,
y: self.gridY
},
// left
{
x: self.gridX + 1,
y: self.gridY
} // right
];
for (var i = 0; i < adjacentPositions.length; i++) {
var pos = adjacentPositions[i];
if (pos.x >= 0 && pos.x < grid.gridSize && pos.y >= 0 && pos.y < grid.gridSize && grid.flowers[pos.y] && grid.flowers[pos.y][pos.x] && grid.flowers[pos.y][pos.x] !== self) {
flowersToExplode.push(pos);
}
}
// Explode all flowers in the list with smooth timing
var explosionsCompleted = 0;
var totalExplosions = flowersToExplode.length;
for (var i = 0; i < flowersToExplode.length; i++) {
var pos = flowersToExplode[i];
if (pos.y >= 0 && pos.y < grid.gridSize && pos.x >= 0 && pos.x < grid.gridSize && grid.flowers[pos.y] && grid.flowers[pos.y][pos.x]) {
var flower = grid.flowers[pos.y][pos.x];
if (flower && flower.animateMatch) {
flower.animateMatch(onExplosionComplete);
grid.flowers[pos.y][pos.x] = null;
} else {
onExplosionComplete();
}
} else {
onExplosionComplete();
}
}
} catch (e) {
console.log('Shimmer effect error:', e);
}
};
self.createShimmerExplosion = function () {
// Create optimized shimmer explosion with performance scaling
try {
// Reduce particle count based on score for performance
var particleCount = LK.getScore() > 8000 ? 4 : LK.getScore() > 5000 ? 6 : 8;
// Create shimmer burst effect
for (var i = 0; i < particleCount; i++) {
var shimmerParticle = self.attachAsset('shimmerEffect', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0 + Math.random() * 0.4,
scaleY: 1.0 + Math.random() * 0.4,
alpha: 0.9
});
var angle = i / particleCount * Math.PI * 2;
var distance = 100 + Math.random() * 50;
var targetX = Math.cos(angle) * distance;
var targetY = Math.sin(angle) * distance;
tween(shimmerParticle, {
x: targetX,
y: targetY,
scaleX: 0.05,
scaleY: 0.05,
alpha: 0,
rotation: Math.random() * Math.PI * 2
}, {
duration: LK.getScore() > 5000 ? 300 : 400,
easing: tween.easeOut,
onFinish: function onFinish() {
if (shimmerParticle && shimmerParticle.destroy) shimmerParticle.destroy();
}
});
}
} catch (e) {
console.log('Shimmer explosion creation error:', e);
}
};
self.explode = function () {
// Create optimized explosion effect
try {
for (var i = 0; i < 12; i++) {
var particle = self.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5 + Math.random() * 1.0,
scaleY: 1.5 + Math.random() * 1.0,
alpha: 1.0
});
var angle = i / 12 * Math.PI * 2 + Math.random() * 0.5;
var distance = 150 + Math.random() * 100;
var targetX = Math.cos(angle) * distance;
var targetY = Math.sin(angle) * distance;
tween(particle, {
x: targetX,
y: targetY,
scaleX: 0.1,
scaleY: 0.1,
alpha: 0,
rotation: Math.random() * Math.PI * 6
}, {
duration: 600 + Math.random() * 300,
easing: tween.easeOut,
onFinish: function onFinish() {
if (particle && particle.destroy) particle.destroy();
}
});
}
} catch (e) {
console.log('Shimmer explosion error:', e);
}
};
return self;
});
var RainbowFlower = Flower.expand(function (type) {
var self = Flower.call(this, type);
self.isRainbow = true;
// Add rainbow particles around the flower
self.rainbowParticles = [];
for (var i = 0; i < 6; i++) {
var rainbow = self.attachAsset('rainbowEffect', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7
});
self.rainbowParticles.push(rainbow);
}
// Animate rainbow effect continuously
self.animateRainbow = function () {
for (var i = 0; i < self.rainbowParticles.length; i++) {
var particle = self.rainbowParticles[i];
var angle = i / self.rainbowParticles.length * Math.PI * 2;
var radius = 70 + Math.sin(LK.ticks * 0.04 + i * 0.3) * 12;
particle.x = Math.cos(angle + LK.ticks * 0.03) * radius;
particle.y = Math.sin(angle + LK.ticks * 0.03) * radius;
particle.scaleX = 0.95 + Math.sin(LK.ticks * 0.05 + i * 0.4) * 0.15;
particle.scaleY = 0.95 + Math.sin(LK.ticks * 0.05 + i * 0.4) * 0.15;
particle.alpha = 0.75 + Math.sin(LK.ticks * 0.06 + i) * 0.15;
}
};
self.update = function () {
self.animateRainbow();
};
self.activateRainbowEffect = function (targetType) {
if (!grid || !grid.flowers || !targetType || !self.parent) return;
// Rainbow Effect: Clears all flowers of the target type across the entire board
try {
// Create special rainbow explosion effect on self first
self.createRainbowExplosion();
// Activate rainbow power-up on target flower type
var clearedCount = self.explodeAndClear(targetType);
// Remove self from grid
self.visible = false;
if (grid.flowers[self.gridY] && grid.flowers[self.gridY][self.gridX]) {
grid.flowers[self.gridY][self.gridX] = null;
}
var newScore = LK.getScore() + clearedCount * 30;
LK.setScore(newScore);
if (scoreTxt && scoreTxt.setText) scoreTxt.setText('Score: ' + newScore);
// Optimized high score update with error handling
if (game && game.updateHighScore) game.updateHighScore(newScore);
LK.setTimeout(function () {
if (grid && grid.dropFlowers) {
grid.dropFlowers(function () {
if (game && game.checkForMatches) game.checkForMatches();
});
}
}, 300);
} catch (e) {
console.log('Rainbow effect error:', e);
}
};
self.createRainbowExplosion = function () {
// Create optimized rainbow explosion with performance scaling
try {
// Scale particle count based on performance needs
var particleCount = LK.getScore() > 8000 ? 8 : LK.getScore() > 5000 ? 12 : 16;
var animationDuration = LK.getScore() > 5000 ? 400 : 600;
// Create rainbow wave effect
for (var i = 0; i < particleCount; i++) {
var rainbowParticle = self.attachAsset('rainbowEffect', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.9 + Math.random() * 0.3,
scaleY: 0.9 + Math.random() * 0.3,
alpha: 0.8
});
var angle = i / particleCount * Math.PI * 2;
var distance = 150 + Math.random() * 80;
var targetX = Math.cos(angle) * distance;
var targetY = Math.sin(angle) * distance;
// Reduced stagger timing for faster performance
var staggerDelay = LK.getScore() > 5000 ? i * 15 : i * 25;
LK.setTimeout(function (particle, tx, ty) {
return function () {
tween(particle, {
x: tx,
y: ty,
scaleX: 0.05,
scaleY: 0.05,
alpha: 0,
rotation: Math.random() * Math.PI * 2
}, {
duration: animationDuration,
easing: tween.easeOut,
onFinish: function onFinish() {
if (particle && particle.destroy) particle.destroy();
}
});
};
}(rainbowParticle, targetX, targetY), staggerDelay);
}
} catch (e) {
console.log('Rainbow explosion creation error:', e);
}
};
self.explodeAndClear = function (targetType) {
// Find all flowers of target type and clear them
var clearedFlowers = [];
try {
for (var y = 0; y < grid.gridSize; y++) {
for (var x = 0; x < grid.gridSize; x++) {
var flower = grid.flowers[y][x];
if (flower && flower.flowerType === targetType && !flower.isShimmer && !flower.isRainbow && flower !== self) {
clearedFlowers.push({
x: x,
y: y
});
}
}
}
// Animate explosion for each cleared flower with staggered timing
for (var i = 0; i < clearedFlowers.length; i++) {
var pos = clearedFlowers[i];
// Validate array bounds and flower existence
if (pos.y >= 0 && pos.y < grid.gridSize && pos.x >= 0 && pos.x < grid.gridSize && grid.flowers[pos.y] && grid.flowers[pos.y][pos.x]) {
var flower = grid.flowers[pos.y][pos.x];
// Stagger the explosions for visual effect
(function (f, delay) {
LK.setTimeout(function () {
if (f && f.animateMatch) f.animateMatch();
}, delay);
})(flower, i * 50);
grid.flowers[pos.y][pos.x] = null;
}
}
} catch (e) {
console.log('Rainbow clear error:', e);
}
return clearedFlowers.length;
};
return self;
});
var Grid = Container.expand(function () {
var self = Container.call(this);
self.gridSize = 8;
self.cellSize = 200;
self.flowers = [];
self.flowerTypes = ['rose', 'tulip', 'daisy', 'sunflower', 'lily'];
// Initialize 2D array
for (var i = 0; i < self.gridSize; i++) {
self.flowers[i] = [];
for (var j = 0; j < self.gridSize; j++) {
self.flowers[i][j] = null;
}
}
self.createFlower = function (gridX, gridY, flowerType, special) {
// Validate input parameters
if (gridX < 0 || gridX >= self.gridSize || gridY < 0 || gridY >= self.gridSize) {
return null;
}
if (!self.flowers[gridY]) {
self.flowers[gridY] = [];
}
var type = flowerType || self.flowerTypes[Math.floor(Math.random() * self.flowerTypes.length)];
var flower;
try {
if (special === 'shimmer') {
flower = new ShimmerFlower(type);
} else if (special === 'rainbow') {
flower = new RainbowFlower(type);
} else {
flower = new Flower(type);
}
if (!flower) return null;
flower.gridX = gridX;
flower.gridY = gridY;
flower.x = gridX * self.cellSize + self.cellSize / 2;
flower.y = gridY * self.cellSize + self.cellSize / 2;
self.flowers[gridY][gridX] = flower;
self.addChild(flower);
return flower;
} catch (e) {
console.log('Error creating flower:', e);
return null;
}
};
self.initializeGrid = function () {
// Initialize 2D array properly
for (var y = 0; y < self.gridSize; y++) {
if (!self.flowers[y]) self.flowers[y] = [];
for (var x = 0; x < self.gridSize; x++) {
try {
self.createFlower(x, y);
} catch (e) {
console.log('Error creating flower at', x, y, e);
}
}
}
};
self.swapFlowers = function (flower1, flower2, callback) {
if (!flower1 || !flower2 || flower1.destroyed || flower2.destroyed) {
if (callback) callback();
return;
}
var tempX = flower1.gridX;
var tempY = flower1.gridY;
// Update grid positions
flower1.gridX = flower2.gridX;
flower1.gridY = flower2.gridY;
flower2.gridX = tempX;
flower2.gridY = tempY;
// Update array
self.flowers[flower1.gridY][flower1.gridX] = flower1;
self.flowers[flower2.gridY][flower2.gridX] = flower2;
// Animate swap with smooth, consistent movement
var targetX1 = flower1.gridX * self.cellSize + self.cellSize / 2;
var targetY1 = flower1.gridY * self.cellSize + self.cellSize / 2;
var targetX2 = flower2.gridX * self.cellSize + self.cellSize / 2;
var targetY2 = flower2.gridY * self.cellSize + self.cellSize / 2;
var completed = 0;
function onComplete() {
completed++;
if (completed === 2 && callback) {
callback();
}
}
// Stop any existing tweens on flowers to prevent conflicts
tween.stop(flower1);
tween.stop(flower2);
// Ensure flowers are in proper state
flower1.scaleX = 1.0;
flower1.scaleY = 1.0;
flower1.rotation = 0;
flower2.scaleX = 1.0;
flower2.scaleY = 1.0;
flower2.rotation = 0;
// Create smooth arc motion effect
var midX1 = (flower1.x + targetX1) / 2;
var midY1 = (flower1.y + targetY1) / 2 - 30;
var midX2 = (flower2.x + targetX2) / 2;
var midY2 = (flower2.y + targetY2) / 2 - 30;
// First phase - move to mid point with slight scale effect
tween(flower1, {
x: midX1,
y: midY1,
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 125,
easing: tween.easeOut,
onFinish: function onFinish() {
// Second phase - complete the swap
tween(flower1, {
x: targetX1,
y: targetY1,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 125,
easing: tween.easeIn,
onFinish: onComplete
});
}
});
tween(flower2, {
x: midX2,
y: midY2,
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 125,
easing: tween.easeOut,
onFinish: function onFinish() {
// Second phase - complete the swap
tween(flower2, {
x: targetX2,
y: targetY2,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 125,
easing: tween.easeIn,
onFinish: onComplete
});
}
});
};
self.findMatches = function () {
var matches = [];
var matchGroups = [];
// Check horizontal matches
for (var y = 0; y < self.gridSize; y++) {
if (!self.flowers[y]) continue;
var count = 1;
var currentType = self.flowers[y][0] ? self.flowers[y][0].flowerType : null;
for (var x = 1; x < self.gridSize; x++) {
var flower = self.flowers[y][x] || null;
if (flower && flower.flowerType === currentType) {
count++;
} else {
if (count >= 3 && currentType) {
var group = {
type: currentType,
count: count,
positions: []
};
for (var i = x - count; i < x; i++) {
var pos = {
x: i,
y: y
};
matches.push(pos);
group.positions.push(pos);
}
matchGroups.push(group);
}
count = 1;
currentType = flower ? flower.flowerType : null;
}
}
if (count >= 3 && currentType) {
var group = {
type: currentType,
count: count,
positions: []
};
for (var i = self.gridSize - count; i < self.gridSize; i++) {
var pos = {
x: i,
y: y
};
matches.push(pos);
group.positions.push(pos);
}
matchGroups.push(group);
}
}
// Check vertical matches
for (var x = 0; x < self.gridSize; x++) {
var count = 1;
var currentType = self.flowers[0] && self.flowers[0][x] ? self.flowers[0][x].flowerType : null;
for (var y = 1; y < self.gridSize; y++) {
if (!self.flowers[y]) continue;
var flower = self.flowers[y][x] || null;
if (flower && flower.flowerType === currentType) {
count++;
} else {
if (count >= 3 && currentType) {
var group = {
type: currentType,
count: count,
positions: []
};
for (var i = y - count; i < y; i++) {
var pos = {
x: x,
y: i
};
matches.push(pos);
group.positions.push(pos);
}
matchGroups.push(group);
}
count = 1;
currentType = flower ? flower.flowerType : null;
}
}
if (count >= 3 && currentType) {
var group = {
type: currentType,
count: count,
positions: []
};
for (var i = self.gridSize - count; i < self.gridSize; i++) {
var pos = {
x: x,
y: i
};
matches.push(pos);
group.positions.push(pos);
}
matchGroups.push(group);
}
}
return {
matches: matches,
groups: matchGroups
};
};
self.removeMatches = function (matchResult, callback) {
if (!matchResult || matchResult.matches.length === 0) {
if (callback) callback();
return;
}
var matches = matchResult.matches;
var groups = matchResult.groups || [];
var powerUpsCreated = [];
var animationsCompleted = 0;
var totalAnimations = 0;
var callbackExecuted = false;
// Check for power-up creation opportunities
try {
for (var g = 0; g < groups.length; g++) {
var group = groups[g];
if (group && group.count >= 5 && group.positions && group.positions.length > 0) {
// Create rainbow flower at center position
var centerPos = group.positions[Math.floor(group.positions.length / 2)];
powerUpsCreated.push({
pos: centerPos,
type: group.type,
special: 'rainbow'
});
} else if (group && group.count >= 4 && group.positions && group.positions.length > 0) {
// Create shimmer flower at center position
var centerPos = group.positions[Math.floor(group.positions.length / 2)];
powerUpsCreated.push({
pos: centerPos,
type: group.type,
special: 'shimmer'
});
}
}
} catch (e) {
console.log('Power-up creation error:', e);
}
function onAnimationComplete() {
animationsCompleted++;
if (animationsCompleted === totalAnimations && !callbackExecuted) {
callbackExecuted = true;
// Create power-up flowers after all match animations complete
try {
for (var p = 0; p < powerUpsCreated.length; p++) {
var powerUp = powerUpsCreated[p];
// Validate position is within bounds and empty
if (powerUp && powerUp.pos && powerUp.pos.y >= 0 && powerUp.pos.y < self.gridSize && powerUp.pos.x >= 0 && powerUp.pos.x < self.gridSize && self.flowers[powerUp.pos.y] && !self.flowers[powerUp.pos.y][powerUp.pos.x]) {
var newPowerUpFlower = self.createFlower(powerUp.pos.x, powerUp.pos.y, powerUp.type, powerUp.special);
// Add visual feedback for power-up creation
newPowerUpFlower.scaleX = 0.3;
newPowerUpFlower.scaleY = 0.3;
newPowerUpFlower.alpha = 0.8;
tween(newPowerUpFlower, {
scaleX: 1.1,
scaleY: 1.1,
alpha: 1.0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(newPowerUpFlower, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut
});
}
});
}
}
} catch (e) {
console.log('Power-up placement error:', e);
}
// Execute callback with timeout to prevent stack overflow
LK.setTimeout(function () {
if (callback) callback();
}, 16);
}
}
// Process matches and start animations
for (var i = 0; i < matches.length; i++) {
var match = matches[i];
if (match && match.y >= 0 && match.y < self.gridSize && match.x >= 0 && match.x < self.gridSize && self.flowers[match.y] && self.flowers[match.y][match.x]) {
var flower = self.flowers[match.y][match.x];
if (flower && flower.animateMatch) {
self.flowers[match.y][match.x] = null; // Clear immediately
totalAnimations++;
flower.animateMatch(onAnimationComplete);
}
}
}
// Handle case where no animations are needed
if (totalAnimations === 0) {
// Create power-ups immediately if no animations
try {
for (var p = 0; p < powerUpsCreated.length; p++) {
var powerUp = powerUpsCreated[p];
if (powerUp && powerUp.pos && powerUp.pos.y >= 0 && powerUp.pos.y < self.gridSize && powerUp.pos.x >= 0 && powerUp.pos.x < self.gridSize && self.flowers[powerUp.pos.y] && !self.flowers[powerUp.pos.y][powerUp.pos.x]) {
var newPowerUpFlower = self.createFlower(powerUp.pos.x, powerUp.pos.y, powerUp.type, powerUp.special);
newPowerUpFlower.scaleX = 0.2;
newPowerUpFlower.scaleY = 0.2;
tween(newPowerUpFlower, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 300,
easing: tween.easeOut
});
}
}
} catch (e) {
console.log('Immediate power-up creation error:', e);
}
if (callback) callback();
}
};
self.dropFlowers = function (callback) {
var animations = 0;
var animationCompleted = false;
var timeoutId = null;
function onAnimationComplete() {
animations--;
if (animations === 0 && !animationCompleted && callback) {
animationCompleted = true;
if (timeoutId) LK.clearTimeout(timeoutId);
callback();
}
}
// Safety timeout to prevent infinite waiting
timeoutId = LK.setTimeout(function () {
if (!animationCompleted && callback) {
animationCompleted = true;
callback();
}
}, 5000);
// Process each column independently
for (var x = 0; x < self.gridSize; x++) {
// Ensure column exists
if (!self.flowers) self.flowers = [];
for (var y = 0; y < self.gridSize; y++) {
if (!self.flowers[y]) self.flowers[y] = [];
}
// Collect existing flowers in this column (bottom to top)
var existingFlowers = [];
for (var y = self.gridSize - 1; y >= 0; y--) {
if (self.flowers[y][x]) {
existingFlowers.push(self.flowers[y][x]);
self.flowers[y][x] = null; // Clear the position
}
}
// Place existing flowers at bottom positions
var currentPos = self.gridSize - 1;
for (var i = 0; i < existingFlowers.length; i++) {
var flower = existingFlowers[i];
if (flower && currentPos >= 0) {
self.flowers[currentPos][x] = flower;
flower.gridX = x;
flower.gridY = currentPos;
// Animate fall if needed
var targetY = currentPos * self.cellSize + self.cellSize / 2;
if (Math.abs(flower.y - targetY) > 10) {
animations++;
(function (f, ty) {
// Stop any existing fall animations
tween.stop(f);
f.isFalling = false;
f.animateFall(ty, onAnimationComplete);
})(flower, targetY);
} else {
flower.y = targetY; // Snap to position if very close
}
currentPos--;
}
}
// Create new flowers for remaining empty spaces
for (var y = 0; y <= currentPos; y++) {
var newFlower = self.createFlower(x, y);
if (newFlower) {
newFlower.y = -self.cellSize - (currentPos - y) * 50; // Stagger starting positions
var targetY = y * self.cellSize + self.cellSize / 2;
animations++;
(function (f, ty) {
f.animateFall(ty, onAnimationComplete);
})(newFlower, targetY);
}
}
}
// Handle case where no animations are needed
if (animations === 0 && callback) {
callback();
}
};
self.isAdjacent = function (flower1, flower2) {
var dx = Math.abs(flower1.gridX - flower2.gridX);
var dy = Math.abs(flower1.gridY - flower2.gridY);
return dx === 1 && dy === 0 || dx === 0 && dy === 1;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x98fb98
});
/****
* Game Code
****/
// Create beautiful flower garden background
var gardenBg = game.attachAsset('gardenBackground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
var grid = new Grid();
var selectedFlower = null;
var isSwapping = false;
var isMatching = false;
// Position grid in center of screen
grid.x = (2048 - grid.gridSize * grid.cellSize) / 2;
grid.y = (2732 - grid.gridSize * grid.cellSize) / 2 + 200;
game.addChild(grid);
// Score display
var scoreTxt = new Text2('Score: 0', {
size: 120,
fill: 0x2D5016
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// High score display
var highScoreTxt = new Text2('High Score: 0', {
size: 80,
fill: 0x1a3d0b
});
highScoreTxt.anchor.set(0.5, 0);
highScoreTxt.y = 140;
LK.gui.top.addChild(highScoreTxt);
// Initialize high score from storage with error handling
var highScore = 0;
try {
if (storage && typeof storage.highScore === 'number') {
highScore = storage.highScore;
} else {
highScore = 0;
}
} catch (e) {
console.log('Storage read error, using default high score');
highScore = 0;
}
try {
if (highScoreTxt && highScoreTxt.setText) {
highScoreTxt.setText('High Score: ' + highScore);
}
} catch (e) {
console.log('High score display initialization error:', e);
}
// Centralized high score update system with debouncing
var highScoreUpdateTimer = null;
var pendingHighScoreUpdate = false;
var lastHighScoreUpdate = 0;
game.updateHighScore = function (newScore) {
if (!newScore || typeof newScore !== 'number') return;
var currentTime = Date.now();
var updateDelay = LK.getScore() > 8000 ? 2000 : LK.getScore() > 5000 ? 1500 : 500;
if (newScore > highScore && !pendingHighScoreUpdate && currentTime - lastHighScoreUpdate > updateDelay) {
lastHighScoreUpdate = currentTime;
highScore = newScore;
try {
if (highScoreTxt && highScoreTxt.setText) {
highScoreTxt.setText('High Score: ' + highScore);
}
} catch (e) {
console.log('High score display error:', e);
}
pendingHighScoreUpdate = true;
// Increased debounce time for high scores to prevent performance issues
if (highScoreUpdateTimer) {
LK.clearTimeout(highScoreUpdateTimer);
}
var storageDelay = LK.getScore() > 8000 ? 3000 : LK.getScore() > 5000 ? 2000 : 1000;
highScoreUpdateTimer = LK.setTimeout(function () {
try {
if (storage) {
storage.highScore = highScore;
}
} catch (e) {
console.log('Storage write error:', e);
}
highScoreUpdateTimer = null;
pendingHighScoreUpdate = false;
}, storageDelay);
}
};
// Add performance monitoring variables
var performanceOptimized = false;
var particlePool = [];
var maxPoolSize = 50;
// Performance optimization function
function optimizeForHighScore() {
if (!performanceOptimized && LK.getScore() > 5000) {
performanceOptimized = true;
// Reduce animation complexity for better performance
maxMatchChecks = 15;
minSwipeDistance = 40;
console.log('Performance mode activated for high score gameplay');
}
}
// Initialize grid with staggered creation to prevent startup lag
var initializationStep = 0;
var totalCells = grid.gridSize * grid.gridSize;
function initializeGridStep() {
var cellsPerStep = 8; // Create 8 flowers per frame
var endStep = Math.min(initializationStep + cellsPerStep, totalCells);
for (var i = initializationStep; i < endStep; i++) {
var x = i % grid.gridSize;
var y = Math.floor(i / grid.gridSize);
try {
grid.createFlower(x, y);
} catch (e) {
console.log('Error creating flower at', x, y, e);
}
}
initializationStep = endStep;
if (initializationStep < totalCells) {
LK.setTimeout(initializeGridStep, 16); // Continue next frame
} else {
// Grid initialization complete, start game
LK.setTimeout(function () {
game.checkForMatches();
}, 300);
}
}
// Start staggered initialization
initializeGridStep();
game.selectFlower = function (flower) {
// Prevent selection during animations
if (isSwapping || isMatching || !flower || flower.destroyed) {
return;
}
if (selectedFlower === null) {
// First selection with smooth scale animation
selectedFlower = flower;
// Stop any existing tweens to prevent conflicts
tween.stop(flower);
// Set initial state for smooth animation
flower.scaleX = 1.0;
flower.scaleY = 1.0;
flower.rotation = 0;
tween(flower, {
scaleX: 1.15,
scaleY: 1.15
}, {
duration: 200,
easing: tween.easeInOut
});
// Add smooth pulse effect
tween(flower, {
rotation: 0.05
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(flower, {
rotation: -0.05
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(flower, {
rotation: 0
}, {
duration: 200,
easing: tween.easeInOut
});
}
});
}
});
} else if (selectedFlower === flower) {
// Deselect same flower with smooth transition
tween.stop(flower);
tween(flower, {
scaleX: 1,
scaleY: 1,
rotation: 0
}, {
duration: 250,
easing: tween.easeInOut
});
selectedFlower = null;
} else if (grid.isAdjacent(selectedFlower, flower)) {
// Valid swap
isSwapping = true;
var firstFlower = selectedFlower;
// Smooth deselection animation
tween.stop(firstFlower);
tween(firstFlower, {
scaleX: 1,
scaleY: 1,
rotation: 0
}, {
duration: 200,
easing: tween.easeInOut
});
LK.getSound('swap').play();
// Check for special flower activation during swap
if (firstFlower.isShimmer) {
// Shimmer flower swapped - activate shimmer effect
selectedFlower = null;
firstFlower.activateShimmerEffect();
isSwapping = false;
} else if (flower.isShimmer) {
// Target flower is shimmer - activate shimmer effect
selectedFlower = null;
flower.activateShimmerEffect();
isSwapping = false;
} else if (firstFlower.isRainbow && flower.flowerType && !flower.isRainbow && !flower.isShimmer) {
// Rainbow flower swapped with regular flower - activate rainbow effect
selectedFlower = null;
firstFlower.activateRainbowEffect(flower.flowerType);
isSwapping = false;
} else if (flower.isRainbow && firstFlower.flowerType && !firstFlower.isRainbow && !firstFlower.isShimmer) {
// Target flower is rainbow - activate rainbow effect
selectedFlower = null;
flower.activateRainbowEffect(firstFlower.flowerType);
isSwapping = false;
} else {
// Regular swap
grid.swapFlowers(firstFlower, flower, function () {
selectedFlower = null;
isSwapping = false;
game.checkForMatches();
});
}
} else {
// Invalid swap - select new flower with smooth transition
tween.stop(selectedFlower);
tween(selectedFlower, {
scaleX: 1,
scaleY: 1,
rotation: 0
}, {
duration: 250,
easing: tween.easeInOut
});
selectedFlower = flower;
// Stop any existing tweens on new flower
tween.stop(flower);
flower.scaleX = 1.0;
flower.scaleY = 1.0;
flower.rotation = 0;
tween(flower, {
scaleX: 1.15,
scaleY: 1.15
}, {
duration: 200,
easing: tween.easeInOut
});
// Add smooth pulse effect for new selection
tween(flower, {
rotation: 0.05
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(flower, {
rotation: -0.05
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(flower, {
rotation: 0
}, {
duration: 200,
easing: tween.easeInOut
});
}
});
}
});
}
};
var matchCheckCount = 0;
var maxMatchChecks = 20;
var lastMatchCheckTime = 0;
var matchCheckInProgress = false;
game.checkForMatches = function () {
// Prevent multiple simultaneous match checks and infinite loops
var currentTime = Date.now();
var minDelay = LK.getScore() > 8000 ? 100 : LK.getScore() > 5000 ? 120 : 150;
if (isMatching || isSwapping || matchCheckInProgress || matchCheckCount >= maxMatchChecks || currentTime - lastMatchCheckTime < minDelay) {
return;
}
lastMatchCheckTime = currentTime;
matchCheckCount++;
matchCheckInProgress = true;
try {
var matchResult = grid.findMatches();
if (matchResult && matchResult.matches && matchResult.matches.length > 0) {
isMatching = true;
// Reduce sound frequency at high scores to prevent audio lag
if (LK.getScore() <= 8000) {
try {
LK.getSound('match').play();
} catch (e) {
console.log('Sound play error:', e);
}
}
// Update score using LK score system only
var newScore = LK.getScore() + matchResult.matches.length * 10;
LK.setScore(newScore);
if (scoreTxt && scoreTxt.setText) scoreTxt.setText('Score: ' + newScore);
// Optimized high score update with throttling for high scores
if (newScore <= 8000 || newScore % 100 === 0) {
// Only update high score every 100 points at high scores
try {
game.updateHighScore(newScore);
} catch (e) {
console.log('High score update error:', e);
}
}
grid.removeMatches(matchResult, function () {
grid.dropFlowers(function () {
isMatching = false;
matchCheckInProgress = false;
// Check for cascading matches with adaptive timing
var cascadeDelay = LK.getScore() > 8000 ? 250 : LK.getScore() > 5000 ? 300 : 400;
LK.setTimeout(function () {
if (matchCheckCount < maxMatchChecks && !isMatching && !isSwapping) {
game.checkForMatches();
} else {
matchCheckCount = 0; // Reset counter
matchCheckInProgress = false;
}
}, cascadeDelay);
});
});
} else {
// No matches found - ensure matching flag is cleared
isMatching = false;
matchCheckInProgress = false;
matchCheckCount = 0; // Reset counter when no matches
}
} catch (e) {
console.log('Match check error:', e);
isMatching = false;
matchCheckInProgress = false;
matchCheckCount = 0;
}
};
// Swipe detection variables
var swipeStartFlower = null;
var swipeStartX = 0;
var swipeStartY = 0;
var isSwipeActive = false;
var minSwipeDistance = 50;
// Add swipe detection to game
game.down = function (x, y, obj) {
if (isSwapping || isMatching) return;
// Convert screen coordinates to game coordinates safely
var gamePos;
try {
if (obj && obj.parent && obj.parent.toGlobal && obj.position) {
gamePos = game.toLocal(obj.parent.toGlobal(obj.position));
} else {
// Fallback to using direct x,y coordinates
gamePos = {
x: x,
y: y
};
}
} catch (e) {
// Fallback to using direct x,y coordinates if conversion fails
gamePos = {
x: x,
y: y
};
}
// Find flower at touch position more accurately
var touchedFlower = null;
var gridLocalX = gamePos.x - grid.x;
var gridLocalY = gamePos.y - grid.y;
// More precise grid coordinate calculation
var gridX = Math.floor((gridLocalX + grid.cellSize * 0.1) / grid.cellSize);
var gridY = Math.floor((gridLocalY + grid.cellSize * 0.1) / grid.cellSize);
// Validate grid bounds and flower existence
if (gridX >= 0 && gridX < grid.gridSize && gridY >= 0 && gridY < grid.gridSize && grid.flowers[gridY] && grid.flowers[gridY][gridX]) {
touchedFlower = grid.flowers[gridY][gridX];
}
if (touchedFlower) {
swipeStartFlower = touchedFlower;
swipeStartX = gamePos.x;
swipeStartY = gamePos.y;
isSwipeActive = true;
}
};
game.up = function (x, y, obj) {
if (!isSwipeActive || !swipeStartFlower || isSwapping || isMatching) {
isSwipeActive = false;
swipeStartFlower = null;
return;
}
// Convert end coordinates to game coordinates safely
var gameEndPos;
try {
if (obj && obj.parent && obj.parent.toGlobal && obj.position) {
gameEndPos = game.toLocal(obj.parent.toGlobal(obj.position));
} else {
// Fallback to using direct x,y coordinates
gameEndPos = {
x: x,
y: y
};
}
} catch (e) {
// Fallback to using direct x,y coordinates if conversion fails
gameEndPos = {
x: x,
y: y
};
}
var deltaX = gameEndPos.x - swipeStartX;
var deltaY = gameEndPos.y - swipeStartY;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance >= minSwipeDistance) {
// Determine swipe direction with better accuracy
var targetGridX = swipeStartFlower.gridX;
var targetGridY = swipeStartFlower.gridY;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// Horizontal swipe
if (deltaX > 0) {
targetGridX++; // Right
} else {
targetGridX--; // Left
}
} else {
// Vertical swipe
if (deltaY > 0) {
targetGridY++; // Down
} else {
targetGridY--; // Up
}
}
// Check if target position is valid and has a flower
if (targetGridX >= 0 && targetGridX < grid.gridSize && targetGridY >= 0 && targetGridY < grid.gridSize && grid.flowers[targetGridY] && grid.flowers[targetGridY][targetGridX]) {
var targetFlower = grid.flowers[targetGridY][targetGridX];
// Use smooth selection and swap with proper timing
if (swipeStartFlower && swipeStartFlower.scaleX !== undefined) {
tween(swipeStartFlower, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 100,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (swipeStartFlower && swipeStartFlower.scaleX !== undefined) {
tween(swipeStartFlower, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeInOut
});
}
if (swipeStartFlower) {
game.selectFlower(swipeStartFlower);
LK.setTimeout(function () {
if (targetFlower) {
game.selectFlower(targetFlower);
}
}, 50);
}
}
});
}
}
}
isSwipeActive = false;
swipeStartFlower = null;
};
// Add move handler for visual feedback during swipe
game.move = function (x, y, obj) {
if (!isSwipeActive || !swipeStartFlower || isSwapping || isMatching) return;
// Convert coordinates for consistent tracking safely
var gameMovePos;
try {
if (obj && obj.parent && obj.parent.toGlobal && obj.position) {
gameMovePos = game.toLocal(obj.parent.toGlobal(obj.position));
} else {
// Fallback to using direct x,y coordinates
gameMovePos = {
x: x,
y: y
};
}
} catch (e) {
// Fallback to using direct x,y coordinates if conversion fails
gameMovePos = {
x: x,
y: y
};
}
var deltaX = gameMovePos.x - swipeStartX;
var deltaY = gameMovePos.y - swipeStartY;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// Provide visual feedback when swipe distance is sufficient
if (distance >= minSwipeDistance * 0.7) {
// Scale up the flower being swiped to indicate readiness
if (swipeStartFlower && swipeStartFlower.scaleX !== undefined && swipeStartFlower.scaleX <= 1.0) {
tween.stop(swipeStartFlower);
tween(swipeStartFlower, {
scaleX: 1.08,
scaleY: 1.08
}, {
duration: 150,
easing: tween.easeOut
});
}
} else if (swipeStartFlower && swipeStartFlower.scaleX !== undefined && swipeStartFlower.scaleX > 1.0) {
// Scale back down if swipe distance is insufficient
tween.stop(swipeStartFlower);
tween(swipeStartFlower, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 150,
easing: tween.easeOut
});
}
};
// Initial match check after grid creation
LK.setTimeout(function () {
game.checkForMatches();
}, 300);
game.update = function () {
// Game runs automatically through event-driven mechanics
// Add basic error catching for any update cycle issues
try {
// Ensure game state consistency
if (typeof isMatching === 'undefined') isMatching = false;
if (typeof isSwapping === 'undefined') isSwapping = false;
if (typeof matchCheckCount === 'undefined') matchCheckCount = 0;
// Performance optimization check every 60 frames
if (LK.ticks % 60 === 0) {
optimizeForHighScore();
}
// Memory management - clean up unused tweens every 300 frames
if (LK.ticks % 300 === 0 && LK.getScore() > 5000) {
// Force garbage collection opportunity
if (particlePool.length > maxPoolSize) {
particlePool.length = maxPoolSize;
}
}
// Adaptive frame skipping for very high scores to maintain smoothness
if (LK.getScore() > 10000 && LK.ticks % 2 === 0) {
return; // Skip every other frame at very high scores
}
} catch (e) {
console.log('Game update error:', e);
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Flower = Container.expand(function (type) {
var self = Container.call(this);
self.flowerType = type;
self.gridX = 0;
self.gridY = 0;
self.destroyed = false;
var flowerGraphics = self.attachAsset(type, {
anchorX: 0.5,
anchorY: 0.5
});
// Add subtle glow effect
flowerGraphics.alpha = 0.9;
self.down = function (x, y, obj) {
if (!isSwapping && !isMatching) {
game.selectFlower(self);
}
};
self.animateMatch = function (callback) {
if (self.isAnimating) {
if (callback) callback();
return;
}
if (!flowerGraphics || !self.parent || self.destroyed) {
if (callback) callback();
return;
}
self.isAnimating = true;
var particlesCompleted = 0;
var totalParticles = Math.min(3, LK.getScore() > 5000 ? 2 : 3); // Reduce particles at high scores
var flowerAnimationDone = false;
var callbackCalled = false;
function checkAllComplete() {
if (particlesCompleted >= totalParticles && flowerAnimationDone && callback && !callbackCalled) {
callbackCalled = true;
self.isAnimating = false;
// Use timeout to prevent stack overflow during heavy explosions
LK.setTimeout(function () {
try {
callback();
} catch (e) {
console.log('Animation callback error:', e);
}
}, 16); // Faster callback timing
}
}
// Create optimized particle explosion effect with performance scaling
for (var i = 0; i < totalParticles; i++) {
try {
var particle = self.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8 + Math.random() * 0.3,
scaleY: 0.8 + Math.random() * 0.3,
alpha: 0.8
});
var angle = i / totalParticles * Math.PI * 2 + Math.random() * 0.2;
var distance = 60 + Math.random() * 40; // Reduced distance for performance
var targetX = Math.cos(angle) * distance;
var targetY = Math.sin(angle) * distance;
// Simplified initial motion
particle.x = Math.cos(angle) * 8;
particle.y = Math.sin(angle) * 8;
tween(particle, {
x: targetX,
y: targetY,
scaleX: 0.1,
scaleY: 0.1,
alpha: 0,
rotation: Math.random() * Math.PI
}, {
duration: LK.getScore() > 5000 ? 250 : 300,
// Faster animations at high scores
easing: tween.easeOut,
onFinish: function onFinish() {
if (particle && particle.destroy) {
particle.destroy();
}
particlesCompleted++;
checkAllComplete();
}
});
} catch (e) {
particlesCompleted++;
checkAllComplete();
}
}
// Add flower scaling and fade animation with optimized timing
try {
// Stop any existing tweens
tween.stop(flowerGraphics);
tween(flowerGraphics, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0,
rotation: Math.random() * Math.PI * 0.1
}, {
duration: LK.getScore() > 5000 ? 200 : 250,
easing: tween.easeOut,
onFinish: function onFinish() {
self.visible = false;
flowerAnimationDone = true;
checkAllComplete();
}
});
} catch (e) {
self.visible = false;
flowerAnimationDone = true;
checkAllComplete();
}
};
self.animateFall = function (targetY, callback) {
// Prevent multiple fall animations on same flower
if (self.isFalling || self.destroyed || !self.parent) {
if (callback) {
LK.setTimeout(callback, 8); // Faster callback for performance
}
return;
}
self.isFalling = true;
// Calculate fall distance for dynamic duration with performance scaling
var fallDistance = Math.abs(targetY - self.y);
var baseDuration = LK.getScore() > 8000 ? 80 : LK.getScore() > 5000 ? 100 : 120;
var dynamicDuration = Math.min(baseDuration + fallDistance / 600 * 40, LK.getScore() > 5000 ? 200 : 300);
// Stop all existing tweens to prevent conflicts
tween.stop(self);
// Ensure flower is in proper state
self.scaleX = 1.0;
self.scaleY = 1.0;
self.rotation = 0;
// Optimized fall animation with performance-based easing
tween(self, {
y: targetY
}, {
duration: dynamicDuration,
easing: LK.getScore() > 5000 ? tween.linear : tween.easeOut,
onFinish: function onFinish() {
self.isFalling = false;
if (callback) {
// Use faster setTimeout to prevent stack overflow while maintaining performance
LK.setTimeout(function () {
try {
callback();
} catch (e) {
console.log('Fall callback error:', e);
}
}, 8);
}
}
});
};
// Override destroy to set destroyed flag
var originalDestroy = self.destroy;
self.destroy = function () {
self.destroyed = true;
self.isFalling = false;
self.isAnimating = false;
tween.stop(self);
if (originalDestroy) originalDestroy.call(self);
};
return self;
});
var ShimmerFlower = Flower.expand(function (type) {
var self = Flower.call(this, type);
self.isShimmer = true;
// Add shimmer particles around the flower
self.shimmerParticles = [];
for (var i = 0; i < 6; i++) {
var shimmer = self.attachAsset('shimmerEffect', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
self.shimmerParticles.push(shimmer);
}
// Animate shimmer effect continuously
self.animateShimmer = function () {
for (var i = 0; i < self.shimmerParticles.length; i++) {
var particle = self.shimmerParticles[i];
var angle = i / self.shimmerParticles.length * Math.PI * 2;
var radius = 55 + Math.sin(LK.ticks * 0.05 + i) * 8;
particle.x = Math.cos(angle + LK.ticks * 0.04) * radius;
particle.y = Math.sin(angle + LK.ticks * 0.04) * radius;
particle.alpha = 0.7 + Math.sin(LK.ticks * 0.08 + i) * 0.2;
particle.scaleX = 0.9 + Math.sin(LK.ticks * 0.06 + i) * 0.15;
particle.scaleY = 0.9 + Math.sin(LK.ticks * 0.06 + i) * 0.15;
}
};
self.update = function () {
self.animateShimmer();
};
self.activateShimmerEffect = function () {
if (!grid || !grid.flowers || !self.parent) return;
// Shimmer Effect: Explodes in a cross pattern (self + adjacent flowers)
try {
var onExplosionComplete = function onExplosionComplete() {
explosionsCompleted++;
if (explosionsCompleted >= totalExplosions) {
var newScore = LK.getScore() + flowersToExplode.length * 25;
LK.setScore(newScore);
if (scoreTxt && scoreTxt.setText) scoreTxt.setText('Score: ' + newScore);
// Optimized high score update with error handling
if (game && game.updateHighScore) game.updateHighScore(newScore);
LK.setTimeout(function () {
if (grid && grid.dropFlowers) {
grid.dropFlowers(function () {
if (game && game.checkForMatches) game.checkForMatches();
});
}
}, 200);
}
}; // Explode all flowers in the list
// Create special shimmer explosion effect on self first
self.createShimmerExplosion();
var flowersToExplode = [];
// Add self to explosion list
flowersToExplode.push({
x: self.gridX,
y: self.gridY
});
// Add adjacent flowers (up, down, left, right)
var adjacentPositions = [{
x: self.gridX,
y: self.gridY - 1
},
// up
{
x: self.gridX,
y: self.gridY + 1
},
// down
{
x: self.gridX - 1,
y: self.gridY
},
// left
{
x: self.gridX + 1,
y: self.gridY
} // right
];
for (var i = 0; i < adjacentPositions.length; i++) {
var pos = adjacentPositions[i];
if (pos.x >= 0 && pos.x < grid.gridSize && pos.y >= 0 && pos.y < grid.gridSize && grid.flowers[pos.y] && grid.flowers[pos.y][pos.x] && grid.flowers[pos.y][pos.x] !== self) {
flowersToExplode.push(pos);
}
}
// Explode all flowers in the list with smooth timing
var explosionsCompleted = 0;
var totalExplosions = flowersToExplode.length;
for (var i = 0; i < flowersToExplode.length; i++) {
var pos = flowersToExplode[i];
if (pos.y >= 0 && pos.y < grid.gridSize && pos.x >= 0 && pos.x < grid.gridSize && grid.flowers[pos.y] && grid.flowers[pos.y][pos.x]) {
var flower = grid.flowers[pos.y][pos.x];
if (flower && flower.animateMatch) {
flower.animateMatch(onExplosionComplete);
grid.flowers[pos.y][pos.x] = null;
} else {
onExplosionComplete();
}
} else {
onExplosionComplete();
}
}
} catch (e) {
console.log('Shimmer effect error:', e);
}
};
self.createShimmerExplosion = function () {
// Create optimized shimmer explosion with performance scaling
try {
// Reduce particle count based on score for performance
var particleCount = LK.getScore() > 8000 ? 4 : LK.getScore() > 5000 ? 6 : 8;
// Create shimmer burst effect
for (var i = 0; i < particleCount; i++) {
var shimmerParticle = self.attachAsset('shimmerEffect', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0 + Math.random() * 0.4,
scaleY: 1.0 + Math.random() * 0.4,
alpha: 0.9
});
var angle = i / particleCount * Math.PI * 2;
var distance = 100 + Math.random() * 50;
var targetX = Math.cos(angle) * distance;
var targetY = Math.sin(angle) * distance;
tween(shimmerParticle, {
x: targetX,
y: targetY,
scaleX: 0.05,
scaleY: 0.05,
alpha: 0,
rotation: Math.random() * Math.PI * 2
}, {
duration: LK.getScore() > 5000 ? 300 : 400,
easing: tween.easeOut,
onFinish: function onFinish() {
if (shimmerParticle && shimmerParticle.destroy) shimmerParticle.destroy();
}
});
}
} catch (e) {
console.log('Shimmer explosion creation error:', e);
}
};
self.explode = function () {
// Create optimized explosion effect
try {
for (var i = 0; i < 12; i++) {
var particle = self.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5 + Math.random() * 1.0,
scaleY: 1.5 + Math.random() * 1.0,
alpha: 1.0
});
var angle = i / 12 * Math.PI * 2 + Math.random() * 0.5;
var distance = 150 + Math.random() * 100;
var targetX = Math.cos(angle) * distance;
var targetY = Math.sin(angle) * distance;
tween(particle, {
x: targetX,
y: targetY,
scaleX: 0.1,
scaleY: 0.1,
alpha: 0,
rotation: Math.random() * Math.PI * 6
}, {
duration: 600 + Math.random() * 300,
easing: tween.easeOut,
onFinish: function onFinish() {
if (particle && particle.destroy) particle.destroy();
}
});
}
} catch (e) {
console.log('Shimmer explosion error:', e);
}
};
return self;
});
var RainbowFlower = Flower.expand(function (type) {
var self = Flower.call(this, type);
self.isRainbow = true;
// Add rainbow particles around the flower
self.rainbowParticles = [];
for (var i = 0; i < 6; i++) {
var rainbow = self.attachAsset('rainbowEffect', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7
});
self.rainbowParticles.push(rainbow);
}
// Animate rainbow effect continuously
self.animateRainbow = function () {
for (var i = 0; i < self.rainbowParticles.length; i++) {
var particle = self.rainbowParticles[i];
var angle = i / self.rainbowParticles.length * Math.PI * 2;
var radius = 70 + Math.sin(LK.ticks * 0.04 + i * 0.3) * 12;
particle.x = Math.cos(angle + LK.ticks * 0.03) * radius;
particle.y = Math.sin(angle + LK.ticks * 0.03) * radius;
particle.scaleX = 0.95 + Math.sin(LK.ticks * 0.05 + i * 0.4) * 0.15;
particle.scaleY = 0.95 + Math.sin(LK.ticks * 0.05 + i * 0.4) * 0.15;
particle.alpha = 0.75 + Math.sin(LK.ticks * 0.06 + i) * 0.15;
}
};
self.update = function () {
self.animateRainbow();
};
self.activateRainbowEffect = function (targetType) {
if (!grid || !grid.flowers || !targetType || !self.parent) return;
// Rainbow Effect: Clears all flowers of the target type across the entire board
try {
// Create special rainbow explosion effect on self first
self.createRainbowExplosion();
// Activate rainbow power-up on target flower type
var clearedCount = self.explodeAndClear(targetType);
// Remove self from grid
self.visible = false;
if (grid.flowers[self.gridY] && grid.flowers[self.gridY][self.gridX]) {
grid.flowers[self.gridY][self.gridX] = null;
}
var newScore = LK.getScore() + clearedCount * 30;
LK.setScore(newScore);
if (scoreTxt && scoreTxt.setText) scoreTxt.setText('Score: ' + newScore);
// Optimized high score update with error handling
if (game && game.updateHighScore) game.updateHighScore(newScore);
LK.setTimeout(function () {
if (grid && grid.dropFlowers) {
grid.dropFlowers(function () {
if (game && game.checkForMatches) game.checkForMatches();
});
}
}, 300);
} catch (e) {
console.log('Rainbow effect error:', e);
}
};
self.createRainbowExplosion = function () {
// Create optimized rainbow explosion with performance scaling
try {
// Scale particle count based on performance needs
var particleCount = LK.getScore() > 8000 ? 8 : LK.getScore() > 5000 ? 12 : 16;
var animationDuration = LK.getScore() > 5000 ? 400 : 600;
// Create rainbow wave effect
for (var i = 0; i < particleCount; i++) {
var rainbowParticle = self.attachAsset('rainbowEffect', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.9 + Math.random() * 0.3,
scaleY: 0.9 + Math.random() * 0.3,
alpha: 0.8
});
var angle = i / particleCount * Math.PI * 2;
var distance = 150 + Math.random() * 80;
var targetX = Math.cos(angle) * distance;
var targetY = Math.sin(angle) * distance;
// Reduced stagger timing for faster performance
var staggerDelay = LK.getScore() > 5000 ? i * 15 : i * 25;
LK.setTimeout(function (particle, tx, ty) {
return function () {
tween(particle, {
x: tx,
y: ty,
scaleX: 0.05,
scaleY: 0.05,
alpha: 0,
rotation: Math.random() * Math.PI * 2
}, {
duration: animationDuration,
easing: tween.easeOut,
onFinish: function onFinish() {
if (particle && particle.destroy) particle.destroy();
}
});
};
}(rainbowParticle, targetX, targetY), staggerDelay);
}
} catch (e) {
console.log('Rainbow explosion creation error:', e);
}
};
self.explodeAndClear = function (targetType) {
// Find all flowers of target type and clear them
var clearedFlowers = [];
try {
for (var y = 0; y < grid.gridSize; y++) {
for (var x = 0; x < grid.gridSize; x++) {
var flower = grid.flowers[y][x];
if (flower && flower.flowerType === targetType && !flower.isShimmer && !flower.isRainbow && flower !== self) {
clearedFlowers.push({
x: x,
y: y
});
}
}
}
// Animate explosion for each cleared flower with staggered timing
for (var i = 0; i < clearedFlowers.length; i++) {
var pos = clearedFlowers[i];
// Validate array bounds and flower existence
if (pos.y >= 0 && pos.y < grid.gridSize && pos.x >= 0 && pos.x < grid.gridSize && grid.flowers[pos.y] && grid.flowers[pos.y][pos.x]) {
var flower = grid.flowers[pos.y][pos.x];
// Stagger the explosions for visual effect
(function (f, delay) {
LK.setTimeout(function () {
if (f && f.animateMatch) f.animateMatch();
}, delay);
})(flower, i * 50);
grid.flowers[pos.y][pos.x] = null;
}
}
} catch (e) {
console.log('Rainbow clear error:', e);
}
return clearedFlowers.length;
};
return self;
});
var Grid = Container.expand(function () {
var self = Container.call(this);
self.gridSize = 8;
self.cellSize = 200;
self.flowers = [];
self.flowerTypes = ['rose', 'tulip', 'daisy', 'sunflower', 'lily'];
// Initialize 2D array
for (var i = 0; i < self.gridSize; i++) {
self.flowers[i] = [];
for (var j = 0; j < self.gridSize; j++) {
self.flowers[i][j] = null;
}
}
self.createFlower = function (gridX, gridY, flowerType, special) {
// Validate input parameters
if (gridX < 0 || gridX >= self.gridSize || gridY < 0 || gridY >= self.gridSize) {
return null;
}
if (!self.flowers[gridY]) {
self.flowers[gridY] = [];
}
var type = flowerType || self.flowerTypes[Math.floor(Math.random() * self.flowerTypes.length)];
var flower;
try {
if (special === 'shimmer') {
flower = new ShimmerFlower(type);
} else if (special === 'rainbow') {
flower = new RainbowFlower(type);
} else {
flower = new Flower(type);
}
if (!flower) return null;
flower.gridX = gridX;
flower.gridY = gridY;
flower.x = gridX * self.cellSize + self.cellSize / 2;
flower.y = gridY * self.cellSize + self.cellSize / 2;
self.flowers[gridY][gridX] = flower;
self.addChild(flower);
return flower;
} catch (e) {
console.log('Error creating flower:', e);
return null;
}
};
self.initializeGrid = function () {
// Initialize 2D array properly
for (var y = 0; y < self.gridSize; y++) {
if (!self.flowers[y]) self.flowers[y] = [];
for (var x = 0; x < self.gridSize; x++) {
try {
self.createFlower(x, y);
} catch (e) {
console.log('Error creating flower at', x, y, e);
}
}
}
};
self.swapFlowers = function (flower1, flower2, callback) {
if (!flower1 || !flower2 || flower1.destroyed || flower2.destroyed) {
if (callback) callback();
return;
}
var tempX = flower1.gridX;
var tempY = flower1.gridY;
// Update grid positions
flower1.gridX = flower2.gridX;
flower1.gridY = flower2.gridY;
flower2.gridX = tempX;
flower2.gridY = tempY;
// Update array
self.flowers[flower1.gridY][flower1.gridX] = flower1;
self.flowers[flower2.gridY][flower2.gridX] = flower2;
// Animate swap with smooth, consistent movement
var targetX1 = flower1.gridX * self.cellSize + self.cellSize / 2;
var targetY1 = flower1.gridY * self.cellSize + self.cellSize / 2;
var targetX2 = flower2.gridX * self.cellSize + self.cellSize / 2;
var targetY2 = flower2.gridY * self.cellSize + self.cellSize / 2;
var completed = 0;
function onComplete() {
completed++;
if (completed === 2 && callback) {
callback();
}
}
// Stop any existing tweens on flowers to prevent conflicts
tween.stop(flower1);
tween.stop(flower2);
// Ensure flowers are in proper state
flower1.scaleX = 1.0;
flower1.scaleY = 1.0;
flower1.rotation = 0;
flower2.scaleX = 1.0;
flower2.scaleY = 1.0;
flower2.rotation = 0;
// Create smooth arc motion effect
var midX1 = (flower1.x + targetX1) / 2;
var midY1 = (flower1.y + targetY1) / 2 - 30;
var midX2 = (flower2.x + targetX2) / 2;
var midY2 = (flower2.y + targetY2) / 2 - 30;
// First phase - move to mid point with slight scale effect
tween(flower1, {
x: midX1,
y: midY1,
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 125,
easing: tween.easeOut,
onFinish: function onFinish() {
// Second phase - complete the swap
tween(flower1, {
x: targetX1,
y: targetY1,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 125,
easing: tween.easeIn,
onFinish: onComplete
});
}
});
tween(flower2, {
x: midX2,
y: midY2,
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 125,
easing: tween.easeOut,
onFinish: function onFinish() {
// Second phase - complete the swap
tween(flower2, {
x: targetX2,
y: targetY2,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 125,
easing: tween.easeIn,
onFinish: onComplete
});
}
});
};
self.findMatches = function () {
var matches = [];
var matchGroups = [];
// Check horizontal matches
for (var y = 0; y < self.gridSize; y++) {
if (!self.flowers[y]) continue;
var count = 1;
var currentType = self.flowers[y][0] ? self.flowers[y][0].flowerType : null;
for (var x = 1; x < self.gridSize; x++) {
var flower = self.flowers[y][x] || null;
if (flower && flower.flowerType === currentType) {
count++;
} else {
if (count >= 3 && currentType) {
var group = {
type: currentType,
count: count,
positions: []
};
for (var i = x - count; i < x; i++) {
var pos = {
x: i,
y: y
};
matches.push(pos);
group.positions.push(pos);
}
matchGroups.push(group);
}
count = 1;
currentType = flower ? flower.flowerType : null;
}
}
if (count >= 3 && currentType) {
var group = {
type: currentType,
count: count,
positions: []
};
for (var i = self.gridSize - count; i < self.gridSize; i++) {
var pos = {
x: i,
y: y
};
matches.push(pos);
group.positions.push(pos);
}
matchGroups.push(group);
}
}
// Check vertical matches
for (var x = 0; x < self.gridSize; x++) {
var count = 1;
var currentType = self.flowers[0] && self.flowers[0][x] ? self.flowers[0][x].flowerType : null;
for (var y = 1; y < self.gridSize; y++) {
if (!self.flowers[y]) continue;
var flower = self.flowers[y][x] || null;
if (flower && flower.flowerType === currentType) {
count++;
} else {
if (count >= 3 && currentType) {
var group = {
type: currentType,
count: count,
positions: []
};
for (var i = y - count; i < y; i++) {
var pos = {
x: x,
y: i
};
matches.push(pos);
group.positions.push(pos);
}
matchGroups.push(group);
}
count = 1;
currentType = flower ? flower.flowerType : null;
}
}
if (count >= 3 && currentType) {
var group = {
type: currentType,
count: count,
positions: []
};
for (var i = self.gridSize - count; i < self.gridSize; i++) {
var pos = {
x: x,
y: i
};
matches.push(pos);
group.positions.push(pos);
}
matchGroups.push(group);
}
}
return {
matches: matches,
groups: matchGroups
};
};
self.removeMatches = function (matchResult, callback) {
if (!matchResult || matchResult.matches.length === 0) {
if (callback) callback();
return;
}
var matches = matchResult.matches;
var groups = matchResult.groups || [];
var powerUpsCreated = [];
var animationsCompleted = 0;
var totalAnimations = 0;
var callbackExecuted = false;
// Check for power-up creation opportunities
try {
for (var g = 0; g < groups.length; g++) {
var group = groups[g];
if (group && group.count >= 5 && group.positions && group.positions.length > 0) {
// Create rainbow flower at center position
var centerPos = group.positions[Math.floor(group.positions.length / 2)];
powerUpsCreated.push({
pos: centerPos,
type: group.type,
special: 'rainbow'
});
} else if (group && group.count >= 4 && group.positions && group.positions.length > 0) {
// Create shimmer flower at center position
var centerPos = group.positions[Math.floor(group.positions.length / 2)];
powerUpsCreated.push({
pos: centerPos,
type: group.type,
special: 'shimmer'
});
}
}
} catch (e) {
console.log('Power-up creation error:', e);
}
function onAnimationComplete() {
animationsCompleted++;
if (animationsCompleted === totalAnimations && !callbackExecuted) {
callbackExecuted = true;
// Create power-up flowers after all match animations complete
try {
for (var p = 0; p < powerUpsCreated.length; p++) {
var powerUp = powerUpsCreated[p];
// Validate position is within bounds and empty
if (powerUp && powerUp.pos && powerUp.pos.y >= 0 && powerUp.pos.y < self.gridSize && powerUp.pos.x >= 0 && powerUp.pos.x < self.gridSize && self.flowers[powerUp.pos.y] && !self.flowers[powerUp.pos.y][powerUp.pos.x]) {
var newPowerUpFlower = self.createFlower(powerUp.pos.x, powerUp.pos.y, powerUp.type, powerUp.special);
// Add visual feedback for power-up creation
newPowerUpFlower.scaleX = 0.3;
newPowerUpFlower.scaleY = 0.3;
newPowerUpFlower.alpha = 0.8;
tween(newPowerUpFlower, {
scaleX: 1.1,
scaleY: 1.1,
alpha: 1.0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(newPowerUpFlower, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut
});
}
});
}
}
} catch (e) {
console.log('Power-up placement error:', e);
}
// Execute callback with timeout to prevent stack overflow
LK.setTimeout(function () {
if (callback) callback();
}, 16);
}
}
// Process matches and start animations
for (var i = 0; i < matches.length; i++) {
var match = matches[i];
if (match && match.y >= 0 && match.y < self.gridSize && match.x >= 0 && match.x < self.gridSize && self.flowers[match.y] && self.flowers[match.y][match.x]) {
var flower = self.flowers[match.y][match.x];
if (flower && flower.animateMatch) {
self.flowers[match.y][match.x] = null; // Clear immediately
totalAnimations++;
flower.animateMatch(onAnimationComplete);
}
}
}
// Handle case where no animations are needed
if (totalAnimations === 0) {
// Create power-ups immediately if no animations
try {
for (var p = 0; p < powerUpsCreated.length; p++) {
var powerUp = powerUpsCreated[p];
if (powerUp && powerUp.pos && powerUp.pos.y >= 0 && powerUp.pos.y < self.gridSize && powerUp.pos.x >= 0 && powerUp.pos.x < self.gridSize && self.flowers[powerUp.pos.y] && !self.flowers[powerUp.pos.y][powerUp.pos.x]) {
var newPowerUpFlower = self.createFlower(powerUp.pos.x, powerUp.pos.y, powerUp.type, powerUp.special);
newPowerUpFlower.scaleX = 0.2;
newPowerUpFlower.scaleY = 0.2;
tween(newPowerUpFlower, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 300,
easing: tween.easeOut
});
}
}
} catch (e) {
console.log('Immediate power-up creation error:', e);
}
if (callback) callback();
}
};
self.dropFlowers = function (callback) {
var animations = 0;
var animationCompleted = false;
var timeoutId = null;
function onAnimationComplete() {
animations--;
if (animations === 0 && !animationCompleted && callback) {
animationCompleted = true;
if (timeoutId) LK.clearTimeout(timeoutId);
callback();
}
}
// Safety timeout to prevent infinite waiting
timeoutId = LK.setTimeout(function () {
if (!animationCompleted && callback) {
animationCompleted = true;
callback();
}
}, 5000);
// Process each column independently
for (var x = 0; x < self.gridSize; x++) {
// Ensure column exists
if (!self.flowers) self.flowers = [];
for (var y = 0; y < self.gridSize; y++) {
if (!self.flowers[y]) self.flowers[y] = [];
}
// Collect existing flowers in this column (bottom to top)
var existingFlowers = [];
for (var y = self.gridSize - 1; y >= 0; y--) {
if (self.flowers[y][x]) {
existingFlowers.push(self.flowers[y][x]);
self.flowers[y][x] = null; // Clear the position
}
}
// Place existing flowers at bottom positions
var currentPos = self.gridSize - 1;
for (var i = 0; i < existingFlowers.length; i++) {
var flower = existingFlowers[i];
if (flower && currentPos >= 0) {
self.flowers[currentPos][x] = flower;
flower.gridX = x;
flower.gridY = currentPos;
// Animate fall if needed
var targetY = currentPos * self.cellSize + self.cellSize / 2;
if (Math.abs(flower.y - targetY) > 10) {
animations++;
(function (f, ty) {
// Stop any existing fall animations
tween.stop(f);
f.isFalling = false;
f.animateFall(ty, onAnimationComplete);
})(flower, targetY);
} else {
flower.y = targetY; // Snap to position if very close
}
currentPos--;
}
}
// Create new flowers for remaining empty spaces
for (var y = 0; y <= currentPos; y++) {
var newFlower = self.createFlower(x, y);
if (newFlower) {
newFlower.y = -self.cellSize - (currentPos - y) * 50; // Stagger starting positions
var targetY = y * self.cellSize + self.cellSize / 2;
animations++;
(function (f, ty) {
f.animateFall(ty, onAnimationComplete);
})(newFlower, targetY);
}
}
}
// Handle case where no animations are needed
if (animations === 0 && callback) {
callback();
}
};
self.isAdjacent = function (flower1, flower2) {
var dx = Math.abs(flower1.gridX - flower2.gridX);
var dy = Math.abs(flower1.gridY - flower2.gridY);
return dx === 1 && dy === 0 || dx === 0 && dy === 1;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x98fb98
});
/****
* Game Code
****/
// Create beautiful flower garden background
var gardenBg = game.attachAsset('gardenBackground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
var grid = new Grid();
var selectedFlower = null;
var isSwapping = false;
var isMatching = false;
// Position grid in center of screen
grid.x = (2048 - grid.gridSize * grid.cellSize) / 2;
grid.y = (2732 - grid.gridSize * grid.cellSize) / 2 + 200;
game.addChild(grid);
// Score display
var scoreTxt = new Text2('Score: 0', {
size: 120,
fill: 0x2D5016
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// High score display
var highScoreTxt = new Text2('High Score: 0', {
size: 80,
fill: 0x1a3d0b
});
highScoreTxt.anchor.set(0.5, 0);
highScoreTxt.y = 140;
LK.gui.top.addChild(highScoreTxt);
// Initialize high score from storage with error handling
var highScore = 0;
try {
if (storage && typeof storage.highScore === 'number') {
highScore = storage.highScore;
} else {
highScore = 0;
}
} catch (e) {
console.log('Storage read error, using default high score');
highScore = 0;
}
try {
if (highScoreTxt && highScoreTxt.setText) {
highScoreTxt.setText('High Score: ' + highScore);
}
} catch (e) {
console.log('High score display initialization error:', e);
}
// Centralized high score update system with debouncing
var highScoreUpdateTimer = null;
var pendingHighScoreUpdate = false;
var lastHighScoreUpdate = 0;
game.updateHighScore = function (newScore) {
if (!newScore || typeof newScore !== 'number') return;
var currentTime = Date.now();
var updateDelay = LK.getScore() > 8000 ? 2000 : LK.getScore() > 5000 ? 1500 : 500;
if (newScore > highScore && !pendingHighScoreUpdate && currentTime - lastHighScoreUpdate > updateDelay) {
lastHighScoreUpdate = currentTime;
highScore = newScore;
try {
if (highScoreTxt && highScoreTxt.setText) {
highScoreTxt.setText('High Score: ' + highScore);
}
} catch (e) {
console.log('High score display error:', e);
}
pendingHighScoreUpdate = true;
// Increased debounce time for high scores to prevent performance issues
if (highScoreUpdateTimer) {
LK.clearTimeout(highScoreUpdateTimer);
}
var storageDelay = LK.getScore() > 8000 ? 3000 : LK.getScore() > 5000 ? 2000 : 1000;
highScoreUpdateTimer = LK.setTimeout(function () {
try {
if (storage) {
storage.highScore = highScore;
}
} catch (e) {
console.log('Storage write error:', e);
}
highScoreUpdateTimer = null;
pendingHighScoreUpdate = false;
}, storageDelay);
}
};
// Add performance monitoring variables
var performanceOptimized = false;
var particlePool = [];
var maxPoolSize = 50;
// Performance optimization function
function optimizeForHighScore() {
if (!performanceOptimized && LK.getScore() > 5000) {
performanceOptimized = true;
// Reduce animation complexity for better performance
maxMatchChecks = 15;
minSwipeDistance = 40;
console.log('Performance mode activated for high score gameplay');
}
}
// Initialize grid with staggered creation to prevent startup lag
var initializationStep = 0;
var totalCells = grid.gridSize * grid.gridSize;
function initializeGridStep() {
var cellsPerStep = 8; // Create 8 flowers per frame
var endStep = Math.min(initializationStep + cellsPerStep, totalCells);
for (var i = initializationStep; i < endStep; i++) {
var x = i % grid.gridSize;
var y = Math.floor(i / grid.gridSize);
try {
grid.createFlower(x, y);
} catch (e) {
console.log('Error creating flower at', x, y, e);
}
}
initializationStep = endStep;
if (initializationStep < totalCells) {
LK.setTimeout(initializeGridStep, 16); // Continue next frame
} else {
// Grid initialization complete, start game
LK.setTimeout(function () {
game.checkForMatches();
}, 300);
}
}
// Start staggered initialization
initializeGridStep();
game.selectFlower = function (flower) {
// Prevent selection during animations
if (isSwapping || isMatching || !flower || flower.destroyed) {
return;
}
if (selectedFlower === null) {
// First selection with smooth scale animation
selectedFlower = flower;
// Stop any existing tweens to prevent conflicts
tween.stop(flower);
// Set initial state for smooth animation
flower.scaleX = 1.0;
flower.scaleY = 1.0;
flower.rotation = 0;
tween(flower, {
scaleX: 1.15,
scaleY: 1.15
}, {
duration: 200,
easing: tween.easeInOut
});
// Add smooth pulse effect
tween(flower, {
rotation: 0.05
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(flower, {
rotation: -0.05
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(flower, {
rotation: 0
}, {
duration: 200,
easing: tween.easeInOut
});
}
});
}
});
} else if (selectedFlower === flower) {
// Deselect same flower with smooth transition
tween.stop(flower);
tween(flower, {
scaleX: 1,
scaleY: 1,
rotation: 0
}, {
duration: 250,
easing: tween.easeInOut
});
selectedFlower = null;
} else if (grid.isAdjacent(selectedFlower, flower)) {
// Valid swap
isSwapping = true;
var firstFlower = selectedFlower;
// Smooth deselection animation
tween.stop(firstFlower);
tween(firstFlower, {
scaleX: 1,
scaleY: 1,
rotation: 0
}, {
duration: 200,
easing: tween.easeInOut
});
LK.getSound('swap').play();
// Check for special flower activation during swap
if (firstFlower.isShimmer) {
// Shimmer flower swapped - activate shimmer effect
selectedFlower = null;
firstFlower.activateShimmerEffect();
isSwapping = false;
} else if (flower.isShimmer) {
// Target flower is shimmer - activate shimmer effect
selectedFlower = null;
flower.activateShimmerEffect();
isSwapping = false;
} else if (firstFlower.isRainbow && flower.flowerType && !flower.isRainbow && !flower.isShimmer) {
// Rainbow flower swapped with regular flower - activate rainbow effect
selectedFlower = null;
firstFlower.activateRainbowEffect(flower.flowerType);
isSwapping = false;
} else if (flower.isRainbow && firstFlower.flowerType && !firstFlower.isRainbow && !firstFlower.isShimmer) {
// Target flower is rainbow - activate rainbow effect
selectedFlower = null;
flower.activateRainbowEffect(firstFlower.flowerType);
isSwapping = false;
} else {
// Regular swap
grid.swapFlowers(firstFlower, flower, function () {
selectedFlower = null;
isSwapping = false;
game.checkForMatches();
});
}
} else {
// Invalid swap - select new flower with smooth transition
tween.stop(selectedFlower);
tween(selectedFlower, {
scaleX: 1,
scaleY: 1,
rotation: 0
}, {
duration: 250,
easing: tween.easeInOut
});
selectedFlower = flower;
// Stop any existing tweens on new flower
tween.stop(flower);
flower.scaleX = 1.0;
flower.scaleY = 1.0;
flower.rotation = 0;
tween(flower, {
scaleX: 1.15,
scaleY: 1.15
}, {
duration: 200,
easing: tween.easeInOut
});
// Add smooth pulse effect for new selection
tween(flower, {
rotation: 0.05
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(flower, {
rotation: -0.05
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(flower, {
rotation: 0
}, {
duration: 200,
easing: tween.easeInOut
});
}
});
}
});
}
};
var matchCheckCount = 0;
var maxMatchChecks = 20;
var lastMatchCheckTime = 0;
var matchCheckInProgress = false;
game.checkForMatches = function () {
// Prevent multiple simultaneous match checks and infinite loops
var currentTime = Date.now();
var minDelay = LK.getScore() > 8000 ? 100 : LK.getScore() > 5000 ? 120 : 150;
if (isMatching || isSwapping || matchCheckInProgress || matchCheckCount >= maxMatchChecks || currentTime - lastMatchCheckTime < minDelay) {
return;
}
lastMatchCheckTime = currentTime;
matchCheckCount++;
matchCheckInProgress = true;
try {
var matchResult = grid.findMatches();
if (matchResult && matchResult.matches && matchResult.matches.length > 0) {
isMatching = true;
// Reduce sound frequency at high scores to prevent audio lag
if (LK.getScore() <= 8000) {
try {
LK.getSound('match').play();
} catch (e) {
console.log('Sound play error:', e);
}
}
// Update score using LK score system only
var newScore = LK.getScore() + matchResult.matches.length * 10;
LK.setScore(newScore);
if (scoreTxt && scoreTxt.setText) scoreTxt.setText('Score: ' + newScore);
// Optimized high score update with throttling for high scores
if (newScore <= 8000 || newScore % 100 === 0) {
// Only update high score every 100 points at high scores
try {
game.updateHighScore(newScore);
} catch (e) {
console.log('High score update error:', e);
}
}
grid.removeMatches(matchResult, function () {
grid.dropFlowers(function () {
isMatching = false;
matchCheckInProgress = false;
// Check for cascading matches with adaptive timing
var cascadeDelay = LK.getScore() > 8000 ? 250 : LK.getScore() > 5000 ? 300 : 400;
LK.setTimeout(function () {
if (matchCheckCount < maxMatchChecks && !isMatching && !isSwapping) {
game.checkForMatches();
} else {
matchCheckCount = 0; // Reset counter
matchCheckInProgress = false;
}
}, cascadeDelay);
});
});
} else {
// No matches found - ensure matching flag is cleared
isMatching = false;
matchCheckInProgress = false;
matchCheckCount = 0; // Reset counter when no matches
}
} catch (e) {
console.log('Match check error:', e);
isMatching = false;
matchCheckInProgress = false;
matchCheckCount = 0;
}
};
// Swipe detection variables
var swipeStartFlower = null;
var swipeStartX = 0;
var swipeStartY = 0;
var isSwipeActive = false;
var minSwipeDistance = 50;
// Add swipe detection to game
game.down = function (x, y, obj) {
if (isSwapping || isMatching) return;
// Convert screen coordinates to game coordinates safely
var gamePos;
try {
if (obj && obj.parent && obj.parent.toGlobal && obj.position) {
gamePos = game.toLocal(obj.parent.toGlobal(obj.position));
} else {
// Fallback to using direct x,y coordinates
gamePos = {
x: x,
y: y
};
}
} catch (e) {
// Fallback to using direct x,y coordinates if conversion fails
gamePos = {
x: x,
y: y
};
}
// Find flower at touch position more accurately
var touchedFlower = null;
var gridLocalX = gamePos.x - grid.x;
var gridLocalY = gamePos.y - grid.y;
// More precise grid coordinate calculation
var gridX = Math.floor((gridLocalX + grid.cellSize * 0.1) / grid.cellSize);
var gridY = Math.floor((gridLocalY + grid.cellSize * 0.1) / grid.cellSize);
// Validate grid bounds and flower existence
if (gridX >= 0 && gridX < grid.gridSize && gridY >= 0 && gridY < grid.gridSize && grid.flowers[gridY] && grid.flowers[gridY][gridX]) {
touchedFlower = grid.flowers[gridY][gridX];
}
if (touchedFlower) {
swipeStartFlower = touchedFlower;
swipeStartX = gamePos.x;
swipeStartY = gamePos.y;
isSwipeActive = true;
}
};
game.up = function (x, y, obj) {
if (!isSwipeActive || !swipeStartFlower || isSwapping || isMatching) {
isSwipeActive = false;
swipeStartFlower = null;
return;
}
// Convert end coordinates to game coordinates safely
var gameEndPos;
try {
if (obj && obj.parent && obj.parent.toGlobal && obj.position) {
gameEndPos = game.toLocal(obj.parent.toGlobal(obj.position));
} else {
// Fallback to using direct x,y coordinates
gameEndPos = {
x: x,
y: y
};
}
} catch (e) {
// Fallback to using direct x,y coordinates if conversion fails
gameEndPos = {
x: x,
y: y
};
}
var deltaX = gameEndPos.x - swipeStartX;
var deltaY = gameEndPos.y - swipeStartY;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance >= minSwipeDistance) {
// Determine swipe direction with better accuracy
var targetGridX = swipeStartFlower.gridX;
var targetGridY = swipeStartFlower.gridY;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// Horizontal swipe
if (deltaX > 0) {
targetGridX++; // Right
} else {
targetGridX--; // Left
}
} else {
// Vertical swipe
if (deltaY > 0) {
targetGridY++; // Down
} else {
targetGridY--; // Up
}
}
// Check if target position is valid and has a flower
if (targetGridX >= 0 && targetGridX < grid.gridSize && targetGridY >= 0 && targetGridY < grid.gridSize && grid.flowers[targetGridY] && grid.flowers[targetGridY][targetGridX]) {
var targetFlower = grid.flowers[targetGridY][targetGridX];
// Use smooth selection and swap with proper timing
if (swipeStartFlower && swipeStartFlower.scaleX !== undefined) {
tween(swipeStartFlower, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 100,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (swipeStartFlower && swipeStartFlower.scaleX !== undefined) {
tween(swipeStartFlower, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeInOut
});
}
if (swipeStartFlower) {
game.selectFlower(swipeStartFlower);
LK.setTimeout(function () {
if (targetFlower) {
game.selectFlower(targetFlower);
}
}, 50);
}
}
});
}
}
}
isSwipeActive = false;
swipeStartFlower = null;
};
// Add move handler for visual feedback during swipe
game.move = function (x, y, obj) {
if (!isSwipeActive || !swipeStartFlower || isSwapping || isMatching) return;
// Convert coordinates for consistent tracking safely
var gameMovePos;
try {
if (obj && obj.parent && obj.parent.toGlobal && obj.position) {
gameMovePos = game.toLocal(obj.parent.toGlobal(obj.position));
} else {
// Fallback to using direct x,y coordinates
gameMovePos = {
x: x,
y: y
};
}
} catch (e) {
// Fallback to using direct x,y coordinates if conversion fails
gameMovePos = {
x: x,
y: y
};
}
var deltaX = gameMovePos.x - swipeStartX;
var deltaY = gameMovePos.y - swipeStartY;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// Provide visual feedback when swipe distance is sufficient
if (distance >= minSwipeDistance * 0.7) {
// Scale up the flower being swiped to indicate readiness
if (swipeStartFlower && swipeStartFlower.scaleX !== undefined && swipeStartFlower.scaleX <= 1.0) {
tween.stop(swipeStartFlower);
tween(swipeStartFlower, {
scaleX: 1.08,
scaleY: 1.08
}, {
duration: 150,
easing: tween.easeOut
});
}
} else if (swipeStartFlower && swipeStartFlower.scaleX !== undefined && swipeStartFlower.scaleX > 1.0) {
// Scale back down if swipe distance is insufficient
tween.stop(swipeStartFlower);
tween(swipeStartFlower, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 150,
easing: tween.easeOut
});
}
};
// Initial match check after grid creation
LK.setTimeout(function () {
game.checkForMatches();
}, 300);
game.update = function () {
// Game runs automatically through event-driven mechanics
// Add basic error catching for any update cycle issues
try {
// Ensure game state consistency
if (typeof isMatching === 'undefined') isMatching = false;
if (typeof isSwapping === 'undefined') isSwapping = false;
if (typeof matchCheckCount === 'undefined') matchCheckCount = 0;
// Performance optimization check every 60 frames
if (LK.ticks % 60 === 0) {
optimizeForHighScore();
}
// Memory management - clean up unused tweens every 300 frames
if (LK.ticks % 300 === 0 && LK.getScore() > 5000) {
// Force garbage collection opportunity
if (particlePool.length > maxPoolSize) {
particlePool.length = maxPoolSize;
}
}
// Adaptive frame skipping for very high scores to maintain smoothness
if (LK.getScore() > 10000 && LK.ticks % 2 === 0) {
return; // Skip every other frame at very high scores
}
} catch (e) {
console.log('Game update error:', e);
}
};
Tulip. The flower is facing at the center. Make it vibrant. In-Game asset. 2d. High contrast. No shadows
Rose. Front. Make it vibrant. In-Game asset. 2d. High contrast. No shadows
Daisy. Front. Make it vibrant.. In-Game asset. 2d. High contrast. No shadows
Sunflower. Front. Make it vibrant.. In-Game asset. 2d. High contrast. No shadows
Lily. Front. Make it vibrant.. In-Game asset. 2d. High contrast. No shadows
Grass background.. In-Game asset. 2d. High contrast. No shadows
Flowery particle. Vibrant.. In-Game asset. 2d. High contrast. No shadows
Shimmer effect. Vibrant.. In-Game asset. 2d. High contrast. No shadows
Rainbow effect. Vibrant.. In-Game asset. 2d. High contrast. No shadows
Blossom match with rose,lily,sunflower,tulips,and daisy. Vibrant.. In-Game asset. 2d. High contrast. No shadows