User prompt
Rokete sallanma ve ucundan tutuşma efekti ver ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Roketlerin patlattığı alanı 2x10 yap
User prompt
Roketlerin oluşma oranını arttır
User prompt
Roket ekleyelim alanını 5 hareket hakkı harcar ve 10x2 bir patlatır
User prompt
IT DID NOT WORK TRY AGAIN
User prompt
When you enter Freeze Frenzy mode, replace the background asset with the Erek asset in the assets.
User prompt
Freeze Frenzy moduna geçilince arka plan assetlerini assetlerdeki bacg asseti ile değiştir
User prompt
Replace the background in freeze frenzy mode with the bacg asset in the assets
User prompt
Just replace the background of freeze frenzy mode with the bacg asset in assets
User prompt
Change the ice asset to the ice asset in assets
User prompt
Hadi bir donma çılgınlığı modu ekleyelim. Bu modda zincirlenmiş toplar veya sağlık kutuları yok. Topların %30'u buzlu. Buzlu toplar etraflarında 2 kibrit olduğunda çözülüyor. Sadece 1 harita var, oda 8'e 8 kare. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Delete the image of the dead pig
User prompt
May the fallen pigs perish
User prompt
Add a special swinging animation for the pigs and make a 3-lane map for the pig mode, just that map, the thickness of the stripes is 3 and the length is 7 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Fix all the errors
User prompt
Falling pigs keep appearing fix
User prompt
Fix the errors
User prompt
Let's add an event mode, let's have a pig farm in the mode, let's have pigs instead of some balls, let's try to knock them down through matches ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
A cinematic will appear at the beginning of the game. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
The previous request did not work, repeat it
User prompt
Replace the main menu asset with the asset named in the menu in assets
User prompt
Anamenü ye bir arka plan yap
User prompt
Change the name of the game in the main menu to Feelings Match
User prompt
Put the combo counter on the top left and make it more prominent ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'TypeError: Cannot set properties of undefined (setting 'fill')' in or related to this line: 'comboText.style.fill = comboColor;' Line Number: 655
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Ball = Container.expand(function (color) { var self = Container.call(this); self.ballColor = color; self.gridX = 0; self.gridY = 0; self.isAnimating = false; var ballAssets = ['redBall', 'blueBall', 'greenBall', 'yellowBall', 'purpleBall', 'orangeBall']; var assetToUse = ballAssets[color]; var ballGraphics = self.attachAsset(assetToUse, { anchorX: 0.5, anchorY: 0.5 }); self.setGridPosition = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; self.x = gridStartX + gridX * cellSize + cellSize / 2; self.y = gridStartY + gridY * cellSize + cellSize / 2; }; self.explode = function () { // Scale up and fade out explosion effect tween(self, { scaleX: 1.3, scaleY: 1.3, alpha: 0 }, { duration: 600, easing: tween.easeInOut, onFinish: function onFinish() { if (self.parent) { self.destroy(); } } }); }; return self; }); var Bomb = Container.expand(function () { var self = Container.call(this); self.gridX = 0; self.gridY = 0; self.isAnimating = false; self.isBomb = true; self.isDragging = false; // Create bomb visual using the Bomba asset var bombGraphics = self.attachAsset('Bomba', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); // Add pulsing animation to make it stand out self.pulseAnimation = function () { tween(self, { scaleX: 1.1, scaleY: 1.1 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { tween(self, { scaleX: 0.8, scaleY: 0.8 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { if (self.parent && !self.isDragging) { self.pulseAnimation(); } } }); } }); }; self.setGridPosition = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; self.x = gridStartX + gridX * cellSize + cellSize / 2; self.y = gridStartY + gridY * cellSize + cellSize / 2; }; self.explode = function () { // Simple bomb explosion effect (same as Ball explode method) tween(self, { scaleX: 1.3, scaleY: 1.3, alpha: 0 }, { duration: 600, easing: tween.easeInOut, onFinish: function onFinish() { if (self.parent) { self.destroy(); } } }); }; self.explode3x3 = function () { // Explode all balls in a 3x3 area around the bomb var centerX = self.gridX; var centerY = self.gridY; var explodedCount = 0; for (var dx = -1; dx <= 1; dx++) { for (var dy = -1; dy <= 1; dy++) { var targetX = centerX + dx; var targetY = centerY + dy; if (targetX >= 0 && targetX < gridSize && targetY >= 0 && targetY < gridSize) { var ball = grid[targetY][targetX]; if (ball) { // If it's a chained ball, break the chain first if (ball.isChained) { ball.breakChain(); // Give extra points for breaking chains with bombs LK.setScore(LK.getScore() + 5); } ball.explode(); grid[targetY][targetX] = null; explodedCount++; } } } } // Award points for exploded balls (apply combo multiplier) var bombPoints = Math.floor(explodedCount * 15 * comboMultiplier); LK.setScore(LK.getScore() + bombPoints); scoreText.setText('Score: ' + LK.getScore()); // Update progress bar var currentScore = LK.getScore(); // Calculate score needed for current level progression function getScoreForLevel(level) { if (level <= 1) return 0; var totalScore = 0; for (var i = 1; i < level; i++) { totalScore += 400 + (i - 1) * 150; } return totalScore; } function getScoreNeededForNextLevel(level) { return 400 + (level - 1) * 150; } var scoreInCurrentLevel = currentScore - getScoreForLevel(currentLevel); var scoreNeededThisLevel = getScoreNeededForNextLevel(currentLevel); var progress = Math.min(1, scoreInCurrentLevel / scoreNeededThisLevel); progressBarFill.width = progress * 400; progressText.setText(scoreInCurrentLevel + '/' + scoreNeededThisLevel); checkLevelProgression(); // Remove bomb from grid grid[self.gridY][self.gridX] = null; // Explosion effect for bomb itself tween(self, { scaleX: 2, scaleY: 2, alpha: 0 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { if (self.parent) { self.destroy(); } } }); }; // Start pulsing animation when created self.pulseAnimation(); return self; }); var ChainedBall = Container.expand(function (color) { var self = Container.call(this); self.ballColor = color; self.gridX = 0; self.gridY = 0; self.isAnimating = false; self.isChained = true; // Create chain background first (larger, darker) var chainGraphics = self.attachAsset('chain', { anchorX: 0.5, anchorY: 0.5, alpha: 0.7 }); // Create ball on top of chain var ballAssets = ['redBall', 'blueBall', 'greenBall', 'yellowBall', 'purpleBall', 'orangeBall']; var assetToUse = ballAssets[color]; var ballGraphics = self.attachAsset(assetToUse, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.85, scaleY: 0.85 }); // Add subtle chain animation self.chainAnimation = function () { tween(chainGraphics, { rotation: 0.1 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { tween(chainGraphics, { rotation: -0.1 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { if (self.parent && self.isChained) { self.chainAnimation(); } } }); } }); }; self.setGridPosition = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; self.x = gridStartX + gridX * cellSize + cellSize / 2; self.y = gridStartY + gridY * cellSize + cellSize / 2; }; self.explode = function () { // Scale up and fade out explosion effect tween(self, { scaleX: 1.3, scaleY: 1.3, alpha: 0 }, { duration: 600, easing: tween.easeInOut, onFinish: function onFinish() { if (self.parent) { self.destroy(); } } }); }; self.breakChain = function () { // Break the chain and convert to regular ball self.isChained = false; // Animate chain breaking tween(chainGraphics, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 300, easing: tween.easeOut }); // Scale ball back to normal size tween(ballGraphics, { scaleX: 1.0, scaleY: 1.0 }, { duration: 300, easing: tween.easeOut }); // Flash effect when chain breaks LK.effects.flashObject(self, 0xFFFFFF, 400); }; // Start chain animation self.chainAnimation(); return self; }); var HealthBox = Container.expand(function () { var self = Container.call(this); self.gridX = 0; self.gridY = 0; self.isAnimating = false; self.isHealthBox = true; self.isDragging = false; // Create health box visual using the healthBox asset var healthGraphics = self.attachAsset('healthBox', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); // Add gentle glow animation self.glowAnimation = function () { tween(self, { alpha: 0.7 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { tween(self, { alpha: 1.0 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { if (self.parent && !self.isDragging) { self.glowAnimation(); } } }); } }); }; self.setGridPosition = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; self.x = gridStartX + gridX * cellSize + cellSize / 2; self.y = gridStartY + gridY * cellSize + cellSize / 2; }; self.explode = function () { // Simple health box explosion effect tween(self, { scaleX: 1.3, scaleY: 1.3, alpha: 0 }, { duration: 600, easing: tween.easeInOut, onFinish: function onFinish() { if (self.parent) { self.destroy(); } } }); }; self.activateHealthBonus = function () { // Add 8 moves when health box is clicked movesLeft += 8; movesText.setText('Moves: ' + movesLeft); // Remove health box from grid grid[self.gridY][self.gridX] = null; // Visual effect for health box activation tween(self, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { if (self.parent) { self.destroy(); } // After health box is removed, trigger ball dropping and filling LK.setTimeout(function () { dropBalls(); fillEmptySpaces(); // Check for any new matches that might form processMatches(); }, 100); } }); // Flash screen green to indicate health bonus LK.effects.flashScreen(0x00FF00, 500); }; // Start glow animation when created self.glowAnimation(); return self; }); var TimeBonus = Container.expand(function () { var self = Container.call(this); self.gridX = 0; self.gridY = 0; self.isAnimating = false; self.isTimeBonus = true; self.isDragging = false; // Create time bonus visual using the Saat asset var timeBonusGraphics = self.attachAsset('Saat', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.9, scaleY: 0.9 }); // Add clock-like pulsing animation self.clockAnimation = function () { tween(self, { scaleX: 1.1, scaleY: 1.1 }, { duration: 600, easing: tween.easeInOut, onFinish: function onFinish() { tween(self, { scaleX: 0.9, scaleY: 0.9 }, { duration: 600, easing: tween.easeInOut, onFinish: function onFinish() { if (self.parent && !self.isDragging) { self.clockAnimation(); } } }); } }); }; self.setGridPosition = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; self.x = gridStartX + gridX * cellSize + cellSize / 2; self.y = gridStartY + gridY * cellSize + cellSize / 2; }; self.explode = function () { // Simple time bonus explosion effect tween(self, { scaleX: 1.3, scaleY: 1.3, alpha: 0 }, { duration: 600, easing: tween.easeInOut, onFinish: function onFinish() { if (self.parent) { self.destroy(); } } }); }; self.activateTimeBonus = function () { // Add 10 seconds when time bonus is clicked in time trial mode if (gameMode === 'timetrial') { timeLeft += 10; updateTimer(); } // Remove time bonus from grid grid[self.gridY][self.gridX] = null; // Visual effect for time bonus activation tween(self, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { if (self.parent) { self.destroy(); } // After time bonus is removed, trigger ball dropping and filling LK.setTimeout(function () { dropBalls(); fillEmptySpaces(); // Check for any new matches that might form processMatches(); }, 100); } }); // Flash screen cyan to indicate time bonus LK.effects.flashScreen(0x00FFFF, 500); }; // Start clock animation when created self.clockAnimation(); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2c3e50 }); /**** * Game Code ****/ // Game state management var gameState = 'menu'; // 'menu' or 'playing' var gameMode = 'classic'; // 'classic' or 'timetrial' var menuContainer; var gameContainer; // Time trial specific variables var timeTrialDuration = 120; // 2 minutes in seconds var timeLeft = timeTrialDuration; var timerText; var gameTimer; // Grid configuration variables needed by Ball class var gridSize = 8; var cellSize = 220; var gridStartX = (2048 - gridSize * cellSize) / 2; var gridStartY = 400; var grid = []; var gridShape = []; // Array to define which cells are active in current level // Define different grid shapes for each level var levelShapes = [ // Level 1: Full 8x8 square function (size) { var shape = []; for (var y = 0; y < size; y++) { shape[y] = []; for (var x = 0; x < size; x++) { shape[y][x] = true; } } return shape; }, // Level 2: Diamond shape function (size) { var shape = []; var center = Math.floor(size / 2); for (var y = 0; y < size; y++) { shape[y] = []; for (var x = 0; x < size; x++) { var distanceFromCenter = Math.abs(x - center) + Math.abs(y - center); shape[y][x] = distanceFromCenter <= center; } } return shape; }, // Level 3: Cross shape function (size) { var shape = []; var center = Math.floor(size / 2); for (var y = 0; y < size; y++) { shape[y] = []; for (var x = 0; x < size; x++) { shape[y][x] = x === center || y === center; } } return shape; }, // Level 4: Hollow square (border only) function (size) { var shape = []; for (var y = 0; y < size; y++) { shape[y] = []; for (var x = 0; x < size; x++) { shape[y][x] = x === 0 || x === size - 1 || y === 0 || y === size - 1; } } return shape; }, // Level 5: L shape function (size) { var shape = []; for (var y = 0; y < size; y++) { shape[y] = []; for (var x = 0; x < size; x++) { shape[y][x] = x >= 0 && x < 4 && y >= size - 7 || y >= size - 4 && x >= 0 && x < 7; } } return shape; }, // Level 6: Circle shape function (size) { var shape = []; var center = size / 2 - 0.5; var radius = center; for (var y = 0; y < size; y++) { shape[y] = []; for (var x = 0; x < size; x++) { var dx = x - center; var dy = y - center; var distance = Math.sqrt(dx * dx + dy * dy); shape[y][x] = distance <= radius; } } return shape; }]; // Function to get current level's grid shape function getCurrentGridShape() { var shapeIndex = (currentLevel - 1) % levelShapes.length; return levelShapes[shapeIndex](gridSize); } // Function to check if a grid position is valid for current shape function isValidGridPosition(x, y) { return gridShape[y] && gridShape[y][x]; } var selectedBall = null; var movesLeft = getMovesForLevel(1); // Start with moves calculated for level 1 var isProcessingMatches = false; var ballColors = 4; // Start with 4 colors for easier beginning var bombSpawnChance = 0.01; // 1% chance to spawn bomb instead of ball var healthBoxSpawnChance = 0.04; // 4% chance to spawn health box instead of ball var timeBonusSpawnChance = 0.03; // 3% chance to spawn time bonus instead of ball (time trial mode only) var draggedBomb = null; var lastMoveTime = 0; var currentLevel = 1; var baseMovesPerLevel = 30; var pointsToNextLevel = 400; // Points needed to advance to next level var totalPointsEarned = 0; // Combo system variables var currentCombo = 0; var comboText; var comboMultiplier = 1; var maxCombo = 0; var comboTimeout; // Function to calculate moves for current level function getMovesForLevel(level) { // Start with 40 moves at level 1, decrease by 3 moves per level, minimum 15 moves return Math.max(15, 40 - (level - 1) * 3); } // Combo system functions function updateCombo() { currentCombo++; maxCombo = Math.max(maxCombo, currentCombo); // Calculate multiplier based on combo (1x, 1.5x, 2x, 2.5x, 3x, max) comboMultiplier = 1 + Math.min(2, (currentCombo - 1) * 0.5); // Show combo text with animation if (currentCombo >= 2) { comboText.setText('COMBO x' + currentCombo + '\n' + comboMultiplier.toFixed(1) + 'x POINTS!'); comboText.visible = true; // Animate combo text comboText.scaleX = 0.5; comboText.scaleY = 0.5; comboText.alpha = 0; tween(comboText, { scaleX: 1.2, scaleY: 1.2, alpha: 1 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { tween(comboText, { scaleX: 1, scaleY: 1 }, { duration: 200, easing: tween.easeOut }); } }); // Change color based on combo level var comboColor = '#FF6B6B'; // Red for 2-3 if (currentCombo >= 4 && currentCombo < 6) { comboColor = '#FF9500'; // Orange for 4-5 } else if (currentCombo >= 6 && currentCombo < 8) { comboColor = '#FFD700'; // Gold for 6-7 } else if (currentCombo >= 8) { comboColor = '#9D4EDD'; // Purple for 8+ } comboText.fill = comboColor; // Screen flash effect for high combos if (currentCombo >= 4) { var flashColor = 0xFF9500; if (currentCombo >= 6) flashColor = 0xFFD700; if (currentCombo >= 8) flashColor = 0x9D4EDD; LK.effects.flashScreen(flashColor, 300); } } // Clear existing timeout and set new one if (comboTimeout) { LK.clearTimeout(comboTimeout); } comboTimeout = LK.setTimeout(function () { resetCombo(); }, 3000); // Reset combo after 3 seconds of no matches } function resetCombo() { if (currentCombo >= 2) { // Fade out combo text tween(comboText, { alpha: 0, scaleX: 0.5, scaleY: 0.5 }, { duration: 400, easing: tween.easeIn, onFinish: function onFinish() { comboText.visible = false; } }); } currentCombo = 0; comboMultiplier = 1; if (comboTimeout) { LK.clearTimeout(comboTimeout); comboTimeout = null; } } // UI Elements var movesText = new Text2('Moves: ' + getMovesForLevel(1), { size: 80, fill: 0xFFFFFF }); movesText.anchor.set(0.5, 0); LK.gui.top.addChild(movesText); movesText.y = 150; // Timer text for time trial mode timerText = new Text2('Time: 2:00', { size: 80, fill: 0xFFFFFF }); timerText.anchor.set(0.5, 0); LK.gui.top.addChild(timerText); timerText.y = 150; timerText.visible = false; // Hidden by default // Time trial timer function function updateTimer() { timeLeft--; var minutes = Math.floor(timeLeft / 60); var seconds = timeLeft % 60; var timeString = minutes + ':' + (seconds < 10 ? '0' : '') + seconds; timerText.setText('Time: ' + timeString); if (timeLeft <= 0) { LK.clearInterval(gameTimer); LK.showGameOver(); } else if (timeLeft <= 10) { // Flash red when time is running out LK.effects.flashObject(timerText, 0xFF0000, 200); } } var scoreText = new Text2('Score: 0', { size: 80, fill: 0xFFFFFF }); scoreText.anchor.set(0, 0); LK.gui.top.addChild(scoreText); scoreText.x = 150; scoreText.y = 250; // Progress bar to show points needed for next level var progressBarBackground = LK.getAsset('gridCell', { width: 400, height: 40, anchorX: 1.0, anchorY: 0, alpha: 0.3 }); LK.gui.topRight.addChild(progressBarBackground); progressBarBackground.x = -50; progressBarBackground.y = 150; var progressBarFill = LK.getAsset('gridCell', { width: 1, height: 40, anchorX: 0, anchorY: 0, color: 0x00FF00 }); progressBarBackground.addChild(progressBarFill); var progressText = new Text2('0/400', { size: 50, fill: 0xFFFFFF }); progressText.anchor.set(1, 0); LK.gui.topRight.addChild(progressText); progressText.x = -50; progressText.y = 200; // Combo text display comboText = new Text2('', { size: 120, fill: '#FF6B6B' }); comboText.anchor.set(0.5, 0.5); comboText.x = 1024; comboText.y = 300; comboText.visible = false; game.addChild(comboText); // Initialize grid function initializeGrid() { // Get the shape for current level gridShape = getCurrentGridShape(); grid = []; for (var y = 0; y < gridSize; y++) { grid[y] = []; for (var x = 0; x < gridSize; x++) { grid[y][x] = null; } } // Fill grid with balls only in valid positions for (var y = 0; y < gridSize; y++) { for (var x = 0; x < gridSize; x++) { if (isValidGridPosition(x, y)) { createBallAt(x, y); } } } // Remove initial matches while (findMatches().length > 0) { removeMatches(); dropBalls(); fillEmptySpaces(); } } function createBallAt(x, y) { // Only create balls in valid grid positions if (!isValidGridPosition(x, y)) { return null; } // Also check if position is within grid bounds if (x < 0 || x >= gridSize || y < 0 || y >= gridSize) { return null; } // Sometimes create a bomb instead of a ball if (Math.random() < bombSpawnChance) { var bomb = new Bomb(); bomb.setGridPosition(x, y); grid[y][x] = bomb; game.addChild(bomb); // Animate bomb appearing bomb.scaleX = 0; bomb.scaleY = 0; bomb.alpha = 0; tween(bomb, { scaleX: 0.8, scaleY: 0.8, alpha: 1 }, { duration: 400, easing: tween.easeOut }); return bomb; } else if (gameMode === 'classic' && Math.random() < healthBoxSpawnChance) { var healthBox = new HealthBox(); healthBox.setGridPosition(x, y); grid[y][x] = healthBox; game.addChild(healthBox); // Animate health box appearing healthBox.scaleX = 0; healthBox.scaleY = 0; healthBox.alpha = 0; tween(healthBox, { scaleX: 0.8, scaleY: 0.8, alpha: 1 }, { duration: 400, easing: tween.easeOut }); return healthBox; } else if (gameMode === 'timetrial' && Math.random() < timeBonusSpawnChance) { var timeBonus = new TimeBonus(); timeBonus.setGridPosition(x, y); grid[y][x] = timeBonus; game.addChild(timeBonus); // Animate time bonus appearing timeBonus.scaleX = 0; timeBonus.scaleY = 0; timeBonus.alpha = 0; tween(timeBonus, { scaleX: 0.9, scaleY: 0.9, alpha: 1 }, { duration: 400, easing: tween.easeOut }); return timeBonus; } else if (Math.random() < 0.05) { // 5% chance to create chained ball - use smart color selection var adjacentColors = []; // Check all 4 directions for adjacent ball colors var directions = [{ dx: -1, dy: 0 }, { dx: 1, dy: 0 }, { dx: 0, dy: -1 }, { dx: 0, dy: 1 }]; for (var d = 0; d < directions.length; d++) { var dir = directions[d]; var adjX = x + dir.dx; var adjY = y + dir.dy; if (adjX >= 0 && adjX < gridSize && adjY >= 0 && adjY < gridSize && isValidGridPosition(adjX, adjY) && grid[adjY][adjX] && grid[adjY][adjX].ballColor !== undefined) { adjacentColors.push(grid[adjY][adjX].ballColor); } } var color; if (adjacentColors.length > 0 && Math.random() < 0.4) { // 40% chance to match an adjacent color for potential future matches color = adjacentColors[Math.floor(Math.random() * adjacentColors.length)]; } else { color = Math.floor(Math.random() * ballColors); } var chainedBall = new ChainedBall(color); chainedBall.setGridPosition(x, y); grid[y][x] = chainedBall; game.addChild(chainedBall); // Animate chained ball appearing chainedBall.scaleX = 0; chainedBall.scaleY = 0; chainedBall.alpha = 0; tween(chainedBall, { scaleX: 1, scaleY: 1, alpha: 1 }, { duration: 400, easing: tween.easeOut }); return chainedBall; } else { // Smart color selection for regular balls var adjacentColors = []; var belowColors = []; // Check all 4 directions for adjacent ball colors var directions = [{ dx: -1, dy: 0 }, { dx: 1, dy: 0 }, { dx: 0, dy: -1 }, { dx: 0, dy: 1 }]; for (var d = 0; d < directions.length; d++) { var dir = directions[d]; var adjX = x + dir.dx; var adjY = y + dir.dy; if (adjX >= 0 && adjX < gridSize && adjY >= 0 && adjY < gridSize && isValidGridPosition(adjX, adjY) && grid[adjY][adjX] && grid[adjY][adjX].ballColor !== undefined) { adjacentColors.push(grid[adjY][adjX].ballColor); // Special attention to ball below for dropping compatibility if (dir.dy === 1) { belowColors.push(grid[adjY][adjX].ballColor); } } } var color; // 53% chance to be compatible with environment if adjacent balls exist if (adjacentColors.length > 0 && Math.random() < 0.53) { // 60% chance to match an adjacent color (for potential matches) if (Math.random() < 0.6) { // Prioritize matching ball below if it exists if (belowColors.length > 0) { color = belowColors[Math.floor(Math.random() * belowColors.length)]; } else { color = adjacentColors[Math.floor(Math.random() * adjacentColors.length)]; } } else { // 40% chance to choose a color that would create diversity but still be strategic var usedColors = {}; for (var i = 0; i < adjacentColors.length; i++) { usedColors[adjacentColors[i]] = true; } var availableColors = []; for (var c = 0; c < ballColors; c++) { if (!usedColors[c]) { availableColors.push(c); } } if (availableColors.length > 0) { color = availableColors[Math.floor(Math.random() * availableColors.length)]; } else { color = Math.floor(Math.random() * ballColors); } } } else { // Random color when no adjacent balls or 50% random chance color = Math.floor(Math.random() * ballColors); } var ball = new Ball(color); ball.setGridPosition(x, y); grid[y][x] = ball; game.addChild(ball); // Animate ball appearing with scale and bounce ball.scaleX = 0; ball.scaleY = 0; ball.alpha = 0; tween(ball, { scaleX: 1, scaleY: 1, alpha: 1 }, { duration: 400, easing: tween.easeOut }); return ball; } } function getBallAt(x, y) { if (x < 0 || x >= gridSize || y < 0 || y >= gridSize) { return null; } if (!grid || !grid[y]) { return null; } return grid[y][x]; } function swapBalls(ball1, ball2) { if (!ball1 || !ball2) { return false; } // Check if either ball is chained - they cannot be moved if (ball1.isChained && ball1.isChained === true || ball2.isChained && ball2.isChained === true) { // Flash red to indicate chained balls cannot move LK.effects.flashScreen(0xFF0000, 200); return false; } var x1 = ball1.gridX, y1 = ball1.gridY; var x2 = ball2.gridX, y2 = ball2.gridY; // Check if balls are adjacent var dx = Math.abs(x1 - x2); var dy = Math.abs(y1 - y2); if (dx + dy !== 1) { return false; } // Mark balls as animating ball1.isAnimating = true; ball2.isAnimating = true; // Store old positions var oldX1 = ball1.x, oldY1 = ball1.y; var oldX2 = ball2.x, oldY2 = ball2.y; // Swap positions in grid grid[y1][x1] = ball2; grid[y2][x2] = ball1; // Update ball positions ball1.setGridPosition(x2, y2); ball2.setGridPosition(x1, y1); // Store new positions var newX1 = ball1.x, newY1 = ball1.y; var newX2 = ball2.x, newY2 = ball2.y; // Reset to old positions for animation ball1.x = oldX1; ball1.y = oldY1; ball2.x = oldX2; ball2.y = oldY2; // Animate to new positions tween(ball1, { x: newX1, y: newY1 }, { duration: 250, easing: tween.easeInOut, onFinish: function onFinish() { ball1.isAnimating = false; } }); tween(ball2, { x: newX2, y: newY2 }, { duration: 250, easing: tween.easeInOut, onFinish: function onFinish() { ball2.isAnimating = false; } }); LK.getSound('swap').play(); return true; } function findMatches() { var matches = []; // Check horizontal matches for (var y = 0; y < gridSize; y++) { var count = 0; var currentColor = -1; var matchPositions = []; for (var x = 0; x < gridSize; x++) { if (!isValidGridPosition(x, y)) { // Invalid position, reset match counting if (count >= 3) { for (var i = 0; i < matchPositions.length; i++) { matches.push(matchPositions[i]); } } count = 0; currentColor = -1; matchPositions = []; continue; } var ball = grid[y][x]; if (ball && ball.ballColor !== undefined && ball.ballColor === currentColor) { count++; matchPositions.push({ x: x, y: y }); } else { if (count >= 3) { for (var i = 0; i < matchPositions.length; i++) { matches.push(matchPositions[i]); } } count = ball && ball.ballColor !== undefined ? 1 : 0; currentColor = ball && ball.ballColor !== undefined ? ball.ballColor : -1; matchPositions = ball && ball.ballColor !== undefined ? [{ x: x, y: y }] : []; } } if (count >= 3) { for (var i = 0; i < matchPositions.length; i++) { matches.push(matchPositions[i]); } } } // Check vertical matches for (var x = 0; x < gridSize; x++) { var count = 0; var currentColor = -1; var matchPositions = []; for (var y = 0; y < gridSize; y++) { if (!isValidGridPosition(x, y)) { // Invalid position, reset match counting if (count >= 3) { for (var i = 0; i < matchPositions.length; i++) { matches.push(matchPositions[i]); } } count = 0; currentColor = -1; matchPositions = []; continue; } var ball = grid[y][x]; if (ball && ball.ballColor !== undefined && ball.ballColor === currentColor) { count++; matchPositions.push({ x: x, y: y }); } else { if (count >= 3) { for (var i = 0; i < matchPositions.length; i++) { matches.push(matchPositions[i]); } } count = ball && ball.ballColor !== undefined ? 1 : 0; currentColor = ball && ball.ballColor !== undefined ? ball.ballColor : -1; matchPositions = ball && ball.ballColor !== undefined ? [{ x: x, y: y }] : []; } } if (count >= 3) { for (var i = 0; i < matchPositions.length; i++) { matches.push(matchPositions[i]); } } } // Check diagonal matches (top-left to bottom-right) for (var startY = 0; startY < gridSize; startY++) { for (var startX = 0; startX < gridSize; startX++) { if (!isValidGridPosition(startX, startY)) continue; var startBall = grid[startY][startX]; if (!startBall || startBall.ballColor === undefined) continue; var count = 1; var currentColor = startBall.ballColor; var matchPositions = [{ x: startX, y: startY }]; for (var step = 1; startX + step < gridSize && startY + step < gridSize; step++) { var x = startX + step; var y = startY + step; if (!isValidGridPosition(x, y)) break; var ball = grid[y][x]; if (ball && ball.ballColor === currentColor) { count++; matchPositions.push({ x: x, y: y }); } else { break; } } if (count >= 3) { for (var i = 0; i < matchPositions.length; i++) { matches.push(matchPositions[i]); } } } } // Check diagonal matches (top-right to bottom-left) for (var startY = 0; startY < gridSize; startY++) { for (var startX = gridSize - 1; startX >= 0; startX--) { if (!isValidGridPosition(startX, startY)) continue; var startBall = grid[startY][startX]; if (!startBall || startBall.ballColor === undefined) continue; var count = 1; var currentColor = startBall.ballColor; var matchPositions = [{ x: startX, y: startY }]; for (var step = 1; startX - step >= 0 && startY + step < gridSize; step++) { var x = startX - step; var y = startY + step; if (!isValidGridPosition(x, y)) break; var ball = grid[y][x]; if (ball && ball.ballColor === currentColor) { count++; matchPositions.push({ x: x, y: y }); } else { break; } } if (count >= 3) { for (var i = 0; i < matchPositions.length; i++) { matches.push(matchPositions[i]); } } } } return matches; } function removeMatches() { var matches = findMatches(); if (matches.length === 0) { // Reset combo when no matches found resetCombo(); return false; } // Update combo system updateCombo(); var pointsEarned = matches.length * 10; // Apply combo multiplier to points pointsEarned = Math.floor(pointsEarned * comboMultiplier); // Check if this is a 4-ball match for special rewards var isFourBallMatch = matches.length === 4; if (isFourBallMatch) { // Award bonus 10 points for 4-ball match (also affected by multiplier) pointsEarned += Math.floor(10 * comboMultiplier); } for (var i = 0; i < matches.length; i++) { var match = matches[i]; var ball = grid[match.y][match.x]; if (ball) { // If it's a chained ball, break the chain first then explode if (ball.isChained) { ball.breakChain(); // Give extra points for breaking chains pointsEarned += 5; } ball.explode(); grid[match.y][match.x] = null; } } LK.setScore(LK.getScore() + pointsEarned); scoreText.setText('Score: ' + LK.getScore()); // Update progress bar var currentScore = LK.getScore(); // Calculate score needed for current level progression function getScoreForLevel(level) { if (level <= 1) return 0; var totalScore = 0; for (var i = 1; i < level; i++) { totalScore += 400 + (i - 1) * 150; } return totalScore; } function getScoreNeededForNextLevel(level) { return 400 + (level - 1) * 150; } var scoreInCurrentLevel = currentScore - getScoreForLevel(currentLevel); var scoreNeededThisLevel = getScoreNeededForNextLevel(currentLevel); var progress = Math.min(1, scoreInCurrentLevel / scoreNeededThisLevel); progressBarFill.width = progress * 400; progressText.setText(scoreInCurrentLevel + '/' + scoreNeededThisLevel); // Spawn a bomb as reward for 4-ball match if (isFourBallMatch) { // Find a random empty valid position to place the bomb var emptyPositions = []; for (var y = 0; y < gridSize; y++) { for (var x = 0; x < gridSize; x++) { if (grid[y][x] === null && isValidGridPosition(x, y)) { emptyPositions.push({ x: x, y: y }); } } } if (emptyPositions.length > 0) { var randomPos = emptyPositions[Math.floor(Math.random() * emptyPositions.length)]; var rewardBomb = new Bomb(); rewardBomb.setGridPosition(randomPos.x, randomPos.y); grid[randomPos.y][randomPos.x] = rewardBomb; game.addChild(rewardBomb); // Animate bomb appearing with special effect rewardBomb.scaleX = 0; rewardBomb.scaleY = 0; rewardBomb.alpha = 0; tween(rewardBomb, { scaleX: 1.2, scaleY: 1.2, alpha: 1 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { tween(rewardBomb, { scaleX: 0.8, scaleY: 0.8 }, { duration: 200, easing: tween.easeOut }); } }); // Flash screen gold to indicate bomb reward LK.effects.flashScreen(0xFFD700, 500); } } LK.getSound('match').play(); checkLevelProgression(); return true; } function dropBalls() { for (var x = 0; x < gridSize; x++) { var writePos = gridSize - 1; // Find the lowest valid position for this column while (writePos >= 0 && !isValidGridPosition(x, writePos)) { writePos--; } for (var y = gridSize - 1; y >= 0; y--) { if (grid[y][x] !== null && isValidGridPosition(x, y)) { if (y !== writePos && writePos >= 0) { grid[writePos][x] = grid[y][x]; grid[y][x] = null; var ball = grid[writePos][x]; if (ball) { ball.isAnimating = true; var oldY = ball.y; ball.setGridPosition(x, writePos); var newY = ball.y; ball.y = oldY; // Animate ball falling down (function (ballToAnimate) { tween(ballToAnimate, { y: newY }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { ballToAnimate.isAnimating = false; } }); })(ball); } } // Only decrement writePos if we're at a valid position if (writePos >= 0) { writePos--; // Find next valid position while (writePos >= 0 && !isValidGridPosition(x, writePos)) { writePos--; } } } } } } function fillEmptySpaces() { for (var x = 0; x < gridSize; x++) { for (var y = 0; y < gridSize; y++) { if (grid[y][x] === null && isValidGridPosition(x, y)) { createBallAt(x, y); } } } } function processMatches() { if (isProcessingMatches) { return; } isProcessingMatches = true; function processStep() { if (removeMatches()) { dropBalls(); fillEmptySpaces(); LK.setTimeout(function () { processStep(); }, 500); } else { isProcessingMatches = false; checkGameState(); } } processStep(); } function checkLevelProgression() { // Calculate cumulative score needed for each level // Level 1: 0-399 (400 points) // Level 2: 400-949 (550 points) // Level 3: 950-1649 (700 points) // And so on, increasing by 150 each level function getScoreForLevel(level) { if (level <= 1) return 0; var totalScore = 0; for (var i = 1; i < level; i++) { totalScore += 400 + (i - 1) * 150; } return totalScore; } function getScoreNeededForNextLevel(level) { return 400 + (level - 1) * 150; } var currentScore = LK.getScore(); var newLevel = currentLevel; // Check if we've reached the next level while (currentScore >= getScoreForLevel(newLevel + 1)) { newLevel++; } if (newLevel > currentLevel) { // Level up! currentLevel = newLevel; pointsToNextLevel = getScoreNeededForNextLevel(currentLevel + 1); // Increase difficulty if (ballColors < 6) { ballColors = Math.min(6, 3 + currentLevel); // Max 6 colors } // Set moves based on new level difficulty (progressively fewer moves) if (gameMode === 'classic') { movesLeft = getMovesForLevel(currentLevel); // Update UI movesText.setText('Moves: ' + movesLeft); } else { // In time trial mode, keep unlimited moves movesLeft = 999; } // Reset progress bar for new level var scoreInCurrentLevel = currentScore - getScoreForLevel(currentLevel); var scoreNeededThisLevel = getScoreNeededForNextLevel(currentLevel); var progress = Math.min(1, scoreInCurrentLevel / scoreNeededThisLevel); progressBarFill.width = progress * 400; progressText.setText(scoreInCurrentLevel + '/' + scoreNeededThisLevel); // Change grid shape for new level - clear existing balls first for (var y = 0; y < gridSize; y++) { for (var x = 0; x < gridSize; x++) { if (grid[y][x]) { grid[y][x].destroy(); grid[y][x] = null; } } } // Initialize new grid shape gridShape = getCurrentGridShape(); // Fill new shape with balls for (var y = 0; y < gridSize; y++) { for (var x = 0; x < gridSize; x++) { if (isValidGridPosition(x, y)) { createBallAt(x, y); } } } // Flash screen green for level up LK.effects.flashScreen(0x00FF00, 800); } } function checkGameState() { if (gameMode === 'classic') { checkLevelProgression(); if (movesLeft <= 0) { LK.showGameOver(); return; } } else if (gameMode === 'timetrial') { // In time trial mode, only check for time running out // (timer already handles game over when time reaches 0) if (timeLeft <= 0) { LK.showGameOver(); return; } } } function getClickedBall(x, y) { var gridX = Math.floor((x - gridStartX) / cellSize); var gridY = Math.floor((y - gridStartY) / cellSize); if (gridX >= 0 && gridX < gridSize && gridY >= 0 && gridY < gridSize) { return getBallAt(gridX, gridY); } return null; } game.down = function (x, y, obj) { if (isProcessingMatches) { return; } var clickedBall = getClickedBall(x, y); if (clickedBall && !clickedBall.isAnimating) { // Check if it's a health box if (clickedBall.isHealthBox) { clickedBall.activateHealthBonus(); return; } // Check if it's a time bonus if (clickedBall.isTimeBonus) { clickedBall.activateTimeBonus(); return; } // Check if it's a bomb if (clickedBall.isBomb) { draggedBomb = clickedBall; draggedBomb.isDragging = true; // Stop pulsing animation tween.stop(draggedBomb); // Make bomb slightly larger while dragging tween(draggedBomb, { scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.easeOut }); return; } // Regular ball logic if (selectedBall === null) { selectedBall = clickedBall; LK.effects.flashObject(selectedBall, 0xffffff, 200); } else if (selectedBall === clickedBall) { selectedBall = null; } else { if (swapBalls(selectedBall, clickedBall)) { if (gameMode === 'classic') { movesLeft--; movesText.setText('Moves: ' + movesLeft); } LK.setTimeout(function () { var matches = findMatches(); if (matches.length > 0) { processMatches(); } else { // Wasted move - swap back and apply additional penalty swapBalls(selectedBall, clickedBall); // No additional penalty for wasted move (already lost 1 move from the attempt) movesText.setText('Moves: ' + movesLeft); // Flash screen red to indicate wasted move LK.effects.flashScreen(0xFF0000, 300); } }, 300); } selectedBall = null; } } }; game.move = function (x, y, obj) { if (draggedBomb && Date.now() - lastMoveTime > 16) { // Throttle to ~60fps draggedBomb.x = x; draggedBomb.y = y; lastMoveTime = Date.now(); } }; game.up = function (x, y, obj) { if (draggedBomb) { // Explode bomb at current position draggedBomb.explode3x3(); // Using a bomb costs 2 moves in classic mode if (gameMode === 'classic') { movesLeft -= 2; movesText.setText('Moves: ' + movesLeft); } // Process any resulting matches LK.setTimeout(function () { dropBalls(); fillEmptySpaces(); processMatches(); }, 100); draggedBomb = null; } }; // Add background var backgroundImage = game.attachAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0, alpha: 0.3 }); // Create main menu function createMainMenu() { menuContainer = new Container(); game.addChild(menuContainer); // Menu background overlay var menuBg = LK.getAsset('gridCell', { width: 2048, height: 2732, anchorX: 0, anchorY: 0, color: 0x1a1a2e, alpha: 0.95 }); menuContainer.addChild(menuBg); // Game title var titleText = new Text2('MATCH-3\nPUZZLE', { size: 200, fill: '#FFD700' }); titleText.anchor.set(0.5, 0.5); titleText.x = 1024; titleText.y = 800; menuContainer.addChild(titleText); // Subtitle var subtitleText = new Text2('Swap colored balls to create matches!', { size: 80, fill: '#FFFFFF' }); subtitleText.anchor.set(0.5, 0.5); subtitleText.x = 1024; subtitleText.y = 1100; menuContainer.addChild(subtitleText); // Game mode buttons var classicButton = LK.getAsset('gridCell', { width: 400, height: 120, anchorX: 0.5, anchorY: 0.5, color: 0x4CAF50 }); classicButton.x = 1024; classicButton.y = 1400; menuContainer.addChild(classicButton); var classicButtonText = new Text2('CLASSIC', { size: 80, fill: '#FFFFFF' }); classicButtonText.anchor.set(0.5, 0.5); classicButton.addChild(classicButtonText); var timeTrialButton = LK.getAsset('gridCell', { width: 400, height: 120, anchorX: 0.5, anchorY: 0.5, color: 0xFF9800 }); timeTrialButton.x = 1024; timeTrialButton.y = 1600; menuContainer.addChild(timeTrialButton); var timeTrialButtonText = new Text2('TIME TRIAL', { size: 80, fill: '#FFFFFF' }); timeTrialButtonText.anchor.set(0.5, 0.5); timeTrialButton.addChild(timeTrialButtonText); // Instructions var instructionText = new Text2('• Create lines of 3+ matching balls\n• Use bombs to clear large areas\n• Collect health boxes for extra moves\n• Classic: Limited moves, unlock levels\n• Time Trial: Race against the clock!', { size: 60, fill: '#CCCCCC' }); instructionText.anchor.set(0.5, 0.5); instructionText.x = 1024; instructionText.y = 1900; menuContainer.addChild(instructionText); // Animate title entrance titleText.scaleX = 0; titleText.scaleY = 0; titleText.alpha = 0; tween(titleText, { scaleX: 1, scaleY: 1, alpha: 1 }, { duration: 800, easing: tween.easeOut }); // Animate subtitle subtitleText.alpha = 0; tween(subtitleText, { alpha: 1 }, { duration: 600, delay: 400, easing: tween.easeOut }); // Animate game mode buttons classicButton.scaleX = 0; classicButton.scaleY = 0; tween(classicButton, { scaleX: 1, scaleY: 1 }, { duration: 500, delay: 800, easing: tween.easeOut }); timeTrialButton.scaleX = 0; timeTrialButton.scaleY = 0; tween(timeTrialButton, { scaleX: 1, scaleY: 1 }, { duration: 500, delay: 900, easing: tween.easeOut }); // Animate instructions instructionText.alpha = 0; tween(instructionText, { alpha: 1 }, { duration: 600, delay: 1200, easing: tween.easeOut }); // Add pulsing animation to game mode buttons function pulseGameModeButtons() { tween(classicButton, { scaleX: 1.1, scaleY: 1.1 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { tween(classicButton, { scaleX: 1, scaleY: 1 }, { duration: 800, easing: tween.easeInOut }); } }); tween(timeTrialButton, { scaleX: 1.05, scaleY: 1.05 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { tween(timeTrialButton, { scaleX: 1, scaleY: 1 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { if (gameState === 'menu') { pulseGameModeButtons(); } } }); } }); } // Start pulsing after initial animation LK.setTimeout(function () { if (gameState === 'menu') { pulseGameModeButtons(); } }, 1500); // Game mode button click handlers classicButton.down = function (x, y, obj) { startGame('classic'); }; timeTrialButton.down = function (x, y, obj) { startGame('timetrial'); }; return menuContainer; } // Function to start the actual game function startGame(mode) { gameState = 'playing'; gameMode = mode || 'classic'; // Reset combo system resetCombo(); // Animate menu out tween(menuContainer, { alpha: 0, scaleX: 0.8, scaleY: 0.8 }, { duration: 500, easing: tween.easeIn, onFinish: function onFinish() { menuContainer.destroy(); // Initialize game elements createGameContainer(); initializeGrid(); // Setup UI based on game mode setupGameModeUI(); // Start background music LK.playMusic('Ana'); } }); } // Function to setup UI based on game mode function setupGameModeUI() { if (gameMode === 'timetrial') { // Hide moves counter and show timer movesText.visible = false; timerText.visible = true; timeLeft = timeTrialDuration; updateTimer(); gameTimer = LK.setInterval(updateTimer, 1000); // In time trial, give unlimited moves movesLeft = 999; } else { // Classic mode - show moves counter, hide timer movesText.visible = true; timerText.visible = false; movesLeft = getMovesForLevel(1); movesText.setText('Moves: ' + movesLeft); } } // Function to create game container function createGameContainer() { gameContainer = new Container(); game.addChild(gameContainer); } // Start with main menu instead of immediately initializing grid if (gameState === 'menu') { createMainMenu(); } else { // Initialize the game initializeGrid(); } // Reset button for swapping balls var resetButton = LK.getAsset('gridCell', { width: 200, height: 80, anchorX: 0.5, anchorY: 0.5, color: 0xFF6B6B }); LK.gui.bottom.addChild(resetButton); resetButton.y = -100; var resetButtonText = new Text2('RESET', { size: 40, fill: 0xFFFFFF }); resetButtonText.anchor.set(0.5, 0.5); resetButton.addChild(resetButtonText); resetButton.down = function (x, y, obj) { if (isProcessingMatches) { return; } // Check if player has enough score to reset (cost 100 points) var resetCost = 100; if (LK.getScore() < resetCost) { // Flash screen red to indicate insufficient score LK.effects.flashScreen(0xFF0000, 400); return; } // Clear current selection selectedBall = null; // Reset combo system resetCombo(); // Clear the grid for (var y = 0; y < gridSize; y++) { for (var x = 0; x < gridSize; x++) { if (grid[y][x]) { grid[y][x].destroy(); grid[y][x] = null; } } } // Reinitialize the grid with new balls initializeGrid(); // Cost 100 score points to reset LK.setScore(LK.getScore() - resetCost); scoreText.setText('Score: ' + LK.getScore()); // Update progress bar var currentScore = LK.getScore(); // Calculate score needed for current level progression function getScoreForLevel(level) { if (level <= 1) return 0; var totalScore = 0; for (var i = 1; i < level; i++) { totalScore += 400 + (i - 1) * 150; } return totalScore; } function getScoreNeededForNextLevel(level) { return 400 + (level - 1) * 150; } var scoreInCurrentLevel = currentScore - getScoreForLevel(currentLevel); var scoreNeededThisLevel = getScoreNeededForNextLevel(currentLevel); var progress = Math.min(1, scoreInCurrentLevel / scoreNeededThisLevel); progressBarFill.width = progress * 400; progressText.setText(scoreInCurrentLevel + '/' + scoreNeededThisLevel); // Flash screen blue to indicate reset LK.effects.flashScreen(0x0066FF, 400); }; // Background music will be started when game begins from menu ; ;
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Ball = Container.expand(function (color) {
var self = Container.call(this);
self.ballColor = color;
self.gridX = 0;
self.gridY = 0;
self.isAnimating = false;
var ballAssets = ['redBall', 'blueBall', 'greenBall', 'yellowBall', 'purpleBall', 'orangeBall'];
var assetToUse = ballAssets[color];
var ballGraphics = self.attachAsset(assetToUse, {
anchorX: 0.5,
anchorY: 0.5
});
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = gridStartX + gridX * cellSize + cellSize / 2;
self.y = gridStartY + gridY * cellSize + cellSize / 2;
};
self.explode = function () {
// Scale up and fade out explosion effect
tween(self, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 0
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.parent) {
self.destroy();
}
}
});
};
return self;
});
var Bomb = Container.expand(function () {
var self = Container.call(this);
self.gridX = 0;
self.gridY = 0;
self.isAnimating = false;
self.isBomb = true;
self.isDragging = false;
// Create bomb visual using the Bomba asset
var bombGraphics = self.attachAsset('Bomba', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
// Add pulsing animation to make it stand out
self.pulseAnimation = function () {
tween(self, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.parent && !self.isDragging) {
self.pulseAnimation();
}
}
});
}
});
};
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = gridStartX + gridX * cellSize + cellSize / 2;
self.y = gridStartY + gridY * cellSize + cellSize / 2;
};
self.explode = function () {
// Simple bomb explosion effect (same as Ball explode method)
tween(self, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 0
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.parent) {
self.destroy();
}
}
});
};
self.explode3x3 = function () {
// Explode all balls in a 3x3 area around the bomb
var centerX = self.gridX;
var centerY = self.gridY;
var explodedCount = 0;
for (var dx = -1; dx <= 1; dx++) {
for (var dy = -1; dy <= 1; dy++) {
var targetX = centerX + dx;
var targetY = centerY + dy;
if (targetX >= 0 && targetX < gridSize && targetY >= 0 && targetY < gridSize) {
var ball = grid[targetY][targetX];
if (ball) {
// If it's a chained ball, break the chain first
if (ball.isChained) {
ball.breakChain();
// Give extra points for breaking chains with bombs
LK.setScore(LK.getScore() + 5);
}
ball.explode();
grid[targetY][targetX] = null;
explodedCount++;
}
}
}
}
// Award points for exploded balls (apply combo multiplier)
var bombPoints = Math.floor(explodedCount * 15 * comboMultiplier);
LK.setScore(LK.getScore() + bombPoints);
scoreText.setText('Score: ' + LK.getScore());
// Update progress bar
var currentScore = LK.getScore();
// Calculate score needed for current level progression
function getScoreForLevel(level) {
if (level <= 1) return 0;
var totalScore = 0;
for (var i = 1; i < level; i++) {
totalScore += 400 + (i - 1) * 150;
}
return totalScore;
}
function getScoreNeededForNextLevel(level) {
return 400 + (level - 1) * 150;
}
var scoreInCurrentLevel = currentScore - getScoreForLevel(currentLevel);
var scoreNeededThisLevel = getScoreNeededForNextLevel(currentLevel);
var progress = Math.min(1, scoreInCurrentLevel / scoreNeededThisLevel);
progressBarFill.width = progress * 400;
progressText.setText(scoreInCurrentLevel + '/' + scoreNeededThisLevel);
checkLevelProgression();
// Remove bomb from grid
grid[self.gridY][self.gridX] = null;
// Explosion effect for bomb itself
tween(self, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
if (self.parent) {
self.destroy();
}
}
});
};
// Start pulsing animation when created
self.pulseAnimation();
return self;
});
var ChainedBall = Container.expand(function (color) {
var self = Container.call(this);
self.ballColor = color;
self.gridX = 0;
self.gridY = 0;
self.isAnimating = false;
self.isChained = true;
// Create chain background first (larger, darker)
var chainGraphics = self.attachAsset('chain', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7
});
// Create ball on top of chain
var ballAssets = ['redBall', 'blueBall', 'greenBall', 'yellowBall', 'purpleBall', 'orangeBall'];
var assetToUse = ballAssets[color];
var ballGraphics = self.attachAsset(assetToUse, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.85,
scaleY: 0.85
});
// Add subtle chain animation
self.chainAnimation = function () {
tween(chainGraphics, {
rotation: 0.1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(chainGraphics, {
rotation: -0.1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.parent && self.isChained) {
self.chainAnimation();
}
}
});
}
});
};
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = gridStartX + gridX * cellSize + cellSize / 2;
self.y = gridStartY + gridY * cellSize + cellSize / 2;
};
self.explode = function () {
// Scale up and fade out explosion effect
tween(self, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 0
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.parent) {
self.destroy();
}
}
});
};
self.breakChain = function () {
// Break the chain and convert to regular ball
self.isChained = false;
// Animate chain breaking
tween(chainGraphics, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut
});
// Scale ball back to normal size
tween(ballGraphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 300,
easing: tween.easeOut
});
// Flash effect when chain breaks
LK.effects.flashObject(self, 0xFFFFFF, 400);
};
// Start chain animation
self.chainAnimation();
return self;
});
var HealthBox = Container.expand(function () {
var self = Container.call(this);
self.gridX = 0;
self.gridY = 0;
self.isAnimating = false;
self.isHealthBox = true;
self.isDragging = false;
// Create health box visual using the healthBox asset
var healthGraphics = self.attachAsset('healthBox', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
// Add gentle glow animation
self.glowAnimation = function () {
tween(self, {
alpha: 0.7
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self, {
alpha: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.parent && !self.isDragging) {
self.glowAnimation();
}
}
});
}
});
};
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = gridStartX + gridX * cellSize + cellSize / 2;
self.y = gridStartY + gridY * cellSize + cellSize / 2;
};
self.explode = function () {
// Simple health box explosion effect
tween(self, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 0
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.parent) {
self.destroy();
}
}
});
};
self.activateHealthBonus = function () {
// Add 8 moves when health box is clicked
movesLeft += 8;
movesText.setText('Moves: ' + movesLeft);
// Remove health box from grid
grid[self.gridY][self.gridX] = null;
// Visual effect for health box activation
tween(self, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
if (self.parent) {
self.destroy();
}
// After health box is removed, trigger ball dropping and filling
LK.setTimeout(function () {
dropBalls();
fillEmptySpaces();
// Check for any new matches that might form
processMatches();
}, 100);
}
});
// Flash screen green to indicate health bonus
LK.effects.flashScreen(0x00FF00, 500);
};
// Start glow animation when created
self.glowAnimation();
return self;
});
var TimeBonus = Container.expand(function () {
var self = Container.call(this);
self.gridX = 0;
self.gridY = 0;
self.isAnimating = false;
self.isTimeBonus = true;
self.isDragging = false;
// Create time bonus visual using the Saat asset
var timeBonusGraphics = self.attachAsset('Saat', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.9,
scaleY: 0.9
});
// Add clock-like pulsing animation
self.clockAnimation = function () {
tween(self, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.parent && !self.isDragging) {
self.clockAnimation();
}
}
});
}
});
};
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = gridStartX + gridX * cellSize + cellSize / 2;
self.y = gridStartY + gridY * cellSize + cellSize / 2;
};
self.explode = function () {
// Simple time bonus explosion effect
tween(self, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 0
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.parent) {
self.destroy();
}
}
});
};
self.activateTimeBonus = function () {
// Add 10 seconds when time bonus is clicked in time trial mode
if (gameMode === 'timetrial') {
timeLeft += 10;
updateTimer();
}
// Remove time bonus from grid
grid[self.gridY][self.gridX] = null;
// Visual effect for time bonus activation
tween(self, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
if (self.parent) {
self.destroy();
}
// After time bonus is removed, trigger ball dropping and filling
LK.setTimeout(function () {
dropBalls();
fillEmptySpaces();
// Check for any new matches that might form
processMatches();
}, 100);
}
});
// Flash screen cyan to indicate time bonus
LK.effects.flashScreen(0x00FFFF, 500);
};
// Start clock animation when created
self.clockAnimation();
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2c3e50
});
/****
* Game Code
****/
// Game state management
var gameState = 'menu'; // 'menu' or 'playing'
var gameMode = 'classic'; // 'classic' or 'timetrial'
var menuContainer;
var gameContainer;
// Time trial specific variables
var timeTrialDuration = 120; // 2 minutes in seconds
var timeLeft = timeTrialDuration;
var timerText;
var gameTimer;
// Grid configuration variables needed by Ball class
var gridSize = 8;
var cellSize = 220;
var gridStartX = (2048 - gridSize * cellSize) / 2;
var gridStartY = 400;
var grid = [];
var gridShape = []; // Array to define which cells are active in current level
// Define different grid shapes for each level
var levelShapes = [
// Level 1: Full 8x8 square
function (size) {
var shape = [];
for (var y = 0; y < size; y++) {
shape[y] = [];
for (var x = 0; x < size; x++) {
shape[y][x] = true;
}
}
return shape;
},
// Level 2: Diamond shape
function (size) {
var shape = [];
var center = Math.floor(size / 2);
for (var y = 0; y < size; y++) {
shape[y] = [];
for (var x = 0; x < size; x++) {
var distanceFromCenter = Math.abs(x - center) + Math.abs(y - center);
shape[y][x] = distanceFromCenter <= center;
}
}
return shape;
},
// Level 3: Cross shape
function (size) {
var shape = [];
var center = Math.floor(size / 2);
for (var y = 0; y < size; y++) {
shape[y] = [];
for (var x = 0; x < size; x++) {
shape[y][x] = x === center || y === center;
}
}
return shape;
},
// Level 4: Hollow square (border only)
function (size) {
var shape = [];
for (var y = 0; y < size; y++) {
shape[y] = [];
for (var x = 0; x < size; x++) {
shape[y][x] = x === 0 || x === size - 1 || y === 0 || y === size - 1;
}
}
return shape;
},
// Level 5: L shape
function (size) {
var shape = [];
for (var y = 0; y < size; y++) {
shape[y] = [];
for (var x = 0; x < size; x++) {
shape[y][x] = x >= 0 && x < 4 && y >= size - 7 || y >= size - 4 && x >= 0 && x < 7;
}
}
return shape;
},
// Level 6: Circle shape
function (size) {
var shape = [];
var center = size / 2 - 0.5;
var radius = center;
for (var y = 0; y < size; y++) {
shape[y] = [];
for (var x = 0; x < size; x++) {
var dx = x - center;
var dy = y - center;
var distance = Math.sqrt(dx * dx + dy * dy);
shape[y][x] = distance <= radius;
}
}
return shape;
}];
// Function to get current level's grid shape
function getCurrentGridShape() {
var shapeIndex = (currentLevel - 1) % levelShapes.length;
return levelShapes[shapeIndex](gridSize);
}
// Function to check if a grid position is valid for current shape
function isValidGridPosition(x, y) {
return gridShape[y] && gridShape[y][x];
}
var selectedBall = null;
var movesLeft = getMovesForLevel(1); // Start with moves calculated for level 1
var isProcessingMatches = false;
var ballColors = 4; // Start with 4 colors for easier beginning
var bombSpawnChance = 0.01; // 1% chance to spawn bomb instead of ball
var healthBoxSpawnChance = 0.04; // 4% chance to spawn health box instead of ball
var timeBonusSpawnChance = 0.03; // 3% chance to spawn time bonus instead of ball (time trial mode only)
var draggedBomb = null;
var lastMoveTime = 0;
var currentLevel = 1;
var baseMovesPerLevel = 30;
var pointsToNextLevel = 400; // Points needed to advance to next level
var totalPointsEarned = 0;
// Combo system variables
var currentCombo = 0;
var comboText;
var comboMultiplier = 1;
var maxCombo = 0;
var comboTimeout;
// Function to calculate moves for current level
function getMovesForLevel(level) {
// Start with 40 moves at level 1, decrease by 3 moves per level, minimum 15 moves
return Math.max(15, 40 - (level - 1) * 3);
}
// Combo system functions
function updateCombo() {
currentCombo++;
maxCombo = Math.max(maxCombo, currentCombo);
// Calculate multiplier based on combo (1x, 1.5x, 2x, 2.5x, 3x, max)
comboMultiplier = 1 + Math.min(2, (currentCombo - 1) * 0.5);
// Show combo text with animation
if (currentCombo >= 2) {
comboText.setText('COMBO x' + currentCombo + '\n' + comboMultiplier.toFixed(1) + 'x POINTS!');
comboText.visible = true;
// Animate combo text
comboText.scaleX = 0.5;
comboText.scaleY = 0.5;
comboText.alpha = 0;
tween(comboText, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 1
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(comboText, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeOut
});
}
});
// Change color based on combo level
var comboColor = '#FF6B6B'; // Red for 2-3
if (currentCombo >= 4 && currentCombo < 6) {
comboColor = '#FF9500'; // Orange for 4-5
} else if (currentCombo >= 6 && currentCombo < 8) {
comboColor = '#FFD700'; // Gold for 6-7
} else if (currentCombo >= 8) {
comboColor = '#9D4EDD'; // Purple for 8+
}
comboText.fill = comboColor;
// Screen flash effect for high combos
if (currentCombo >= 4) {
var flashColor = 0xFF9500;
if (currentCombo >= 6) flashColor = 0xFFD700;
if (currentCombo >= 8) flashColor = 0x9D4EDD;
LK.effects.flashScreen(flashColor, 300);
}
}
// Clear existing timeout and set new one
if (comboTimeout) {
LK.clearTimeout(comboTimeout);
}
comboTimeout = LK.setTimeout(function () {
resetCombo();
}, 3000); // Reset combo after 3 seconds of no matches
}
function resetCombo() {
if (currentCombo >= 2) {
// Fade out combo text
tween(comboText, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 400,
easing: tween.easeIn,
onFinish: function onFinish() {
comboText.visible = false;
}
});
}
currentCombo = 0;
comboMultiplier = 1;
if (comboTimeout) {
LK.clearTimeout(comboTimeout);
comboTimeout = null;
}
}
// UI Elements
var movesText = new Text2('Moves: ' + getMovesForLevel(1), {
size: 80,
fill: 0xFFFFFF
});
movesText.anchor.set(0.5, 0);
LK.gui.top.addChild(movesText);
movesText.y = 150;
// Timer text for time trial mode
timerText = new Text2('Time: 2:00', {
size: 80,
fill: 0xFFFFFF
});
timerText.anchor.set(0.5, 0);
LK.gui.top.addChild(timerText);
timerText.y = 150;
timerText.visible = false; // Hidden by default
// Time trial timer function
function updateTimer() {
timeLeft--;
var minutes = Math.floor(timeLeft / 60);
var seconds = timeLeft % 60;
var timeString = minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
timerText.setText('Time: ' + timeString);
if (timeLeft <= 0) {
LK.clearInterval(gameTimer);
LK.showGameOver();
} else if (timeLeft <= 10) {
// Flash red when time is running out
LK.effects.flashObject(timerText, 0xFF0000, 200);
}
}
var scoreText = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreText.anchor.set(0, 0);
LK.gui.top.addChild(scoreText);
scoreText.x = 150;
scoreText.y = 250;
// Progress bar to show points needed for next level
var progressBarBackground = LK.getAsset('gridCell', {
width: 400,
height: 40,
anchorX: 1.0,
anchorY: 0,
alpha: 0.3
});
LK.gui.topRight.addChild(progressBarBackground);
progressBarBackground.x = -50;
progressBarBackground.y = 150;
var progressBarFill = LK.getAsset('gridCell', {
width: 1,
height: 40,
anchorX: 0,
anchorY: 0,
color: 0x00FF00
});
progressBarBackground.addChild(progressBarFill);
var progressText = new Text2('0/400', {
size: 50,
fill: 0xFFFFFF
});
progressText.anchor.set(1, 0);
LK.gui.topRight.addChild(progressText);
progressText.x = -50;
progressText.y = 200;
// Combo text display
comboText = new Text2('', {
size: 120,
fill: '#FF6B6B'
});
comboText.anchor.set(0.5, 0.5);
comboText.x = 1024;
comboText.y = 300;
comboText.visible = false;
game.addChild(comboText);
// Initialize grid
function initializeGrid() {
// Get the shape for current level
gridShape = getCurrentGridShape();
grid = [];
for (var y = 0; y < gridSize; y++) {
grid[y] = [];
for (var x = 0; x < gridSize; x++) {
grid[y][x] = null;
}
}
// Fill grid with balls only in valid positions
for (var y = 0; y < gridSize; y++) {
for (var x = 0; x < gridSize; x++) {
if (isValidGridPosition(x, y)) {
createBallAt(x, y);
}
}
}
// Remove initial matches
while (findMatches().length > 0) {
removeMatches();
dropBalls();
fillEmptySpaces();
}
}
function createBallAt(x, y) {
// Only create balls in valid grid positions
if (!isValidGridPosition(x, y)) {
return null;
}
// Also check if position is within grid bounds
if (x < 0 || x >= gridSize || y < 0 || y >= gridSize) {
return null;
}
// Sometimes create a bomb instead of a ball
if (Math.random() < bombSpawnChance) {
var bomb = new Bomb();
bomb.setGridPosition(x, y);
grid[y][x] = bomb;
game.addChild(bomb);
// Animate bomb appearing
bomb.scaleX = 0;
bomb.scaleY = 0;
bomb.alpha = 0;
tween(bomb, {
scaleX: 0.8,
scaleY: 0.8,
alpha: 1
}, {
duration: 400,
easing: tween.easeOut
});
return bomb;
} else if (gameMode === 'classic' && Math.random() < healthBoxSpawnChance) {
var healthBox = new HealthBox();
healthBox.setGridPosition(x, y);
grid[y][x] = healthBox;
game.addChild(healthBox);
// Animate health box appearing
healthBox.scaleX = 0;
healthBox.scaleY = 0;
healthBox.alpha = 0;
tween(healthBox, {
scaleX: 0.8,
scaleY: 0.8,
alpha: 1
}, {
duration: 400,
easing: tween.easeOut
});
return healthBox;
} else if (gameMode === 'timetrial' && Math.random() < timeBonusSpawnChance) {
var timeBonus = new TimeBonus();
timeBonus.setGridPosition(x, y);
grid[y][x] = timeBonus;
game.addChild(timeBonus);
// Animate time bonus appearing
timeBonus.scaleX = 0;
timeBonus.scaleY = 0;
timeBonus.alpha = 0;
tween(timeBonus, {
scaleX: 0.9,
scaleY: 0.9,
alpha: 1
}, {
duration: 400,
easing: tween.easeOut
});
return timeBonus;
} else if (Math.random() < 0.05) {
// 5% chance to create chained ball - use smart color selection
var adjacentColors = [];
// Check all 4 directions for adjacent ball colors
var directions = [{
dx: -1,
dy: 0
}, {
dx: 1,
dy: 0
}, {
dx: 0,
dy: -1
}, {
dx: 0,
dy: 1
}];
for (var d = 0; d < directions.length; d++) {
var dir = directions[d];
var adjX = x + dir.dx;
var adjY = y + dir.dy;
if (adjX >= 0 && adjX < gridSize && adjY >= 0 && adjY < gridSize && isValidGridPosition(adjX, adjY) && grid[adjY][adjX] && grid[adjY][adjX].ballColor !== undefined) {
adjacentColors.push(grid[adjY][adjX].ballColor);
}
}
var color;
if (adjacentColors.length > 0 && Math.random() < 0.4) {
// 40% chance to match an adjacent color for potential future matches
color = adjacentColors[Math.floor(Math.random() * adjacentColors.length)];
} else {
color = Math.floor(Math.random() * ballColors);
}
var chainedBall = new ChainedBall(color);
chainedBall.setGridPosition(x, y);
grid[y][x] = chainedBall;
game.addChild(chainedBall);
// Animate chained ball appearing
chainedBall.scaleX = 0;
chainedBall.scaleY = 0;
chainedBall.alpha = 0;
tween(chainedBall, {
scaleX: 1,
scaleY: 1,
alpha: 1
}, {
duration: 400,
easing: tween.easeOut
});
return chainedBall;
} else {
// Smart color selection for regular balls
var adjacentColors = [];
var belowColors = [];
// Check all 4 directions for adjacent ball colors
var directions = [{
dx: -1,
dy: 0
}, {
dx: 1,
dy: 0
}, {
dx: 0,
dy: -1
}, {
dx: 0,
dy: 1
}];
for (var d = 0; d < directions.length; d++) {
var dir = directions[d];
var adjX = x + dir.dx;
var adjY = y + dir.dy;
if (adjX >= 0 && adjX < gridSize && adjY >= 0 && adjY < gridSize && isValidGridPosition(adjX, adjY) && grid[adjY][adjX] && grid[adjY][adjX].ballColor !== undefined) {
adjacentColors.push(grid[adjY][adjX].ballColor);
// Special attention to ball below for dropping compatibility
if (dir.dy === 1) {
belowColors.push(grid[adjY][adjX].ballColor);
}
}
}
var color;
// 53% chance to be compatible with environment if adjacent balls exist
if (adjacentColors.length > 0 && Math.random() < 0.53) {
// 60% chance to match an adjacent color (for potential matches)
if (Math.random() < 0.6) {
// Prioritize matching ball below if it exists
if (belowColors.length > 0) {
color = belowColors[Math.floor(Math.random() * belowColors.length)];
} else {
color = adjacentColors[Math.floor(Math.random() * adjacentColors.length)];
}
} else {
// 40% chance to choose a color that would create diversity but still be strategic
var usedColors = {};
for (var i = 0; i < adjacentColors.length; i++) {
usedColors[adjacentColors[i]] = true;
}
var availableColors = [];
for (var c = 0; c < ballColors; c++) {
if (!usedColors[c]) {
availableColors.push(c);
}
}
if (availableColors.length > 0) {
color = availableColors[Math.floor(Math.random() * availableColors.length)];
} else {
color = Math.floor(Math.random() * ballColors);
}
}
} else {
// Random color when no adjacent balls or 50% random chance
color = Math.floor(Math.random() * ballColors);
}
var ball = new Ball(color);
ball.setGridPosition(x, y);
grid[y][x] = ball;
game.addChild(ball);
// Animate ball appearing with scale and bounce
ball.scaleX = 0;
ball.scaleY = 0;
ball.alpha = 0;
tween(ball, {
scaleX: 1,
scaleY: 1,
alpha: 1
}, {
duration: 400,
easing: tween.easeOut
});
return ball;
}
}
function getBallAt(x, y) {
if (x < 0 || x >= gridSize || y < 0 || y >= gridSize) {
return null;
}
if (!grid || !grid[y]) {
return null;
}
return grid[y][x];
}
function swapBalls(ball1, ball2) {
if (!ball1 || !ball2) {
return false;
}
// Check if either ball is chained - they cannot be moved
if (ball1.isChained && ball1.isChained === true || ball2.isChained && ball2.isChained === true) {
// Flash red to indicate chained balls cannot move
LK.effects.flashScreen(0xFF0000, 200);
return false;
}
var x1 = ball1.gridX,
y1 = ball1.gridY;
var x2 = ball2.gridX,
y2 = ball2.gridY;
// Check if balls are adjacent
var dx = Math.abs(x1 - x2);
var dy = Math.abs(y1 - y2);
if (dx + dy !== 1) {
return false;
}
// Mark balls as animating
ball1.isAnimating = true;
ball2.isAnimating = true;
// Store old positions
var oldX1 = ball1.x,
oldY1 = ball1.y;
var oldX2 = ball2.x,
oldY2 = ball2.y;
// Swap positions in grid
grid[y1][x1] = ball2;
grid[y2][x2] = ball1;
// Update ball positions
ball1.setGridPosition(x2, y2);
ball2.setGridPosition(x1, y1);
// Store new positions
var newX1 = ball1.x,
newY1 = ball1.y;
var newX2 = ball2.x,
newY2 = ball2.y;
// Reset to old positions for animation
ball1.x = oldX1;
ball1.y = oldY1;
ball2.x = oldX2;
ball2.y = oldY2;
// Animate to new positions
tween(ball1, {
x: newX1,
y: newY1
}, {
duration: 250,
easing: tween.easeInOut,
onFinish: function onFinish() {
ball1.isAnimating = false;
}
});
tween(ball2, {
x: newX2,
y: newY2
}, {
duration: 250,
easing: tween.easeInOut,
onFinish: function onFinish() {
ball2.isAnimating = false;
}
});
LK.getSound('swap').play();
return true;
}
function findMatches() {
var matches = [];
// Check horizontal matches
for (var y = 0; y < gridSize; y++) {
var count = 0;
var currentColor = -1;
var matchPositions = [];
for (var x = 0; x < gridSize; x++) {
if (!isValidGridPosition(x, y)) {
// Invalid position, reset match counting
if (count >= 3) {
for (var i = 0; i < matchPositions.length; i++) {
matches.push(matchPositions[i]);
}
}
count = 0;
currentColor = -1;
matchPositions = [];
continue;
}
var ball = grid[y][x];
if (ball && ball.ballColor !== undefined && ball.ballColor === currentColor) {
count++;
matchPositions.push({
x: x,
y: y
});
} else {
if (count >= 3) {
for (var i = 0; i < matchPositions.length; i++) {
matches.push(matchPositions[i]);
}
}
count = ball && ball.ballColor !== undefined ? 1 : 0;
currentColor = ball && ball.ballColor !== undefined ? ball.ballColor : -1;
matchPositions = ball && ball.ballColor !== undefined ? [{
x: x,
y: y
}] : [];
}
}
if (count >= 3) {
for (var i = 0; i < matchPositions.length; i++) {
matches.push(matchPositions[i]);
}
}
}
// Check vertical matches
for (var x = 0; x < gridSize; x++) {
var count = 0;
var currentColor = -1;
var matchPositions = [];
for (var y = 0; y < gridSize; y++) {
if (!isValidGridPosition(x, y)) {
// Invalid position, reset match counting
if (count >= 3) {
for (var i = 0; i < matchPositions.length; i++) {
matches.push(matchPositions[i]);
}
}
count = 0;
currentColor = -1;
matchPositions = [];
continue;
}
var ball = grid[y][x];
if (ball && ball.ballColor !== undefined && ball.ballColor === currentColor) {
count++;
matchPositions.push({
x: x,
y: y
});
} else {
if (count >= 3) {
for (var i = 0; i < matchPositions.length; i++) {
matches.push(matchPositions[i]);
}
}
count = ball && ball.ballColor !== undefined ? 1 : 0;
currentColor = ball && ball.ballColor !== undefined ? ball.ballColor : -1;
matchPositions = ball && ball.ballColor !== undefined ? [{
x: x,
y: y
}] : [];
}
}
if (count >= 3) {
for (var i = 0; i < matchPositions.length; i++) {
matches.push(matchPositions[i]);
}
}
}
// Check diagonal matches (top-left to bottom-right)
for (var startY = 0; startY < gridSize; startY++) {
for (var startX = 0; startX < gridSize; startX++) {
if (!isValidGridPosition(startX, startY)) continue;
var startBall = grid[startY][startX];
if (!startBall || startBall.ballColor === undefined) continue;
var count = 1;
var currentColor = startBall.ballColor;
var matchPositions = [{
x: startX,
y: startY
}];
for (var step = 1; startX + step < gridSize && startY + step < gridSize; step++) {
var x = startX + step;
var y = startY + step;
if (!isValidGridPosition(x, y)) break;
var ball = grid[y][x];
if (ball && ball.ballColor === currentColor) {
count++;
matchPositions.push({
x: x,
y: y
});
} else {
break;
}
}
if (count >= 3) {
for (var i = 0; i < matchPositions.length; i++) {
matches.push(matchPositions[i]);
}
}
}
}
// Check diagonal matches (top-right to bottom-left)
for (var startY = 0; startY < gridSize; startY++) {
for (var startX = gridSize - 1; startX >= 0; startX--) {
if (!isValidGridPosition(startX, startY)) continue;
var startBall = grid[startY][startX];
if (!startBall || startBall.ballColor === undefined) continue;
var count = 1;
var currentColor = startBall.ballColor;
var matchPositions = [{
x: startX,
y: startY
}];
for (var step = 1; startX - step >= 0 && startY + step < gridSize; step++) {
var x = startX - step;
var y = startY + step;
if (!isValidGridPosition(x, y)) break;
var ball = grid[y][x];
if (ball && ball.ballColor === currentColor) {
count++;
matchPositions.push({
x: x,
y: y
});
} else {
break;
}
}
if (count >= 3) {
for (var i = 0; i < matchPositions.length; i++) {
matches.push(matchPositions[i]);
}
}
}
}
return matches;
}
function removeMatches() {
var matches = findMatches();
if (matches.length === 0) {
// Reset combo when no matches found
resetCombo();
return false;
}
// Update combo system
updateCombo();
var pointsEarned = matches.length * 10;
// Apply combo multiplier to points
pointsEarned = Math.floor(pointsEarned * comboMultiplier);
// Check if this is a 4-ball match for special rewards
var isFourBallMatch = matches.length === 4;
if (isFourBallMatch) {
// Award bonus 10 points for 4-ball match (also affected by multiplier)
pointsEarned += Math.floor(10 * comboMultiplier);
}
for (var i = 0; i < matches.length; i++) {
var match = matches[i];
var ball = grid[match.y][match.x];
if (ball) {
// If it's a chained ball, break the chain first then explode
if (ball.isChained) {
ball.breakChain();
// Give extra points for breaking chains
pointsEarned += 5;
}
ball.explode();
grid[match.y][match.x] = null;
}
}
LK.setScore(LK.getScore() + pointsEarned);
scoreText.setText('Score: ' + LK.getScore());
// Update progress bar
var currentScore = LK.getScore();
// Calculate score needed for current level progression
function getScoreForLevel(level) {
if (level <= 1) return 0;
var totalScore = 0;
for (var i = 1; i < level; i++) {
totalScore += 400 + (i - 1) * 150;
}
return totalScore;
}
function getScoreNeededForNextLevel(level) {
return 400 + (level - 1) * 150;
}
var scoreInCurrentLevel = currentScore - getScoreForLevel(currentLevel);
var scoreNeededThisLevel = getScoreNeededForNextLevel(currentLevel);
var progress = Math.min(1, scoreInCurrentLevel / scoreNeededThisLevel);
progressBarFill.width = progress * 400;
progressText.setText(scoreInCurrentLevel + '/' + scoreNeededThisLevel);
// Spawn a bomb as reward for 4-ball match
if (isFourBallMatch) {
// Find a random empty valid position to place the bomb
var emptyPositions = [];
for (var y = 0; y < gridSize; y++) {
for (var x = 0; x < gridSize; x++) {
if (grid[y][x] === null && isValidGridPosition(x, y)) {
emptyPositions.push({
x: x,
y: y
});
}
}
}
if (emptyPositions.length > 0) {
var randomPos = emptyPositions[Math.floor(Math.random() * emptyPositions.length)];
var rewardBomb = new Bomb();
rewardBomb.setGridPosition(randomPos.x, randomPos.y);
grid[randomPos.y][randomPos.x] = rewardBomb;
game.addChild(rewardBomb);
// Animate bomb appearing with special effect
rewardBomb.scaleX = 0;
rewardBomb.scaleY = 0;
rewardBomb.alpha = 0;
tween(rewardBomb, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 1
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(rewardBomb, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 200,
easing: tween.easeOut
});
}
});
// Flash screen gold to indicate bomb reward
LK.effects.flashScreen(0xFFD700, 500);
}
}
LK.getSound('match').play();
checkLevelProgression();
return true;
}
function dropBalls() {
for (var x = 0; x < gridSize; x++) {
var writePos = gridSize - 1;
// Find the lowest valid position for this column
while (writePos >= 0 && !isValidGridPosition(x, writePos)) {
writePos--;
}
for (var y = gridSize - 1; y >= 0; y--) {
if (grid[y][x] !== null && isValidGridPosition(x, y)) {
if (y !== writePos && writePos >= 0) {
grid[writePos][x] = grid[y][x];
grid[y][x] = null;
var ball = grid[writePos][x];
if (ball) {
ball.isAnimating = true;
var oldY = ball.y;
ball.setGridPosition(x, writePos);
var newY = ball.y;
ball.y = oldY;
// Animate ball falling down
(function (ballToAnimate) {
tween(ballToAnimate, {
y: newY
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
ballToAnimate.isAnimating = false;
}
});
})(ball);
}
}
// Only decrement writePos if we're at a valid position
if (writePos >= 0) {
writePos--;
// Find next valid position
while (writePos >= 0 && !isValidGridPosition(x, writePos)) {
writePos--;
}
}
}
}
}
}
function fillEmptySpaces() {
for (var x = 0; x < gridSize; x++) {
for (var y = 0; y < gridSize; y++) {
if (grid[y][x] === null && isValidGridPosition(x, y)) {
createBallAt(x, y);
}
}
}
}
function processMatches() {
if (isProcessingMatches) {
return;
}
isProcessingMatches = true;
function processStep() {
if (removeMatches()) {
dropBalls();
fillEmptySpaces();
LK.setTimeout(function () {
processStep();
}, 500);
} else {
isProcessingMatches = false;
checkGameState();
}
}
processStep();
}
function checkLevelProgression() {
// Calculate cumulative score needed for each level
// Level 1: 0-399 (400 points)
// Level 2: 400-949 (550 points)
// Level 3: 950-1649 (700 points)
// And so on, increasing by 150 each level
function getScoreForLevel(level) {
if (level <= 1) return 0;
var totalScore = 0;
for (var i = 1; i < level; i++) {
totalScore += 400 + (i - 1) * 150;
}
return totalScore;
}
function getScoreNeededForNextLevel(level) {
return 400 + (level - 1) * 150;
}
var currentScore = LK.getScore();
var newLevel = currentLevel;
// Check if we've reached the next level
while (currentScore >= getScoreForLevel(newLevel + 1)) {
newLevel++;
}
if (newLevel > currentLevel) {
// Level up!
currentLevel = newLevel;
pointsToNextLevel = getScoreNeededForNextLevel(currentLevel + 1);
// Increase difficulty
if (ballColors < 6) {
ballColors = Math.min(6, 3 + currentLevel); // Max 6 colors
}
// Set moves based on new level difficulty (progressively fewer moves)
if (gameMode === 'classic') {
movesLeft = getMovesForLevel(currentLevel);
// Update UI
movesText.setText('Moves: ' + movesLeft);
} else {
// In time trial mode, keep unlimited moves
movesLeft = 999;
}
// Reset progress bar for new level
var scoreInCurrentLevel = currentScore - getScoreForLevel(currentLevel);
var scoreNeededThisLevel = getScoreNeededForNextLevel(currentLevel);
var progress = Math.min(1, scoreInCurrentLevel / scoreNeededThisLevel);
progressBarFill.width = progress * 400;
progressText.setText(scoreInCurrentLevel + '/' + scoreNeededThisLevel);
// Change grid shape for new level - clear existing balls first
for (var y = 0; y < gridSize; y++) {
for (var x = 0; x < gridSize; x++) {
if (grid[y][x]) {
grid[y][x].destroy();
grid[y][x] = null;
}
}
}
// Initialize new grid shape
gridShape = getCurrentGridShape();
// Fill new shape with balls
for (var y = 0; y < gridSize; y++) {
for (var x = 0; x < gridSize; x++) {
if (isValidGridPosition(x, y)) {
createBallAt(x, y);
}
}
}
// Flash screen green for level up
LK.effects.flashScreen(0x00FF00, 800);
}
}
function checkGameState() {
if (gameMode === 'classic') {
checkLevelProgression();
if (movesLeft <= 0) {
LK.showGameOver();
return;
}
} else if (gameMode === 'timetrial') {
// In time trial mode, only check for time running out
// (timer already handles game over when time reaches 0)
if (timeLeft <= 0) {
LK.showGameOver();
return;
}
}
}
function getClickedBall(x, y) {
var gridX = Math.floor((x - gridStartX) / cellSize);
var gridY = Math.floor((y - gridStartY) / cellSize);
if (gridX >= 0 && gridX < gridSize && gridY >= 0 && gridY < gridSize) {
return getBallAt(gridX, gridY);
}
return null;
}
game.down = function (x, y, obj) {
if (isProcessingMatches) {
return;
}
var clickedBall = getClickedBall(x, y);
if (clickedBall && !clickedBall.isAnimating) {
// Check if it's a health box
if (clickedBall.isHealthBox) {
clickedBall.activateHealthBonus();
return;
}
// Check if it's a time bonus
if (clickedBall.isTimeBonus) {
clickedBall.activateTimeBonus();
return;
}
// Check if it's a bomb
if (clickedBall.isBomb) {
draggedBomb = clickedBall;
draggedBomb.isDragging = true;
// Stop pulsing animation
tween.stop(draggedBomb);
// Make bomb slightly larger while dragging
tween(draggedBomb, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut
});
return;
}
// Regular ball logic
if (selectedBall === null) {
selectedBall = clickedBall;
LK.effects.flashObject(selectedBall, 0xffffff, 200);
} else if (selectedBall === clickedBall) {
selectedBall = null;
} else {
if (swapBalls(selectedBall, clickedBall)) {
if (gameMode === 'classic') {
movesLeft--;
movesText.setText('Moves: ' + movesLeft);
}
LK.setTimeout(function () {
var matches = findMatches();
if (matches.length > 0) {
processMatches();
} else {
// Wasted move - swap back and apply additional penalty
swapBalls(selectedBall, clickedBall);
// No additional penalty for wasted move (already lost 1 move from the attempt)
movesText.setText('Moves: ' + movesLeft);
// Flash screen red to indicate wasted move
LK.effects.flashScreen(0xFF0000, 300);
}
}, 300);
}
selectedBall = null;
}
}
};
game.move = function (x, y, obj) {
if (draggedBomb && Date.now() - lastMoveTime > 16) {
// Throttle to ~60fps
draggedBomb.x = x;
draggedBomb.y = y;
lastMoveTime = Date.now();
}
};
game.up = function (x, y, obj) {
if (draggedBomb) {
// Explode bomb at current position
draggedBomb.explode3x3();
// Using a bomb costs 2 moves in classic mode
if (gameMode === 'classic') {
movesLeft -= 2;
movesText.setText('Moves: ' + movesLeft);
}
// Process any resulting matches
LK.setTimeout(function () {
dropBalls();
fillEmptySpaces();
processMatches();
}, 100);
draggedBomb = null;
}
};
// Add background
var backgroundImage = game.attachAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
alpha: 0.3
});
// Create main menu
function createMainMenu() {
menuContainer = new Container();
game.addChild(menuContainer);
// Menu background overlay
var menuBg = LK.getAsset('gridCell', {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0,
color: 0x1a1a2e,
alpha: 0.95
});
menuContainer.addChild(menuBg);
// Game title
var titleText = new Text2('MATCH-3\nPUZZLE', {
size: 200,
fill: '#FFD700'
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 800;
menuContainer.addChild(titleText);
// Subtitle
var subtitleText = new Text2('Swap colored balls to create matches!', {
size: 80,
fill: '#FFFFFF'
});
subtitleText.anchor.set(0.5, 0.5);
subtitleText.x = 1024;
subtitleText.y = 1100;
menuContainer.addChild(subtitleText);
// Game mode buttons
var classicButton = LK.getAsset('gridCell', {
width: 400,
height: 120,
anchorX: 0.5,
anchorY: 0.5,
color: 0x4CAF50
});
classicButton.x = 1024;
classicButton.y = 1400;
menuContainer.addChild(classicButton);
var classicButtonText = new Text2('CLASSIC', {
size: 80,
fill: '#FFFFFF'
});
classicButtonText.anchor.set(0.5, 0.5);
classicButton.addChild(classicButtonText);
var timeTrialButton = LK.getAsset('gridCell', {
width: 400,
height: 120,
anchorX: 0.5,
anchorY: 0.5,
color: 0xFF9800
});
timeTrialButton.x = 1024;
timeTrialButton.y = 1600;
menuContainer.addChild(timeTrialButton);
var timeTrialButtonText = new Text2('TIME TRIAL', {
size: 80,
fill: '#FFFFFF'
});
timeTrialButtonText.anchor.set(0.5, 0.5);
timeTrialButton.addChild(timeTrialButtonText);
// Instructions
var instructionText = new Text2('• Create lines of 3+ matching balls\n• Use bombs to clear large areas\n• Collect health boxes for extra moves\n• Classic: Limited moves, unlock levels\n• Time Trial: Race against the clock!', {
size: 60,
fill: '#CCCCCC'
});
instructionText.anchor.set(0.5, 0.5);
instructionText.x = 1024;
instructionText.y = 1900;
menuContainer.addChild(instructionText);
// Animate title entrance
titleText.scaleX = 0;
titleText.scaleY = 0;
titleText.alpha = 0;
tween(titleText, {
scaleX: 1,
scaleY: 1,
alpha: 1
}, {
duration: 800,
easing: tween.easeOut
});
// Animate subtitle
subtitleText.alpha = 0;
tween(subtitleText, {
alpha: 1
}, {
duration: 600,
delay: 400,
easing: tween.easeOut
});
// Animate game mode buttons
classicButton.scaleX = 0;
classicButton.scaleY = 0;
tween(classicButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 500,
delay: 800,
easing: tween.easeOut
});
timeTrialButton.scaleX = 0;
timeTrialButton.scaleY = 0;
tween(timeTrialButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 500,
delay: 900,
easing: tween.easeOut
});
// Animate instructions
instructionText.alpha = 0;
tween(instructionText, {
alpha: 1
}, {
duration: 600,
delay: 1200,
easing: tween.easeOut
});
// Add pulsing animation to game mode buttons
function pulseGameModeButtons() {
tween(classicButton, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(classicButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
tween(timeTrialButton, {
scaleX: 1.05,
scaleY: 1.05
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(timeTrialButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (gameState === 'menu') {
pulseGameModeButtons();
}
}
});
}
});
}
// Start pulsing after initial animation
LK.setTimeout(function () {
if (gameState === 'menu') {
pulseGameModeButtons();
}
}, 1500);
// Game mode button click handlers
classicButton.down = function (x, y, obj) {
startGame('classic');
};
timeTrialButton.down = function (x, y, obj) {
startGame('timetrial');
};
return menuContainer;
}
// Function to start the actual game
function startGame(mode) {
gameState = 'playing';
gameMode = mode || 'classic';
// Reset combo system
resetCombo();
// Animate menu out
tween(menuContainer, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 500,
easing: tween.easeIn,
onFinish: function onFinish() {
menuContainer.destroy();
// Initialize game elements
createGameContainer();
initializeGrid();
// Setup UI based on game mode
setupGameModeUI();
// Start background music
LK.playMusic('Ana');
}
});
}
// Function to setup UI based on game mode
function setupGameModeUI() {
if (gameMode === 'timetrial') {
// Hide moves counter and show timer
movesText.visible = false;
timerText.visible = true;
timeLeft = timeTrialDuration;
updateTimer();
gameTimer = LK.setInterval(updateTimer, 1000);
// In time trial, give unlimited moves
movesLeft = 999;
} else {
// Classic mode - show moves counter, hide timer
movesText.visible = true;
timerText.visible = false;
movesLeft = getMovesForLevel(1);
movesText.setText('Moves: ' + movesLeft);
}
}
// Function to create game container
function createGameContainer() {
gameContainer = new Container();
game.addChild(gameContainer);
}
// Start with main menu instead of immediately initializing grid
if (gameState === 'menu') {
createMainMenu();
} else {
// Initialize the game
initializeGrid();
}
// Reset button for swapping balls
var resetButton = LK.getAsset('gridCell', {
width: 200,
height: 80,
anchorX: 0.5,
anchorY: 0.5,
color: 0xFF6B6B
});
LK.gui.bottom.addChild(resetButton);
resetButton.y = -100;
var resetButtonText = new Text2('RESET', {
size: 40,
fill: 0xFFFFFF
});
resetButtonText.anchor.set(0.5, 0.5);
resetButton.addChild(resetButtonText);
resetButton.down = function (x, y, obj) {
if (isProcessingMatches) {
return;
}
// Check if player has enough score to reset (cost 100 points)
var resetCost = 100;
if (LK.getScore() < resetCost) {
// Flash screen red to indicate insufficient score
LK.effects.flashScreen(0xFF0000, 400);
return;
}
// Clear current selection
selectedBall = null;
// Reset combo system
resetCombo();
// Clear the grid
for (var y = 0; y < gridSize; y++) {
for (var x = 0; x < gridSize; x++) {
if (grid[y][x]) {
grid[y][x].destroy();
grid[y][x] = null;
}
}
}
// Reinitialize the grid with new balls
initializeGrid();
// Cost 100 score points to reset
LK.setScore(LK.getScore() - resetCost);
scoreText.setText('Score: ' + LK.getScore());
// Update progress bar
var currentScore = LK.getScore();
// Calculate score needed for current level progression
function getScoreForLevel(level) {
if (level <= 1) return 0;
var totalScore = 0;
for (var i = 1; i < level; i++) {
totalScore += 400 + (i - 1) * 150;
}
return totalScore;
}
function getScoreNeededForNextLevel(level) {
return 400 + (level - 1) * 150;
}
var scoreInCurrentLevel = currentScore - getScoreForLevel(currentLevel);
var scoreNeededThisLevel = getScoreNeededForNextLevel(currentLevel);
var progress = Math.min(1, scoreInCurrentLevel / scoreNeededThisLevel);
progressBarFill.width = progress * 400;
progressText.setText(scoreInCurrentLevel + '/' + scoreNeededThisLevel);
// Flash screen blue to indicate reset
LK.effects.flashScreen(0x0066FF, 400);
};
// Background music will be started when game begins from menu
;
;
An orange round ball with a silly smile. In-Game asset. High contrast. No shadows
A clever yellow round ball with a yellow confused expression
A cool blue round ball. In-Game asset. High contrast. No shadows He has sunglasses and a cool smile
A rich, greedy green ball has a wad of cash in his hand. In-Game asset. High contrast. No shadows
A nervous red ball with an angry face. In-Game asset. High contrast. No shadows
A black bomb. In-Game asset. No shadows
A wooden box with a heart symbol on it. In-Game asset. High contrast. No shadows
Metal chains in x shape. In-Game asset. High contrast. No shadows
A forest background. In-Game asset. High contrast. No shadows
A pocket watch. In-Game asset. High contrast. No shadows
Blue sky with sparse clouds. In-Game asset. High contrast. Realistic anime style
A pink woman ball. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
A pig's face. High contrast. No shadows
Fark fence. In-Game asset. 2d. High contrast. No shadows
Ice cube. In-Game asset. High contrast. No shadows
A snowy forest. In-Game asset. 2d. High contrast. No shadows
A rocket. In-Game asset. 2d. High contrast. No shadows