/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Candy = Container.expand(function (type, gridX, gridY) {
var self = Container.call(this);
self.candyType = type;
self.gridX = gridX;
self.gridY = gridY;
self.isMoving = false;
self.isMarkedForDestroy = false;
var candyGraphics = self.attachAsset('candy_' + type, {
anchorX: 0.5,
anchorY: 0.5
});
self.moveTo = function (newGridX, newGridY, duration) {
self.gridX = newGridX;
self.gridY = newGridY;
self.isMoving = true;
var targetX = GRID_START_X + newGridX * CELL_SIZE + CELL_SIZE / 2;
var targetY = GRID_START_Y + newGridY * CELL_SIZE + CELL_SIZE / 2;
// Add slight anticipation before movement for falling candies
var isVerticalMove = Math.abs(self.y - targetY) > Math.abs(self.x - targetX);
if (isVerticalMove && targetY > self.y) {
// Falling motion - use bounce easing
tween(self, {
x: targetX,
y: targetY
}, {
duration: duration || 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Add subtle landing effect
tween(self, {
scaleX: 1.1,
scaleY: 0.9
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
self.isMoving = false;
}
});
}
});
}
});
} else {
// Horizontal movement or upward movement
tween(self, {
x: targetX,
y: targetY
}, {
duration: duration || 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
self.isMoving = false;
}
});
}
};
self.destroy = function () {
self.isMarkedForDestroy = true;
tween(self, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
};
return self;
});
var GridCell = Container.expand(function (gridX, gridY) {
var self = Container.call(this);
self.gridX = gridX;
self.gridY = gridY;
var cellGraphics = self.attachAsset('grid_cell', {
anchorX: 0.5,
anchorY: 0.5
});
cellGraphics.alpha = 0.3;
return self;
});
var HoneyCandy = Container.expand(function (type, gridX, gridY) {
var self = Container.call(this);
self.candyType = type;
self.gridX = gridX;
self.gridY = gridY;
self.isMoving = false;
self.isMarkedForDestroy = false;
self.isSticky = true;
self.movesSlowly = true;
var candyGraphics = self.attachAsset('candy_' + type, {
anchorX: 0.5,
anchorY: 0.5
});
var honeyOverlay = self.attachAsset('honey_overlay', {
anchorX: 0.5,
anchorY: 0.5
});
honeyOverlay.alpha = 0.4;
self.moveTo = function (newGridX, newGridY, duration) {
self.gridX = newGridX;
self.gridY = newGridY;
self.isMoving = true;
var targetX = GRID_START_X + newGridX * CELL_SIZE + CELL_SIZE / 2;
var targetY = GRID_START_Y + newGridY * CELL_SIZE + CELL_SIZE / 2;
// Honey moves slower
var slowDuration = (duration || 300) * 1.5;
tween(self, {
x: targetX,
y: targetY
}, {
duration: slowDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
self.isMoving = false;
}
});
};
self.destroy = function () {
self.isMarkedForDestroy = true;
tween(self, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
};
return self;
});
var LockedCandy = Container.expand(function (type, gridX, gridY, lockLevel) {
var self = Container.call(this);
self.candyType = type;
self.gridX = gridX;
self.gridY = gridY;
self.isMoving = false;
self.isMarkedForDestroy = false;
self.isLocked = true;
self.lockLevel = lockLevel || 1; // How many matches needed to unlock
var candyGraphics = self.attachAsset('candy_' + type, {
anchorX: 0.5,
anchorY: 0.5
});
var lockOverlay = self.attachAsset('locked_candy', {
anchorX: 0.5,
anchorY: 0.5
});
lockOverlay.alpha = 0.7;
self.unlock = function () {
self.lockLevel--;
if (self.lockLevel <= 0) {
self.isLocked = false;
tween(lockOverlay, {
alpha: 0,
scaleX: 0,
scaleY: 0
}, {
duration: 300,
easing: tween.easeOut
});
}
};
self.destroy = function () {
self.isMarkedForDestroy = true;
tween(self, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
};
return self;
});
var StoneBlock = Container.expand(function (gridX, gridY, durability) {
var self = Container.call(this);
self.gridX = gridX;
self.gridY = gridY;
self.isMoving = false;
self.isMarkedForDestroy = false;
self.isObstacle = true;
self.durability = durability || 2;
var stoneGraphics = self.attachAsset('stone_block', {
anchorX: 0.5,
anchorY: 0.5
});
self.hit = function () {
self.durability--;
// Visual feedback for hit
tween(self, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeIn
});
}
});
if (self.durability <= 0) {
self.destroy();
}
};
self.destroy = function () {
self.isMarkedForDestroy = true;
createExplosionEffect(self.x, self.y);
tween(self, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2a1810
});
/****
* Game Code
****/
// Create beautiful gradient background
// Beautiful gradient background layers
// Decorative elements
var backgroundContainer = new Container();
game.addChild(backgroundContainer);
// Create multiple layers for depth
var bgLayer1 = LK.getAsset('bg_gradient1', {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
var bgLayer2 = LK.getAsset('bg_gradient2', {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
var bgLayer3 = LK.getAsset('bg_pattern', {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
backgroundContainer.addChild(bgLayer1);
backgroundContainer.addChild(bgLayer2);
backgroundContainer.addChild(bgLayer3);
// Set transparency for layering effect
bgLayer2.alpha = 0.7;
bgLayer3.alpha = 0.3;
var GRID_WIDTH = 10;
var GRID_HEIGHT = 10;
var CELL_SIZE = 150;
var GRID_START_X = (2048 - GRID_WIDTH * CELL_SIZE) / 2;
var GRID_START_Y = 750;
var CANDY_TYPES = ['green', 'blue', 'orange', 'yellow', 'red'];
var grid = [];
var candies = [];
var selectedCandy = null;
var draggedCandy = null;
var isDragging = false;
var dragStartX = 0;
var dragStartY = 0;
var movesLeft = 20;
var currentScore = 0;
var targetScore = 1000;
var isProcessing = false;
var currentLevel = 1;
var maxLevel = 1;
var gameState = 'menu'; // 'menu', 'playing', 'settings'
var menuContainer = null;
var settingsContainer = null;
var soundEnabled = true;
var musicEnabled = true;
var masterVolume = 1.0;
var playerLives = 5;
var maxLives = 5;
var livesRegenerationTimer = null;
var lastLifeLostTime = null;
var livesRegenerationInterval = 600000; // 10 minutes in milliseconds - doubled regeneration time
// Level configurations with challenging obstacles and requirements - Extended to 500 levels
var levelConfigs = [];
// Generate 500 levels with progressive difficulty
for (var levelNum = 1; levelNum <= 500; levelNum++) {
// Base difficulty scaling - significantly increased
var difficultyMultiplier = Math.floor((levelNum - 1) / 5) + 1;
var baseScore = 1500 + (levelNum - 1) * 200; // Increased from 1000 + 150
var baseMoves = Math.max(3, 18 - Math.floor(levelNum / 8)); // Reduced from 20 and faster decrease
// Obstacle scaling with caps to prevent impossibility
var stoneBlocks = Math.min(25, Math.floor(2 + levelNum * 0.5)); // Increased from 1 + 0.4
var lockedCandies = Math.min(20, Math.floor(levelNum * 0.4)); // Increased from 0.3
var honeyCandies = Math.min(15, Math.floor(levelNum * 0.3)); // Increased from 0.25
// Special milestone levels (every 50 levels) with increased difficulty
if (levelNum % 50 === 0) {
baseScore = Math.floor(baseScore * 1.8); // Increased from 1.5
baseMoves = Math.max(2, baseMoves - 3); // Reduced from 3 and increased penalty
stoneBlocks = Math.min(30, Math.floor(stoneBlocks * 1.4)); // Increased from 1.3
lockedCandies = Math.min(25, Math.floor(lockedCandies * 1.4)); // Increased from 1.3
honeyCandies = Math.min(20, Math.floor(honeyCandies * 1.4)); // Increased from 1.3
}
// Boss levels (every 100 levels) with maximum difficulty
if (levelNum % 100 === 0) {
baseScore = Math.floor(baseScore * 2.5); // Increased from 2
baseMoves = Math.max(2, baseMoves - 4); // Reduced from 3 and increased penalty
stoneBlocks = Math.min(35, Math.floor(stoneBlocks * 1.6)); // Increased from 1.5
lockedCandies = Math.min(30, Math.floor(lockedCandies * 1.6)); // Increased from 1.5
honeyCandies = Math.min(25, Math.floor(honeyCandies * 1.6)); // Increased from 1.5
}
levelConfigs.push({
level: levelNum,
targetScore: baseScore,
moves: baseMoves,
gridSize: 10,
obstacles: {
stoneBlocks: stoneBlocks,
lockedCandies: lockedCandies,
honeyCandies: honeyCandies
}
});
}
// Initialize grid cells
for (var y = 0; y < GRID_HEIGHT; y++) {
grid[y] = [];
for (var x = 0; x < GRID_WIDTH; x++) {
var cell = new GridCell(x, y);
cell.x = GRID_START_X + x * CELL_SIZE + CELL_SIZE / 2;
cell.y = GRID_START_Y + y * CELL_SIZE + CELL_SIZE / 2;
game.addChild(cell);
grid[y][x] = null;
}
}
// UI Elements - Score with button background
var scoreButtonBorder = LK.getAsset('score_button_border', {
anchorX: 0.5,
anchorY: 0.5
});
var scoreButtonBg = LK.getAsset('score_button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
var scoreButtonHighlight = LK.getAsset('score_button_highlight', {
anchorX: 0.5,
anchorY: 0.5
});
// Create score button container
var scoreButtonContainer = new Container();
scoreButtonContainer.addChild(scoreButtonBorder);
scoreButtonContainer.addChild(scoreButtonBg);
scoreButtonContainer.addChild(scoreButtonHighlight);
// Position button elements
scoreButtonBorder.y = 0;
scoreButtonBg.y = -2;
scoreButtonHighlight.y = -5;
scoreButtonHighlight.alpha = 0.6;
// Add gradient effect with tween
tween(scoreButtonHighlight, {
alpha: 0.3
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(scoreButtonHighlight, {
alpha: 0.6
}, {
duration: 2000,
easing: tween.easeInOut
});
}
});
var scoreText = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0.5);
scoreButtonContainer.addChild(scoreText);
LK.gui.top.addChild(scoreButtonContainer);
scoreButtonContainer.y = 90;
scoreButtonContainer.visible = false; // Initially hidden when game opens in menu
// UI Elements - Moves with button background
var movesButtonBorder = LK.getAsset('score_button_border', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
var movesButtonBg = LK.getAsset('score_button_bg', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
var movesButtonHighlight = LK.getAsset('score_button_highlight', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
// Create moves button container
var movesButtonContainer = new Container();
movesButtonContainer.addChild(movesButtonBorder);
movesButtonContainer.addChild(movesButtonBg);
movesButtonContainer.addChild(movesButtonHighlight);
// Position button elements
movesButtonBorder.y = 0;
movesButtonBg.y = -2;
movesButtonHighlight.y = -5;
movesButtonHighlight.alpha = 0.6;
// Add gradient effect with tween
tween(movesButtonHighlight, {
alpha: 0.3
}, {
duration: 2200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(movesButtonHighlight, {
alpha: 0.6
}, {
duration: 2200,
easing: tween.easeInOut
});
}
});
var movesText = new Text2('Moves: ' + movesLeft, {
size: 50,
fill: 0xFFFFFF
});
movesText.anchor.set(0.5, 0.5);
movesButtonContainer.addChild(movesText);
LK.gui.top.addChild(movesButtonContainer);
movesButtonContainer.y = 180;
movesButtonContainer.visible = false; // Initially hidden when game opens in menu
// UI Elements - Target with button background
var targetButtonBorder = LK.getAsset('score_button_border', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7
});
var targetButtonBg = LK.getAsset('score_button_bg', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7
});
var targetButtonHighlight = LK.getAsset('score_button_highlight', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7
});
// Create target button container
var targetButtonContainer = new Container();
targetButtonContainer.addChild(targetButtonBorder);
targetButtonContainer.addChild(targetButtonBg);
targetButtonContainer.addChild(targetButtonHighlight);
// Position button elements
targetButtonBorder.y = 0;
targetButtonBg.y = -2;
targetButtonHighlight.y = -5;
targetButtonHighlight.alpha = 0.6;
// Add gradient effect with tween
tween(targetButtonHighlight, {
alpha: 0.3
}, {
duration: 2400,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(targetButtonHighlight, {
alpha: 0.6
}, {
duration: 2400,
easing: tween.easeInOut
});
}
});
var targetText = new Text2('Target: ' + targetScore, {
size: 40,
fill: 0xFFDD44
});
targetText.anchor.set(0.5, 0.5);
targetButtonContainer.addChild(targetText);
LK.gui.top.addChild(targetButtonContainer);
targetButtonContainer.y = 260;
targetButtonContainer.visible = false; // Initially hidden when game opens in menu
// UI Elements - Level with button background
var levelButtonBorder = LK.getAsset('score_button_border', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
var levelButtonBg = LK.getAsset('score_button_bg', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
var levelButtonHighlight = LK.getAsset('score_button_highlight', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
// Create level button container
var levelButtonContainer = new Container();
levelButtonContainer.addChild(levelButtonBorder);
levelButtonContainer.addChild(levelButtonBg);
levelButtonContainer.addChild(levelButtonHighlight);
// Position button elements
levelButtonBorder.y = 0;
levelButtonBg.y = -2;
levelButtonHighlight.y = -5;
levelButtonHighlight.alpha = 0.6;
// Add gradient effect with tween
tween(levelButtonHighlight, {
alpha: 0.3
}, {
duration: 2600,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(levelButtonHighlight, {
alpha: 0.6
}, {
duration: 2600,
easing: tween.easeInOut
});
}
});
var levelText = new Text2('Level: ' + currentLevel, {
size: 50,
fill: 0x00FF00
});
levelText.anchor.set(0.5, 0.5);
levelButtonContainer.addChild(levelText);
LK.gui.top.addChild(levelButtonContainer);
levelButtonContainer.y = 330;
levelButtonContainer.visible = false; // Initially hidden when game opens in menu
// Menu button
// UI Elements - Lives with button background
var livesButtonBorder = LK.getAsset('score_button_border', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
var livesButtonBg = LK.getAsset('score_button_bg', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
var livesButtonHighlight = LK.getAsset('score_button_highlight', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
// Create lives button container
var livesButtonContainer = new Container();
livesButtonContainer.addChild(livesButtonBorder);
livesButtonContainer.addChild(livesButtonBg);
livesButtonContainer.addChild(livesButtonHighlight);
// Position button elements
livesButtonBorder.y = 0;
livesButtonBg.y = -2;
livesButtonHighlight.y = -5;
livesButtonHighlight.alpha = 0.6;
// Add gradient effect with tween
tween(livesButtonHighlight, {
alpha: 0.3
}, {
duration: 2800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(livesButtonHighlight, {
alpha: 0.6
}, {
duration: 2800,
easing: tween.easeInOut
});
}
});
var livesText = new Text2('Lives: ' + playerLives, {
size: 50,
fill: 0xFF4444
});
livesText.anchor.set(0.5, 0.5);
livesButtonContainer.addChild(livesText);
LK.gui.top.addChild(livesButtonContainer);
livesButtonContainer.y = 400;
livesButtonContainer.visible = false; // Initially hidden when game opens in menu
var menuButtonBorder = LK.getAsset('menu_button_border', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4
});
var menuButton = LK.getAsset('menu_button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4
});
var menuButtonContainer = new Container();
menuButtonContainer.addChild(menuButtonBorder);
menuButtonContainer.addChild(menuButton);
var menuButtonText = new Text2('MENU', {
size: 40,
fill: 0xFFFFFF
});
menuButtonText.anchor.set(0.5, 0.5);
menuButtonContainer.addChild(menuButtonText);
menuButtonContainer.down = function (x, y, obj) {
if (gameState === 'playing') {
// Exit game and return to menu
gameState = 'menu';
// Reset game event handlers
game.move = null;
game.up = null;
// Show menu
showMenu();
} else {
showMenu();
}
};
LK.gui.top.addChild(menuButtonContainer);
menuButtonContainer.y = 470;
menuButtonContainer.visible = false; // Initially hidden when game opens in menu
function getRandomCandyType() {
return CANDY_TYPES[Math.floor(Math.random() * CANDY_TYPES.length)];
}
function loadProgress() {
var savedLevel = storage.currentLevel;
var savedMaxLevel = storage.maxLevel;
if (savedLevel) {
currentLevel = parseInt(savedLevel);
}
if (savedMaxLevel) {
maxLevel = parseInt(savedMaxLevel);
}
// Load settings
var savedSoundEnabled = storage.soundEnabled;
var savedMusicEnabled = storage.musicEnabled;
var savedMasterVolume = storage.masterVolume;
if (savedSoundEnabled !== undefined) {
soundEnabled = savedSoundEnabled === 'true';
}
if (savedMusicEnabled !== undefined) {
musicEnabled = savedMusicEnabled === 'true';
}
if (savedMasterVolume !== undefined) {
masterVolume = parseFloat(savedMasterVolume);
}
// Load lives
var savedLives = storage.playerLives;
if (savedLives !== undefined) {
playerLives = parseInt(savedLives);
}
}
function saveProgress() {
storage.currentLevel = currentLevel.toString();
storage.maxLevel = maxLevel.toString();
storage.playerLives = playerLives.toString();
}
function getCurrentLevelConfig() {
if (currentLevel <= levelConfigs.length) {
return levelConfigs[currentLevel - 1];
}
// Fallback for any level beyond 500 (shouldn't happen, but safety measure)
var obstacleCount = Math.min(30, currentLevel);
return {
level: currentLevel,
targetScore: 1000 + (currentLevel - 1) * 200,
moves: Math.max(3, 20 - Math.floor(currentLevel / 10)),
gridSize: 10,
obstacles: {
stoneBlocks: Math.min(30, obstacleCount),
lockedCandies: Math.min(25, Math.floor(obstacleCount * 0.8)),
honeyCandies: Math.min(18, Math.floor(obstacleCount * 0.6))
}
};
}
function createMenu() {
if (menuContainer) {
game.removeChild(menuContainer);
}
menuContainer = new Container();
game.addChild(menuContainer);
// Create dynamic gradient background with multiple layers
var gradientLayer1 = LK.getAsset('bg_gradient1', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
alpha: 0.9
});
var gradientLayer2 = LK.getAsset('bg_gradient2', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
alpha: 0.7
});
var gradientLayer3 = LK.getAsset('bg_pattern', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
alpha: 0.4
});
menuContainer.addChild(gradientLayer1);
menuContainer.addChild(gradientLayer2);
menuContainer.addChild(gradientLayer3);
// Animate background layers with subtle movement
tween(gradientLayer2, {
x: 50,
y: 30
}, {
duration: 8000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(gradientLayer2, {
x: -30,
y: -20
}, {
duration: 8000,
easing: tween.easeInOut
});
}
});
tween(gradientLayer3, {
x: -40,
y: 25
}, {
duration: 10000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(gradientLayer3, {
x: 20,
y: -15
}, {
duration: 10000,
easing: tween.easeInOut
});
}
});
// Create modern card-based layout with rounded corners effect
var mainCard = LK.getAsset('menu_overlay', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 0.95,
scaleY: 0.9,
alpha: 0.85
});
menuContainer.addChild(mainCard);
// Add animated border effect around main card
var borderEffect = LK.getAsset('score_button_highlight', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 15,
scaleY: 12,
alpha: 0.3
});
menuContainer.addChild(borderEffect);
// Animate border glow
tween(borderEffect, {
alpha: 0.6,
scaleX: 15.2,
scaleY: 12.2
}, {
duration: 3000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(borderEffect, {
alpha: 0.3,
scaleX: 15,
scaleY: 12
}, {
duration: 3000,
easing: tween.easeInOut
});
}
});
// Floating geometric shapes for modern look
for (var i = 0; i < 8; i++) {
var shape = LK.getAsset('grid_cell', {
anchorX: 0.5,
anchorY: 0.5,
x: 200 + Math.random() * 1648,
y: 200 + Math.random() * 2332,
scaleX: 0.3 + Math.random() * 0.4,
scaleY: 0.3 + Math.random() * 0.4,
rotation: Math.random() * Math.PI * 2
});
shape.alpha = 0.1 + Math.random() * 0.15;
menuContainer.addChild(shape);
// Slow rotating animation for geometric shapes
var rotationDuration = 15000 + Math.random() * 10000;
tween(shape, {
rotation: shape.rotation + Math.PI * 2,
x: shape.x + (Math.random() - 0.5) * 300,
y: shape.y + (Math.random() - 0.5) * 300
}, {
duration: rotationDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(shape, {
rotation: shape.rotation + Math.PI * 2,
x: 200 + Math.random() * 1648,
y: 200 + Math.random() * 2332
}, {
duration: rotationDuration,
easing: tween.easeInOut
});
}
});
}
// Modern minimalist candy accents
for (var j = 0; j < 6; j++) {
var candyAccent = LK.getAsset('candy_' + CANDY_TYPES[j % CANDY_TYPES.length], {
anchorX: 0.5,
anchorY: 0.5,
x: 150 + j * 320,
y: 150 + j % 2 * 2400,
scaleX: 0.6,
scaleY: 0.6
});
candyAccent.alpha = 0.4;
candyAccent.rotation = j * Math.PI / 3;
menuContainer.addChild(candyAccent);
// Gentle floating animation for candy accents
tween(candyAccent, {
y: candyAccent.y + (Math.random() - 0.5) * 100,
rotation: candyAccent.rotation + Math.PI / 4
}, {
duration: 4000 + Math.random() * 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(candyAccent, {
y: candyAccent.y - (Math.random() - 0.5) * 100,
rotation: candyAccent.rotation + Math.PI / 4
}, {
duration: 4000 + Math.random() * 2000,
easing: tween.easeInOut
});
}
});
}
// Modern typography - Main title with shadow effect
var titleShadow = new Text2('SWEET MATCH', {
size: 160,
fill: 0x000000
});
titleShadow.anchor.set(0.5, 0.5);
titleShadow.x = 2048 / 2 + 8;
titleShadow.y = 450 + 8;
titleShadow.alpha = 0.3;
menuContainer.addChild(titleShadow);
var titleText = new Text2('SWEET MATCH', {
size: 160,
fill: 0xFFD700
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 450;
titleText.scaleX = 0;
titleText.scaleY = 0;
menuContainer.addChild(titleText);
// Modern subtitle
var subtitleText = new Text2('Premium Puzzle Experience', {
size: 55,
fill: 0xFFFFFF
});
subtitleText.anchor.set(0.5, 0.5);
subtitleText.x = 2048 / 2;
subtitleText.y = 550;
subtitleText.alpha = 0;
menuContainer.addChild(subtitleText);
// Developer credit
var developerText = new Text2('By Luanity', {
size: 45,
fill: 0xFFD700
});
developerText.anchor.set(0.5, 0.5);
developerText.x = 2048 / 2;
developerText.y = 620;
developerText.alpha = 0;
menuContainer.addChild(developerText);
// Animate title with modern entrance
tween(titleText, {
scaleX: 1.1,
scaleY: 1.1,
alpha: 1
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(titleText, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 400,
easing: tween.easeInOut
});
}
});
// Animate subtitle
tween(subtitleText, {
alpha: 0.8,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
delay: 600,
easing: tween.easeOut
});
// Animate developer credit
tween(developerText, {
alpha: 0.9,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 600,
delay: 800,
easing: tween.easeOut
});
// Create modern button container with card design
var buttonCard = LK.getAsset('menu_button_border', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 1366,
scaleX: 2.5,
scaleY: 8,
alpha: 0.2
});
menuContainer.addChild(buttonCard);
// Main action button - redesigned
var playButtonBorder = LK.getAsset('menu_button_border', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.4,
scaleY: 1.6
});
var playButton = LK.getAsset('menu_button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.4,
scaleY: 1.6
});
var playButtonGlow = LK.getAsset('score_button_highlight', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.35,
scaleY: 1.55
});
var playButtonContainer = new Container();
playButtonContainer.addChild(playButtonBorder);
playButtonContainer.addChild(playButton);
playButtonContainer.addChild(playButtonGlow);
playButtonContainer.x = 2048 / 2;
playButtonContainer.y = 850;
playButtonContainer.scaleX = 0;
playButtonContainer.scaleY = 0;
// Position button elements
playButtonBorder.y = 0;
playButton.y = -4;
playButtonGlow.y = -8;
playButtonGlow.alpha = 0.5;
var playText = new Text2('PLAY NOW', {
size: 75,
fill: 0xFFFFFF
});
playText.anchor.set(0.5, 0.5);
playButtonContainer.addChild(playText);
// Modern button entrance animation
tween(playButtonContainer, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1
}, {
duration: 800,
delay: 1000,
easing: tween.easeOut
});
// Enhanced glow effect
tween(playButtonGlow, {
alpha: 0.8,
scaleX: 1.4,
scaleY: 1.6
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(playButtonGlow, {
alpha: 0.5,
scaleX: 1.35,
scaleY: 1.55
}, {
duration: 2000,
easing: tween.easeInOut
});
}
});
// Modern press interaction
playButtonContainer.down = function (x, y, obj) {
tween(playButtonContainer, {
scaleX: 0.93,
scaleY: 0.93
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(playButtonContainer, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
startGame();
}
});
}
});
};
menuContainer.addChild(playButtonContainer);
// Level selection section with modern design
var levelSectionTitle = new Text2('LEVEL SELECT', {
size: 70,
fill: 0xFFDD44
});
levelSectionTitle.anchor.set(0.5, 0.5);
levelSectionTitle.x = 2048 / 2;
levelSectionTitle.y = 1100;
levelSectionTitle.alpha = 0;
menuContainer.addChild(levelSectionTitle);
tween(levelSectionTitle, {
alpha: 1,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 600,
delay: 1200,
easing: tween.easeOut
});
// Create level grid with modern card-based design
var buttonsPerRow = 5;
var buttonSpacing = 140;
var startX = 2048 / 2 - (buttonsPerRow - 1) * buttonSpacing / 2;
var startY = 1250;
var maxVisibleLevels = 20; // Show 20 levels (4 rows)
var levelPage = Math.floor((currentLevel - 1) / maxVisibleLevels);
var startLevel = levelPage * maxVisibleLevels + 1;
var endLevel = Math.min(startLevel + maxVisibleLevels - 1, maxLevel);
// Page indicator with modern styling
if (maxLevel > maxVisibleLevels) {
var pageIndicator = new Text2('Page ' + (levelPage + 1) + ' of ' + Math.ceil(maxLevel / maxVisibleLevels), {
size: 45,
fill: 0xCCCCCC
});
pageIndicator.anchor.set(0.5, 0.5);
pageIndicator.x = 2048 / 2;
pageIndicator.y = startY - 60;
pageIndicator.alpha = 0;
menuContainer.addChild(pageIndicator);
tween(pageIndicator, {
alpha: 0.8
}, {
duration: 500,
delay: 1400,
easing: tween.easeOut
});
}
// Modern level buttons with card design
for (var i = startLevel; i <= endLevel; i++) {
var row = Math.floor((i - startLevel) / buttonsPerRow);
var col = (i - startLevel) % buttonsPerRow;
// Level card background
var levelCard = LK.getAsset('menu_button_border', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.32,
scaleY: 0.45
});
var levelButton = LK.getAsset('menu_button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.32,
scaleY: 0.45
});
var levelGlow = LK.getAsset('score_button_highlight', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.42
});
var levelButtonContainer = new Container();
levelButtonContainer.addChild(levelCard);
levelButtonContainer.addChild(levelButton);
levelButtonContainer.addChild(levelGlow);
levelButtonContainer.x = startX + col * buttonSpacing;
levelButtonContainer.y = startY + row * 120;
levelButtonContainer.scaleX = 0;
levelButtonContainer.scaleY = 0;
// Position elements
levelCard.y = 0;
levelButton.y = -2;
levelGlow.y = -4;
levelGlow.alpha = i === currentLevel ? 0.6 : 0.2;
// Level number with modern typography
var levelNumText = new Text2(i.toString(), {
size: 40,
fill: i === currentLevel ? 0xFFD700 : 0xFFFFFF
});
levelNumText.anchor.set(0.5, 0.5);
levelButtonContainer.addChild(levelNumText);
// Staggered entrance animation
var animDelay = 1400 + (i - startLevel) * 100;
tween(levelButtonContainer, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1
}, {
duration: 400,
delay: animDelay,
easing: tween.easeOut
});
// Current level highlighting
if (i === currentLevel) {
tween(levelGlow, {
alpha: 0.8,
scaleX: 0.32,
scaleY: 0.44
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(levelGlow, {
alpha: 0.6,
scaleX: 0.3,
scaleY: 0.42
}, {
duration: 1500,
easing: tween.easeInOut
});
}
});
}
// Modern interaction
levelButtonContainer.levelNum = i;
levelButtonContainer.down = function (x, y, obj) {
tween(this, {
scaleX: 0.88,
scaleY: 0.88
}, {
duration: 80,
easing: tween.easeOut,
onFinish: function () {
tween(this, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 80,
easing: tween.easeOut,
onFinish: function () {
currentLevel = this.levelNum;
startGame();
}.bind(this)
});
}.bind(this)
});
};
menuContainer.addChild(levelButtonContainer);
}
// Modern settings button at bottom
var settingsContainer = new Container();
var settingsCard = LK.getAsset('settings_button_border', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.9,
scaleY: 1.1
});
var settingsButton = LK.getAsset('settings_button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.9,
scaleY: 1.1
});
var settingsGlow = LK.getAsset('score_button_highlight', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.85,
scaleY: 1.05
});
settingsContainer.addChild(settingsCard);
settingsContainer.addChild(settingsButton);
settingsContainer.addChild(settingsGlow);
settingsContainer.x = 2048 / 2;
settingsContainer.y = 2200;
settingsContainer.scaleX = 0;
settingsContainer.scaleY = 0;
settingsCard.y = 0;
settingsButton.y = -3;
settingsGlow.y = -6;
settingsGlow.alpha = 0.3;
var settingsText = new Text2('SETTINGS', {
size: 48,
fill: 0xFFFFFF
});
settingsText.anchor.set(0.5, 0.5);
settingsContainer.addChild(settingsText);
// Animate settings button entrance
tween(settingsContainer, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1
}, {
duration: 500,
delay: 2000,
easing: tween.easeOut
});
// Settings button interaction
settingsContainer.down = function (x, y, obj) {
tween(settingsContainer, {
scaleX: 0.92,
scaleY: 0.92
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(settingsContainer, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
showSettings();
}
});
}
});
};
menuContainer.addChild(settingsContainer);
gameState = 'menu';
}
function startGame() {
if (menuContainer) {
game.removeChild(menuContainer);
menuContainer = null;
}
if (settingsContainer) {
game.removeChild(settingsContainer);
settingsContainer = null;
}
gameState = 'playing';
// Show UI elements when playing
scoreButtonContainer.visible = true;
movesButtonContainer.visible = true;
targetButtonContainer.visible = true;
levelButtonContainer.visible = true;
livesButtonContainer.visible = true;
menuButtonContainer.visible = true;
// Reset any custom event handlers from settings
game.move = null;
game.up = null;
initializeLevel();
// Start lives regeneration if we have lost lives
if (playerLives < maxLives) {
startLivesRegeneration();
}
}
function showMenu() {
gameState = 'menu';
// Reset game event handlers when returning to menu
game.move = null;
game.up = null;
// Hide UI elements when in menu
scoreButtonContainer.visible = false;
movesButtonContainer.visible = false;
targetButtonContainer.visible = false;
levelButtonContainer.visible = false;
livesButtonContainer.visible = false;
menuButtonContainer.visible = false;
// Remove settings container if it exists
if (settingsContainer) {
game.removeChild(settingsContainer);
settingsContainer = null;
}
// Clear all game elements when returning to menu from playing state
for (var i = candies.length - 1; i >= 0; i--) {
var candy = candies[i];
if (candy.parent) {
candy.parent.removeChild(candy);
}
}
candies = [];
// Reset grid
for (var y = 0; y < GRID_HEIGHT; y++) {
for (var x = 0; x < GRID_WIDTH; x++) {
grid[y][x] = null;
}
}
// Stop lives regeneration when in menu
stopLivesRegeneration();
createMenu();
}
function showSettings() {
gameState = 'settings';
// Hide UI elements when in settings
scoreButtonContainer.visible = false;
movesButtonContainer.visible = false;
targetButtonContainer.visible = false;
levelButtonContainer.visible = false;
livesButtonContainer.visible = false;
menuButtonContainer.visible = false;
// Stop lives regeneration when in settings
stopLivesRegeneration();
createSettingsMenu();
}
function createSettingsMenu() {
if (settingsContainer) {
game.removeChild(settingsContainer);
}
settingsContainer = new Container();
game.addChild(settingsContainer);
// Settings overlay background
var settingsOverlay = LK.getAsset('menu_overlay', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
alpha: 0.9
});
settingsContainer.addChild(settingsOverlay);
// Settings title
var settingsTitle = new Text2('SETTINGS', {
size: 120,
fill: 0xFFD700
});
settingsTitle.anchor.set(0.5, 0.5);
settingsTitle.x = 2048 / 2;
settingsTitle.y = 400;
settingsTitle.scaleX = 0;
settingsTitle.scaleY = 0;
settingsContainer.addChild(settingsTitle);
// Animate title entrance
tween(settingsTitle, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1
}, {
duration: 600,
easing: tween.easeOut
});
var yPosition = 700;
var spacing = 150;
// Sound toggle
createToggleOption('SOUND EFFECTS', soundEnabled, yPosition, function (enabled) {
soundEnabled = enabled;
storage.soundEnabled = enabled.toString();
});
yPosition += spacing;
// Music toggle
createToggleOption('MUSIC', musicEnabled, yPosition, function (enabled) {
musicEnabled = enabled;
storage.musicEnabled = enabled.toString();
});
yPosition += spacing;
// Master volume slider
createVolumeSlider('MASTER VOLUME', masterVolume, yPosition, function (volume) {
masterVolume = volume;
storage.masterVolume = volume.toString();
});
yPosition += spacing + 100;
// Back button
var backButtonBorder = LK.getAsset('menu_button_border', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
var backButton = LK.getAsset('menu_button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
var backButtonContainer = new Container();
backButtonContainer.addChild(backButtonBorder);
backButtonContainer.addChild(backButton);
backButtonContainer.x = 2048 / 2;
backButtonContainer.y = yPosition;
backButtonContainer.scaleX = 0;
backButtonContainer.scaleY = 0;
var backText = new Text2('BACK', {
size: 60,
fill: 0xFFFFFF
});
backText.anchor.set(0.5, 0.5);
backButtonContainer.addChild(backText);
// Animate back button
tween(backButtonContainer, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1
}, {
duration: 400,
delay: 800,
easing: tween.easeOut
});
backButtonContainer.down = function (x, y, obj) {
tween(backButtonContainer, {
scaleX: 0.75,
scaleY: 0.75
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(backButtonContainer, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
showMenu();
}
});
}
});
};
settingsContainer.addChild(backButtonContainer);
function createToggleOption(label, currentValue, yPos, callback) {
// Label text
var labelText = new Text2(label, {
size: 60,
fill: 0xFFFFFF
});
labelText.anchor.set(0, 0.5);
labelText.x = 400;
labelText.y = yPos;
labelText.alpha = 0;
settingsContainer.addChild(labelText);
// Toggle button
var toggleBorder = LK.getAsset('settings_button_border', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.6
});
var toggleButton = LK.getAsset(currentValue ? 'toggle_button_on' : 'toggle_button_off', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.6
});
var toggleContainer = new Container();
toggleContainer.addChild(toggleBorder);
toggleContainer.addChild(toggleButton);
toggleContainer.x = 1500;
toggleContainer.y = yPos;
toggleContainer.alpha = 0;
var toggleText = new Text2(currentValue ? 'ON' : 'OFF', {
size: 40,
fill: 0xFFFFFF
});
toggleText.anchor.set(0.5, 0.5);
toggleContainer.addChild(toggleText);
// Animate entrance
tween(labelText, {
alpha: 1
}, {
duration: 400,
delay: 400,
easing: tween.easeOut
});
tween(toggleContainer, {
alpha: 1
}, {
duration: 400,
delay: 500,
easing: tween.easeOut
});
toggleContainer.down = function (x, y, obj) {
var newValue = !currentValue;
currentValue = newValue;
// Update visual
toggleContainer.removeChild(toggleButton);
toggleButton = LK.getAsset(newValue ? 'toggle_button_on' : 'toggle_button_off', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.6
});
toggleContainer.addChild(toggleButton);
toggleText.setText(newValue ? 'ON' : 'OFF');
// Animate press
tween(toggleContainer, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(toggleContainer, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut
});
}
});
callback(newValue);
};
settingsContainer.addChild(toggleContainer);
}
function createVolumeSlider(label, currentValue, yPos, callback) {
// Label text
var labelText = new Text2(label, {
size: 60,
fill: 0xFFFFFF
});
labelText.anchor.set(0, 0.5);
labelText.x = 400;
labelText.y = yPos;
labelText.alpha = 0;
settingsContainer.addChild(labelText);
// Slider track
var sliderTrack = LK.getAsset('slider_track', {
anchorX: 0.5,
anchorY: 0.5
});
sliderTrack.x = 1400;
sliderTrack.y = yPos;
sliderTrack.alpha = 0;
settingsContainer.addChild(sliderTrack);
// Slider handle
var sliderHandle = LK.getAsset('slider_handle', {
anchorX: 0.5,
anchorY: 0.5
});
sliderHandle.x = 1400 - 100 + 200 * currentValue;
sliderHandle.y = yPos;
sliderHandle.alpha = 0;
settingsContainer.addChild(sliderHandle);
// Volume percentage text
var volumeText = new Text2(Math.round(currentValue * 100) + '%', {
size: 45,
fill: 0xFFDD44
});
volumeText.anchor.set(0.5, 0.5);
volumeText.x = 1600;
volumeText.y = yPos;
volumeText.alpha = 0;
settingsContainer.addChild(volumeText);
// Animate entrance
tween(labelText, {
alpha: 1
}, {
duration: 400,
delay: 400,
easing: tween.easeOut
});
tween(sliderTrack, {
alpha: 1
}, {
duration: 400,
delay: 500,
easing: tween.easeOut
});
tween(sliderHandle, {
alpha: 1
}, {
duration: 400,
delay: 600,
easing: tween.easeOut
});
tween(volumeText, {
alpha: 1
}, {
duration: 400,
delay: 700,
easing: tween.easeOut
});
var isDraggingSlider = false;
sliderHandle.down = function (x, y, obj) {
isDraggingSlider = true;
tween(sliderHandle, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
easing: tween.easeOut
});
};
game.move = function (x, y, obj) {
if (isDraggingSlider) {
var sliderX = Math.max(1300, Math.min(1500, x));
sliderHandle.x = sliderX;
var newValue = (sliderX - 1300) / 200;
volumeText.setText(Math.round(newValue * 100) + '%');
callback(newValue);
}
};
game.up = function (x, y, obj) {
if (isDraggingSlider) {
isDraggingSlider = false;
tween(sliderHandle, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut
});
}
};
}
}
function startLivesRegeneration() {
// Only start regeneration if we have lost lives and timer isn't already running
if (playerLives < maxLives && !livesRegenerationTimer) {
livesRegenerationTimer = LK.setInterval(function () {
if (playerLives < maxLives) {
playerLives++;
livesText.setText('Lives: ' + playerLives);
saveProgress();
// Show life regenerated notification
var lifeRegenText = new Text2('Life Restored! ' + playerLives + '/' + maxLives, {
size: 60,
fill: 0x00FF00
});
lifeRegenText.anchor.set(0.5, 0.5);
lifeRegenText.x = 2048 / 2;
lifeRegenText.y = 2732 / 2 - 150;
lifeRegenText.alpha = 0;
game.addChild(lifeRegenText);
// Animate notification
tween(lifeRegenText, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(lifeRegenText, {
alpha: 0,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 400,
delay: 1200,
easing: tween.easeIn,
onFinish: function onFinish() {
if (lifeRegenText.parent) {
lifeRegenText.parent.removeChild(lifeRegenText);
}
}
});
}
});
// Stop regeneration when max lives reached
if (playerLives >= maxLives) {
LK.clearInterval(livesRegenerationTimer);
livesRegenerationTimer = null;
}
} else {
// Stop timer if lives are full
LK.clearInterval(livesRegenerationTimer);
livesRegenerationTimer = null;
}
}, livesRegenerationInterval);
}
}
function stopLivesRegeneration() {
if (livesRegenerationTimer) {
LK.clearInterval(livesRegenerationTimer);
livesRegenerationTimer = null;
}
}
function initializeLevel() {
var config = getCurrentLevelConfig();
targetScore = config.targetScore;
movesLeft = config.moves;
currentScore = 0;
// Update UI
levelText.setText('Level: ' + currentLevel);
scoreText.setText('Score: 0');
movesText.setText('Moves: ' + movesLeft);
targetText.setText('Target: ' + targetScore);
livesText.setText('Lives: ' + playerLives);
// Clear existing candies
for (var i = candies.length - 1; i >= 0; i--) {
var candy = candies[i];
if (candy.parent) {
candy.parent.removeChild(candy);
}
}
candies = [];
// Reset grid
for (var y = 0; y < GRID_HEIGHT; y++) {
for (var x = 0; x < GRID_WIDTH; x++) {
grid[y][x] = null;
}
}
// Initialize new grid
initializeGrid();
// Set up game event handlers for playing state
setupGameHandlers();
}
function completeLevel() {
if (currentLevel >= maxLevel) {
maxLevel = currentLevel + 1;
}
currentLevel++;
saveProgress();
// Show level complete message briefly before starting next level
var levelCompleteText = new Text2('Level Complete!', {
size: 80,
fill: 0x00FF00
});
levelCompleteText.anchor.set(0.5, 0.5);
levelCompleteText.x = 2048 / 2;
levelCompleteText.y = 2732 / 2;
game.addChild(levelCompleteText);
// Animate text
levelCompleteText.scaleX = 0;
levelCompleteText.scaleY = 0;
tween(levelCompleteText, {
scaleX: 1,
scaleY: 1,
alpha: 1
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(levelCompleteText, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 500,
delay: 1000,
easing: tween.easeIn,
onFinish: function onFinish() {
if (levelCompleteText.parent) {
levelCompleteText.parent.removeChild(levelCompleteText);
}
// Show ad every 3 levels
if (currentLevel % 3 === 1 && currentLevel > 1) {
showLevelTransitionAd();
} else {
initializeLevel();
}
}
});
}
});
}
function showLevelTransitionAd() {
// Show interstitial ad between levels
ads.showInterstitial({
onAdClosed: function onAdClosed() {
// Continue to next level after ad is closed
initializeLevel();
},
onAdFailed: function onAdFailed() {
// If ad fails to load, continue anyway
initializeLevel();
}
});
}
function createCandy(x, y, type) {
type = type || getRandomCandyType();
var candy = new Candy(type, x, y);
candy.x = GRID_START_X + x * CELL_SIZE + CELL_SIZE / 2;
candy.y = GRID_START_Y + y * CELL_SIZE + CELL_SIZE / 2;
game.addChild(candy);
candies.push(candy);
grid[y][x] = candy;
return candy;
}
function placeObstacles(config) {
if (!config.obstacles) {
return;
}
var obstacles = config.obstacles;
var placedPositions = [];
var totalObstacles = obstacles.stoneBlocks + obstacles.lockedCandies + obstacles.honeyCandies;
// Place stone blocks first (highest priority)
for (var i = 0; i < obstacles.stoneBlocks; i++) {
var placed = false;
var attempts = 0;
while (!placed && attempts < 50) {
var x = Math.floor(Math.random() * GRID_WIDTH);
var y = Math.floor(Math.random() * GRID_HEIGHT);
var posKey = x + ',' + y;
// Avoid corners and center, prefer edges and strategic positions
if (!placedPositions[posKey] && grid[y][x] && !(x < 2 && y < 2) && !(x > GRID_WIDTH - 3 && y < 2) && !(x < 2 && y > GRID_HEIGHT - 3) && !(x > GRID_WIDTH - 3 && y > GRID_HEIGHT - 3)) {
// Remove existing candy
var existingCandy = grid[y][x];
if (existingCandy && existingCandy.parent) {
existingCandy.parent.removeChild(existingCandy);
var candyIndex = candies.indexOf(existingCandy);
if (candyIndex > -1) {
candies.splice(candyIndex, 1);
}
}
// Create stone block
var stone = new StoneBlock(x, y, 1 + Math.floor(currentLevel / 3));
stone.x = GRID_START_X + x * CELL_SIZE + CELL_SIZE / 2;
stone.y = GRID_START_Y + y * CELL_SIZE + CELL_SIZE / 2;
game.addChild(stone);
grid[y][x] = stone;
placedPositions[posKey] = true;
placed = true;
}
attempts++;
}
}
// Place locked candies
for (var j = 0; j < obstacles.lockedCandies; j++) {
var placed = false;
var attempts = 0;
while (!placed && attempts < 50) {
var x = Math.floor(Math.random() * GRID_WIDTH);
var y = Math.floor(Math.random() * GRID_HEIGHT);
var posKey = x + ',' + y;
if (!placedPositions[posKey] && grid[y][x] && !grid[y][x].isObstacle) {
// Convert existing candy to locked candy
var existingCandy = grid[y][x];
var type = existingCandy.candyType;
if (existingCandy.parent) {
existingCandy.parent.removeChild(existingCandy);
var candyIndex = candies.indexOf(existingCandy);
if (candyIndex > -1) {
candies.splice(candyIndex, 1);
}
}
var lockedCandy = new LockedCandy(type, x, y, 1 + Math.floor(currentLevel / 4));
lockedCandy.x = GRID_START_X + x * CELL_SIZE + CELL_SIZE / 2;
lockedCandy.y = GRID_START_Y + y * CELL_SIZE + CELL_SIZE / 2;
game.addChild(lockedCandy);
candies.push(lockedCandy);
grid[y][x] = lockedCandy;
placedPositions[posKey] = true;
placed = true;
}
attempts++;
}
}
// Place honey candies
for (var k = 0; k < obstacles.honeyCandies; k++) {
var placed = false;
var attempts = 0;
while (!placed && attempts < 50) {
var x = Math.floor(Math.random() * GRID_WIDTH);
var y = Math.floor(Math.random() * GRID_HEIGHT);
var posKey = x + ',' + y;
if (!placedPositions[posKey] && grid[y][x] && !grid[y][x].isObstacle && !grid[y][x].isLocked) {
// Convert existing candy to honey candy
var existingCandy = grid[y][x];
var type = existingCandy.candyType;
if (existingCandy.parent) {
existingCandy.parent.removeChild(existingCandy);
var candyIndex = candies.indexOf(existingCandy);
if (candyIndex > -1) {
candies.splice(candyIndex, 1);
}
}
var honeyCandy = new HoneyCandy(type, x, y);
honeyCandy.x = GRID_START_X + x * CELL_SIZE + CELL_SIZE / 2;
honeyCandy.y = GRID_START_Y + y * CELL_SIZE + CELL_SIZE / 2;
game.addChild(honeyCandy);
candies.push(honeyCandy);
grid[y][x] = honeyCandy;
placedPositions[posKey] = true;
placed = true;
}
attempts++;
}
}
}
function getLevelSpecificCandyType(x, y, level) {
// Define specific patterns for each level
switch (level) {
case 1:
// Level 1: More green and blue candies in corners
if (x < 3 && y < 3 || x > 6 && y > 6) {
return Math.random() < 0.6 ? Math.random() < 0.5 ? 'green' : 'blue' : getRandomCandyType();
}
break;
case 2:
// Level 2: Checkerboard pattern influence with green and yellow
if ((x + y) % 2 === 0) {
return Math.random() < 0.5 ? Math.random() < 0.5 ? 'green' : 'yellow' : getRandomCandyType();
}
break;
case 3:
// Level 3: Center focus with purple and orange
var centerDistance = Math.abs(x - 4.5) + Math.abs(y - 4.5);
if (centerDistance < 4) {
return Math.random() < 0.7 ? Math.random() < 0.5 ? 'purple' : 'orange' : getRandomCandyType();
}
break;
case 4:
// Level 4: Diagonal pattern with orange, green, and red
if (Math.abs(x - y) < 2 || Math.abs(x + y - 9) < 2) {
var candyChoice = Math.random();
if (candyChoice < 0.33) {
return 'orange';
} else if (candyChoice < 0.66) {
return 'green';
} else if (candyChoice < 0.8) {
return 'red';
} else {
return getRandomCandyType();
}
}
break;
case 5:
// Level 5: Border emphasis with blue and yellow
if (x < 2 || x > 7 || y < 2 || y > 7) {
return Math.random() < 0.7 ? Math.random() < 0.5 ? 'blue' : 'yellow' : getRandomCandyType();
}
break;
case 6:
// Level 6: Cross pattern with blue, orange, and red
if (x === 4 || x === 5 || y === 4 || y === 5) {
var candyChoice = Math.random();
if (candyChoice < 0.33) {
return 'blue';
} else if (candyChoice < 0.66) {
return 'orange';
} else if (candyChoice < 0.8) {
return 'red';
} else {
return getRandomCandyType();
}
}
break;
case 7:
// Level 7: Ring pattern with orange and green
var ringDistance = Math.max(Math.abs(x - 4.5), Math.abs(y - 4.5));
if (ringDistance > 2 && ringDistance < 5) {
return Math.random() < 0.6 ? Math.random() < 0.5 ? 'orange' : 'green' : getRandomCandyType();
}
break;
case 8:
// Level 8: Spiral influence with blue and yellow
var spiralValue = (x * 3 + y * 2) % 7;
if (spiralValue < 3) {
return Math.random() < 0.7 ? Math.random() < 0.5 ? 'blue' : 'yellow' : getRandomCandyType();
}
break;
case 9:
// Level 9: Scattered clusters with yellow, green, and red
if (x % 3 === 0 && y % 3 === 0 || x % 3 === 2 && y % 3 === 2) {
var candyChoice = Math.random();
if (candyChoice < 0.33) {
return 'yellow';
} else if (candyChoice < 0.66) {
return 'green';
} else if (candyChoice < 0.8) {
return 'red';
} else {
return getRandomCandyType();
}
}
break;
case 10:
// Level 10: Complex pattern with all colors but emphasis on green and orange
if ((x + y) % 4 < 2) {
return Math.random() < 0.5 ? Math.random() < 0.5 ? 'green' : 'orange' : getRandomCandyType();
}
break;
default:
// For levels beyond 10, create dynamic patterns based on level number
var patternType = level % 5;
switch (patternType) {
case 0:
// Diagonal emphasis
if (Math.abs(x - y) < 3) {
return Math.random() < 0.6 ? CANDY_TYPES[level % CANDY_TYPES.length] : getRandomCandyType();
}
break;
case 1:
// Corner concentration
if (x < 3 && y < 3 || x > 6 && y > 6 || x < 3 && y > 6 || x > 6 && y < 3) {
return Math.random() < 0.7 ? CANDY_TYPES[(level + 1) % CANDY_TYPES.length] : getRandomCandyType();
}
break;
case 2:
// Center ring
var centerDist = Math.max(Math.abs(x - 4.5), Math.abs(y - 4.5));
if (centerDist > 1 && centerDist < 4) {
return Math.random() < 0.6 ? CANDY_TYPES[(level + 2) % CANDY_TYPES.length] : getRandomCandyType();
}
break;
case 3:
// Checkerboard influence
if ((x + y + level) % 3 === 0) {
return Math.random() < 0.7 ? CANDY_TYPES[(level + 3) % CANDY_TYPES.length] : getRandomCandyType();
}
break;
case 4:
// Random clusters
if ((x * y + level) % 11 < 4) {
return Math.random() < 0.6 ? CANDY_TYPES[(level + 4) % CANDY_TYPES.length] : getRandomCandyType();
}
break;
}
break;
}
return getRandomCandyType();
}
function initializeGrid() {
for (var y = 0; y < GRID_HEIGHT; y++) {
for (var x = 0; x < GRID_WIDTH; x++) {
var type;
var attempts = 0;
do {
// Use level-specific candy type generation
type = getLevelSpecificCandyType(x, y, currentLevel);
attempts++;
if (attempts > 10) {
// Fallback to completely random if level pattern is too restrictive
type = getRandomCandyType();
break;
}
} while (wouldCreateMatch(x, y, type));
createCandy(x, y, type);
}
}
// Place obstacles after initial candy generation
var config = getCurrentLevelConfig();
placeObstacles(config);
}
function wouldCreateMatch(x, y, type) {
var horizontalCount = 1;
var verticalCount = 1;
// Check horizontal
var i = x - 1;
while (i >= 0 && grid[y][i] && grid[y][i].candyType === type) {
horizontalCount++;
i--;
}
i = x + 1;
while (i < GRID_WIDTH && grid[y][i] && grid[y][i].candyType === type) {
horizontalCount++;
i++;
}
// Check vertical
i = y - 1;
while (i >= 0 && grid[i][x] && grid[i][x].candyType === type) {
verticalCount++;
i--;
}
i = y + 1;
while (i < GRID_HEIGHT && grid[i][x] && grid[i][x].candyType === type) {
verticalCount++;
i++;
}
return horizontalCount >= 3 || verticalCount >= 3;
}
function findMatches() {
var matches = [];
var processed = [];
for (var y = 0; y < GRID_HEIGHT; y++) {
processed[y] = [];
for (var x = 0; x < GRID_WIDTH; x++) {
processed[y][x] = false;
}
}
// Find horizontal matches
for (var y = 0; y < GRID_HEIGHT; y++) {
for (var x = 0; x < GRID_WIDTH - 2; x++) {
if (!grid[y][x] || processed[y][x]) {
continue;
}
var type = grid[y][x].candyType;
var matchLength = 1;
var startX = x;
while (x + matchLength < GRID_WIDTH && grid[y][x + matchLength] && grid[y][x + matchLength].candyType === type) {
matchLength++;
}
if (matchLength >= 3) {
for (var i = 0; i < matchLength; i++) {
if (!processed[y][startX + i]) {
matches.push(grid[y][startX + i]);
processed[y][startX + i] = true;
}
}
}
}
}
// Find vertical matches
for (var x = 0; x < GRID_WIDTH; x++) {
for (var y = 0; y < GRID_HEIGHT - 2; y++) {
if (!grid[y][x] || processed[y][x]) {
continue;
}
var type = grid[y][x].candyType;
var matchLength = 1;
var startY = y;
while (y + matchLength < GRID_HEIGHT && grid[y + matchLength][x] && grid[y + matchLength][x].candyType === type) {
matchLength++;
}
if (matchLength >= 3) {
for (var i = 0; i < matchLength; i++) {
if (!processed[startY + i][x]) {
matches.push(grid[startY + i][x]);
processed[startY + i][x] = true;
}
}
}
}
}
return matches;
}
function createExplosionEffect(x, y) {
// Create multiple particles for explosion effect
var particleCount = 8 + Math.floor(Math.random() * 4);
for (var i = 0; i < particleCount; i++) {
var particle = LK.getAsset('explosion_particle', {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
scaleX: 0.8 + Math.random() * 0.4,
scaleY: 0.8 + Math.random() * 0.4
});
game.addChild(particle);
// Random direction and distance
var angle = Math.PI * 2 * i / particleCount + (Math.random() - 0.5) * 0.5;
var distance = 60 + Math.random() * 40;
var targetX = x + Math.cos(angle) * distance;
var targetY = y + Math.sin(angle) * distance;
// Animate particle explosion
tween(particle, {
x: targetX,
y: targetY,
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 400 + Math.random() * 200,
easing: tween.easeOut,
onFinish: function onFinish() {
if (particle.parent) {
particle.parent.removeChild(particle);
}
}
});
}
// Create star particles for extra sparkle
var starCount = 4 + Math.floor(Math.random() * 3);
for (var j = 0; j < starCount; j++) {
var star = LK.getAsset('explosion_star', {
anchorX: 0.5,
anchorY: 0.5,
x: x + (Math.random() - 0.5) * 40,
y: y + (Math.random() - 0.5) * 40,
rotation: Math.random() * Math.PI * 2
});
game.addChild(star);
// Animate star particles
tween(star, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0,
rotation: star.rotation + Math.PI * 2
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
if (star.parent) {
star.parent.removeChild(star);
}
}
});
}
}
function createEnhancedExplosionEffect(x, y) {
// Create larger, more intense explosion for special matches
var particleCount = 15 + Math.floor(Math.random() * 8);
for (var i = 0; i < particleCount; i++) {
var particle = LK.getAsset('explosion_particle', {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
scaleX: 1.2 + Math.random() * 0.8,
scaleY: 1.2 + Math.random() * 0.8
});
game.addChild(particle);
// Random direction and larger distance
var angle = Math.PI * 2 * i / particleCount + (Math.random() - 0.5) * 0.7;
var distance = 80 + Math.random() * 60;
var targetX = x + Math.cos(angle) * distance;
var targetY = y + Math.sin(angle) * distance;
// Animate particle explosion with more dramatic effect
tween(particle, {
x: targetX,
y: targetY,
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 600 + Math.random() * 300,
easing: tween.easeOut,
onFinish: function onFinish() {
if (particle.parent) {
particle.parent.removeChild(particle);
}
}
});
}
// Create more star particles
var starCount = 8 + Math.floor(Math.random() * 6);
for (var j = 0; j < starCount; j++) {
var star = LK.getAsset('explosion_star', {
anchorX: 0.5,
anchorY: 0.5,
x: x + (Math.random() - 0.5) * 80,
y: y + (Math.random() - 0.5) * 80,
rotation: Math.random() * Math.PI * 2,
scaleX: 1.5,
scaleY: 1.5
});
game.addChild(star);
// Animate star particles with more dramatic effect
tween(star, {
scaleX: 2.5,
scaleY: 2.5,
alpha: 0,
rotation: star.rotation + Math.PI * 4
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
if (star.parent) {
star.parent.removeChild(star);
}
}
});
}
}
function showScorePopup(score, isSpecial) {
var scorePopup = new Text2('+' + score, {
size: isSpecial ? 100 : 70,
fill: isSpecial ? 0xFFD700 : 0x00FF00
});
scorePopup.anchor.set(0.5, 0.5);
scorePopup.x = 2048 / 2 + (Math.random() - 0.5) * 200;
scorePopup.y = 2732 / 2 + (Math.random() - 0.5) * 200;
scorePopup.scaleX = 0;
scorePopup.scaleY = 0;
game.addChild(scorePopup);
// Animate score popup
tween(scorePopup, {
scaleX: 1.2,
scaleY: 1.2,
y: scorePopup.y - 100,
alpha: 1
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(scorePopup, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 300,
delay: 400,
easing: tween.easeIn,
onFinish: function onFinish() {
if (scorePopup.parent) {
scorePopup.parent.removeChild(scorePopup);
}
}
});
}
});
}
function clearMatches(matches) {
if (matches.length === 0) {
return;
}
// Play explosion sound when candies are cleared
if (soundEnabled) {
var explosionSound = LK.getSound('explosion');
if (explosionSound) {
explosionSound.play();
}
}
if (soundEnabled) {
var matchSound = LK.getSound('match');
if (matchSound) {
matchSound.play();
}
}
// Handle matches with combo multiplier - reduced scoring for increased difficulty
var baseScore = matches.length * 30; // Reduced from 50
var comboMultiplier = game.comboMultiplier || 1;
var bonusScore = Math.floor(baseScore * comboMultiplier * 0.8); // Apply 20% reduction
currentScore += bonusScore;
scoreText.setText('Score: ' + currentScore);
// Show score popup for significant matches
if (matches.length >= 4 || comboMultiplier > 1) {
showScorePopup(bonusScore, matches.length >= 4);
}
for (var i = 0; i < matches.length; i++) {
var candy = matches[i];
// Handle locked candies - unlock instead of destroying
if (candy.isLocked) {
candy.unlock();
if (candy.lockLevel > 0) {
continue; // Don't destroy if still locked
}
}
// Create enhanced explosion effect for larger matches
if (matches.length >= 4) {
createEnhancedExplosionEffect(candy.x, candy.y);
} else {
createExplosionEffect(candy.x, candy.y);
}
// Check adjacent cells for obstacles to damage
var adjacentPositions = [{
x: candy.gridX - 1,
y: candy.gridY
}, {
x: candy.gridX + 1,
y: candy.gridY
}, {
x: candy.gridX,
y: candy.gridY - 1
}, {
x: candy.gridX,
y: candy.gridY + 1
}];
for (var j = 0; j < adjacentPositions.length; j++) {
var pos = adjacentPositions[j];
if (pos.x >= 0 && pos.x < GRID_WIDTH && pos.y >= 0 && pos.y < GRID_HEIGHT) {
var adjacentCell = grid[pos.y][pos.x];
if (adjacentCell && adjacentCell.isObstacle && adjacentCell.hit) {
adjacentCell.hit();
if (adjacentCell.isMarkedForDestroy) {
grid[pos.y][pos.x] = null;
}
}
}
}
grid[candy.gridY][candy.gridX] = null;
candy.destroy();
// Remove from candies array
var index = candies.indexOf(candy);
if (index > -1) {
candies.splice(index, 1);
}
}
}
function applyGravity() {
var moved = false;
var animationsToWait = [];
for (var x = 0; x < GRID_WIDTH; x++) {
var writeY = GRID_HEIGHT - 1;
for (var y = GRID_HEIGHT - 1; y >= 0; y--) {
if (grid[y][x] && !grid[y][x].isMarkedForDestroy) {
if (y !== writeY) {
grid[writeY][x] = grid[y][x];
grid[y][x] = null;
var fallDistance = writeY - y;
var animDuration = 150 + fallDistance * 50; // Longer fall = longer animation
if (grid[writeY][x] && grid[writeY][x].moveTo) {
grid[writeY][x].moveTo(x, writeY, animDuration);
}
moved = true;
}
writeY--;
}
}
// Fill empty spaces at top with staggered animation
for (var emptyY = writeY; emptyY >= 0; emptyY--) {
var newCandy = createCandy(x, emptyY, getRandomCandyType());
var dropDistance = writeY - emptyY + 1;
newCandy.y = GRID_START_Y - dropDistance * CELL_SIZE + CELL_SIZE / 2;
var dropAnimDuration = 200 + dropDistance * 60;
newCandy.moveTo(x, emptyY, dropAnimDuration);
// Add bounce effect for new candies
tween(newCandy, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: dropAnimDuration / 2,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(newCandy, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: dropAnimDuration / 2,
easing: tween.easeIn
});
}
});
moved = true;
}
}
return moved;
}
function areAdjacent(candy1, candy2) {
// Check if candies are valid before checking adjacency
if (!candy1 || !candy2 || candy1.isMarkedForDestroy || candy2.isMarkedForDestroy) {
return false;
}
var dx = Math.abs(candy1.gridX - candy2.gridX);
var dy = Math.abs(candy1.gridY - candy2.gridY);
return dx === 1 && dy === 0 || dx === 0 && dy === 1;
}
function swapCandies(candy1, candy2) {
// Check if candies are valid before swapping
if (!candy1 || !candy2 || candy1.isMarkedForDestroy || candy2.isMarkedForDestroy) {
return false;
}
// Temporarily swap in grid to test for matches
var tempGridX1 = candy1.gridX;
var tempGridY1 = candy1.gridY;
var tempGridX2 = candy2.gridX;
var tempGridY2 = candy2.gridY;
// Update grid positions temporarily
grid[candy1.gridY][candy1.gridX] = candy2;
grid[candy2.gridY][candy2.gridX] = candy1;
candy1.gridX = tempGridX2;
candy1.gridY = tempGridY2;
candy2.gridX = tempGridX1;
candy2.gridY = tempGridY1;
// Check if this swap creates matches
var matches = findMatches();
var willCreateMatches = matches.length > 0;
// If no matches will be created, revert the temporary swap
if (!willCreateMatches) {
grid[tempGridY1][tempGridX1] = candy1;
grid[tempGridY2][tempGridX2] = candy2;
candy1.gridX = tempGridX1;
candy1.gridY = tempGridY1;
candy2.gridX = tempGridX2;
candy2.gridY = tempGridY2;
return false;
}
// If matches will be created, perform the actual visual swap for both candies
candy1.moveTo(tempGridX2, tempGridY2, 200);
candy2.moveTo(tempGridX1, tempGridY1, 200);
return true;
}
function processMatches() {
if (isProcessing) {
return;
}
isProcessing = true;
// Track combo multiplier
if (!game.comboMultiplier) {
game.comboMultiplier = 1;
}
if (!game.comboCount) {
game.comboCount = 0;
}
LK.setTimeout(function () {
var matches = findMatches();
if (matches.length > 0) {
// Increase combo count for cascading matches
game.comboCount++;
if (game.comboCount > 1) {
game.comboMultiplier = Math.min(game.comboCount, 5); // Max 5x multiplier
// Show combo text
showComboText(game.comboMultiplier);
}
clearMatches(matches);
LK.setTimeout(function () {
if (applyGravity()) {
LK.setTimeout(function () {
isProcessing = false;
processMatches(); // Check for cascading matches
}, 500); // Slightly longer delay for better visual flow
} else {
isProcessing = false;
// Reset combo after no more matches
game.comboMultiplier = 1;
game.comboCount = 0;
checkGameState();
}
}, 350); // Longer delay between match clearing and gravity
} else {
isProcessing = false;
// Reset combo when no matches found
game.comboMultiplier = 1;
game.comboCount = 0;
checkGameState();
}
}, 200); // Faster initial processing
}
function showComboText(multiplier) {
var comboText = new Text2('COMBO x' + multiplier + '!', {
size: 80,
fill: 0xFFD700
});
comboText.anchor.set(0.5, 0.5);
comboText.x = 2048 / 2;
comboText.y = 2732 / 2 - 200;
comboText.scaleX = 0;
comboText.scaleY = 0;
game.addChild(comboText);
// Animate combo text
tween(comboText, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 1
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(comboText, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 400,
delay: 500,
easing: tween.easeIn,
onFinish: function onFinish() {
if (comboText.parent) {
comboText.parent.removeChild(comboText);
}
}
});
}
});
}
function checkGameState() {
if (currentScore >= targetScore) {
completeLevel();
return;
}
if (movesLeft <= 0) {
// Decrease lives instead of immediate game over
playerLives--;
livesText.setText('Lives: ' + playerLives);
lastLifeLostTime = Date.now();
saveProgress();
// Start lives regeneration timer when lives are lost
startLivesRegeneration();
if (playerLives <= 0) {
// Game over when no lives left
LK.setTimeout(function () {
// Reset lives to max when game over
playerLives = maxLives;
saveProgress();
showMenu();
}, 2000);
LK.showGameOver();
} else {
// Show lives lost message and restart level
var livesLostText = new Text2('Life Lost! ' + playerLives + ' Lives Remaining', {
size: 70,
fill: 0xFF4444
});
livesLostText.anchor.set(0.5, 0.5);
livesLostText.x = 2048 / 2;
livesLostText.y = 2732 / 2;
game.addChild(livesLostText);
// Animate text
livesLostText.scaleX = 0;
livesLostText.scaleY = 0;
tween(livesLostText, {
scaleX: 1,
scaleY: 1,
alpha: 1
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(livesLostText, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 500,
delay: 1500,
easing: tween.easeIn,
onFinish: function onFinish() {
if (livesLostText.parent) {
livesLostText.parent.removeChild(livesLostText);
}
initializeLevel(); // Restart the same level
}
});
}
});
}
return;
}
}
function setupGameHandlers() {
// Set up the main game event handlers
game.down = function (x, y, obj) {
if (isProcessing) {
return;
}
var gridX = Math.floor((x - GRID_START_X) / CELL_SIZE);
var gridY = Math.floor((y - GRID_START_Y) / CELL_SIZE);
if (gridX < 0 || gridX >= GRID_WIDTH || gridY < 0 || gridY >= GRID_HEIGHT) {
selectedCandy = null;
return;
}
var clickedCandy = grid[gridY][gridX];
if (!clickedCandy || clickedCandy.isMoving || clickedCandy.isMarkedForDestroy) {
selectedCandy = null;
return;
}
// Start drag functionality
draggedCandy = clickedCandy;
isDragging = false;
dragStartX = x;
dragStartY = y;
if (selectedCandy === null) {
selectedCandy = clickedCandy;
selectedCandy.scaleX = 1.1;
selectedCandy.scaleY = 1.1;
} else if (selectedCandy === clickedCandy) {
selectedCandy.scaleX = 1.0;
selectedCandy.scaleY = 1.0;
selectedCandy = null;
} else if (areAdjacent(selectedCandy, clickedCandy)) {
selectedCandy.scaleX = 1.0;
selectedCandy.scaleY = 1.0;
// Try swap - only proceed if it will create matches
var swapSuccessful = swapCandies(selectedCandy, clickedCandy);
if (swapSuccessful) {
if (soundEnabled) {
LK.getSound('swap').play();
}
LK.setTimeout(function () {
movesLeft--;
movesText.setText('Moves: ' + movesLeft);
processMatches();
selectedCandy = null;
}, 250);
} else {
// No swap occurred because no matches would be created
selectedCandy = null;
}
} else {
selectedCandy.scaleX = 1.0;
selectedCandy.scaleY = 1.0;
selectedCandy = clickedCandy;
selectedCandy.scaleX = 1.1;
selectedCandy.scaleY = 1.1;
}
};
game.move = function (x, y, obj) {
if (!draggedCandy || isProcessing) {
return;
}
var dragDistance = Math.sqrt((x - dragStartX) * (x - dragStartX) + (y - dragStartY) * (y - dragStartY));
if (dragDistance > 30 && !isDragging) {
isDragging = true;
// Scale up dragged candy for visual feedback
tween(draggedCandy, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 150,
easing: tween.easeOut
});
}
if (isDragging) {
// Move candy with finger/mouse
draggedCandy.x = GRID_START_X + draggedCandy.gridX * CELL_SIZE + CELL_SIZE / 2 + (x - dragStartX);
draggedCandy.y = GRID_START_Y + draggedCandy.gridY * CELL_SIZE + CELL_SIZE / 2 + (y - dragStartY);
}
};
game.up = function (x, y, obj) {
if (!draggedCandy) {
return;
}
if (isDragging) {
// Reset candy visual state
tween(draggedCandy, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 150,
easing: tween.easeOut
});
// Determine target grid position based on drag direction
var deltaX = x - dragStartX;
var deltaY = y - dragStartY;
var targetGridX = draggedCandy.gridX;
var targetGridY = draggedCandy.gridY;
// Check drag direction and distance
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// Horizontal drag
if (Math.abs(deltaX) > 50) {
if (deltaX > 0 && draggedCandy.gridX < GRID_WIDTH - 1) {
targetGridX = draggedCandy.gridX + 1;
} else if (deltaX < 0 && draggedCandy.gridX > 0) {
targetGridX = draggedCandy.gridX - 1;
}
}
} else {
// Vertical drag
if (Math.abs(deltaY) > 50) {
if (deltaY > 0 && draggedCandy.gridY < GRID_HEIGHT - 1) {
targetGridY = draggedCandy.gridY + 1;
} else if (deltaY < 0 && draggedCandy.gridY > 0) {
targetGridY = draggedCandy.gridY - 1;
}
}
}
// Reset candy position
draggedCandy.x = GRID_START_X + draggedCandy.gridX * CELL_SIZE + CELL_SIZE / 2;
draggedCandy.y = GRID_START_Y + draggedCandy.gridY * CELL_SIZE + CELL_SIZE / 2;
// Perform swap if valid target
if ((targetGridX !== draggedCandy.gridX || targetGridY !== draggedCandy.gridY) && targetGridX >= 0 && targetGridX < GRID_WIDTH && targetGridY >= 0 && targetGridY < GRID_HEIGHT) {
var targetCandy = grid[targetGridY][targetGridX];
if (targetCandy && !targetCandy.isMoving) {
// Clear any previous selection
if (selectedCandy) {
selectedCandy.scaleX = 1.0;
selectedCandy.scaleY = 1.0;
}
// Perform swap - only if it will create matches
var swapSuccessful = swapCandies(draggedCandy, targetCandy);
if (swapSuccessful) {
if (soundEnabled) {
LK.getSound('swap').play();
}
LK.setTimeout(function () {
movesLeft--;
movesText.setText('Moves: ' + movesLeft);
processMatches();
}, 250);
}
}
}
}
// Reset drag state
draggedCandy = null;
isDragging = false;
selectedCandy = null;
};
}
// Add decorative candy elements floating in background
var decorations = [];
for (var i = 0; i < 8; i++) {
var decoration = LK.getAsset('candy_decoration', {
anchorX: 0.5,
anchorY: 0.5,
x: Math.random() * 2048,
y: Math.random() * 2732,
scaleX: 0.5 + Math.random() * 0.5,
scaleY: 0.5 + Math.random() * 0.5
});
decoration.alpha = 0.2 + Math.random() * 0.3;
game.addChild(decoration);
decorations.push(decoration);
// Animate floating motion
var animDuration = 3000 + Math.random() * 2000;
var targetY = decoration.y + (Math.random() - 0.5) * 400;
tween(decoration, {
y: targetY,
rotation: Math.random() * Math.PI * 2
}, {
duration: animDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Restart animation
var newTargetY = decoration.y + (Math.random() - 0.5) * 400;
tween(decoration, {
y: newTargetY,
rotation: decoration.rotation + Math.PI * 2
}, {
duration: animDuration,
easing: tween.easeInOut
});
}
});
}
// Add sparkle effects
var sparkles = [];
for (var j = 0; j < 15; j++) {
var sparkle = LK.getAsset('sparkle', {
anchorX: 0.5,
anchorY: 0.5,
x: Math.random() * 2048,
y: Math.random() * 2732,
scaleX: 0.3 + Math.random() * 0.4,
scaleY: 0.3 + Math.random() * 0.4
});
sparkle.alpha = 0.4 + Math.random() * 0.6;
game.addChild(sparkle);
sparkles.push(sparkle);
// Twinkling animation
var twinkleDuration = 1000 + Math.random() * 1500;
tween(sparkle, {
alpha: 0.1,
scaleX: sparkle.scaleX * 0.5,
scaleY: sparkle.scaleY * 0.5
}, {
duration: twinkleDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(sparkle, {
alpha: 0.4 + Math.random() * 0.6,
scaleX: 0.3 + Math.random() * 0.4,
scaleY: 0.3 + Math.random() * 0.4
}, {
duration: twinkleDuration,
easing: tween.easeInOut
});
}
});
}
// Initialize the game
loadProgress();
createMenu(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Candy = Container.expand(function (type, gridX, gridY) {
var self = Container.call(this);
self.candyType = type;
self.gridX = gridX;
self.gridY = gridY;
self.isMoving = false;
self.isMarkedForDestroy = false;
var candyGraphics = self.attachAsset('candy_' + type, {
anchorX: 0.5,
anchorY: 0.5
});
self.moveTo = function (newGridX, newGridY, duration) {
self.gridX = newGridX;
self.gridY = newGridY;
self.isMoving = true;
var targetX = GRID_START_X + newGridX * CELL_SIZE + CELL_SIZE / 2;
var targetY = GRID_START_Y + newGridY * CELL_SIZE + CELL_SIZE / 2;
// Add slight anticipation before movement for falling candies
var isVerticalMove = Math.abs(self.y - targetY) > Math.abs(self.x - targetX);
if (isVerticalMove && targetY > self.y) {
// Falling motion - use bounce easing
tween(self, {
x: targetX,
y: targetY
}, {
duration: duration || 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Add subtle landing effect
tween(self, {
scaleX: 1.1,
scaleY: 0.9
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
self.isMoving = false;
}
});
}
});
}
});
} else {
// Horizontal movement or upward movement
tween(self, {
x: targetX,
y: targetY
}, {
duration: duration || 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
self.isMoving = false;
}
});
}
};
self.destroy = function () {
self.isMarkedForDestroy = true;
tween(self, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
};
return self;
});
var GridCell = Container.expand(function (gridX, gridY) {
var self = Container.call(this);
self.gridX = gridX;
self.gridY = gridY;
var cellGraphics = self.attachAsset('grid_cell', {
anchorX: 0.5,
anchorY: 0.5
});
cellGraphics.alpha = 0.3;
return self;
});
var HoneyCandy = Container.expand(function (type, gridX, gridY) {
var self = Container.call(this);
self.candyType = type;
self.gridX = gridX;
self.gridY = gridY;
self.isMoving = false;
self.isMarkedForDestroy = false;
self.isSticky = true;
self.movesSlowly = true;
var candyGraphics = self.attachAsset('candy_' + type, {
anchorX: 0.5,
anchorY: 0.5
});
var honeyOverlay = self.attachAsset('honey_overlay', {
anchorX: 0.5,
anchorY: 0.5
});
honeyOverlay.alpha = 0.4;
self.moveTo = function (newGridX, newGridY, duration) {
self.gridX = newGridX;
self.gridY = newGridY;
self.isMoving = true;
var targetX = GRID_START_X + newGridX * CELL_SIZE + CELL_SIZE / 2;
var targetY = GRID_START_Y + newGridY * CELL_SIZE + CELL_SIZE / 2;
// Honey moves slower
var slowDuration = (duration || 300) * 1.5;
tween(self, {
x: targetX,
y: targetY
}, {
duration: slowDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
self.isMoving = false;
}
});
};
self.destroy = function () {
self.isMarkedForDestroy = true;
tween(self, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
};
return self;
});
var LockedCandy = Container.expand(function (type, gridX, gridY, lockLevel) {
var self = Container.call(this);
self.candyType = type;
self.gridX = gridX;
self.gridY = gridY;
self.isMoving = false;
self.isMarkedForDestroy = false;
self.isLocked = true;
self.lockLevel = lockLevel || 1; // How many matches needed to unlock
var candyGraphics = self.attachAsset('candy_' + type, {
anchorX: 0.5,
anchorY: 0.5
});
var lockOverlay = self.attachAsset('locked_candy', {
anchorX: 0.5,
anchorY: 0.5
});
lockOverlay.alpha = 0.7;
self.unlock = function () {
self.lockLevel--;
if (self.lockLevel <= 0) {
self.isLocked = false;
tween(lockOverlay, {
alpha: 0,
scaleX: 0,
scaleY: 0
}, {
duration: 300,
easing: tween.easeOut
});
}
};
self.destroy = function () {
self.isMarkedForDestroy = true;
tween(self, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
};
return self;
});
var StoneBlock = Container.expand(function (gridX, gridY, durability) {
var self = Container.call(this);
self.gridX = gridX;
self.gridY = gridY;
self.isMoving = false;
self.isMarkedForDestroy = false;
self.isObstacle = true;
self.durability = durability || 2;
var stoneGraphics = self.attachAsset('stone_block', {
anchorX: 0.5,
anchorY: 0.5
});
self.hit = function () {
self.durability--;
// Visual feedback for hit
tween(self, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeIn
});
}
});
if (self.durability <= 0) {
self.destroy();
}
};
self.destroy = function () {
self.isMarkedForDestroy = true;
createExplosionEffect(self.x, self.y);
tween(self, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2a1810
});
/****
* Game Code
****/
// Create beautiful gradient background
// Beautiful gradient background layers
// Decorative elements
var backgroundContainer = new Container();
game.addChild(backgroundContainer);
// Create multiple layers for depth
var bgLayer1 = LK.getAsset('bg_gradient1', {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
var bgLayer2 = LK.getAsset('bg_gradient2', {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
var bgLayer3 = LK.getAsset('bg_pattern', {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
backgroundContainer.addChild(bgLayer1);
backgroundContainer.addChild(bgLayer2);
backgroundContainer.addChild(bgLayer3);
// Set transparency for layering effect
bgLayer2.alpha = 0.7;
bgLayer3.alpha = 0.3;
var GRID_WIDTH = 10;
var GRID_HEIGHT = 10;
var CELL_SIZE = 150;
var GRID_START_X = (2048 - GRID_WIDTH * CELL_SIZE) / 2;
var GRID_START_Y = 750;
var CANDY_TYPES = ['green', 'blue', 'orange', 'yellow', 'red'];
var grid = [];
var candies = [];
var selectedCandy = null;
var draggedCandy = null;
var isDragging = false;
var dragStartX = 0;
var dragStartY = 0;
var movesLeft = 20;
var currentScore = 0;
var targetScore = 1000;
var isProcessing = false;
var currentLevel = 1;
var maxLevel = 1;
var gameState = 'menu'; // 'menu', 'playing', 'settings'
var menuContainer = null;
var settingsContainer = null;
var soundEnabled = true;
var musicEnabled = true;
var masterVolume = 1.0;
var playerLives = 5;
var maxLives = 5;
var livesRegenerationTimer = null;
var lastLifeLostTime = null;
var livesRegenerationInterval = 600000; // 10 minutes in milliseconds - doubled regeneration time
// Level configurations with challenging obstacles and requirements - Extended to 500 levels
var levelConfigs = [];
// Generate 500 levels with progressive difficulty
for (var levelNum = 1; levelNum <= 500; levelNum++) {
// Base difficulty scaling - significantly increased
var difficultyMultiplier = Math.floor((levelNum - 1) / 5) + 1;
var baseScore = 1500 + (levelNum - 1) * 200; // Increased from 1000 + 150
var baseMoves = Math.max(3, 18 - Math.floor(levelNum / 8)); // Reduced from 20 and faster decrease
// Obstacle scaling with caps to prevent impossibility
var stoneBlocks = Math.min(25, Math.floor(2 + levelNum * 0.5)); // Increased from 1 + 0.4
var lockedCandies = Math.min(20, Math.floor(levelNum * 0.4)); // Increased from 0.3
var honeyCandies = Math.min(15, Math.floor(levelNum * 0.3)); // Increased from 0.25
// Special milestone levels (every 50 levels) with increased difficulty
if (levelNum % 50 === 0) {
baseScore = Math.floor(baseScore * 1.8); // Increased from 1.5
baseMoves = Math.max(2, baseMoves - 3); // Reduced from 3 and increased penalty
stoneBlocks = Math.min(30, Math.floor(stoneBlocks * 1.4)); // Increased from 1.3
lockedCandies = Math.min(25, Math.floor(lockedCandies * 1.4)); // Increased from 1.3
honeyCandies = Math.min(20, Math.floor(honeyCandies * 1.4)); // Increased from 1.3
}
// Boss levels (every 100 levels) with maximum difficulty
if (levelNum % 100 === 0) {
baseScore = Math.floor(baseScore * 2.5); // Increased from 2
baseMoves = Math.max(2, baseMoves - 4); // Reduced from 3 and increased penalty
stoneBlocks = Math.min(35, Math.floor(stoneBlocks * 1.6)); // Increased from 1.5
lockedCandies = Math.min(30, Math.floor(lockedCandies * 1.6)); // Increased from 1.5
honeyCandies = Math.min(25, Math.floor(honeyCandies * 1.6)); // Increased from 1.5
}
levelConfigs.push({
level: levelNum,
targetScore: baseScore,
moves: baseMoves,
gridSize: 10,
obstacles: {
stoneBlocks: stoneBlocks,
lockedCandies: lockedCandies,
honeyCandies: honeyCandies
}
});
}
// Initialize grid cells
for (var y = 0; y < GRID_HEIGHT; y++) {
grid[y] = [];
for (var x = 0; x < GRID_WIDTH; x++) {
var cell = new GridCell(x, y);
cell.x = GRID_START_X + x * CELL_SIZE + CELL_SIZE / 2;
cell.y = GRID_START_Y + y * CELL_SIZE + CELL_SIZE / 2;
game.addChild(cell);
grid[y][x] = null;
}
}
// UI Elements - Score with button background
var scoreButtonBorder = LK.getAsset('score_button_border', {
anchorX: 0.5,
anchorY: 0.5
});
var scoreButtonBg = LK.getAsset('score_button_bg', {
anchorX: 0.5,
anchorY: 0.5
});
var scoreButtonHighlight = LK.getAsset('score_button_highlight', {
anchorX: 0.5,
anchorY: 0.5
});
// Create score button container
var scoreButtonContainer = new Container();
scoreButtonContainer.addChild(scoreButtonBorder);
scoreButtonContainer.addChild(scoreButtonBg);
scoreButtonContainer.addChild(scoreButtonHighlight);
// Position button elements
scoreButtonBorder.y = 0;
scoreButtonBg.y = -2;
scoreButtonHighlight.y = -5;
scoreButtonHighlight.alpha = 0.6;
// Add gradient effect with tween
tween(scoreButtonHighlight, {
alpha: 0.3
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(scoreButtonHighlight, {
alpha: 0.6
}, {
duration: 2000,
easing: tween.easeInOut
});
}
});
var scoreText = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0.5);
scoreButtonContainer.addChild(scoreText);
LK.gui.top.addChild(scoreButtonContainer);
scoreButtonContainer.y = 90;
scoreButtonContainer.visible = false; // Initially hidden when game opens in menu
// UI Elements - Moves with button background
var movesButtonBorder = LK.getAsset('score_button_border', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
var movesButtonBg = LK.getAsset('score_button_bg', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
var movesButtonHighlight = LK.getAsset('score_button_highlight', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
// Create moves button container
var movesButtonContainer = new Container();
movesButtonContainer.addChild(movesButtonBorder);
movesButtonContainer.addChild(movesButtonBg);
movesButtonContainer.addChild(movesButtonHighlight);
// Position button elements
movesButtonBorder.y = 0;
movesButtonBg.y = -2;
movesButtonHighlight.y = -5;
movesButtonHighlight.alpha = 0.6;
// Add gradient effect with tween
tween(movesButtonHighlight, {
alpha: 0.3
}, {
duration: 2200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(movesButtonHighlight, {
alpha: 0.6
}, {
duration: 2200,
easing: tween.easeInOut
});
}
});
var movesText = new Text2('Moves: ' + movesLeft, {
size: 50,
fill: 0xFFFFFF
});
movesText.anchor.set(0.5, 0.5);
movesButtonContainer.addChild(movesText);
LK.gui.top.addChild(movesButtonContainer);
movesButtonContainer.y = 180;
movesButtonContainer.visible = false; // Initially hidden when game opens in menu
// UI Elements - Target with button background
var targetButtonBorder = LK.getAsset('score_button_border', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7
});
var targetButtonBg = LK.getAsset('score_button_bg', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7
});
var targetButtonHighlight = LK.getAsset('score_button_highlight', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7
});
// Create target button container
var targetButtonContainer = new Container();
targetButtonContainer.addChild(targetButtonBorder);
targetButtonContainer.addChild(targetButtonBg);
targetButtonContainer.addChild(targetButtonHighlight);
// Position button elements
targetButtonBorder.y = 0;
targetButtonBg.y = -2;
targetButtonHighlight.y = -5;
targetButtonHighlight.alpha = 0.6;
// Add gradient effect with tween
tween(targetButtonHighlight, {
alpha: 0.3
}, {
duration: 2400,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(targetButtonHighlight, {
alpha: 0.6
}, {
duration: 2400,
easing: tween.easeInOut
});
}
});
var targetText = new Text2('Target: ' + targetScore, {
size: 40,
fill: 0xFFDD44
});
targetText.anchor.set(0.5, 0.5);
targetButtonContainer.addChild(targetText);
LK.gui.top.addChild(targetButtonContainer);
targetButtonContainer.y = 260;
targetButtonContainer.visible = false; // Initially hidden when game opens in menu
// UI Elements - Level with button background
var levelButtonBorder = LK.getAsset('score_button_border', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
var levelButtonBg = LK.getAsset('score_button_bg', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
var levelButtonHighlight = LK.getAsset('score_button_highlight', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
// Create level button container
var levelButtonContainer = new Container();
levelButtonContainer.addChild(levelButtonBorder);
levelButtonContainer.addChild(levelButtonBg);
levelButtonContainer.addChild(levelButtonHighlight);
// Position button elements
levelButtonBorder.y = 0;
levelButtonBg.y = -2;
levelButtonHighlight.y = -5;
levelButtonHighlight.alpha = 0.6;
// Add gradient effect with tween
tween(levelButtonHighlight, {
alpha: 0.3
}, {
duration: 2600,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(levelButtonHighlight, {
alpha: 0.6
}, {
duration: 2600,
easing: tween.easeInOut
});
}
});
var levelText = new Text2('Level: ' + currentLevel, {
size: 50,
fill: 0x00FF00
});
levelText.anchor.set(0.5, 0.5);
levelButtonContainer.addChild(levelText);
LK.gui.top.addChild(levelButtonContainer);
levelButtonContainer.y = 330;
levelButtonContainer.visible = false; // Initially hidden when game opens in menu
// Menu button
// UI Elements - Lives with button background
var livesButtonBorder = LK.getAsset('score_button_border', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
var livesButtonBg = LK.getAsset('score_button_bg', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
var livesButtonHighlight = LK.getAsset('score_button_highlight', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
// Create lives button container
var livesButtonContainer = new Container();
livesButtonContainer.addChild(livesButtonBorder);
livesButtonContainer.addChild(livesButtonBg);
livesButtonContainer.addChild(livesButtonHighlight);
// Position button elements
livesButtonBorder.y = 0;
livesButtonBg.y = -2;
livesButtonHighlight.y = -5;
livesButtonHighlight.alpha = 0.6;
// Add gradient effect with tween
tween(livesButtonHighlight, {
alpha: 0.3
}, {
duration: 2800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(livesButtonHighlight, {
alpha: 0.6
}, {
duration: 2800,
easing: tween.easeInOut
});
}
});
var livesText = new Text2('Lives: ' + playerLives, {
size: 50,
fill: 0xFF4444
});
livesText.anchor.set(0.5, 0.5);
livesButtonContainer.addChild(livesText);
LK.gui.top.addChild(livesButtonContainer);
livesButtonContainer.y = 400;
livesButtonContainer.visible = false; // Initially hidden when game opens in menu
var menuButtonBorder = LK.getAsset('menu_button_border', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4
});
var menuButton = LK.getAsset('menu_button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4
});
var menuButtonContainer = new Container();
menuButtonContainer.addChild(menuButtonBorder);
menuButtonContainer.addChild(menuButton);
var menuButtonText = new Text2('MENU', {
size: 40,
fill: 0xFFFFFF
});
menuButtonText.anchor.set(0.5, 0.5);
menuButtonContainer.addChild(menuButtonText);
menuButtonContainer.down = function (x, y, obj) {
if (gameState === 'playing') {
// Exit game and return to menu
gameState = 'menu';
// Reset game event handlers
game.move = null;
game.up = null;
// Show menu
showMenu();
} else {
showMenu();
}
};
LK.gui.top.addChild(menuButtonContainer);
menuButtonContainer.y = 470;
menuButtonContainer.visible = false; // Initially hidden when game opens in menu
function getRandomCandyType() {
return CANDY_TYPES[Math.floor(Math.random() * CANDY_TYPES.length)];
}
function loadProgress() {
var savedLevel = storage.currentLevel;
var savedMaxLevel = storage.maxLevel;
if (savedLevel) {
currentLevel = parseInt(savedLevel);
}
if (savedMaxLevel) {
maxLevel = parseInt(savedMaxLevel);
}
// Load settings
var savedSoundEnabled = storage.soundEnabled;
var savedMusicEnabled = storage.musicEnabled;
var savedMasterVolume = storage.masterVolume;
if (savedSoundEnabled !== undefined) {
soundEnabled = savedSoundEnabled === 'true';
}
if (savedMusicEnabled !== undefined) {
musicEnabled = savedMusicEnabled === 'true';
}
if (savedMasterVolume !== undefined) {
masterVolume = parseFloat(savedMasterVolume);
}
// Load lives
var savedLives = storage.playerLives;
if (savedLives !== undefined) {
playerLives = parseInt(savedLives);
}
}
function saveProgress() {
storage.currentLevel = currentLevel.toString();
storage.maxLevel = maxLevel.toString();
storage.playerLives = playerLives.toString();
}
function getCurrentLevelConfig() {
if (currentLevel <= levelConfigs.length) {
return levelConfigs[currentLevel - 1];
}
// Fallback for any level beyond 500 (shouldn't happen, but safety measure)
var obstacleCount = Math.min(30, currentLevel);
return {
level: currentLevel,
targetScore: 1000 + (currentLevel - 1) * 200,
moves: Math.max(3, 20 - Math.floor(currentLevel / 10)),
gridSize: 10,
obstacles: {
stoneBlocks: Math.min(30, obstacleCount),
lockedCandies: Math.min(25, Math.floor(obstacleCount * 0.8)),
honeyCandies: Math.min(18, Math.floor(obstacleCount * 0.6))
}
};
}
function createMenu() {
if (menuContainer) {
game.removeChild(menuContainer);
}
menuContainer = new Container();
game.addChild(menuContainer);
// Create dynamic gradient background with multiple layers
var gradientLayer1 = LK.getAsset('bg_gradient1', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
alpha: 0.9
});
var gradientLayer2 = LK.getAsset('bg_gradient2', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
alpha: 0.7
});
var gradientLayer3 = LK.getAsset('bg_pattern', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
alpha: 0.4
});
menuContainer.addChild(gradientLayer1);
menuContainer.addChild(gradientLayer2);
menuContainer.addChild(gradientLayer3);
// Animate background layers with subtle movement
tween(gradientLayer2, {
x: 50,
y: 30
}, {
duration: 8000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(gradientLayer2, {
x: -30,
y: -20
}, {
duration: 8000,
easing: tween.easeInOut
});
}
});
tween(gradientLayer3, {
x: -40,
y: 25
}, {
duration: 10000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(gradientLayer3, {
x: 20,
y: -15
}, {
duration: 10000,
easing: tween.easeInOut
});
}
});
// Create modern card-based layout with rounded corners effect
var mainCard = LK.getAsset('menu_overlay', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 0.95,
scaleY: 0.9,
alpha: 0.85
});
menuContainer.addChild(mainCard);
// Add animated border effect around main card
var borderEffect = LK.getAsset('score_button_highlight', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 15,
scaleY: 12,
alpha: 0.3
});
menuContainer.addChild(borderEffect);
// Animate border glow
tween(borderEffect, {
alpha: 0.6,
scaleX: 15.2,
scaleY: 12.2
}, {
duration: 3000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(borderEffect, {
alpha: 0.3,
scaleX: 15,
scaleY: 12
}, {
duration: 3000,
easing: tween.easeInOut
});
}
});
// Floating geometric shapes for modern look
for (var i = 0; i < 8; i++) {
var shape = LK.getAsset('grid_cell', {
anchorX: 0.5,
anchorY: 0.5,
x: 200 + Math.random() * 1648,
y: 200 + Math.random() * 2332,
scaleX: 0.3 + Math.random() * 0.4,
scaleY: 0.3 + Math.random() * 0.4,
rotation: Math.random() * Math.PI * 2
});
shape.alpha = 0.1 + Math.random() * 0.15;
menuContainer.addChild(shape);
// Slow rotating animation for geometric shapes
var rotationDuration = 15000 + Math.random() * 10000;
tween(shape, {
rotation: shape.rotation + Math.PI * 2,
x: shape.x + (Math.random() - 0.5) * 300,
y: shape.y + (Math.random() - 0.5) * 300
}, {
duration: rotationDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(shape, {
rotation: shape.rotation + Math.PI * 2,
x: 200 + Math.random() * 1648,
y: 200 + Math.random() * 2332
}, {
duration: rotationDuration,
easing: tween.easeInOut
});
}
});
}
// Modern minimalist candy accents
for (var j = 0; j < 6; j++) {
var candyAccent = LK.getAsset('candy_' + CANDY_TYPES[j % CANDY_TYPES.length], {
anchorX: 0.5,
anchorY: 0.5,
x: 150 + j * 320,
y: 150 + j % 2 * 2400,
scaleX: 0.6,
scaleY: 0.6
});
candyAccent.alpha = 0.4;
candyAccent.rotation = j * Math.PI / 3;
menuContainer.addChild(candyAccent);
// Gentle floating animation for candy accents
tween(candyAccent, {
y: candyAccent.y + (Math.random() - 0.5) * 100,
rotation: candyAccent.rotation + Math.PI / 4
}, {
duration: 4000 + Math.random() * 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(candyAccent, {
y: candyAccent.y - (Math.random() - 0.5) * 100,
rotation: candyAccent.rotation + Math.PI / 4
}, {
duration: 4000 + Math.random() * 2000,
easing: tween.easeInOut
});
}
});
}
// Modern typography - Main title with shadow effect
var titleShadow = new Text2('SWEET MATCH', {
size: 160,
fill: 0x000000
});
titleShadow.anchor.set(0.5, 0.5);
titleShadow.x = 2048 / 2 + 8;
titleShadow.y = 450 + 8;
titleShadow.alpha = 0.3;
menuContainer.addChild(titleShadow);
var titleText = new Text2('SWEET MATCH', {
size: 160,
fill: 0xFFD700
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 450;
titleText.scaleX = 0;
titleText.scaleY = 0;
menuContainer.addChild(titleText);
// Modern subtitle
var subtitleText = new Text2('Premium Puzzle Experience', {
size: 55,
fill: 0xFFFFFF
});
subtitleText.anchor.set(0.5, 0.5);
subtitleText.x = 2048 / 2;
subtitleText.y = 550;
subtitleText.alpha = 0;
menuContainer.addChild(subtitleText);
// Developer credit
var developerText = new Text2('By Luanity', {
size: 45,
fill: 0xFFD700
});
developerText.anchor.set(0.5, 0.5);
developerText.x = 2048 / 2;
developerText.y = 620;
developerText.alpha = 0;
menuContainer.addChild(developerText);
// Animate title with modern entrance
tween(titleText, {
scaleX: 1.1,
scaleY: 1.1,
alpha: 1
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(titleText, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 400,
easing: tween.easeInOut
});
}
});
// Animate subtitle
tween(subtitleText, {
alpha: 0.8,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
delay: 600,
easing: tween.easeOut
});
// Animate developer credit
tween(developerText, {
alpha: 0.9,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 600,
delay: 800,
easing: tween.easeOut
});
// Create modern button container with card design
var buttonCard = LK.getAsset('menu_button_border', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 1366,
scaleX: 2.5,
scaleY: 8,
alpha: 0.2
});
menuContainer.addChild(buttonCard);
// Main action button - redesigned
var playButtonBorder = LK.getAsset('menu_button_border', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.4,
scaleY: 1.6
});
var playButton = LK.getAsset('menu_button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.4,
scaleY: 1.6
});
var playButtonGlow = LK.getAsset('score_button_highlight', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.35,
scaleY: 1.55
});
var playButtonContainer = new Container();
playButtonContainer.addChild(playButtonBorder);
playButtonContainer.addChild(playButton);
playButtonContainer.addChild(playButtonGlow);
playButtonContainer.x = 2048 / 2;
playButtonContainer.y = 850;
playButtonContainer.scaleX = 0;
playButtonContainer.scaleY = 0;
// Position button elements
playButtonBorder.y = 0;
playButton.y = -4;
playButtonGlow.y = -8;
playButtonGlow.alpha = 0.5;
var playText = new Text2('PLAY NOW', {
size: 75,
fill: 0xFFFFFF
});
playText.anchor.set(0.5, 0.5);
playButtonContainer.addChild(playText);
// Modern button entrance animation
tween(playButtonContainer, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1
}, {
duration: 800,
delay: 1000,
easing: tween.easeOut
});
// Enhanced glow effect
tween(playButtonGlow, {
alpha: 0.8,
scaleX: 1.4,
scaleY: 1.6
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(playButtonGlow, {
alpha: 0.5,
scaleX: 1.35,
scaleY: 1.55
}, {
duration: 2000,
easing: tween.easeInOut
});
}
});
// Modern press interaction
playButtonContainer.down = function (x, y, obj) {
tween(playButtonContainer, {
scaleX: 0.93,
scaleY: 0.93
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(playButtonContainer, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
startGame();
}
});
}
});
};
menuContainer.addChild(playButtonContainer);
// Level selection section with modern design
var levelSectionTitle = new Text2('LEVEL SELECT', {
size: 70,
fill: 0xFFDD44
});
levelSectionTitle.anchor.set(0.5, 0.5);
levelSectionTitle.x = 2048 / 2;
levelSectionTitle.y = 1100;
levelSectionTitle.alpha = 0;
menuContainer.addChild(levelSectionTitle);
tween(levelSectionTitle, {
alpha: 1,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 600,
delay: 1200,
easing: tween.easeOut
});
// Create level grid with modern card-based design
var buttonsPerRow = 5;
var buttonSpacing = 140;
var startX = 2048 / 2 - (buttonsPerRow - 1) * buttonSpacing / 2;
var startY = 1250;
var maxVisibleLevels = 20; // Show 20 levels (4 rows)
var levelPage = Math.floor((currentLevel - 1) / maxVisibleLevels);
var startLevel = levelPage * maxVisibleLevels + 1;
var endLevel = Math.min(startLevel + maxVisibleLevels - 1, maxLevel);
// Page indicator with modern styling
if (maxLevel > maxVisibleLevels) {
var pageIndicator = new Text2('Page ' + (levelPage + 1) + ' of ' + Math.ceil(maxLevel / maxVisibleLevels), {
size: 45,
fill: 0xCCCCCC
});
pageIndicator.anchor.set(0.5, 0.5);
pageIndicator.x = 2048 / 2;
pageIndicator.y = startY - 60;
pageIndicator.alpha = 0;
menuContainer.addChild(pageIndicator);
tween(pageIndicator, {
alpha: 0.8
}, {
duration: 500,
delay: 1400,
easing: tween.easeOut
});
}
// Modern level buttons with card design
for (var i = startLevel; i <= endLevel; i++) {
var row = Math.floor((i - startLevel) / buttonsPerRow);
var col = (i - startLevel) % buttonsPerRow;
// Level card background
var levelCard = LK.getAsset('menu_button_border', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.32,
scaleY: 0.45
});
var levelButton = LK.getAsset('menu_button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.32,
scaleY: 0.45
});
var levelGlow = LK.getAsset('score_button_highlight', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.42
});
var levelButtonContainer = new Container();
levelButtonContainer.addChild(levelCard);
levelButtonContainer.addChild(levelButton);
levelButtonContainer.addChild(levelGlow);
levelButtonContainer.x = startX + col * buttonSpacing;
levelButtonContainer.y = startY + row * 120;
levelButtonContainer.scaleX = 0;
levelButtonContainer.scaleY = 0;
// Position elements
levelCard.y = 0;
levelButton.y = -2;
levelGlow.y = -4;
levelGlow.alpha = i === currentLevel ? 0.6 : 0.2;
// Level number with modern typography
var levelNumText = new Text2(i.toString(), {
size: 40,
fill: i === currentLevel ? 0xFFD700 : 0xFFFFFF
});
levelNumText.anchor.set(0.5, 0.5);
levelButtonContainer.addChild(levelNumText);
// Staggered entrance animation
var animDelay = 1400 + (i - startLevel) * 100;
tween(levelButtonContainer, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1
}, {
duration: 400,
delay: animDelay,
easing: tween.easeOut
});
// Current level highlighting
if (i === currentLevel) {
tween(levelGlow, {
alpha: 0.8,
scaleX: 0.32,
scaleY: 0.44
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(levelGlow, {
alpha: 0.6,
scaleX: 0.3,
scaleY: 0.42
}, {
duration: 1500,
easing: tween.easeInOut
});
}
});
}
// Modern interaction
levelButtonContainer.levelNum = i;
levelButtonContainer.down = function (x, y, obj) {
tween(this, {
scaleX: 0.88,
scaleY: 0.88
}, {
duration: 80,
easing: tween.easeOut,
onFinish: function () {
tween(this, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 80,
easing: tween.easeOut,
onFinish: function () {
currentLevel = this.levelNum;
startGame();
}.bind(this)
});
}.bind(this)
});
};
menuContainer.addChild(levelButtonContainer);
}
// Modern settings button at bottom
var settingsContainer = new Container();
var settingsCard = LK.getAsset('settings_button_border', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.9,
scaleY: 1.1
});
var settingsButton = LK.getAsset('settings_button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.9,
scaleY: 1.1
});
var settingsGlow = LK.getAsset('score_button_highlight', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.85,
scaleY: 1.05
});
settingsContainer.addChild(settingsCard);
settingsContainer.addChild(settingsButton);
settingsContainer.addChild(settingsGlow);
settingsContainer.x = 2048 / 2;
settingsContainer.y = 2200;
settingsContainer.scaleX = 0;
settingsContainer.scaleY = 0;
settingsCard.y = 0;
settingsButton.y = -3;
settingsGlow.y = -6;
settingsGlow.alpha = 0.3;
var settingsText = new Text2('SETTINGS', {
size: 48,
fill: 0xFFFFFF
});
settingsText.anchor.set(0.5, 0.5);
settingsContainer.addChild(settingsText);
// Animate settings button entrance
tween(settingsContainer, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1
}, {
duration: 500,
delay: 2000,
easing: tween.easeOut
});
// Settings button interaction
settingsContainer.down = function (x, y, obj) {
tween(settingsContainer, {
scaleX: 0.92,
scaleY: 0.92
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(settingsContainer, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
showSettings();
}
});
}
});
};
menuContainer.addChild(settingsContainer);
gameState = 'menu';
}
function startGame() {
if (menuContainer) {
game.removeChild(menuContainer);
menuContainer = null;
}
if (settingsContainer) {
game.removeChild(settingsContainer);
settingsContainer = null;
}
gameState = 'playing';
// Show UI elements when playing
scoreButtonContainer.visible = true;
movesButtonContainer.visible = true;
targetButtonContainer.visible = true;
levelButtonContainer.visible = true;
livesButtonContainer.visible = true;
menuButtonContainer.visible = true;
// Reset any custom event handlers from settings
game.move = null;
game.up = null;
initializeLevel();
// Start lives regeneration if we have lost lives
if (playerLives < maxLives) {
startLivesRegeneration();
}
}
function showMenu() {
gameState = 'menu';
// Reset game event handlers when returning to menu
game.move = null;
game.up = null;
// Hide UI elements when in menu
scoreButtonContainer.visible = false;
movesButtonContainer.visible = false;
targetButtonContainer.visible = false;
levelButtonContainer.visible = false;
livesButtonContainer.visible = false;
menuButtonContainer.visible = false;
// Remove settings container if it exists
if (settingsContainer) {
game.removeChild(settingsContainer);
settingsContainer = null;
}
// Clear all game elements when returning to menu from playing state
for (var i = candies.length - 1; i >= 0; i--) {
var candy = candies[i];
if (candy.parent) {
candy.parent.removeChild(candy);
}
}
candies = [];
// Reset grid
for (var y = 0; y < GRID_HEIGHT; y++) {
for (var x = 0; x < GRID_WIDTH; x++) {
grid[y][x] = null;
}
}
// Stop lives regeneration when in menu
stopLivesRegeneration();
createMenu();
}
function showSettings() {
gameState = 'settings';
// Hide UI elements when in settings
scoreButtonContainer.visible = false;
movesButtonContainer.visible = false;
targetButtonContainer.visible = false;
levelButtonContainer.visible = false;
livesButtonContainer.visible = false;
menuButtonContainer.visible = false;
// Stop lives regeneration when in settings
stopLivesRegeneration();
createSettingsMenu();
}
function createSettingsMenu() {
if (settingsContainer) {
game.removeChild(settingsContainer);
}
settingsContainer = new Container();
game.addChild(settingsContainer);
// Settings overlay background
var settingsOverlay = LK.getAsset('menu_overlay', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
alpha: 0.9
});
settingsContainer.addChild(settingsOverlay);
// Settings title
var settingsTitle = new Text2('SETTINGS', {
size: 120,
fill: 0xFFD700
});
settingsTitle.anchor.set(0.5, 0.5);
settingsTitle.x = 2048 / 2;
settingsTitle.y = 400;
settingsTitle.scaleX = 0;
settingsTitle.scaleY = 0;
settingsContainer.addChild(settingsTitle);
// Animate title entrance
tween(settingsTitle, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1
}, {
duration: 600,
easing: tween.easeOut
});
var yPosition = 700;
var spacing = 150;
// Sound toggle
createToggleOption('SOUND EFFECTS', soundEnabled, yPosition, function (enabled) {
soundEnabled = enabled;
storage.soundEnabled = enabled.toString();
});
yPosition += spacing;
// Music toggle
createToggleOption('MUSIC', musicEnabled, yPosition, function (enabled) {
musicEnabled = enabled;
storage.musicEnabled = enabled.toString();
});
yPosition += spacing;
// Master volume slider
createVolumeSlider('MASTER VOLUME', masterVolume, yPosition, function (volume) {
masterVolume = volume;
storage.masterVolume = volume.toString();
});
yPosition += spacing + 100;
// Back button
var backButtonBorder = LK.getAsset('menu_button_border', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
var backButton = LK.getAsset('menu_button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
var backButtonContainer = new Container();
backButtonContainer.addChild(backButtonBorder);
backButtonContainer.addChild(backButton);
backButtonContainer.x = 2048 / 2;
backButtonContainer.y = yPosition;
backButtonContainer.scaleX = 0;
backButtonContainer.scaleY = 0;
var backText = new Text2('BACK', {
size: 60,
fill: 0xFFFFFF
});
backText.anchor.set(0.5, 0.5);
backButtonContainer.addChild(backText);
// Animate back button
tween(backButtonContainer, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1
}, {
duration: 400,
delay: 800,
easing: tween.easeOut
});
backButtonContainer.down = function (x, y, obj) {
tween(backButtonContainer, {
scaleX: 0.75,
scaleY: 0.75
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(backButtonContainer, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
showMenu();
}
});
}
});
};
settingsContainer.addChild(backButtonContainer);
function createToggleOption(label, currentValue, yPos, callback) {
// Label text
var labelText = new Text2(label, {
size: 60,
fill: 0xFFFFFF
});
labelText.anchor.set(0, 0.5);
labelText.x = 400;
labelText.y = yPos;
labelText.alpha = 0;
settingsContainer.addChild(labelText);
// Toggle button
var toggleBorder = LK.getAsset('settings_button_border', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.6
});
var toggleButton = LK.getAsset(currentValue ? 'toggle_button_on' : 'toggle_button_off', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.6
});
var toggleContainer = new Container();
toggleContainer.addChild(toggleBorder);
toggleContainer.addChild(toggleButton);
toggleContainer.x = 1500;
toggleContainer.y = yPos;
toggleContainer.alpha = 0;
var toggleText = new Text2(currentValue ? 'ON' : 'OFF', {
size: 40,
fill: 0xFFFFFF
});
toggleText.anchor.set(0.5, 0.5);
toggleContainer.addChild(toggleText);
// Animate entrance
tween(labelText, {
alpha: 1
}, {
duration: 400,
delay: 400,
easing: tween.easeOut
});
tween(toggleContainer, {
alpha: 1
}, {
duration: 400,
delay: 500,
easing: tween.easeOut
});
toggleContainer.down = function (x, y, obj) {
var newValue = !currentValue;
currentValue = newValue;
// Update visual
toggleContainer.removeChild(toggleButton);
toggleButton = LK.getAsset(newValue ? 'toggle_button_on' : 'toggle_button_off', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.6
});
toggleContainer.addChild(toggleButton);
toggleText.setText(newValue ? 'ON' : 'OFF');
// Animate press
tween(toggleContainer, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(toggleContainer, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut
});
}
});
callback(newValue);
};
settingsContainer.addChild(toggleContainer);
}
function createVolumeSlider(label, currentValue, yPos, callback) {
// Label text
var labelText = new Text2(label, {
size: 60,
fill: 0xFFFFFF
});
labelText.anchor.set(0, 0.5);
labelText.x = 400;
labelText.y = yPos;
labelText.alpha = 0;
settingsContainer.addChild(labelText);
// Slider track
var sliderTrack = LK.getAsset('slider_track', {
anchorX: 0.5,
anchorY: 0.5
});
sliderTrack.x = 1400;
sliderTrack.y = yPos;
sliderTrack.alpha = 0;
settingsContainer.addChild(sliderTrack);
// Slider handle
var sliderHandle = LK.getAsset('slider_handle', {
anchorX: 0.5,
anchorY: 0.5
});
sliderHandle.x = 1400 - 100 + 200 * currentValue;
sliderHandle.y = yPos;
sliderHandle.alpha = 0;
settingsContainer.addChild(sliderHandle);
// Volume percentage text
var volumeText = new Text2(Math.round(currentValue * 100) + '%', {
size: 45,
fill: 0xFFDD44
});
volumeText.anchor.set(0.5, 0.5);
volumeText.x = 1600;
volumeText.y = yPos;
volumeText.alpha = 0;
settingsContainer.addChild(volumeText);
// Animate entrance
tween(labelText, {
alpha: 1
}, {
duration: 400,
delay: 400,
easing: tween.easeOut
});
tween(sliderTrack, {
alpha: 1
}, {
duration: 400,
delay: 500,
easing: tween.easeOut
});
tween(sliderHandle, {
alpha: 1
}, {
duration: 400,
delay: 600,
easing: tween.easeOut
});
tween(volumeText, {
alpha: 1
}, {
duration: 400,
delay: 700,
easing: tween.easeOut
});
var isDraggingSlider = false;
sliderHandle.down = function (x, y, obj) {
isDraggingSlider = true;
tween(sliderHandle, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
easing: tween.easeOut
});
};
game.move = function (x, y, obj) {
if (isDraggingSlider) {
var sliderX = Math.max(1300, Math.min(1500, x));
sliderHandle.x = sliderX;
var newValue = (sliderX - 1300) / 200;
volumeText.setText(Math.round(newValue * 100) + '%');
callback(newValue);
}
};
game.up = function (x, y, obj) {
if (isDraggingSlider) {
isDraggingSlider = false;
tween(sliderHandle, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut
});
}
};
}
}
function startLivesRegeneration() {
// Only start regeneration if we have lost lives and timer isn't already running
if (playerLives < maxLives && !livesRegenerationTimer) {
livesRegenerationTimer = LK.setInterval(function () {
if (playerLives < maxLives) {
playerLives++;
livesText.setText('Lives: ' + playerLives);
saveProgress();
// Show life regenerated notification
var lifeRegenText = new Text2('Life Restored! ' + playerLives + '/' + maxLives, {
size: 60,
fill: 0x00FF00
});
lifeRegenText.anchor.set(0.5, 0.5);
lifeRegenText.x = 2048 / 2;
lifeRegenText.y = 2732 / 2 - 150;
lifeRegenText.alpha = 0;
game.addChild(lifeRegenText);
// Animate notification
tween(lifeRegenText, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(lifeRegenText, {
alpha: 0,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 400,
delay: 1200,
easing: tween.easeIn,
onFinish: function onFinish() {
if (lifeRegenText.parent) {
lifeRegenText.parent.removeChild(lifeRegenText);
}
}
});
}
});
// Stop regeneration when max lives reached
if (playerLives >= maxLives) {
LK.clearInterval(livesRegenerationTimer);
livesRegenerationTimer = null;
}
} else {
// Stop timer if lives are full
LK.clearInterval(livesRegenerationTimer);
livesRegenerationTimer = null;
}
}, livesRegenerationInterval);
}
}
function stopLivesRegeneration() {
if (livesRegenerationTimer) {
LK.clearInterval(livesRegenerationTimer);
livesRegenerationTimer = null;
}
}
function initializeLevel() {
var config = getCurrentLevelConfig();
targetScore = config.targetScore;
movesLeft = config.moves;
currentScore = 0;
// Update UI
levelText.setText('Level: ' + currentLevel);
scoreText.setText('Score: 0');
movesText.setText('Moves: ' + movesLeft);
targetText.setText('Target: ' + targetScore);
livesText.setText('Lives: ' + playerLives);
// Clear existing candies
for (var i = candies.length - 1; i >= 0; i--) {
var candy = candies[i];
if (candy.parent) {
candy.parent.removeChild(candy);
}
}
candies = [];
// Reset grid
for (var y = 0; y < GRID_HEIGHT; y++) {
for (var x = 0; x < GRID_WIDTH; x++) {
grid[y][x] = null;
}
}
// Initialize new grid
initializeGrid();
// Set up game event handlers for playing state
setupGameHandlers();
}
function completeLevel() {
if (currentLevel >= maxLevel) {
maxLevel = currentLevel + 1;
}
currentLevel++;
saveProgress();
// Show level complete message briefly before starting next level
var levelCompleteText = new Text2('Level Complete!', {
size: 80,
fill: 0x00FF00
});
levelCompleteText.anchor.set(0.5, 0.5);
levelCompleteText.x = 2048 / 2;
levelCompleteText.y = 2732 / 2;
game.addChild(levelCompleteText);
// Animate text
levelCompleteText.scaleX = 0;
levelCompleteText.scaleY = 0;
tween(levelCompleteText, {
scaleX: 1,
scaleY: 1,
alpha: 1
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(levelCompleteText, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 500,
delay: 1000,
easing: tween.easeIn,
onFinish: function onFinish() {
if (levelCompleteText.parent) {
levelCompleteText.parent.removeChild(levelCompleteText);
}
// Show ad every 3 levels
if (currentLevel % 3 === 1 && currentLevel > 1) {
showLevelTransitionAd();
} else {
initializeLevel();
}
}
});
}
});
}
function showLevelTransitionAd() {
// Show interstitial ad between levels
ads.showInterstitial({
onAdClosed: function onAdClosed() {
// Continue to next level after ad is closed
initializeLevel();
},
onAdFailed: function onAdFailed() {
// If ad fails to load, continue anyway
initializeLevel();
}
});
}
function createCandy(x, y, type) {
type = type || getRandomCandyType();
var candy = new Candy(type, x, y);
candy.x = GRID_START_X + x * CELL_SIZE + CELL_SIZE / 2;
candy.y = GRID_START_Y + y * CELL_SIZE + CELL_SIZE / 2;
game.addChild(candy);
candies.push(candy);
grid[y][x] = candy;
return candy;
}
function placeObstacles(config) {
if (!config.obstacles) {
return;
}
var obstacles = config.obstacles;
var placedPositions = [];
var totalObstacles = obstacles.stoneBlocks + obstacles.lockedCandies + obstacles.honeyCandies;
// Place stone blocks first (highest priority)
for (var i = 0; i < obstacles.stoneBlocks; i++) {
var placed = false;
var attempts = 0;
while (!placed && attempts < 50) {
var x = Math.floor(Math.random() * GRID_WIDTH);
var y = Math.floor(Math.random() * GRID_HEIGHT);
var posKey = x + ',' + y;
// Avoid corners and center, prefer edges and strategic positions
if (!placedPositions[posKey] && grid[y][x] && !(x < 2 && y < 2) && !(x > GRID_WIDTH - 3 && y < 2) && !(x < 2 && y > GRID_HEIGHT - 3) && !(x > GRID_WIDTH - 3 && y > GRID_HEIGHT - 3)) {
// Remove existing candy
var existingCandy = grid[y][x];
if (existingCandy && existingCandy.parent) {
existingCandy.parent.removeChild(existingCandy);
var candyIndex = candies.indexOf(existingCandy);
if (candyIndex > -1) {
candies.splice(candyIndex, 1);
}
}
// Create stone block
var stone = new StoneBlock(x, y, 1 + Math.floor(currentLevel / 3));
stone.x = GRID_START_X + x * CELL_SIZE + CELL_SIZE / 2;
stone.y = GRID_START_Y + y * CELL_SIZE + CELL_SIZE / 2;
game.addChild(stone);
grid[y][x] = stone;
placedPositions[posKey] = true;
placed = true;
}
attempts++;
}
}
// Place locked candies
for (var j = 0; j < obstacles.lockedCandies; j++) {
var placed = false;
var attempts = 0;
while (!placed && attempts < 50) {
var x = Math.floor(Math.random() * GRID_WIDTH);
var y = Math.floor(Math.random() * GRID_HEIGHT);
var posKey = x + ',' + y;
if (!placedPositions[posKey] && grid[y][x] && !grid[y][x].isObstacle) {
// Convert existing candy to locked candy
var existingCandy = grid[y][x];
var type = existingCandy.candyType;
if (existingCandy.parent) {
existingCandy.parent.removeChild(existingCandy);
var candyIndex = candies.indexOf(existingCandy);
if (candyIndex > -1) {
candies.splice(candyIndex, 1);
}
}
var lockedCandy = new LockedCandy(type, x, y, 1 + Math.floor(currentLevel / 4));
lockedCandy.x = GRID_START_X + x * CELL_SIZE + CELL_SIZE / 2;
lockedCandy.y = GRID_START_Y + y * CELL_SIZE + CELL_SIZE / 2;
game.addChild(lockedCandy);
candies.push(lockedCandy);
grid[y][x] = lockedCandy;
placedPositions[posKey] = true;
placed = true;
}
attempts++;
}
}
// Place honey candies
for (var k = 0; k < obstacles.honeyCandies; k++) {
var placed = false;
var attempts = 0;
while (!placed && attempts < 50) {
var x = Math.floor(Math.random() * GRID_WIDTH);
var y = Math.floor(Math.random() * GRID_HEIGHT);
var posKey = x + ',' + y;
if (!placedPositions[posKey] && grid[y][x] && !grid[y][x].isObstacle && !grid[y][x].isLocked) {
// Convert existing candy to honey candy
var existingCandy = grid[y][x];
var type = existingCandy.candyType;
if (existingCandy.parent) {
existingCandy.parent.removeChild(existingCandy);
var candyIndex = candies.indexOf(existingCandy);
if (candyIndex > -1) {
candies.splice(candyIndex, 1);
}
}
var honeyCandy = new HoneyCandy(type, x, y);
honeyCandy.x = GRID_START_X + x * CELL_SIZE + CELL_SIZE / 2;
honeyCandy.y = GRID_START_Y + y * CELL_SIZE + CELL_SIZE / 2;
game.addChild(honeyCandy);
candies.push(honeyCandy);
grid[y][x] = honeyCandy;
placedPositions[posKey] = true;
placed = true;
}
attempts++;
}
}
}
function getLevelSpecificCandyType(x, y, level) {
// Define specific patterns for each level
switch (level) {
case 1:
// Level 1: More green and blue candies in corners
if (x < 3 && y < 3 || x > 6 && y > 6) {
return Math.random() < 0.6 ? Math.random() < 0.5 ? 'green' : 'blue' : getRandomCandyType();
}
break;
case 2:
// Level 2: Checkerboard pattern influence with green and yellow
if ((x + y) % 2 === 0) {
return Math.random() < 0.5 ? Math.random() < 0.5 ? 'green' : 'yellow' : getRandomCandyType();
}
break;
case 3:
// Level 3: Center focus with purple and orange
var centerDistance = Math.abs(x - 4.5) + Math.abs(y - 4.5);
if (centerDistance < 4) {
return Math.random() < 0.7 ? Math.random() < 0.5 ? 'purple' : 'orange' : getRandomCandyType();
}
break;
case 4:
// Level 4: Diagonal pattern with orange, green, and red
if (Math.abs(x - y) < 2 || Math.abs(x + y - 9) < 2) {
var candyChoice = Math.random();
if (candyChoice < 0.33) {
return 'orange';
} else if (candyChoice < 0.66) {
return 'green';
} else if (candyChoice < 0.8) {
return 'red';
} else {
return getRandomCandyType();
}
}
break;
case 5:
// Level 5: Border emphasis with blue and yellow
if (x < 2 || x > 7 || y < 2 || y > 7) {
return Math.random() < 0.7 ? Math.random() < 0.5 ? 'blue' : 'yellow' : getRandomCandyType();
}
break;
case 6:
// Level 6: Cross pattern with blue, orange, and red
if (x === 4 || x === 5 || y === 4 || y === 5) {
var candyChoice = Math.random();
if (candyChoice < 0.33) {
return 'blue';
} else if (candyChoice < 0.66) {
return 'orange';
} else if (candyChoice < 0.8) {
return 'red';
} else {
return getRandomCandyType();
}
}
break;
case 7:
// Level 7: Ring pattern with orange and green
var ringDistance = Math.max(Math.abs(x - 4.5), Math.abs(y - 4.5));
if (ringDistance > 2 && ringDistance < 5) {
return Math.random() < 0.6 ? Math.random() < 0.5 ? 'orange' : 'green' : getRandomCandyType();
}
break;
case 8:
// Level 8: Spiral influence with blue and yellow
var spiralValue = (x * 3 + y * 2) % 7;
if (spiralValue < 3) {
return Math.random() < 0.7 ? Math.random() < 0.5 ? 'blue' : 'yellow' : getRandomCandyType();
}
break;
case 9:
// Level 9: Scattered clusters with yellow, green, and red
if (x % 3 === 0 && y % 3 === 0 || x % 3 === 2 && y % 3 === 2) {
var candyChoice = Math.random();
if (candyChoice < 0.33) {
return 'yellow';
} else if (candyChoice < 0.66) {
return 'green';
} else if (candyChoice < 0.8) {
return 'red';
} else {
return getRandomCandyType();
}
}
break;
case 10:
// Level 10: Complex pattern with all colors but emphasis on green and orange
if ((x + y) % 4 < 2) {
return Math.random() < 0.5 ? Math.random() < 0.5 ? 'green' : 'orange' : getRandomCandyType();
}
break;
default:
// For levels beyond 10, create dynamic patterns based on level number
var patternType = level % 5;
switch (patternType) {
case 0:
// Diagonal emphasis
if (Math.abs(x - y) < 3) {
return Math.random() < 0.6 ? CANDY_TYPES[level % CANDY_TYPES.length] : getRandomCandyType();
}
break;
case 1:
// Corner concentration
if (x < 3 && y < 3 || x > 6 && y > 6 || x < 3 && y > 6 || x > 6 && y < 3) {
return Math.random() < 0.7 ? CANDY_TYPES[(level + 1) % CANDY_TYPES.length] : getRandomCandyType();
}
break;
case 2:
// Center ring
var centerDist = Math.max(Math.abs(x - 4.5), Math.abs(y - 4.5));
if (centerDist > 1 && centerDist < 4) {
return Math.random() < 0.6 ? CANDY_TYPES[(level + 2) % CANDY_TYPES.length] : getRandomCandyType();
}
break;
case 3:
// Checkerboard influence
if ((x + y + level) % 3 === 0) {
return Math.random() < 0.7 ? CANDY_TYPES[(level + 3) % CANDY_TYPES.length] : getRandomCandyType();
}
break;
case 4:
// Random clusters
if ((x * y + level) % 11 < 4) {
return Math.random() < 0.6 ? CANDY_TYPES[(level + 4) % CANDY_TYPES.length] : getRandomCandyType();
}
break;
}
break;
}
return getRandomCandyType();
}
function initializeGrid() {
for (var y = 0; y < GRID_HEIGHT; y++) {
for (var x = 0; x < GRID_WIDTH; x++) {
var type;
var attempts = 0;
do {
// Use level-specific candy type generation
type = getLevelSpecificCandyType(x, y, currentLevel);
attempts++;
if (attempts > 10) {
// Fallback to completely random if level pattern is too restrictive
type = getRandomCandyType();
break;
}
} while (wouldCreateMatch(x, y, type));
createCandy(x, y, type);
}
}
// Place obstacles after initial candy generation
var config = getCurrentLevelConfig();
placeObstacles(config);
}
function wouldCreateMatch(x, y, type) {
var horizontalCount = 1;
var verticalCount = 1;
// Check horizontal
var i = x - 1;
while (i >= 0 && grid[y][i] && grid[y][i].candyType === type) {
horizontalCount++;
i--;
}
i = x + 1;
while (i < GRID_WIDTH && grid[y][i] && grid[y][i].candyType === type) {
horizontalCount++;
i++;
}
// Check vertical
i = y - 1;
while (i >= 0 && grid[i][x] && grid[i][x].candyType === type) {
verticalCount++;
i--;
}
i = y + 1;
while (i < GRID_HEIGHT && grid[i][x] && grid[i][x].candyType === type) {
verticalCount++;
i++;
}
return horizontalCount >= 3 || verticalCount >= 3;
}
function findMatches() {
var matches = [];
var processed = [];
for (var y = 0; y < GRID_HEIGHT; y++) {
processed[y] = [];
for (var x = 0; x < GRID_WIDTH; x++) {
processed[y][x] = false;
}
}
// Find horizontal matches
for (var y = 0; y < GRID_HEIGHT; y++) {
for (var x = 0; x < GRID_WIDTH - 2; x++) {
if (!grid[y][x] || processed[y][x]) {
continue;
}
var type = grid[y][x].candyType;
var matchLength = 1;
var startX = x;
while (x + matchLength < GRID_WIDTH && grid[y][x + matchLength] && grid[y][x + matchLength].candyType === type) {
matchLength++;
}
if (matchLength >= 3) {
for (var i = 0; i < matchLength; i++) {
if (!processed[y][startX + i]) {
matches.push(grid[y][startX + i]);
processed[y][startX + i] = true;
}
}
}
}
}
// Find vertical matches
for (var x = 0; x < GRID_WIDTH; x++) {
for (var y = 0; y < GRID_HEIGHT - 2; y++) {
if (!grid[y][x] || processed[y][x]) {
continue;
}
var type = grid[y][x].candyType;
var matchLength = 1;
var startY = y;
while (y + matchLength < GRID_HEIGHT && grid[y + matchLength][x] && grid[y + matchLength][x].candyType === type) {
matchLength++;
}
if (matchLength >= 3) {
for (var i = 0; i < matchLength; i++) {
if (!processed[startY + i][x]) {
matches.push(grid[startY + i][x]);
processed[startY + i][x] = true;
}
}
}
}
}
return matches;
}
function createExplosionEffect(x, y) {
// Create multiple particles for explosion effect
var particleCount = 8 + Math.floor(Math.random() * 4);
for (var i = 0; i < particleCount; i++) {
var particle = LK.getAsset('explosion_particle', {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
scaleX: 0.8 + Math.random() * 0.4,
scaleY: 0.8 + Math.random() * 0.4
});
game.addChild(particle);
// Random direction and distance
var angle = Math.PI * 2 * i / particleCount + (Math.random() - 0.5) * 0.5;
var distance = 60 + Math.random() * 40;
var targetX = x + Math.cos(angle) * distance;
var targetY = y + Math.sin(angle) * distance;
// Animate particle explosion
tween(particle, {
x: targetX,
y: targetY,
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 400 + Math.random() * 200,
easing: tween.easeOut,
onFinish: function onFinish() {
if (particle.parent) {
particle.parent.removeChild(particle);
}
}
});
}
// Create star particles for extra sparkle
var starCount = 4 + Math.floor(Math.random() * 3);
for (var j = 0; j < starCount; j++) {
var star = LK.getAsset('explosion_star', {
anchorX: 0.5,
anchorY: 0.5,
x: x + (Math.random() - 0.5) * 40,
y: y + (Math.random() - 0.5) * 40,
rotation: Math.random() * Math.PI * 2
});
game.addChild(star);
// Animate star particles
tween(star, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0,
rotation: star.rotation + Math.PI * 2
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
if (star.parent) {
star.parent.removeChild(star);
}
}
});
}
}
function createEnhancedExplosionEffect(x, y) {
// Create larger, more intense explosion for special matches
var particleCount = 15 + Math.floor(Math.random() * 8);
for (var i = 0; i < particleCount; i++) {
var particle = LK.getAsset('explosion_particle', {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
scaleX: 1.2 + Math.random() * 0.8,
scaleY: 1.2 + Math.random() * 0.8
});
game.addChild(particle);
// Random direction and larger distance
var angle = Math.PI * 2 * i / particleCount + (Math.random() - 0.5) * 0.7;
var distance = 80 + Math.random() * 60;
var targetX = x + Math.cos(angle) * distance;
var targetY = y + Math.sin(angle) * distance;
// Animate particle explosion with more dramatic effect
tween(particle, {
x: targetX,
y: targetY,
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 600 + Math.random() * 300,
easing: tween.easeOut,
onFinish: function onFinish() {
if (particle.parent) {
particle.parent.removeChild(particle);
}
}
});
}
// Create more star particles
var starCount = 8 + Math.floor(Math.random() * 6);
for (var j = 0; j < starCount; j++) {
var star = LK.getAsset('explosion_star', {
anchorX: 0.5,
anchorY: 0.5,
x: x + (Math.random() - 0.5) * 80,
y: y + (Math.random() - 0.5) * 80,
rotation: Math.random() * Math.PI * 2,
scaleX: 1.5,
scaleY: 1.5
});
game.addChild(star);
// Animate star particles with more dramatic effect
tween(star, {
scaleX: 2.5,
scaleY: 2.5,
alpha: 0,
rotation: star.rotation + Math.PI * 4
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
if (star.parent) {
star.parent.removeChild(star);
}
}
});
}
}
function showScorePopup(score, isSpecial) {
var scorePopup = new Text2('+' + score, {
size: isSpecial ? 100 : 70,
fill: isSpecial ? 0xFFD700 : 0x00FF00
});
scorePopup.anchor.set(0.5, 0.5);
scorePopup.x = 2048 / 2 + (Math.random() - 0.5) * 200;
scorePopup.y = 2732 / 2 + (Math.random() - 0.5) * 200;
scorePopup.scaleX = 0;
scorePopup.scaleY = 0;
game.addChild(scorePopup);
// Animate score popup
tween(scorePopup, {
scaleX: 1.2,
scaleY: 1.2,
y: scorePopup.y - 100,
alpha: 1
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(scorePopup, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 300,
delay: 400,
easing: tween.easeIn,
onFinish: function onFinish() {
if (scorePopup.parent) {
scorePopup.parent.removeChild(scorePopup);
}
}
});
}
});
}
function clearMatches(matches) {
if (matches.length === 0) {
return;
}
// Play explosion sound when candies are cleared
if (soundEnabled) {
var explosionSound = LK.getSound('explosion');
if (explosionSound) {
explosionSound.play();
}
}
if (soundEnabled) {
var matchSound = LK.getSound('match');
if (matchSound) {
matchSound.play();
}
}
// Handle matches with combo multiplier - reduced scoring for increased difficulty
var baseScore = matches.length * 30; // Reduced from 50
var comboMultiplier = game.comboMultiplier || 1;
var bonusScore = Math.floor(baseScore * comboMultiplier * 0.8); // Apply 20% reduction
currentScore += bonusScore;
scoreText.setText('Score: ' + currentScore);
// Show score popup for significant matches
if (matches.length >= 4 || comboMultiplier > 1) {
showScorePopup(bonusScore, matches.length >= 4);
}
for (var i = 0; i < matches.length; i++) {
var candy = matches[i];
// Handle locked candies - unlock instead of destroying
if (candy.isLocked) {
candy.unlock();
if (candy.lockLevel > 0) {
continue; // Don't destroy if still locked
}
}
// Create enhanced explosion effect for larger matches
if (matches.length >= 4) {
createEnhancedExplosionEffect(candy.x, candy.y);
} else {
createExplosionEffect(candy.x, candy.y);
}
// Check adjacent cells for obstacles to damage
var adjacentPositions = [{
x: candy.gridX - 1,
y: candy.gridY
}, {
x: candy.gridX + 1,
y: candy.gridY
}, {
x: candy.gridX,
y: candy.gridY - 1
}, {
x: candy.gridX,
y: candy.gridY + 1
}];
for (var j = 0; j < adjacentPositions.length; j++) {
var pos = adjacentPositions[j];
if (pos.x >= 0 && pos.x < GRID_WIDTH && pos.y >= 0 && pos.y < GRID_HEIGHT) {
var adjacentCell = grid[pos.y][pos.x];
if (adjacentCell && adjacentCell.isObstacle && adjacentCell.hit) {
adjacentCell.hit();
if (adjacentCell.isMarkedForDestroy) {
grid[pos.y][pos.x] = null;
}
}
}
}
grid[candy.gridY][candy.gridX] = null;
candy.destroy();
// Remove from candies array
var index = candies.indexOf(candy);
if (index > -1) {
candies.splice(index, 1);
}
}
}
function applyGravity() {
var moved = false;
var animationsToWait = [];
for (var x = 0; x < GRID_WIDTH; x++) {
var writeY = GRID_HEIGHT - 1;
for (var y = GRID_HEIGHT - 1; y >= 0; y--) {
if (grid[y][x] && !grid[y][x].isMarkedForDestroy) {
if (y !== writeY) {
grid[writeY][x] = grid[y][x];
grid[y][x] = null;
var fallDistance = writeY - y;
var animDuration = 150 + fallDistance * 50; // Longer fall = longer animation
if (grid[writeY][x] && grid[writeY][x].moveTo) {
grid[writeY][x].moveTo(x, writeY, animDuration);
}
moved = true;
}
writeY--;
}
}
// Fill empty spaces at top with staggered animation
for (var emptyY = writeY; emptyY >= 0; emptyY--) {
var newCandy = createCandy(x, emptyY, getRandomCandyType());
var dropDistance = writeY - emptyY + 1;
newCandy.y = GRID_START_Y - dropDistance * CELL_SIZE + CELL_SIZE / 2;
var dropAnimDuration = 200 + dropDistance * 60;
newCandy.moveTo(x, emptyY, dropAnimDuration);
// Add bounce effect for new candies
tween(newCandy, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: dropAnimDuration / 2,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(newCandy, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: dropAnimDuration / 2,
easing: tween.easeIn
});
}
});
moved = true;
}
}
return moved;
}
function areAdjacent(candy1, candy2) {
// Check if candies are valid before checking adjacency
if (!candy1 || !candy2 || candy1.isMarkedForDestroy || candy2.isMarkedForDestroy) {
return false;
}
var dx = Math.abs(candy1.gridX - candy2.gridX);
var dy = Math.abs(candy1.gridY - candy2.gridY);
return dx === 1 && dy === 0 || dx === 0 && dy === 1;
}
function swapCandies(candy1, candy2) {
// Check if candies are valid before swapping
if (!candy1 || !candy2 || candy1.isMarkedForDestroy || candy2.isMarkedForDestroy) {
return false;
}
// Temporarily swap in grid to test for matches
var tempGridX1 = candy1.gridX;
var tempGridY1 = candy1.gridY;
var tempGridX2 = candy2.gridX;
var tempGridY2 = candy2.gridY;
// Update grid positions temporarily
grid[candy1.gridY][candy1.gridX] = candy2;
grid[candy2.gridY][candy2.gridX] = candy1;
candy1.gridX = tempGridX2;
candy1.gridY = tempGridY2;
candy2.gridX = tempGridX1;
candy2.gridY = tempGridY1;
// Check if this swap creates matches
var matches = findMatches();
var willCreateMatches = matches.length > 0;
// If no matches will be created, revert the temporary swap
if (!willCreateMatches) {
grid[tempGridY1][tempGridX1] = candy1;
grid[tempGridY2][tempGridX2] = candy2;
candy1.gridX = tempGridX1;
candy1.gridY = tempGridY1;
candy2.gridX = tempGridX2;
candy2.gridY = tempGridY2;
return false;
}
// If matches will be created, perform the actual visual swap for both candies
candy1.moveTo(tempGridX2, tempGridY2, 200);
candy2.moveTo(tempGridX1, tempGridY1, 200);
return true;
}
function processMatches() {
if (isProcessing) {
return;
}
isProcessing = true;
// Track combo multiplier
if (!game.comboMultiplier) {
game.comboMultiplier = 1;
}
if (!game.comboCount) {
game.comboCount = 0;
}
LK.setTimeout(function () {
var matches = findMatches();
if (matches.length > 0) {
// Increase combo count for cascading matches
game.comboCount++;
if (game.comboCount > 1) {
game.comboMultiplier = Math.min(game.comboCount, 5); // Max 5x multiplier
// Show combo text
showComboText(game.comboMultiplier);
}
clearMatches(matches);
LK.setTimeout(function () {
if (applyGravity()) {
LK.setTimeout(function () {
isProcessing = false;
processMatches(); // Check for cascading matches
}, 500); // Slightly longer delay for better visual flow
} else {
isProcessing = false;
// Reset combo after no more matches
game.comboMultiplier = 1;
game.comboCount = 0;
checkGameState();
}
}, 350); // Longer delay between match clearing and gravity
} else {
isProcessing = false;
// Reset combo when no matches found
game.comboMultiplier = 1;
game.comboCount = 0;
checkGameState();
}
}, 200); // Faster initial processing
}
function showComboText(multiplier) {
var comboText = new Text2('COMBO x' + multiplier + '!', {
size: 80,
fill: 0xFFD700
});
comboText.anchor.set(0.5, 0.5);
comboText.x = 2048 / 2;
comboText.y = 2732 / 2 - 200;
comboText.scaleX = 0;
comboText.scaleY = 0;
game.addChild(comboText);
// Animate combo text
tween(comboText, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 1
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(comboText, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 400,
delay: 500,
easing: tween.easeIn,
onFinish: function onFinish() {
if (comboText.parent) {
comboText.parent.removeChild(comboText);
}
}
});
}
});
}
function checkGameState() {
if (currentScore >= targetScore) {
completeLevel();
return;
}
if (movesLeft <= 0) {
// Decrease lives instead of immediate game over
playerLives--;
livesText.setText('Lives: ' + playerLives);
lastLifeLostTime = Date.now();
saveProgress();
// Start lives regeneration timer when lives are lost
startLivesRegeneration();
if (playerLives <= 0) {
// Game over when no lives left
LK.setTimeout(function () {
// Reset lives to max when game over
playerLives = maxLives;
saveProgress();
showMenu();
}, 2000);
LK.showGameOver();
} else {
// Show lives lost message and restart level
var livesLostText = new Text2('Life Lost! ' + playerLives + ' Lives Remaining', {
size: 70,
fill: 0xFF4444
});
livesLostText.anchor.set(0.5, 0.5);
livesLostText.x = 2048 / 2;
livesLostText.y = 2732 / 2;
game.addChild(livesLostText);
// Animate text
livesLostText.scaleX = 0;
livesLostText.scaleY = 0;
tween(livesLostText, {
scaleX: 1,
scaleY: 1,
alpha: 1
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(livesLostText, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 500,
delay: 1500,
easing: tween.easeIn,
onFinish: function onFinish() {
if (livesLostText.parent) {
livesLostText.parent.removeChild(livesLostText);
}
initializeLevel(); // Restart the same level
}
});
}
});
}
return;
}
}
function setupGameHandlers() {
// Set up the main game event handlers
game.down = function (x, y, obj) {
if (isProcessing) {
return;
}
var gridX = Math.floor((x - GRID_START_X) / CELL_SIZE);
var gridY = Math.floor((y - GRID_START_Y) / CELL_SIZE);
if (gridX < 0 || gridX >= GRID_WIDTH || gridY < 0 || gridY >= GRID_HEIGHT) {
selectedCandy = null;
return;
}
var clickedCandy = grid[gridY][gridX];
if (!clickedCandy || clickedCandy.isMoving || clickedCandy.isMarkedForDestroy) {
selectedCandy = null;
return;
}
// Start drag functionality
draggedCandy = clickedCandy;
isDragging = false;
dragStartX = x;
dragStartY = y;
if (selectedCandy === null) {
selectedCandy = clickedCandy;
selectedCandy.scaleX = 1.1;
selectedCandy.scaleY = 1.1;
} else if (selectedCandy === clickedCandy) {
selectedCandy.scaleX = 1.0;
selectedCandy.scaleY = 1.0;
selectedCandy = null;
} else if (areAdjacent(selectedCandy, clickedCandy)) {
selectedCandy.scaleX = 1.0;
selectedCandy.scaleY = 1.0;
// Try swap - only proceed if it will create matches
var swapSuccessful = swapCandies(selectedCandy, clickedCandy);
if (swapSuccessful) {
if (soundEnabled) {
LK.getSound('swap').play();
}
LK.setTimeout(function () {
movesLeft--;
movesText.setText('Moves: ' + movesLeft);
processMatches();
selectedCandy = null;
}, 250);
} else {
// No swap occurred because no matches would be created
selectedCandy = null;
}
} else {
selectedCandy.scaleX = 1.0;
selectedCandy.scaleY = 1.0;
selectedCandy = clickedCandy;
selectedCandy.scaleX = 1.1;
selectedCandy.scaleY = 1.1;
}
};
game.move = function (x, y, obj) {
if (!draggedCandy || isProcessing) {
return;
}
var dragDistance = Math.sqrt((x - dragStartX) * (x - dragStartX) + (y - dragStartY) * (y - dragStartY));
if (dragDistance > 30 && !isDragging) {
isDragging = true;
// Scale up dragged candy for visual feedback
tween(draggedCandy, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 150,
easing: tween.easeOut
});
}
if (isDragging) {
// Move candy with finger/mouse
draggedCandy.x = GRID_START_X + draggedCandy.gridX * CELL_SIZE + CELL_SIZE / 2 + (x - dragStartX);
draggedCandy.y = GRID_START_Y + draggedCandy.gridY * CELL_SIZE + CELL_SIZE / 2 + (y - dragStartY);
}
};
game.up = function (x, y, obj) {
if (!draggedCandy) {
return;
}
if (isDragging) {
// Reset candy visual state
tween(draggedCandy, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 150,
easing: tween.easeOut
});
// Determine target grid position based on drag direction
var deltaX = x - dragStartX;
var deltaY = y - dragStartY;
var targetGridX = draggedCandy.gridX;
var targetGridY = draggedCandy.gridY;
// Check drag direction and distance
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// Horizontal drag
if (Math.abs(deltaX) > 50) {
if (deltaX > 0 && draggedCandy.gridX < GRID_WIDTH - 1) {
targetGridX = draggedCandy.gridX + 1;
} else if (deltaX < 0 && draggedCandy.gridX > 0) {
targetGridX = draggedCandy.gridX - 1;
}
}
} else {
// Vertical drag
if (Math.abs(deltaY) > 50) {
if (deltaY > 0 && draggedCandy.gridY < GRID_HEIGHT - 1) {
targetGridY = draggedCandy.gridY + 1;
} else if (deltaY < 0 && draggedCandy.gridY > 0) {
targetGridY = draggedCandy.gridY - 1;
}
}
}
// Reset candy position
draggedCandy.x = GRID_START_X + draggedCandy.gridX * CELL_SIZE + CELL_SIZE / 2;
draggedCandy.y = GRID_START_Y + draggedCandy.gridY * CELL_SIZE + CELL_SIZE / 2;
// Perform swap if valid target
if ((targetGridX !== draggedCandy.gridX || targetGridY !== draggedCandy.gridY) && targetGridX >= 0 && targetGridX < GRID_WIDTH && targetGridY >= 0 && targetGridY < GRID_HEIGHT) {
var targetCandy = grid[targetGridY][targetGridX];
if (targetCandy && !targetCandy.isMoving) {
// Clear any previous selection
if (selectedCandy) {
selectedCandy.scaleX = 1.0;
selectedCandy.scaleY = 1.0;
}
// Perform swap - only if it will create matches
var swapSuccessful = swapCandies(draggedCandy, targetCandy);
if (swapSuccessful) {
if (soundEnabled) {
LK.getSound('swap').play();
}
LK.setTimeout(function () {
movesLeft--;
movesText.setText('Moves: ' + movesLeft);
processMatches();
}, 250);
}
}
}
}
// Reset drag state
draggedCandy = null;
isDragging = false;
selectedCandy = null;
};
}
// Add decorative candy elements floating in background
var decorations = [];
for (var i = 0; i < 8; i++) {
var decoration = LK.getAsset('candy_decoration', {
anchorX: 0.5,
anchorY: 0.5,
x: Math.random() * 2048,
y: Math.random() * 2732,
scaleX: 0.5 + Math.random() * 0.5,
scaleY: 0.5 + Math.random() * 0.5
});
decoration.alpha = 0.2 + Math.random() * 0.3;
game.addChild(decoration);
decorations.push(decoration);
// Animate floating motion
var animDuration = 3000 + Math.random() * 2000;
var targetY = decoration.y + (Math.random() - 0.5) * 400;
tween(decoration, {
y: targetY,
rotation: Math.random() * Math.PI * 2
}, {
duration: animDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Restart animation
var newTargetY = decoration.y + (Math.random() - 0.5) * 400;
tween(decoration, {
y: newTargetY,
rotation: decoration.rotation + Math.PI * 2
}, {
duration: animDuration,
easing: tween.easeInOut
});
}
});
}
// Add sparkle effects
var sparkles = [];
for (var j = 0; j < 15; j++) {
var sparkle = LK.getAsset('sparkle', {
anchorX: 0.5,
anchorY: 0.5,
x: Math.random() * 2048,
y: Math.random() * 2732,
scaleX: 0.3 + Math.random() * 0.4,
scaleY: 0.3 + Math.random() * 0.4
});
sparkle.alpha = 0.4 + Math.random() * 0.6;
game.addChild(sparkle);
sparkles.push(sparkle);
// Twinkling animation
var twinkleDuration = 1000 + Math.random() * 1500;
tween(sparkle, {
alpha: 0.1,
scaleX: sparkle.scaleX * 0.5,
scaleY: sparkle.scaleY * 0.5
}, {
duration: twinkleDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(sparkle, {
alpha: 0.4 + Math.random() * 0.6,
scaleX: 0.3 + Math.random() * 0.4,
scaleY: 0.3 + Math.random() * 0.4
}, {
duration: twinkleDuration,
easing: tween.easeInOut
});
}
});
}
// Initialize the game
loadProgress();
createMenu();
Modern App Store icon, high definition, square with rounded corners, for a game titled "Candy Match Saga" and with the description "A match-3 puzzle game where players swap candies to create matching lines, clear objectives, and progress through challenging levels.". No text on icon!
Mavi şeker resmi. In-Game asset. 2d. High contrast. No shadows
Sarı şeker. In-Game asset. 2d. High contrast. No shadows
Kırmızı şeker. In-Game asset. 2d. High contrast. No shadows
Yeşil şeker. In-Game asset. 2d. High contrast. No shadows
Turuncu şeker. In-Game asset. 2d. High contrast. No shadows