User prompt
Bring combo system ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Prompt giving +10 seconds item Replace asset with the asset named Saat in assets
User prompt
Add an item that only appears in time trial mode +10 seconds
User prompt
Let's add a second game mode, time trial mode
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading '4')' in or related to this line: 'return grid[y][x];' Line Number: 754
User prompt
Add a stylish main menu to your game
User prompt
After each level, the score required to go to the next level increases by 150.
User prompt
The number of moves you have at each level will decrease and it will become more difficult to move from level to level as you progress.
User prompt
Make the L-shaped level with a thickness of 4 and a length of 7.
User prompt
Increase the chance of balls matching by 3%
User prompt
Remove the X shaped level and add an L shaped level instead
User prompt
When you use the box that gives you the right to move, the balls on it will fall to the ground.
User prompt
Let the newly formed balls be compatible with the ball in the environment and below it.
User prompt
Harita boşluğuna eşleşme ile düşmeyi geri getir
User prompt
Oyunu biraz kolaylaştır
User prompt
Levelden levele geçişler 400 skorda bir olsun
User prompt
Reset button spend score
User prompt
Add a reset button to swap balls
User prompt
Fix bugs
User prompt
It is impossible to match maps with fix
User prompt
Let's add a background to the levels
User prompt
Increase the match rate of balls
User prompt
Remove Level text
User prompt
Add a bar to show how many points are left as you move from level to level, put it on the top right of the screen
User prompt
Close matchmaking as soon as the game starts
/**** * 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 LK.setScore(LK.getScore() + explodedCount * 15); scoreText.setText('Score: ' + LK.getScore()); 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 5 moves when health box is clicked movesLeft += 5; 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(); } } }); // Flash screen green to indicate health bonus LK.effects.flashScreen(0x00FF00, 500); }; // Start glow animation when created self.glowAnimation(); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2c3e50 }); /**** * Game Code ****/ // 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: X 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 === y || x === size - 1 - y; } } 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 = 30; var isProcessingMatches = false; var ballColors = 3; // Start with 3 colors for much easier matching var bombSpawnChance = 0.02; // 2% chance to spawn bomb instead of ball var healthBoxSpawnChance = 0.02; // 2% chance to spawn health box instead of ball var draggedBomb = null; var lastMoveTime = 0; var currentLevel = 1; var baseMovesPerLevel = 30; var pointsToNextLevel = 500; // Points needed to advance to next level var totalPointsEarned = 0; // UI Elements var movesText = new Text2('Moves: 30', { size: 80, fill: 0xFFFFFF }); movesText.anchor.set(0.5, 0); LK.gui.top.addChild(movesText); movesText.y = 150; 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; var levelText = new Text2('Level: 1', { size: 80, fill: 0xFFD700 }); levelText.anchor.set(1, 0); LK.gui.top.addChild(levelText); levelText.x = -150; levelText.y = 250; // 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; } // 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 (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 (Math.random() < 0.05) { // 5% chance to create chained ball // Bias toward lower color numbers to increase match probability var colorRoll = Math.random(); var color; if (colorRoll < 0.4) { color = 0; // 40% chance for first color } else if (colorRoll < 0.7) { color = 1; // 30% chance for second color } else if (colorRoll < 0.9) { color = 2; // 20% chance for third color } else { color = Math.floor(Math.random() * ballColors); // 10% chance for any color } 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 { // Bias toward lower color numbers to increase match probability var colorRoll = Math.random(); var color; if (colorRoll < 0.4) { color = 0; // 40% chance for first color } else if (colorRoll < 0.7) { color = 1; // 30% chance for second color } else if (colorRoll < 0.9) { color = 2; // 20% chance for third color } else { color = Math.floor(Math.random() * ballColors); // 10% chance for any color } 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; } 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 = 1; var currentColor = grid[y][0] ? grid[y][0].ballColor : -1; for (var x = 1; x < gridSize; x++) { var ball = grid[y][x]; if (ball && ball.ballColor === currentColor) { count++; } else { if (count >= 3) { for (var i = x - count; i < x; i++) { matches.push({ x: i, y: y }); } } count = 1; currentColor = ball ? ball.ballColor : -1; } } if (count >= 3) { for (var i = gridSize - count; i < gridSize; i++) { matches.push({ x: i, y: y }); } } } // Check vertical matches for (var x = 0; x < gridSize; x++) { var count = 1; var currentColor = grid[0][x] ? grid[0][x].ballColor : -1; for (var y = 1; y < gridSize; y++) { var ball = grid[y][x]; if (ball && ball.ballColor === currentColor) { count++; } else { if (count >= 3) { for (var i = y - count; i < y; i++) { matches.push({ x: x, y: i }); } } count = 1; currentColor = ball ? ball.ballColor : -1; } } if (count >= 3) { for (var i = gridSize - count; i < gridSize; i++) { matches.push({ x: x, y: 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 count = 1; var currentColor = grid[startY][startX] ? grid[startY][startX].ballColor : -1; 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 count = 1; var currentColor = grid[startY][startX] ? grid[startY][startX].ballColor : -1; 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) { return false; } var pointsEarned = matches.length * 10; // 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 pointsEarned += 10; } 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()); // 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; for (var y = gridSize - 1; y >= 0; y--) { if (grid[y][x] !== null) { if (y !== writePos) { grid[writePos][x] = grid[y][x]; grid[y][x] = null; var ball = grid[writePos][x]; 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); } 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() { if (LK.getScore() >= pointsToNextLevel) { // Level up! currentLevel++; pointsToNextLevel += 300 + currentLevel * 200; // Increasingly harder to level up // Increase difficulty more gradually if (ballColors < 5) { ballColors = Math.min(5, 3 + Math.floor(currentLevel / 2)); // Max 5 colors, increase every 2 levels } // Give bonus moves for leveling up, but fewer as levels increase var bonusMoves = Math.max(5, 15 - currentLevel); movesLeft += bonusMoves; // Update UI levelText.setText('Level: ' + currentLevel); movesText.setText('Moves: ' + movesLeft); // 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); } } } // Visual celebration effect tween(levelText, { scaleX: 1.5, scaleY: 1.5, tint: 0x00FF00 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { tween(levelText, { scaleX: 1, scaleY: 1, tint: 0xFFD700 }, { duration: 300, easing: tween.easeOut }); } }); // Flash screen green for level up LK.effects.flashScreen(0x00FF00, 800); } } function findValidMove() { // Look for a move that would create a match for (var y = 0; y < gridSize; y++) { for (var x = 0; x < gridSize; x++) { if (!isValidGridPosition(x, y) || !grid[y][x]) continue; var ball = grid[y][x]; if (ball.isAnimating || ball.isChained || ball.isBomb || ball.isHealthBox) continue; // Check adjacent positions for potential swaps 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 newX = x + directions[d].dx; var newY = y + directions[d].dy; if (newX >= 0 && newX < gridSize && newY >= 0 && newY < gridSize && isValidGridPosition(newX, newY) && grid[newY][newX]) { var targetBall = grid[newY][newX]; if (targetBall.isAnimating || targetBall.isChained || targetBall.isBomb || targetBall.isHealthBox) continue; // Temporarily swap to test for matches var tempBall1 = grid[y][x]; var tempBall2 = grid[newY][newX]; grid[y][x] = tempBall2; grid[newY][newX] = tempBall1; tempBall1.ballColor = tempBall2.ballColor; tempBall2.ballColor = tempBall1.ballColor; var matches = findMatches(); // Restore original state grid[y][x] = tempBall1; grid[newY][newX] = tempBall2; tempBall1.ballColor = ball.ballColor; tempBall2.ballColor = targetBall.ballColor; if (matches.length > 0) { return { ball1: ball, ball2: targetBall }; } } } } } return null; } function executeAutoMove() { var validMove = findValidMove(); if (validMove && !isProcessingMatches) { if (swapBalls(validMove.ball1, validMove.ball2)) { movesLeft--; movesText.setText('Moves: ' + movesLeft); LK.setTimeout(function () { var matches = findMatches(); if (matches.length > 0) { processMatches(); } }, 300); } } } function checkGameState() { checkLevelProgression(); if (movesLeft <= 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 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)) { 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); // Additional penalty for wasted move movesLeft -= 1; // Lose 1 extra move for wasted 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 3 moves movesLeft -= 3; movesText.setText('Moves: ' + movesLeft); // Process any resulting matches LK.setTimeout(function () { dropBalls(); fillEmptySpaces(); processMatches(); }, 100); draggedBomb = null; } }; // Initialize the game initializeGrid(); // Auto move execution removed - players start making their own moves // Play background music LK.playMusic('Ana'); ; ;
/****
* 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
LK.setScore(LK.getScore() + explodedCount * 15);
scoreText.setText('Score: ' + LK.getScore());
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 5 moves when health box is clicked
movesLeft += 5;
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();
}
}
});
// Flash screen green to indicate health bonus
LK.effects.flashScreen(0x00FF00, 500);
};
// Start glow animation when created
self.glowAnimation();
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2c3e50
});
/****
* Game Code
****/
// 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: X 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 === y || x === size - 1 - y;
}
}
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 = 30;
var isProcessingMatches = false;
var ballColors = 3; // Start with 3 colors for much easier matching
var bombSpawnChance = 0.02; // 2% chance to spawn bomb instead of ball
var healthBoxSpawnChance = 0.02; // 2% chance to spawn health box instead of ball
var draggedBomb = null;
var lastMoveTime = 0;
var currentLevel = 1;
var baseMovesPerLevel = 30;
var pointsToNextLevel = 500; // Points needed to advance to next level
var totalPointsEarned = 0;
// UI Elements
var movesText = new Text2('Moves: 30', {
size: 80,
fill: 0xFFFFFF
});
movesText.anchor.set(0.5, 0);
LK.gui.top.addChild(movesText);
movesText.y = 150;
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;
var levelText = new Text2('Level: 1', {
size: 80,
fill: 0xFFD700
});
levelText.anchor.set(1, 0);
LK.gui.top.addChild(levelText);
levelText.x = -150;
levelText.y = 250;
// 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;
}
// 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 (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 (Math.random() < 0.05) {
// 5% chance to create chained ball
// Bias toward lower color numbers to increase match probability
var colorRoll = Math.random();
var color;
if (colorRoll < 0.4) {
color = 0; // 40% chance for first color
} else if (colorRoll < 0.7) {
color = 1; // 30% chance for second color
} else if (colorRoll < 0.9) {
color = 2; // 20% chance for third color
} else {
color = Math.floor(Math.random() * ballColors); // 10% chance for any color
}
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 {
// Bias toward lower color numbers to increase match probability
var colorRoll = Math.random();
var color;
if (colorRoll < 0.4) {
color = 0; // 40% chance for first color
} else if (colorRoll < 0.7) {
color = 1; // 30% chance for second color
} else if (colorRoll < 0.9) {
color = 2; // 20% chance for third color
} else {
color = Math.floor(Math.random() * ballColors); // 10% chance for any color
}
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;
}
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 = 1;
var currentColor = grid[y][0] ? grid[y][0].ballColor : -1;
for (var x = 1; x < gridSize; x++) {
var ball = grid[y][x];
if (ball && ball.ballColor === currentColor) {
count++;
} else {
if (count >= 3) {
for (var i = x - count; i < x; i++) {
matches.push({
x: i,
y: y
});
}
}
count = 1;
currentColor = ball ? ball.ballColor : -1;
}
}
if (count >= 3) {
for (var i = gridSize - count; i < gridSize; i++) {
matches.push({
x: i,
y: y
});
}
}
}
// Check vertical matches
for (var x = 0; x < gridSize; x++) {
var count = 1;
var currentColor = grid[0][x] ? grid[0][x].ballColor : -1;
for (var y = 1; y < gridSize; y++) {
var ball = grid[y][x];
if (ball && ball.ballColor === currentColor) {
count++;
} else {
if (count >= 3) {
for (var i = y - count; i < y; i++) {
matches.push({
x: x,
y: i
});
}
}
count = 1;
currentColor = ball ? ball.ballColor : -1;
}
}
if (count >= 3) {
for (var i = gridSize - count; i < gridSize; i++) {
matches.push({
x: x,
y: 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 count = 1;
var currentColor = grid[startY][startX] ? grid[startY][startX].ballColor : -1;
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 count = 1;
var currentColor = grid[startY][startX] ? grid[startY][startX].ballColor : -1;
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) {
return false;
}
var pointsEarned = matches.length * 10;
// 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
pointsEarned += 10;
}
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());
// 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;
for (var y = gridSize - 1; y >= 0; y--) {
if (grid[y][x] !== null) {
if (y !== writePos) {
grid[writePos][x] = grid[y][x];
grid[y][x] = null;
var ball = grid[writePos][x];
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);
}
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() {
if (LK.getScore() >= pointsToNextLevel) {
// Level up!
currentLevel++;
pointsToNextLevel += 300 + currentLevel * 200; // Increasingly harder to level up
// Increase difficulty more gradually
if (ballColors < 5) {
ballColors = Math.min(5, 3 + Math.floor(currentLevel / 2)); // Max 5 colors, increase every 2 levels
}
// Give bonus moves for leveling up, but fewer as levels increase
var bonusMoves = Math.max(5, 15 - currentLevel);
movesLeft += bonusMoves;
// Update UI
levelText.setText('Level: ' + currentLevel);
movesText.setText('Moves: ' + movesLeft);
// 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);
}
}
}
// Visual celebration effect
tween(levelText, {
scaleX: 1.5,
scaleY: 1.5,
tint: 0x00FF00
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(levelText, {
scaleX: 1,
scaleY: 1,
tint: 0xFFD700
}, {
duration: 300,
easing: tween.easeOut
});
}
});
// Flash screen green for level up
LK.effects.flashScreen(0x00FF00, 800);
}
}
function findValidMove() {
// Look for a move that would create a match
for (var y = 0; y < gridSize; y++) {
for (var x = 0; x < gridSize; x++) {
if (!isValidGridPosition(x, y) || !grid[y][x]) continue;
var ball = grid[y][x];
if (ball.isAnimating || ball.isChained || ball.isBomb || ball.isHealthBox) continue;
// Check adjacent positions for potential swaps
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 newX = x + directions[d].dx;
var newY = y + directions[d].dy;
if (newX >= 0 && newX < gridSize && newY >= 0 && newY < gridSize && isValidGridPosition(newX, newY) && grid[newY][newX]) {
var targetBall = grid[newY][newX];
if (targetBall.isAnimating || targetBall.isChained || targetBall.isBomb || targetBall.isHealthBox) continue;
// Temporarily swap to test for matches
var tempBall1 = grid[y][x];
var tempBall2 = grid[newY][newX];
grid[y][x] = tempBall2;
grid[newY][newX] = tempBall1;
tempBall1.ballColor = tempBall2.ballColor;
tempBall2.ballColor = tempBall1.ballColor;
var matches = findMatches();
// Restore original state
grid[y][x] = tempBall1;
grid[newY][newX] = tempBall2;
tempBall1.ballColor = ball.ballColor;
tempBall2.ballColor = targetBall.ballColor;
if (matches.length > 0) {
return {
ball1: ball,
ball2: targetBall
};
}
}
}
}
}
return null;
}
function executeAutoMove() {
var validMove = findValidMove();
if (validMove && !isProcessingMatches) {
if (swapBalls(validMove.ball1, validMove.ball2)) {
movesLeft--;
movesText.setText('Moves: ' + movesLeft);
LK.setTimeout(function () {
var matches = findMatches();
if (matches.length > 0) {
processMatches();
}
}, 300);
}
}
}
function checkGameState() {
checkLevelProgression();
if (movesLeft <= 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 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)) {
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);
// Additional penalty for wasted move
movesLeft -= 1; // Lose 1 extra move for wasted 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 3 moves
movesLeft -= 3;
movesText.setText('Moves: ' + movesLeft);
// Process any resulting matches
LK.setTimeout(function () {
dropBalls();
fillEmptySpaces();
processMatches();
}, 100);
draggedBomb = null;
}
};
// Initialize the game
initializeGrid();
// Auto move execution removed - players start making their own moves
// Play background music
LK.playMusic('Ana');
;
;
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