/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScore: 0, currentLevel: 1 }); /**** * Classes ****/ var Flower = Container.expand(function (type) { var self = Container.call(this); self.type = type; self.isMatched = false; self.isMoving = false; // Create flower asset based on type var flowerGraphics = self.attachAsset(type, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.9, scaleY: 0.9 }); // Method to handle selection/deselection visual effect self.setSelected = function (selected) { if (selected) { tween(flowerGraphics, { scaleX: 1.1, scaleY: 1.1 }, { duration: 200, easing: tween.easeOut }); } else { tween(flowerGraphics, { scaleX: 0.9, scaleY: 0.9 }, { duration: 200, easing: tween.easeOut }); } }; // Method to handle match animation self.animateMatch = function (callback) { self.isMatched = true; tween(flowerGraphics, { alpha: 0, scaleX: 0.2, scaleY: 0.2 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { if (callback) { callback(); } } }); }; // Method to move flower to a new position self.moveTo = function (newX, newY, duration, callback) { self.isMoving = true; tween(self, { x: newX, y: newY }, { duration: duration || 300, easing: tween.easeOut, onFinish: function onFinish() { self.isMoving = false; if (callback) { callback(); } } }); }; // Event handlers self.down = function (x, y, obj) { // Handle touch/click in game logic if (!self.isMatched && !self.isMoving && !gameOver) { selectFlower(self); } }; return self; }); var GameBoard = Container.expand(function (rows, cols, cellSize) { var self = Container.call(this); self.rows = rows; self.cols = cols; self.cellSize = cellSize; self.boardWidth = cols * cellSize; self.boardHeight = rows * cellSize; self.grid = []; // Create board background var boardBackground = self.attachAsset('board', { anchorX: 0.5, anchorY: 0.5, width: self.boardWidth, height: self.boardHeight }); // Initialize grid with cells self.initializeGrid = function () { // Create grid cells for (var row = 0; row < rows; row++) { self.grid[row] = []; for (var col = 0; col < cols; col++) { // Create cell background var cell = LK.getAsset('cell', { anchorX: 0.5, anchorY: 0.5, width: cellSize - 10, height: cellSize - 10 }); // Position cell cell.x = col * cellSize - self.boardWidth / 2 + cellSize / 2; cell.y = row * cellSize - self.boardHeight / 2 + cellSize / 2; self.addChild(cell); self.grid[row][col] = null; // Will hold flower references } } }; // Add a flower to the grid at specified position self.addFlower = function (flower, row, col) { if (row >= 0 && row < rows && col >= 0 && col < cols) { self.grid[row][col] = flower; flower.row = row; flower.col = col; // Position flower in grid flower.x = col * cellSize - self.boardWidth / 2 + cellSize / 2; flower.y = row * cellSize - self.boardHeight / 2 + cellSize / 2; self.addChild(flower); return true; } return false; }; // Remove a flower from the grid self.removeFlower = function (row, col) { if (row >= 0 && row < rows && col >= 0 && col < cols) { var flower = self.grid[row][col]; if (flower) { self.grid[row][col] = null; return flower; } } return null; }; // Swap two flowers in the grid self.swapFlowers = function (row1, col1, row2, col2, animate, callback) { if (row1 < 0 || row1 >= rows || col1 < 0 || col1 >= cols || row2 < 0 || row2 >= rows || col2 < 0 || col2 >= cols) { return false; } var flower1 = self.grid[row1][col1]; var flower2 = self.grid[row2][col2]; if (!flower1 || !flower2) { return false; } // Update grid references self.grid[row1][col1] = flower2; self.grid[row2][col2] = flower1; // Update flower position properties flower1.row = row2; flower1.col = col2; flower2.row = row1; flower2.col = col1; if (animate) { // Calculate target positions var pos1 = { x: col2 * cellSize - self.boardWidth / 2 + cellSize / 2, y: row2 * cellSize - self.boardHeight / 2 + cellSize / 2 }; var pos2 = { x: col1 * cellSize - self.boardWidth / 2 + cellSize / 2, y: row1 * cellSize - self.boardHeight / 2 + cellSize / 2 }; // Animate the swap flower1.moveTo(pos1.x, pos1.y, 200); flower2.moveTo(pos2.x, pos2.y, 200, callback); } else { // Instantly swap positions flower1.x = col2 * cellSize - self.boardWidth / 2 + cellSize / 2; flower1.y = row2 * cellSize - self.boardHeight / 2 + cellSize / 2; flower2.x = col1 * cellSize - self.boardWidth / 2 + cellSize / 2; flower2.y = row1 * cellSize - self.boardHeight / 2 + cellSize / 2; if (callback) { callback(); } } return true; }; // Get flower at specific grid position self.getFlower = function (row, col) { if (row >= 0 && row < rows && col >= 0 && col < cols) { return self.grid[row][col]; } return null; }; // Check if coordinates are adjacent self.areAdjacent = function (row1, col1, row2, col2) { var rowDiff = Math.abs(row1 - row2); var colDiff = Math.abs(col1 - col2); // Adjacent if exactly one coordinate differs by 1 and the other is the same return rowDiff === 1 && colDiff === 0 || rowDiff === 0 && colDiff === 1; }; return self; }); // LicoriceBall class for combo feature var LicoriceBall = Container.expand(function (row, col) { var self = Container.call(this); self.row = row; self.col = col; self.isExploded = false; // Attach a licorice ball asset (use a unique color/shape for now) var licoriceGraphics = self.attachAsset('cell', { anchorX: 0.5, anchorY: 0.5, width: 90, height: 90, color: 0x222222, tint: 0x222222 }); // Add a visual cue (e.g. a white "O" text) var licoriceText = new Text2('🍬', { size: 60, fill: 0xffffff }); licoriceText.anchor.set(0.5, 0.5); self.addChild(licoriceText); // Animate appearance licoriceGraphics.scaleX = 0.2; licoriceGraphics.scaleY = 0.2; tween(licoriceGraphics, { scaleX: 1, scaleY: 1 }, { duration: 300, easing: tween.bounceOut }); // Handle explosion on tap self.down = function (x, y, obj) { if (!self.isExploded && !isProcessingMatches && !gameOver) { self.isExploded = true; triggerLicoriceExplosion(self.row, self.col); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x4CAF50 }); /**** * Game Code ****/ // Add custom background var customBackground = LK.getAsset('customBackground', { anchorX: 0.5, anchorY: 0.5, width: 2048, height: 2732 }); customBackground.x = 2048 / 2; customBackground.y = 2732 / 2; game.addChild(customBackground); // Language system var SUPPORTED_LANGUAGES = [{ code: 'fr', name: 'Français' }, { code: 'en', name: 'English' }, { code: 'de', name: 'Deutsch' }, { code: 'ja', name: '日本語' }, { code: 'it', name: 'Italiano' }, { code: 'es', name: 'Español' }, { code: 'zh', name: '中文' }, { code: 'el', name: 'Ελληνικά' }, { code: 'ro', name: 'Română' }, { code: 'pl', name: 'Polski' }]; var TRANSLATIONS = { fr: { score: 'Score: ', moves: 'Mouvements: ', level: 'Niveau: ', reset: 'RESET', changeLanguage: 'Changer la langue', chooseLanguage: 'Choisissez votre langue', choice: 'Choisir', back: 'Retour' }, en: { score: 'Score: ', moves: 'Moves: ', level: 'Level: ', reset: 'RESET', changeLanguage: 'Change the Game Language', chooseLanguage: 'Choose your language', choice: 'Choice', back: 'Back' }, de: { score: 'Punkte: ', moves: 'Züge: ', level: 'Level: ', reset: 'RESET', changeLanguage: 'Spielsprache ändern', chooseLanguage: 'Wählen Sie Ihre Sprache', choice: 'Wählen', back: 'Zurück' }, ja: { score: 'スコア: ', moves: '手数: ', level: 'レベル: ', reset: 'リセット', changeLanguage: 'ゲーム言語を変更', chooseLanguage: '言語を選択してください', choice: '選択', back: '戻る' }, it: { score: 'Punteggio: ', moves: 'Mosse: ', level: 'Livello: ', reset: 'RESET', changeLanguage: 'Cambia lingua del gioco', chooseLanguage: 'Scegli la tua lingua', choice: 'Scegli', back: 'Indietro' }, es: { score: 'Puntuación: ', moves: 'Movimientos: ', level: 'Nivel: ', reset: 'RESET', changeLanguage: 'Cambiar idioma del juego', chooseLanguage: 'Elige tu idioma', choice: 'Elegir', back: 'Volver' }, zh: { score: '分数: ', moves: '步数: ', level: '等级: ', reset: '重置', changeLanguage: '更改游戏语言', chooseLanguage: '选择您的语言', choice: '选择', back: '返回' }, el: { score: 'Σκορ: ', moves: 'Κινήσεις: ', level: 'Επίπεδο: ', reset: 'ΕΠΑΝΑΦΟΡΑ', changeLanguage: 'Αλλαγή γλώσσας παιχνιδιού', chooseLanguage: 'Επιλέξτε τη γλώσσα σας', choice: 'Επιλογή', back: 'Πίσω' }, ro: { score: 'Scor: ', moves: 'Mișcări: ', level: 'Nivel: ', reset: 'RESET', changeLanguage: 'Schimbă limba jocului', chooseLanguage: 'Alege limba ta', choice: 'Alege', back: 'Înapoi' }, pl: { score: 'Wynik: ', moves: 'Ruchy: ', level: 'Poziom: ', reset: 'RESET', changeLanguage: 'Zmień język gry', chooseLanguage: 'Wybierz swój język', choice: 'Wybierz', back: 'Wstecz' } }; var currentLanguage = storage.language || 'fr'; var languageMenuOpen = false; var languageMenuContainer = null; function getText(key) { return TRANSLATIONS[currentLanguage][key] || TRANSLATIONS['fr'][key] || key; } // Game constants var ROWS = 7; var COLS = 7; var CELL_SIZE = 100; var FLOWER_TYPES = ['tulip', 'poppy', 'rose', 'violet', 'lily', 'jasmine', 'lilac']; var MAX_MOVES = 50; // Game state variables var gameBoard; var selectedFlower = null; var movesLeft = MAX_MOVES; var currentScore = 0; var currentLevel = storage.currentLevel || 1; var highScore = storage.highScore || 0; var remainingFlowers = ROWS * COLS; var isProcessingMatches = false; var gameOver = false; var matchCounter = 0; var matchesRequiredForNextLevel = 5; // UI elements var scoreText; var movesText; var levelText; // Initialize game function initGame() { // Create game board gameBoard = new GameBoard(ROWS, COLS, CELL_SIZE); gameBoard.x = 2048 / 2; gameBoard.y = 2732 / 2; game.addChild(gameBoard); // Initialize the grid cells gameBoard.initializeGrid(); // Populate the board with flowers populateBoard(); // Create UI createUI(); // Set initial game state movesLeft = MAX_MOVES; currentScore = 0; remainingFlowers = ROWS * COLS; gameOver = false; matchCounter = 0; matchesRequiredForNextLevel = 5 + (currentLevel - 1) * 3; // 5 for level 1, +3 for each level // Update UI updateUI(); // Play background music LK.playMusic('bgmusic'); // Play background music LK.playMusic('bgmusic'); } // Create UI elements function createUI() { // Create score text scoreText = new Text2(getText('score') + '0', { size: 60, fill: 0xFFFFFF }); scoreText.anchor.set(1, 0); // Anchored to top-right LK.gui.topRight.addChild(scoreText); // Create moves text movesText = new Text2(getText('moves') + movesLeft, { size: 60, fill: 0xFFFFFF }); movesText.anchor.set(0, 0); // Anchored to top-left // Don't place in top-left corner due to platform menu icon movesText.x = 120; LK.gui.topLeft.addChild(movesText); // Create level text levelText = new Text2(getText('level') + currentLevel + ' (' + matchCounter + '/' + matchesRequiredForNextLevel + ')', { size: 60, fill: 0xFFFFFF }); levelText.anchor.set(0.5, 0); LK.gui.top.addChild(levelText); // Create language button var languageButton = new Container(); var languageBackground = languageButton.attachAsset('cell', { anchorX: 0.5, anchorY: 0.5, width: 300, height: 60, tint: 0xFF0000 }); var languageText = new Text2(getText('changeLanguage'), { size: 35, fill: 0xFFFFFF }); languageText.anchor.set(0.5, 0.5); languageButton.addChild(languageText); languageButton.x = 0; languageButton.y = 120; LK.gui.top.addChild(languageButton); // Add event handler to language button languageButton.down = function (x, y, obj) { if (!languageMenuOpen) { showLanguageMenu(); } }; // Store reference for updates languageButton.textElement = languageText; // Create reset button var resetButton = new Container(); var resetBackground = resetButton.attachAsset('cell', { anchorX: 0.5, anchorY: 0.5, width: 120, height: 60, tint: 0xFF0000 }); var resetText = new Text2(getText('reset'), { size: 40, fill: 0xFFFFFF }); resetText.anchor.set(0.5, 0.5); resetButton.addChild(resetText); resetButton.x = -150; resetButton.y = 50; LK.gui.topRight.addChild(resetButton); // Store reference for updates resetButton.textElement = resetText; // Add event handler to reset button resetButton.down = function (x, y, obj) { // Reset level to 1 currentLevel = 1; storage.currentLevel = 1; // Reset match counter and requirements matchCounter = 0; matchesRequiredForNextLevel = 5; // Reset game state and board gameOver = true; // To stop game processing temporarily // Clear the board for (var row = 0; row < ROWS; row++) { for (var col = 0; col < COLS; col++) { var flower = gameBoard.removeFlower(row, col); if (flower) { flower.destroy(); } } } // Reset and restart the game initGame(); }; } // Update UI elements function updateUI() { scoreText.setText(getText('score') + currentScore); movesText.setText(getText('moves') + movesLeft); levelText.setText(getText('level') + currentLevel + ' (' + matchCounter + '/' + matchesRequiredForNextLevel + ')'); LK.setScore(currentScore); } // Populate the board with flowers function populateBoard() { // Create flowers and ensure no initial matches for (var row = 0; row < ROWS; row++) { for (var col = 0; col < COLS; col++) { addNewFlower(row, col); } } // Check and resolve any initial matches var initialMatches = findAllMatches(); while (initialMatches.length > 0) { // If we have initial matches, regenerate those flowers for (var i = 0; i < initialMatches.length; i++) { var matchedFlower = gameBoard.removeFlower(initialMatches[i].row, initialMatches[i].col); if (matchedFlower) { matchedFlower.destroy(); addNewFlower(initialMatches[i].row, initialMatches[i].col); } } initialMatches = findAllMatches(); } } // Add a new flower at the specified position function addNewFlower(row, col) { // Choose a random flower type that doesn't create a match var validTypes = getValidFlowerTypes(row, col); var randomType = validTypes[Math.floor(Math.random() * validTypes.length)]; // Create a new flower and add it to the board var flower = new Flower(randomType); gameBoard.addFlower(flower, row, col); return flower; } // Get valid flower types that don't create a match at position function getValidFlowerTypes(row, col) { var invalidTypes = {}; // Use a regular object instead of Set // Check horizontal matches to avoid if (col >= 2) { var flower1 = gameBoard.getFlower(row, col - 1); var flower2 = gameBoard.getFlower(row, col - 2); if (flower1 && flower2 && flower1.type === flower2.type) { invalidTypes[flower1.type] = true; // Mark type as invalid } } // Check vertical matches to avoid if (row >= 2) { var flower1 = gameBoard.getFlower(row - 1, col); var flower2 = gameBoard.getFlower(row - 2, col); if (flower1 && flower2 && flower1.type === flower2.type) { invalidTypes[flower1.type] = true; // Mark type as invalid } } // Return valid types (all types minus invalid ones) return FLOWER_TYPES.filter(function (type) { return !invalidTypes[type]; // Check if type is in invalidTypes object }); } // Handle flower selection function selectFlower(flower) { if (isProcessingMatches || gameOver) { return; } if (selectedFlower === null) { // First selection selectedFlower = flower; selectedFlower.setSelected(true); LK.getSound('swap').play(); } else if (selectedFlower === flower) { // Deselect selectedFlower.setSelected(false); selectedFlower = null; } else { // Second selection - check if adjacent if (gameBoard.areAdjacent(selectedFlower.row, selectedFlower.col, flower.row, flower.col)) { // Try to swap trySwapFlowers(selectedFlower, flower); } else { // Not adjacent, switch selection selectedFlower.setSelected(false); selectedFlower = flower; selectedFlower.setSelected(true); LK.getSound('swap').play(); } } } // Try to swap flowers and check for matches function trySwapFlowers(flower1, flower2) { // Swap flowers var row1 = flower1.row; var col1 = flower1.col; var row2 = flower2.row; var col2 = flower2.col; // Deselect first flower flower1.setSelected(false); selectedFlower = null; // Swap to check for matches gameBoard.swapFlowers(row1, col1, row2, col2, true, function () { // Check for matches after swap var matches = findAllMatches(); if (matches.length > 0) { // Valid move - process matches decreaseMoves(); processMatches(matches); } else { // Invalid move - swap back LK.getSound('invalid').play(); gameBoard.swapFlowers(row2, col2, row1, col1, true); } }); } // Decrease moves and check game over function decreaseMoves() { movesLeft--; updateUI(); // Check if game is over if (movesLeft <= 0 && remainingFlowers > 0) { endGame(false); } } // Process all matches function processMatches(matches) { if (matches.length === 0) { return; } isProcessingMatches = true; // Calculate score based on match length var matchScore = 0; var matchCounts = {}; // Group matches by type matches.forEach(function (match) { var flower = gameBoard.getFlower(match.row, match.col); if (flower) { if (!matchCounts[flower.type]) { matchCounts[flower.type] = 0; } matchCounts[flower.type]++; } }); // Calculate score based on matches // Track licorice ball spawn positions var licoriceToSpawn = []; Object.keys(matchCounts).forEach(function (type) { var count = matchCounts[type]; if (count >= 5) { matchScore += count * 15; LK.getSound('match5').play(); } else if (count === 4) { matchScore += count * 10; LK.getSound('match4').play(); // Find the 4-in-a-row and mark for licorice ball spawn // Horizontal for (var row = 0; row < ROWS; row++) { for (var col = 0; col < COLS - 3; col++) { var f1 = gameBoard.getFlower(row, col); var f2 = gameBoard.getFlower(row, col + 1); var f3 = gameBoard.getFlower(row, col + 2); var f4 = gameBoard.getFlower(row, col + 3); if (f1 && f2 && f3 && f4 && f1.type === type && f2.type === type && f3.type === type && f4.type === type) { // Place licorice ball at the second or third flower (centered) licoriceToSpawn.push({ row: row, col: col + 1 }); } } } // Vertical for (var col = 0; col < COLS; col++) { for (var row = 0; row < ROWS - 3; row++) { var f1 = gameBoard.getFlower(row, col); var f2 = gameBoard.getFlower(row + 1, col); var f3 = gameBoard.getFlower(row + 2, col); var f4 = gameBoard.getFlower(row + 3, col); if (f1 && f2 && f3 && f4 && f1.type === type && f2.type === type && f3.type === type && f4.type === type) { licoriceToSpawn.push({ row: row + 1, col: col }); } } } } else { matchScore += count * 5; LK.getSound('match3').play(); } // Count this as a successful match for level progression matchCounter++; }); // Update score currentScore += matchScore; remainingFlowers -= matches.length; // Animate matches and remove flowers var animationsCompleted = 0; matches.forEach(function (match) { var flower = gameBoard.getFlower(match.row, match.col); if (flower) { gameBoard.removeFlower(match.row, match.col); flower.animateMatch(function () { flower.destroy(); animationsCompleted++; // When all animations complete, make flowers fall and check for new matches if (animationsCompleted === matches.length) { // Spawn licorice balls if needed if (typeof licoriceToSpawn !== "undefined" && licoriceToSpawn.length > 0) { for (var i = 0; i < licoriceToSpawn.length; i++) { var pos = licoriceToSpawn[i]; // Only spawn if cell is empty (flower was matched) if (!gameBoard.getFlower(pos.row, pos.col)) { var licorice = new LicoriceBall(pos.row, pos.col); // Place licorice ball in grid (so it can be found for explosion) gameBoard.grid[pos.row][pos.col] = licorice; // Position licorice ball licorice.x = pos.col * CELL_SIZE - gameBoard.boardWidth / 2 + CELL_SIZE / 2; licorice.y = pos.row * CELL_SIZE - gameBoard.boardHeight / 2 + CELL_SIZE / 2; gameBoard.addChild(licorice); } } } updateUI(); makeFlowersFall(function () { fillEmptySpaces(function () { // Check for new matches after filling var newMatches = findAllMatches(); if (newMatches.length > 0) { processMatches(newMatches); } else { isProcessingMatches = false; // Check if player reached required match count if (matchCounter >= matchesRequiredForNextLevel) { // Level completed endGame(true); } else if (remainingFlowers <= 0) { // All flowers cleared (alternative win condition) endGame(true); } } }); }); } }); } }); } // Make flowers fall to fill empty spaces function makeFlowersFall(callback) { var animationsInProgress = 0; // Process columns bottom to top for (var col = 0; col < COLS; col++) { var emptySpaces = 0; // Process rows bottom to top for (var row = ROWS - 1; row >= 0; row--) { var flower = gameBoard.getFlower(row, col); if (!flower) { emptySpaces++; } else if (emptySpaces > 0) { // Move flower down by the number of empty spaces var newRow = row + emptySpaces; animationsInProgress++; // Update grid reference gameBoard.grid[row][col] = null; gameBoard.grid[newRow][col] = flower; // Update flower position properties flower.row = newRow; // Animate the fall var targetY = newRow * CELL_SIZE - gameBoard.boardHeight / 2 + CELL_SIZE / 2; if (typeof flower.moveTo === "function") { flower.moveTo(flower.x, targetY, 300, function () { animationsInProgress--; if (animationsInProgress === 0 && callback) { callback(); } }); } else { // Fallback for objects without moveTo (e.g. LicoriceBall) flower.y = targetY; animationsInProgress--; if (animationsInProgress === 0 && callback) { callback(); } } } } } if (animationsInProgress === 0 && callback) { callback(); } } // Fill empty spaces at the top with new flowers function fillEmptySpaces(callback) { var animationsInProgress = 0; for (var col = 0; col < COLS; col++) { // Find empty spaces at the top for (var row = 0; row < ROWS; row++) { if (!gameBoard.getFlower(row, col)) { // Create a new flower above the board var flower = new Flower(FLOWER_TYPES[Math.floor(Math.random() * FLOWER_TYPES.length)]); gameBoard.addFlower(flower, row, col); // Position it above the board flower.y -= (row + 1) * CELL_SIZE; // Animate it falling down animationsInProgress++; remainingFlowers++; var targetY = row * CELL_SIZE - gameBoard.boardHeight / 2 + CELL_SIZE / 2; flower.moveTo(flower.x, targetY, 300, function () { animationsInProgress--; if (animationsInProgress === 0 && callback) { callback(); } }); } } } if (animationsInProgress === 0 && callback) { callback(); } } // Find all matching flowers on the board function findAllMatches() { var matches = []; var visited = {}; // Check horizontal matches for (var row = 0; row < ROWS; row++) { for (var col = 0; col < COLS - 2; col++) { var flower1 = gameBoard.getFlower(row, col); var flower2 = gameBoard.getFlower(row, col + 1); var flower3 = gameBoard.getFlower(row, col + 2); if (flower1 && flower2 && flower3 && flower1.type === flower2.type && flower2.type === flower3.type) { // Add match if not already added var key1 = row + "," + col; var key2 = row + "," + (col + 1); var key3 = row + "," + (col + 2); if (!visited[key1]) { matches.push({ row: row, col: col }); visited[key1] = true; } if (!visited[key2]) { matches.push({ row: row, col: col + 1 }); visited[key2] = true; } if (!visited[key3]) { matches.push({ row: row, col: col + 2 }); visited[key3] = true; } } } } // Check vertical matches for (var row = 0; row < ROWS - 2; row++) { for (var col = 0; col < COLS; col++) { var flower1 = gameBoard.getFlower(row, col); var flower2 = gameBoard.getFlower(row + 1, col); var flower3 = gameBoard.getFlower(row + 2, col); if (flower1 && flower2 && flower3 && flower1.type === flower2.type && flower2.type === flower3.type) { // Add match if not already added var key1 = row + "," + col; var key2 = row + 1 + "," + col; var key3 = row + 2 + "," + col; if (!visited[key1]) { matches.push({ row: row, col: col }); visited[key1] = true; } if (!visited[key2]) { matches.push({ row: row + 1, col: col }); visited[key2] = true; } if (!visited[key3]) { matches.push({ row: row + 2, col: col }); visited[key3] = true; } } } } return matches; } // Check if any valid moves exist function hasValidMoves() { // Check each position for potential swaps that create matches for (var row = 0; row < ROWS; row++) { for (var col = 0; col < COLS; col++) { // Check right swap if (col < COLS - 1) { // Simulate swap right var flower1 = gameBoard.getFlower(row, col); var flower2 = gameBoard.getFlower(row, col + 1); if (flower1 && flower2) { // Swap gameBoard.swapFlowers(row, col, row, col + 1, false); // Check for matches var matches = findAllMatches(); // Swap back gameBoard.swapFlowers(row, col + 1, row, col, false); if (matches.length > 0) { return true; } } } // Check down swap if (row < ROWS - 1) { // Simulate swap down var flower1 = gameBoard.getFlower(row, col); var flower2 = gameBoard.getFlower(row + 1, col); if (flower1 && flower2) { // Swap gameBoard.swapFlowers(row, col, row + 1, col, false); // Check for matches var matches = findAllMatches(); // Swap back gameBoard.swapFlowers(row + 1, col, row, col, false); if (matches.length > 0) { return true; } } } } } return false; } // End the game function endGame(success) { gameOver = true; if (success) { // Level completed currentLevel++; storage.currentLevel = currentLevel; // Reset match counter and increase requirements for next level matchCounter = 0; matchesRequiredForNextLevel = 5 + (currentLevel - 1) * 3; // 5 for level 1, +3 for each level // Update high score if (currentScore > highScore) { highScore = currentScore; storage.highScore = highScore; } // Show you win screen LK.getSound('levelup').play(); LK.showYouWin(); } else { // Game over - ran out of moves currentLevel = 1; storage.currentLevel = currentLevel; // Update high score if (currentScore > highScore) { highScore = currentScore; storage.highScore = highScore; } // Show game over screen LK.showGameOver(); } } // Initialize the game initGame(); // Main game update loop game.update = function () { // If no valid moves exist, shuffle the board if (!isProcessingMatches && !gameOver && !hasValidMoves()) { // Shuffle the board var allFlowers = []; // Collect all flowers for (var row = 0; row < ROWS; row++) { for (var col = 0; col < COLS; col++) { var flower = gameBoard.removeFlower(row, col); if (flower) { flower.destroy(); } } } // Repopulate the board populateBoard(); } }; // Handle touch/mouse input on game board game.down = function (x, y, obj) { // This is handled in the Flower class }; game.move = function (x, y, obj) { // Handle dragging of selected flower if (selectedFlower && !isProcessingMatches && !gameOver) { // Get position in gameBoard coordinates var localPos = gameBoard.toLocal({ x: x, y: y }); // Calculate which cell the player is hovering over var hoverCol = Math.floor((localPos.x + gameBoard.boardWidth / 2) / CELL_SIZE); var hoverRow = Math.floor((localPos.y + gameBoard.boardHeight / 2) / CELL_SIZE); // Check if it's a valid cell and adjacent to the original position if (hoverRow >= 0 && hoverRow < ROWS && hoverCol >= 0 && hoverCol < COLS && gameBoard.areAdjacent(selectedFlower.row, selectedFlower.col, hoverRow, hoverCol)) { // Get the flower at the hover position var targetFlower = gameBoard.getFlower(hoverRow, hoverCol); if (targetFlower && targetFlower !== selectedFlower) { // Try to swap the flowers trySwapFlowers(selectedFlower, targetFlower); } } } }; game.up = function (x, y, obj) { // No global up handling needed }; // Show language selection menu function showLanguageMenu() { if (languageMenuOpen) return; languageMenuOpen = true; // Create language menu container languageMenuContainer = new Container(); // Create background overlay var overlay = languageMenuContainer.attachAsset('cell', { anchorX: 0.5, anchorY: 0.5, width: 2048, height: 2732, tint: 0x000000, alpha: 0.8 }); // Create menu background var menuBackground = languageMenuContainer.attachAsset('cell', { anchorX: 0.5, anchorY: 0.5, width: 1200, height: 1800, tint: 0xFFFFFF }); // Create title var titleText = new Text2(getText('chooseLanguage'), { size: 80, fill: 0x000000 }); titleText.anchor.set(0.5, 0.5); titleText.y = -700; languageMenuContainer.addChild(titleText); // Create language buttons var buttonStartY = -500; var buttonSpacing = 120; for (var i = 0; i < SUPPORTED_LANGUAGES.length; i++) { var lang = SUPPORTED_LANGUAGES[i]; var langButton = new Container(); // Button background var buttonBg = langButton.attachAsset('cell', { anchorX: 0.5, anchorY: 0.5, width: 800, height: 100, tint: currentLanguage === lang.code ? 0x00FF00 : 0x808080 }); // Language name var langName = new Text2(lang.name, { size: 50, fill: 0x000000 }); langName.anchor.set(0, 0.5); langName.x = -300; langButton.addChild(langName); // Choice button var choiceButton = new Container(); var choiceBg = choiceButton.attachAsset('cell', { anchorX: 0.5, anchorY: 0.5, width: 200, height: 80, tint: 0x0066CC }); var choiceText = new Text2(getText('choice'), { size: 40, fill: 0xFFFFFF }); choiceText.anchor.set(0.5, 0.5); choiceButton.addChild(choiceText); choiceButton.x = 250; // Add choice button event choiceButton.languageCode = lang.code; choiceButton.down = function (x, y, obj) { selectLanguage(this.languageCode); }; langButton.addChild(choiceButton); langButton.y = buttonStartY + i * buttonSpacing; languageMenuContainer.addChild(langButton); } // Create back button var backButton = new Container(); var backBg = backButton.attachAsset('cell', { anchorX: 0.5, anchorY: 0.5, width: 300, height: 100, tint: 0xFF0000 }); var backText = new Text2(getText('back'), { size: 50, fill: 0xFFFFFF }); backText.anchor.set(0.5, 0.5); backButton.addChild(backText); backButton.y = 700; backButton.down = function (x, y, obj) { hideLanguageMenu(); }; languageMenuContainer.addChild(backButton); // Position and add to game languageMenuContainer.x = 2048 / 2; languageMenuContainer.y = 2732 / 2; game.addChild(languageMenuContainer); } // Hide language menu function hideLanguageMenu() { if (languageMenuContainer) { languageMenuContainer.destroy(); languageMenuContainer = null; } languageMenuOpen = false; } // Select a language function selectLanguage(languageCode) { currentLanguage = languageCode; storage.language = languageCode; // Update all UI text updateAllUIText(); // Hide language menu hideLanguageMenu(); } // Update all UI text with current language function updateAllUIText() { // Update main UI updateUI(); // Update button texts var topContainer = LK.gui.top; for (var i = 0; i < topContainer.children.length; i++) { var child = topContainer.children[i]; if (child.textElement && child.textElement.text) { if (child.textElement.text.indexOf('Change') >= 0 || child.textElement.text.indexOf('Changer') >= 0 || child.textElement.text.indexOf('Cambiar') >= 0 || child.textElement.text.indexOf('Cambia') >= 0 || child.textElement.text.indexOf('Schimbă') >= 0 || child.textElement.text.indexOf('Zmień') >= 0 || child.textElement.text.indexOf('Αλλαγή') >= 0 || child.textElement.text.indexOf('Spielsprache') >= 0 || child.textElement.text.indexOf('更改') >= 0 || child.textElement.text.indexOf('ゲーム言語') >= 0) { child.textElement.setText(getText('changeLanguage')); } } } var topRightContainer = LK.gui.topRight; for (var i = 0; i < topRightContainer.children.length; i++) { var child = topRightContainer.children[i]; if (child.textElement && child.textElement.text) { if (child.textElement.text === 'RESET' || child.textElement.text === 'リセット' || child.textElement.text === 'ΕΠΑΝΑΦΟΡΑ' || child.textElement.text === '重置') { child.textElement.setText(getText('reset')); } } } } // Trigger the licorice explosion: remove all flowers in the column, trigger combo mode for 8 seconds function triggerLicoriceExplosion(row, col) { if (gameOver || isProcessingMatches) return; isProcessingMatches = true; // Remove all flowers in the column (except LicoriceBall itself) for (var r = 0; r < ROWS; r++) { var obj = gameBoard.getFlower(r, col); if (obj && !(obj instanceof LicoriceBall)) { gameBoard.removeFlower(r, col); if (typeof obj.animateMatch === "function") { obj.animateMatch(function () { obj.destroy(); }); } else { obj.destroy(); } remainingFlowers--; } } // Remove the LicoriceBall itself var licorice = gameBoard.getFlower(row, col); if (licorice && licorice instanceof LicoriceBall) { gameBoard.removeFlower(row, col); licorice.destroy(); } // Combo mode: for 8 seconds, after each match, auto-match adjacent flowers var comboEndTime = Date.now() + 8000; var originalProcessMatches = processMatches; var comboActive = true; // Helper to auto-match adjacent flowers after each match function comboProcessMatches(matches) { originalProcessMatches(matches); // After normal match processing, auto-match adjacent flowers if combo is still active if (comboActive && !gameOver) { LK.setTimeout(function () { var newMatches = []; // Find all adjacent pairs and add to matches for (var row = 0; row < ROWS; row++) { for (var col = 0; col < COLS; col++) { var f = gameBoard.getFlower(row, col); if (!f || f instanceof LicoriceBall) continue; // Check right var fRight = gameBoard.getFlower(row, col + 1); if (fRight && fRight.type === f.type && !(fRight instanceof LicoriceBall)) { newMatches.push({ row: row, col: col }); newMatches.push({ row: row, col: col + 1 }); } // Check down var fDown = gameBoard.getFlower(row + 1, col); if (fDown && fDown.type === f.type && !(fDown instanceof LicoriceBall)) { newMatches.push({ row: row, col: col }); newMatches.push({ row: row + 1, col: col }); } } } // Remove duplicates var seen = {}; var uniqueMatches = []; for (var i = 0; i < newMatches.length; i++) { var key = newMatches[i].row + "," + newMatches[i].col; if (!seen[key]) { uniqueMatches.push(newMatches[i]); seen[key] = true; } } if (uniqueMatches.length > 0 && comboActive) { comboProcessMatches(uniqueMatches); } else { isProcessingMatches = false; } }, 350); // Wait for match animation } else { isProcessingMatches = false; } } // Override processMatches for combo mode processMatches = comboProcessMatches; // End combo mode after 8 seconds LK.setTimeout(function () { comboActive = false; processMatches = originalProcessMatches; isProcessingMatches = false; }, 8000); // After explosion, let flowers fall and fill, then check for matches makeFlowersFall(function () { fillEmptySpaces(function () { var matches = findAllMatches(); if (matches.length > 0) { processMatches(matches); } else { isProcessingMatches = false; } }); }); }
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0,
currentLevel: 1
});
/****
* Classes
****/
var Flower = Container.expand(function (type) {
var self = Container.call(this);
self.type = type;
self.isMatched = false;
self.isMoving = false;
// Create flower asset based on type
var flowerGraphics = self.attachAsset(type, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.9,
scaleY: 0.9
});
// Method to handle selection/deselection visual effect
self.setSelected = function (selected) {
if (selected) {
tween(flowerGraphics, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 200,
easing: tween.easeOut
});
} else {
tween(flowerGraphics, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 200,
easing: tween.easeOut
});
}
};
// Method to handle match animation
self.animateMatch = function (callback) {
self.isMatched = true;
tween(flowerGraphics, {
alpha: 0,
scaleX: 0.2,
scaleY: 0.2
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
if (callback) {
callback();
}
}
});
};
// Method to move flower to a new position
self.moveTo = function (newX, newY, duration, callback) {
self.isMoving = true;
tween(self, {
x: newX,
y: newY
}, {
duration: duration || 300,
easing: tween.easeOut,
onFinish: function onFinish() {
self.isMoving = false;
if (callback) {
callback();
}
}
});
};
// Event handlers
self.down = function (x, y, obj) {
// Handle touch/click in game logic
if (!self.isMatched && !self.isMoving && !gameOver) {
selectFlower(self);
}
};
return self;
});
var GameBoard = Container.expand(function (rows, cols, cellSize) {
var self = Container.call(this);
self.rows = rows;
self.cols = cols;
self.cellSize = cellSize;
self.boardWidth = cols * cellSize;
self.boardHeight = rows * cellSize;
self.grid = [];
// Create board background
var boardBackground = self.attachAsset('board', {
anchorX: 0.5,
anchorY: 0.5,
width: self.boardWidth,
height: self.boardHeight
});
// Initialize grid with cells
self.initializeGrid = function () {
// Create grid cells
for (var row = 0; row < rows; row++) {
self.grid[row] = [];
for (var col = 0; col < cols; col++) {
// Create cell background
var cell = LK.getAsset('cell', {
anchorX: 0.5,
anchorY: 0.5,
width: cellSize - 10,
height: cellSize - 10
});
// Position cell
cell.x = col * cellSize - self.boardWidth / 2 + cellSize / 2;
cell.y = row * cellSize - self.boardHeight / 2 + cellSize / 2;
self.addChild(cell);
self.grid[row][col] = null; // Will hold flower references
}
}
};
// Add a flower to the grid at specified position
self.addFlower = function (flower, row, col) {
if (row >= 0 && row < rows && col >= 0 && col < cols) {
self.grid[row][col] = flower;
flower.row = row;
flower.col = col;
// Position flower in grid
flower.x = col * cellSize - self.boardWidth / 2 + cellSize / 2;
flower.y = row * cellSize - self.boardHeight / 2 + cellSize / 2;
self.addChild(flower);
return true;
}
return false;
};
// Remove a flower from the grid
self.removeFlower = function (row, col) {
if (row >= 0 && row < rows && col >= 0 && col < cols) {
var flower = self.grid[row][col];
if (flower) {
self.grid[row][col] = null;
return flower;
}
}
return null;
};
// Swap two flowers in the grid
self.swapFlowers = function (row1, col1, row2, col2, animate, callback) {
if (row1 < 0 || row1 >= rows || col1 < 0 || col1 >= cols || row2 < 0 || row2 >= rows || col2 < 0 || col2 >= cols) {
return false;
}
var flower1 = self.grid[row1][col1];
var flower2 = self.grid[row2][col2];
if (!flower1 || !flower2) {
return false;
}
// Update grid references
self.grid[row1][col1] = flower2;
self.grid[row2][col2] = flower1;
// Update flower position properties
flower1.row = row2;
flower1.col = col2;
flower2.row = row1;
flower2.col = col1;
if (animate) {
// Calculate target positions
var pos1 = {
x: col2 * cellSize - self.boardWidth / 2 + cellSize / 2,
y: row2 * cellSize - self.boardHeight / 2 + cellSize / 2
};
var pos2 = {
x: col1 * cellSize - self.boardWidth / 2 + cellSize / 2,
y: row1 * cellSize - self.boardHeight / 2 + cellSize / 2
};
// Animate the swap
flower1.moveTo(pos1.x, pos1.y, 200);
flower2.moveTo(pos2.x, pos2.y, 200, callback);
} else {
// Instantly swap positions
flower1.x = col2 * cellSize - self.boardWidth / 2 + cellSize / 2;
flower1.y = row2 * cellSize - self.boardHeight / 2 + cellSize / 2;
flower2.x = col1 * cellSize - self.boardWidth / 2 + cellSize / 2;
flower2.y = row1 * cellSize - self.boardHeight / 2 + cellSize / 2;
if (callback) {
callback();
}
}
return true;
};
// Get flower at specific grid position
self.getFlower = function (row, col) {
if (row >= 0 && row < rows && col >= 0 && col < cols) {
return self.grid[row][col];
}
return null;
};
// Check if coordinates are adjacent
self.areAdjacent = function (row1, col1, row2, col2) {
var rowDiff = Math.abs(row1 - row2);
var colDiff = Math.abs(col1 - col2);
// Adjacent if exactly one coordinate differs by 1 and the other is the same
return rowDiff === 1 && colDiff === 0 || rowDiff === 0 && colDiff === 1;
};
return self;
});
// LicoriceBall class for combo feature
var LicoriceBall = Container.expand(function (row, col) {
var self = Container.call(this);
self.row = row;
self.col = col;
self.isExploded = false;
// Attach a licorice ball asset (use a unique color/shape for now)
var licoriceGraphics = self.attachAsset('cell', {
anchorX: 0.5,
anchorY: 0.5,
width: 90,
height: 90,
color: 0x222222,
tint: 0x222222
});
// Add a visual cue (e.g. a white "O" text)
var licoriceText = new Text2('🍬', {
size: 60,
fill: 0xffffff
});
licoriceText.anchor.set(0.5, 0.5);
self.addChild(licoriceText);
// Animate appearance
licoriceGraphics.scaleX = 0.2;
licoriceGraphics.scaleY = 0.2;
tween(licoriceGraphics, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.bounceOut
});
// Handle explosion on tap
self.down = function (x, y, obj) {
if (!self.isExploded && !isProcessingMatches && !gameOver) {
self.isExploded = true;
triggerLicoriceExplosion(self.row, self.col);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x4CAF50
});
/****
* Game Code
****/
// Add custom background
var customBackground = LK.getAsset('customBackground', {
anchorX: 0.5,
anchorY: 0.5,
width: 2048,
height: 2732
});
customBackground.x = 2048 / 2;
customBackground.y = 2732 / 2;
game.addChild(customBackground);
// Language system
var SUPPORTED_LANGUAGES = [{
code: 'fr',
name: 'Français'
}, {
code: 'en',
name: 'English'
}, {
code: 'de',
name: 'Deutsch'
}, {
code: 'ja',
name: '日本語'
}, {
code: 'it',
name: 'Italiano'
}, {
code: 'es',
name: 'Español'
}, {
code: 'zh',
name: '中文'
}, {
code: 'el',
name: 'Ελληνικά'
}, {
code: 'ro',
name: 'Română'
}, {
code: 'pl',
name: 'Polski'
}];
var TRANSLATIONS = {
fr: {
score: 'Score: ',
moves: 'Mouvements: ',
level: 'Niveau: ',
reset: 'RESET',
changeLanguage: 'Changer la langue',
chooseLanguage: 'Choisissez votre langue',
choice: 'Choisir',
back: 'Retour'
},
en: {
score: 'Score: ',
moves: 'Moves: ',
level: 'Level: ',
reset: 'RESET',
changeLanguage: 'Change the Game Language',
chooseLanguage: 'Choose your language',
choice: 'Choice',
back: 'Back'
},
de: {
score: 'Punkte: ',
moves: 'Züge: ',
level: 'Level: ',
reset: 'RESET',
changeLanguage: 'Spielsprache ändern',
chooseLanguage: 'Wählen Sie Ihre Sprache',
choice: 'Wählen',
back: 'Zurück'
},
ja: {
score: 'スコア: ',
moves: '手数: ',
level: 'レベル: ',
reset: 'リセット',
changeLanguage: 'ゲーム言語を変更',
chooseLanguage: '言語を選択してください',
choice: '選択',
back: '戻る'
},
it: {
score: 'Punteggio: ',
moves: 'Mosse: ',
level: 'Livello: ',
reset: 'RESET',
changeLanguage: 'Cambia lingua del gioco',
chooseLanguage: 'Scegli la tua lingua',
choice: 'Scegli',
back: 'Indietro'
},
es: {
score: 'Puntuación: ',
moves: 'Movimientos: ',
level: 'Nivel: ',
reset: 'RESET',
changeLanguage: 'Cambiar idioma del juego',
chooseLanguage: 'Elige tu idioma',
choice: 'Elegir',
back: 'Volver'
},
zh: {
score: '分数: ',
moves: '步数: ',
level: '等级: ',
reset: '重置',
changeLanguage: '更改游戏语言',
chooseLanguage: '选择您的语言',
choice: '选择',
back: '返回'
},
el: {
score: 'Σκορ: ',
moves: 'Κινήσεις: ',
level: 'Επίπεδο: ',
reset: 'ΕΠΑΝΑΦΟΡΑ',
changeLanguage: 'Αλλαγή γλώσσας παιχνιδιού',
chooseLanguage: 'Επιλέξτε τη γλώσσα σας',
choice: 'Επιλογή',
back: 'Πίσω'
},
ro: {
score: 'Scor: ',
moves: 'Mișcări: ',
level: 'Nivel: ',
reset: 'RESET',
changeLanguage: 'Schimbă limba jocului',
chooseLanguage: 'Alege limba ta',
choice: 'Alege',
back: 'Înapoi'
},
pl: {
score: 'Wynik: ',
moves: 'Ruchy: ',
level: 'Poziom: ',
reset: 'RESET',
changeLanguage: 'Zmień język gry',
chooseLanguage: 'Wybierz swój język',
choice: 'Wybierz',
back: 'Wstecz'
}
};
var currentLanguage = storage.language || 'fr';
var languageMenuOpen = false;
var languageMenuContainer = null;
function getText(key) {
return TRANSLATIONS[currentLanguage][key] || TRANSLATIONS['fr'][key] || key;
}
// Game constants
var ROWS = 7;
var COLS = 7;
var CELL_SIZE = 100;
var FLOWER_TYPES = ['tulip', 'poppy', 'rose', 'violet', 'lily', 'jasmine', 'lilac'];
var MAX_MOVES = 50;
// Game state variables
var gameBoard;
var selectedFlower = null;
var movesLeft = MAX_MOVES;
var currentScore = 0;
var currentLevel = storage.currentLevel || 1;
var highScore = storage.highScore || 0;
var remainingFlowers = ROWS * COLS;
var isProcessingMatches = false;
var gameOver = false;
var matchCounter = 0;
var matchesRequiredForNextLevel = 5;
// UI elements
var scoreText;
var movesText;
var levelText;
// Initialize game
function initGame() {
// Create game board
gameBoard = new GameBoard(ROWS, COLS, CELL_SIZE);
gameBoard.x = 2048 / 2;
gameBoard.y = 2732 / 2;
game.addChild(gameBoard);
// Initialize the grid cells
gameBoard.initializeGrid();
// Populate the board with flowers
populateBoard();
// Create UI
createUI();
// Set initial game state
movesLeft = MAX_MOVES;
currentScore = 0;
remainingFlowers = ROWS * COLS;
gameOver = false;
matchCounter = 0;
matchesRequiredForNextLevel = 5 + (currentLevel - 1) * 3; // 5 for level 1, +3 for each level
// Update UI
updateUI();
// Play background music
LK.playMusic('bgmusic');
// Play background music
LK.playMusic('bgmusic');
}
// Create UI elements
function createUI() {
// Create score text
scoreText = new Text2(getText('score') + '0', {
size: 60,
fill: 0xFFFFFF
});
scoreText.anchor.set(1, 0); // Anchored to top-right
LK.gui.topRight.addChild(scoreText);
// Create moves text
movesText = new Text2(getText('moves') + movesLeft, {
size: 60,
fill: 0xFFFFFF
});
movesText.anchor.set(0, 0); // Anchored to top-left
// Don't place in top-left corner due to platform menu icon
movesText.x = 120;
LK.gui.topLeft.addChild(movesText);
// Create level text
levelText = new Text2(getText('level') + currentLevel + ' (' + matchCounter + '/' + matchesRequiredForNextLevel + ')', {
size: 60,
fill: 0xFFFFFF
});
levelText.anchor.set(0.5, 0);
LK.gui.top.addChild(levelText);
// Create language button
var languageButton = new Container();
var languageBackground = languageButton.attachAsset('cell', {
anchorX: 0.5,
anchorY: 0.5,
width: 300,
height: 60,
tint: 0xFF0000
});
var languageText = new Text2(getText('changeLanguage'), {
size: 35,
fill: 0xFFFFFF
});
languageText.anchor.set(0.5, 0.5);
languageButton.addChild(languageText);
languageButton.x = 0;
languageButton.y = 120;
LK.gui.top.addChild(languageButton);
// Add event handler to language button
languageButton.down = function (x, y, obj) {
if (!languageMenuOpen) {
showLanguageMenu();
}
};
// Store reference for updates
languageButton.textElement = languageText;
// Create reset button
var resetButton = new Container();
var resetBackground = resetButton.attachAsset('cell', {
anchorX: 0.5,
anchorY: 0.5,
width: 120,
height: 60,
tint: 0xFF0000
});
var resetText = new Text2(getText('reset'), {
size: 40,
fill: 0xFFFFFF
});
resetText.anchor.set(0.5, 0.5);
resetButton.addChild(resetText);
resetButton.x = -150;
resetButton.y = 50;
LK.gui.topRight.addChild(resetButton);
// Store reference for updates
resetButton.textElement = resetText;
// Add event handler to reset button
resetButton.down = function (x, y, obj) {
// Reset level to 1
currentLevel = 1;
storage.currentLevel = 1;
// Reset match counter and requirements
matchCounter = 0;
matchesRequiredForNextLevel = 5;
// Reset game state and board
gameOver = true; // To stop game processing temporarily
// Clear the board
for (var row = 0; row < ROWS; row++) {
for (var col = 0; col < COLS; col++) {
var flower = gameBoard.removeFlower(row, col);
if (flower) {
flower.destroy();
}
}
}
// Reset and restart the game
initGame();
};
}
// Update UI elements
function updateUI() {
scoreText.setText(getText('score') + currentScore);
movesText.setText(getText('moves') + movesLeft);
levelText.setText(getText('level') + currentLevel + ' (' + matchCounter + '/' + matchesRequiredForNextLevel + ')');
LK.setScore(currentScore);
}
// Populate the board with flowers
function populateBoard() {
// Create flowers and ensure no initial matches
for (var row = 0; row < ROWS; row++) {
for (var col = 0; col < COLS; col++) {
addNewFlower(row, col);
}
}
// Check and resolve any initial matches
var initialMatches = findAllMatches();
while (initialMatches.length > 0) {
// If we have initial matches, regenerate those flowers
for (var i = 0; i < initialMatches.length; i++) {
var matchedFlower = gameBoard.removeFlower(initialMatches[i].row, initialMatches[i].col);
if (matchedFlower) {
matchedFlower.destroy();
addNewFlower(initialMatches[i].row, initialMatches[i].col);
}
}
initialMatches = findAllMatches();
}
}
// Add a new flower at the specified position
function addNewFlower(row, col) {
// Choose a random flower type that doesn't create a match
var validTypes = getValidFlowerTypes(row, col);
var randomType = validTypes[Math.floor(Math.random() * validTypes.length)];
// Create a new flower and add it to the board
var flower = new Flower(randomType);
gameBoard.addFlower(flower, row, col);
return flower;
}
// Get valid flower types that don't create a match at position
function getValidFlowerTypes(row, col) {
var invalidTypes = {}; // Use a regular object instead of Set
// Check horizontal matches to avoid
if (col >= 2) {
var flower1 = gameBoard.getFlower(row, col - 1);
var flower2 = gameBoard.getFlower(row, col - 2);
if (flower1 && flower2 && flower1.type === flower2.type) {
invalidTypes[flower1.type] = true; // Mark type as invalid
}
}
// Check vertical matches to avoid
if (row >= 2) {
var flower1 = gameBoard.getFlower(row - 1, col);
var flower2 = gameBoard.getFlower(row - 2, col);
if (flower1 && flower2 && flower1.type === flower2.type) {
invalidTypes[flower1.type] = true; // Mark type as invalid
}
}
// Return valid types (all types minus invalid ones)
return FLOWER_TYPES.filter(function (type) {
return !invalidTypes[type]; // Check if type is in invalidTypes object
});
}
// Handle flower selection
function selectFlower(flower) {
if (isProcessingMatches || gameOver) {
return;
}
if (selectedFlower === null) {
// First selection
selectedFlower = flower;
selectedFlower.setSelected(true);
LK.getSound('swap').play();
} else if (selectedFlower === flower) {
// Deselect
selectedFlower.setSelected(false);
selectedFlower = null;
} else {
// Second selection - check if adjacent
if (gameBoard.areAdjacent(selectedFlower.row, selectedFlower.col, flower.row, flower.col)) {
// Try to swap
trySwapFlowers(selectedFlower, flower);
} else {
// Not adjacent, switch selection
selectedFlower.setSelected(false);
selectedFlower = flower;
selectedFlower.setSelected(true);
LK.getSound('swap').play();
}
}
}
// Try to swap flowers and check for matches
function trySwapFlowers(flower1, flower2) {
// Swap flowers
var row1 = flower1.row;
var col1 = flower1.col;
var row2 = flower2.row;
var col2 = flower2.col;
// Deselect first flower
flower1.setSelected(false);
selectedFlower = null;
// Swap to check for matches
gameBoard.swapFlowers(row1, col1, row2, col2, true, function () {
// Check for matches after swap
var matches = findAllMatches();
if (matches.length > 0) {
// Valid move - process matches
decreaseMoves();
processMatches(matches);
} else {
// Invalid move - swap back
LK.getSound('invalid').play();
gameBoard.swapFlowers(row2, col2, row1, col1, true);
}
});
}
// Decrease moves and check game over
function decreaseMoves() {
movesLeft--;
updateUI();
// Check if game is over
if (movesLeft <= 0 && remainingFlowers > 0) {
endGame(false);
}
}
// Process all matches
function processMatches(matches) {
if (matches.length === 0) {
return;
}
isProcessingMatches = true;
// Calculate score based on match length
var matchScore = 0;
var matchCounts = {};
// Group matches by type
matches.forEach(function (match) {
var flower = gameBoard.getFlower(match.row, match.col);
if (flower) {
if (!matchCounts[flower.type]) {
matchCounts[flower.type] = 0;
}
matchCounts[flower.type]++;
}
});
// Calculate score based on matches
// Track licorice ball spawn positions
var licoriceToSpawn = [];
Object.keys(matchCounts).forEach(function (type) {
var count = matchCounts[type];
if (count >= 5) {
matchScore += count * 15;
LK.getSound('match5').play();
} else if (count === 4) {
matchScore += count * 10;
LK.getSound('match4').play();
// Find the 4-in-a-row and mark for licorice ball spawn
// Horizontal
for (var row = 0; row < ROWS; row++) {
for (var col = 0; col < COLS - 3; col++) {
var f1 = gameBoard.getFlower(row, col);
var f2 = gameBoard.getFlower(row, col + 1);
var f3 = gameBoard.getFlower(row, col + 2);
var f4 = gameBoard.getFlower(row, col + 3);
if (f1 && f2 && f3 && f4 && f1.type === type && f2.type === type && f3.type === type && f4.type === type) {
// Place licorice ball at the second or third flower (centered)
licoriceToSpawn.push({
row: row,
col: col + 1
});
}
}
}
// Vertical
for (var col = 0; col < COLS; col++) {
for (var row = 0; row < ROWS - 3; row++) {
var f1 = gameBoard.getFlower(row, col);
var f2 = gameBoard.getFlower(row + 1, col);
var f3 = gameBoard.getFlower(row + 2, col);
var f4 = gameBoard.getFlower(row + 3, col);
if (f1 && f2 && f3 && f4 && f1.type === type && f2.type === type && f3.type === type && f4.type === type) {
licoriceToSpawn.push({
row: row + 1,
col: col
});
}
}
}
} else {
matchScore += count * 5;
LK.getSound('match3').play();
}
// Count this as a successful match for level progression
matchCounter++;
});
// Update score
currentScore += matchScore;
remainingFlowers -= matches.length;
// Animate matches and remove flowers
var animationsCompleted = 0;
matches.forEach(function (match) {
var flower = gameBoard.getFlower(match.row, match.col);
if (flower) {
gameBoard.removeFlower(match.row, match.col);
flower.animateMatch(function () {
flower.destroy();
animationsCompleted++;
// When all animations complete, make flowers fall and check for new matches
if (animationsCompleted === matches.length) {
// Spawn licorice balls if needed
if (typeof licoriceToSpawn !== "undefined" && licoriceToSpawn.length > 0) {
for (var i = 0; i < licoriceToSpawn.length; i++) {
var pos = licoriceToSpawn[i];
// Only spawn if cell is empty (flower was matched)
if (!gameBoard.getFlower(pos.row, pos.col)) {
var licorice = new LicoriceBall(pos.row, pos.col);
// Place licorice ball in grid (so it can be found for explosion)
gameBoard.grid[pos.row][pos.col] = licorice;
// Position licorice ball
licorice.x = pos.col * CELL_SIZE - gameBoard.boardWidth / 2 + CELL_SIZE / 2;
licorice.y = pos.row * CELL_SIZE - gameBoard.boardHeight / 2 + CELL_SIZE / 2;
gameBoard.addChild(licorice);
}
}
}
updateUI();
makeFlowersFall(function () {
fillEmptySpaces(function () {
// Check for new matches after filling
var newMatches = findAllMatches();
if (newMatches.length > 0) {
processMatches(newMatches);
} else {
isProcessingMatches = false;
// Check if player reached required match count
if (matchCounter >= matchesRequiredForNextLevel) {
// Level completed
endGame(true);
} else if (remainingFlowers <= 0) {
// All flowers cleared (alternative win condition)
endGame(true);
}
}
});
});
}
});
}
});
}
// Make flowers fall to fill empty spaces
function makeFlowersFall(callback) {
var animationsInProgress = 0;
// Process columns bottom to top
for (var col = 0; col < COLS; col++) {
var emptySpaces = 0;
// Process rows bottom to top
for (var row = ROWS - 1; row >= 0; row--) {
var flower = gameBoard.getFlower(row, col);
if (!flower) {
emptySpaces++;
} else if (emptySpaces > 0) {
// Move flower down by the number of empty spaces
var newRow = row + emptySpaces;
animationsInProgress++;
// Update grid reference
gameBoard.grid[row][col] = null;
gameBoard.grid[newRow][col] = flower;
// Update flower position properties
flower.row = newRow;
// Animate the fall
var targetY = newRow * CELL_SIZE - gameBoard.boardHeight / 2 + CELL_SIZE / 2;
if (typeof flower.moveTo === "function") {
flower.moveTo(flower.x, targetY, 300, function () {
animationsInProgress--;
if (animationsInProgress === 0 && callback) {
callback();
}
});
} else {
// Fallback for objects without moveTo (e.g. LicoriceBall)
flower.y = targetY;
animationsInProgress--;
if (animationsInProgress === 0 && callback) {
callback();
}
}
}
}
}
if (animationsInProgress === 0 && callback) {
callback();
}
}
// Fill empty spaces at the top with new flowers
function fillEmptySpaces(callback) {
var animationsInProgress = 0;
for (var col = 0; col < COLS; col++) {
// Find empty spaces at the top
for (var row = 0; row < ROWS; row++) {
if (!gameBoard.getFlower(row, col)) {
// Create a new flower above the board
var flower = new Flower(FLOWER_TYPES[Math.floor(Math.random() * FLOWER_TYPES.length)]);
gameBoard.addFlower(flower, row, col);
// Position it above the board
flower.y -= (row + 1) * CELL_SIZE;
// Animate it falling down
animationsInProgress++;
remainingFlowers++;
var targetY = row * CELL_SIZE - gameBoard.boardHeight / 2 + CELL_SIZE / 2;
flower.moveTo(flower.x, targetY, 300, function () {
animationsInProgress--;
if (animationsInProgress === 0 && callback) {
callback();
}
});
}
}
}
if (animationsInProgress === 0 && callback) {
callback();
}
}
// Find all matching flowers on the board
function findAllMatches() {
var matches = [];
var visited = {};
// Check horizontal matches
for (var row = 0; row < ROWS; row++) {
for (var col = 0; col < COLS - 2; col++) {
var flower1 = gameBoard.getFlower(row, col);
var flower2 = gameBoard.getFlower(row, col + 1);
var flower3 = gameBoard.getFlower(row, col + 2);
if (flower1 && flower2 && flower3 && flower1.type === flower2.type && flower2.type === flower3.type) {
// Add match if not already added
var key1 = row + "," + col;
var key2 = row + "," + (col + 1);
var key3 = row + "," + (col + 2);
if (!visited[key1]) {
matches.push({
row: row,
col: col
});
visited[key1] = true;
}
if (!visited[key2]) {
matches.push({
row: row,
col: col + 1
});
visited[key2] = true;
}
if (!visited[key3]) {
matches.push({
row: row,
col: col + 2
});
visited[key3] = true;
}
}
}
}
// Check vertical matches
for (var row = 0; row < ROWS - 2; row++) {
for (var col = 0; col < COLS; col++) {
var flower1 = gameBoard.getFlower(row, col);
var flower2 = gameBoard.getFlower(row + 1, col);
var flower3 = gameBoard.getFlower(row + 2, col);
if (flower1 && flower2 && flower3 && flower1.type === flower2.type && flower2.type === flower3.type) {
// Add match if not already added
var key1 = row + "," + col;
var key2 = row + 1 + "," + col;
var key3 = row + 2 + "," + col;
if (!visited[key1]) {
matches.push({
row: row,
col: col
});
visited[key1] = true;
}
if (!visited[key2]) {
matches.push({
row: row + 1,
col: col
});
visited[key2] = true;
}
if (!visited[key3]) {
matches.push({
row: row + 2,
col: col
});
visited[key3] = true;
}
}
}
}
return matches;
}
// Check if any valid moves exist
function hasValidMoves() {
// Check each position for potential swaps that create matches
for (var row = 0; row < ROWS; row++) {
for (var col = 0; col < COLS; col++) {
// Check right swap
if (col < COLS - 1) {
// Simulate swap right
var flower1 = gameBoard.getFlower(row, col);
var flower2 = gameBoard.getFlower(row, col + 1);
if (flower1 && flower2) {
// Swap
gameBoard.swapFlowers(row, col, row, col + 1, false);
// Check for matches
var matches = findAllMatches();
// Swap back
gameBoard.swapFlowers(row, col + 1, row, col, false);
if (matches.length > 0) {
return true;
}
}
}
// Check down swap
if (row < ROWS - 1) {
// Simulate swap down
var flower1 = gameBoard.getFlower(row, col);
var flower2 = gameBoard.getFlower(row + 1, col);
if (flower1 && flower2) {
// Swap
gameBoard.swapFlowers(row, col, row + 1, col, false);
// Check for matches
var matches = findAllMatches();
// Swap back
gameBoard.swapFlowers(row + 1, col, row, col, false);
if (matches.length > 0) {
return true;
}
}
}
}
}
return false;
}
// End the game
function endGame(success) {
gameOver = true;
if (success) {
// Level completed
currentLevel++;
storage.currentLevel = currentLevel;
// Reset match counter and increase requirements for next level
matchCounter = 0;
matchesRequiredForNextLevel = 5 + (currentLevel - 1) * 3; // 5 for level 1, +3 for each level
// Update high score
if (currentScore > highScore) {
highScore = currentScore;
storage.highScore = highScore;
}
// Show you win screen
LK.getSound('levelup').play();
LK.showYouWin();
} else {
// Game over - ran out of moves
currentLevel = 1;
storage.currentLevel = currentLevel;
// Update high score
if (currentScore > highScore) {
highScore = currentScore;
storage.highScore = highScore;
}
// Show game over screen
LK.showGameOver();
}
}
// Initialize the game
initGame();
// Main game update loop
game.update = function () {
// If no valid moves exist, shuffle the board
if (!isProcessingMatches && !gameOver && !hasValidMoves()) {
// Shuffle the board
var allFlowers = [];
// Collect all flowers
for (var row = 0; row < ROWS; row++) {
for (var col = 0; col < COLS; col++) {
var flower = gameBoard.removeFlower(row, col);
if (flower) {
flower.destroy();
}
}
}
// Repopulate the board
populateBoard();
}
};
// Handle touch/mouse input on game board
game.down = function (x, y, obj) {
// This is handled in the Flower class
};
game.move = function (x, y, obj) {
// Handle dragging of selected flower
if (selectedFlower && !isProcessingMatches && !gameOver) {
// Get position in gameBoard coordinates
var localPos = gameBoard.toLocal({
x: x,
y: y
});
// Calculate which cell the player is hovering over
var hoverCol = Math.floor((localPos.x + gameBoard.boardWidth / 2) / CELL_SIZE);
var hoverRow = Math.floor((localPos.y + gameBoard.boardHeight / 2) / CELL_SIZE);
// Check if it's a valid cell and adjacent to the original position
if (hoverRow >= 0 && hoverRow < ROWS && hoverCol >= 0 && hoverCol < COLS && gameBoard.areAdjacent(selectedFlower.row, selectedFlower.col, hoverRow, hoverCol)) {
// Get the flower at the hover position
var targetFlower = gameBoard.getFlower(hoverRow, hoverCol);
if (targetFlower && targetFlower !== selectedFlower) {
// Try to swap the flowers
trySwapFlowers(selectedFlower, targetFlower);
}
}
}
};
game.up = function (x, y, obj) {
// No global up handling needed
};
// Show language selection menu
function showLanguageMenu() {
if (languageMenuOpen) return;
languageMenuOpen = true;
// Create language menu container
languageMenuContainer = new Container();
// Create background overlay
var overlay = languageMenuContainer.attachAsset('cell', {
anchorX: 0.5,
anchorY: 0.5,
width: 2048,
height: 2732,
tint: 0x000000,
alpha: 0.8
});
// Create menu background
var menuBackground = languageMenuContainer.attachAsset('cell', {
anchorX: 0.5,
anchorY: 0.5,
width: 1200,
height: 1800,
tint: 0xFFFFFF
});
// Create title
var titleText = new Text2(getText('chooseLanguage'), {
size: 80,
fill: 0x000000
});
titleText.anchor.set(0.5, 0.5);
titleText.y = -700;
languageMenuContainer.addChild(titleText);
// Create language buttons
var buttonStartY = -500;
var buttonSpacing = 120;
for (var i = 0; i < SUPPORTED_LANGUAGES.length; i++) {
var lang = SUPPORTED_LANGUAGES[i];
var langButton = new Container();
// Button background
var buttonBg = langButton.attachAsset('cell', {
anchorX: 0.5,
anchorY: 0.5,
width: 800,
height: 100,
tint: currentLanguage === lang.code ? 0x00FF00 : 0x808080
});
// Language name
var langName = new Text2(lang.name, {
size: 50,
fill: 0x000000
});
langName.anchor.set(0, 0.5);
langName.x = -300;
langButton.addChild(langName);
// Choice button
var choiceButton = new Container();
var choiceBg = choiceButton.attachAsset('cell', {
anchorX: 0.5,
anchorY: 0.5,
width: 200,
height: 80,
tint: 0x0066CC
});
var choiceText = new Text2(getText('choice'), {
size: 40,
fill: 0xFFFFFF
});
choiceText.anchor.set(0.5, 0.5);
choiceButton.addChild(choiceText);
choiceButton.x = 250;
// Add choice button event
choiceButton.languageCode = lang.code;
choiceButton.down = function (x, y, obj) {
selectLanguage(this.languageCode);
};
langButton.addChild(choiceButton);
langButton.y = buttonStartY + i * buttonSpacing;
languageMenuContainer.addChild(langButton);
}
// Create back button
var backButton = new Container();
var backBg = backButton.attachAsset('cell', {
anchorX: 0.5,
anchorY: 0.5,
width: 300,
height: 100,
tint: 0xFF0000
});
var backText = new Text2(getText('back'), {
size: 50,
fill: 0xFFFFFF
});
backText.anchor.set(0.5, 0.5);
backButton.addChild(backText);
backButton.y = 700;
backButton.down = function (x, y, obj) {
hideLanguageMenu();
};
languageMenuContainer.addChild(backButton);
// Position and add to game
languageMenuContainer.x = 2048 / 2;
languageMenuContainer.y = 2732 / 2;
game.addChild(languageMenuContainer);
}
// Hide language menu
function hideLanguageMenu() {
if (languageMenuContainer) {
languageMenuContainer.destroy();
languageMenuContainer = null;
}
languageMenuOpen = false;
}
// Select a language
function selectLanguage(languageCode) {
currentLanguage = languageCode;
storage.language = languageCode;
// Update all UI text
updateAllUIText();
// Hide language menu
hideLanguageMenu();
}
// Update all UI text with current language
function updateAllUIText() {
// Update main UI
updateUI();
// Update button texts
var topContainer = LK.gui.top;
for (var i = 0; i < topContainer.children.length; i++) {
var child = topContainer.children[i];
if (child.textElement && child.textElement.text) {
if (child.textElement.text.indexOf('Change') >= 0 || child.textElement.text.indexOf('Changer') >= 0 || child.textElement.text.indexOf('Cambiar') >= 0 || child.textElement.text.indexOf('Cambia') >= 0 || child.textElement.text.indexOf('Schimbă') >= 0 || child.textElement.text.indexOf('Zmień') >= 0 || child.textElement.text.indexOf('Αλλαγή') >= 0 || child.textElement.text.indexOf('Spielsprache') >= 0 || child.textElement.text.indexOf('更改') >= 0 || child.textElement.text.indexOf('ゲーム言語') >= 0) {
child.textElement.setText(getText('changeLanguage'));
}
}
}
var topRightContainer = LK.gui.topRight;
for (var i = 0; i < topRightContainer.children.length; i++) {
var child = topRightContainer.children[i];
if (child.textElement && child.textElement.text) {
if (child.textElement.text === 'RESET' || child.textElement.text === 'リセット' || child.textElement.text === 'ΕΠΑΝΑΦΟΡΑ' || child.textElement.text === '重置') {
child.textElement.setText(getText('reset'));
}
}
}
}
// Trigger the licorice explosion: remove all flowers in the column, trigger combo mode for 8 seconds
function triggerLicoriceExplosion(row, col) {
if (gameOver || isProcessingMatches) return;
isProcessingMatches = true;
// Remove all flowers in the column (except LicoriceBall itself)
for (var r = 0; r < ROWS; r++) {
var obj = gameBoard.getFlower(r, col);
if (obj && !(obj instanceof LicoriceBall)) {
gameBoard.removeFlower(r, col);
if (typeof obj.animateMatch === "function") {
obj.animateMatch(function () {
obj.destroy();
});
} else {
obj.destroy();
}
remainingFlowers--;
}
}
// Remove the LicoriceBall itself
var licorice = gameBoard.getFlower(row, col);
if (licorice && licorice instanceof LicoriceBall) {
gameBoard.removeFlower(row, col);
licorice.destroy();
}
// Combo mode: for 8 seconds, after each match, auto-match adjacent flowers
var comboEndTime = Date.now() + 8000;
var originalProcessMatches = processMatches;
var comboActive = true;
// Helper to auto-match adjacent flowers after each match
function comboProcessMatches(matches) {
originalProcessMatches(matches);
// After normal match processing, auto-match adjacent flowers if combo is still active
if (comboActive && !gameOver) {
LK.setTimeout(function () {
var newMatches = [];
// Find all adjacent pairs and add to matches
for (var row = 0; row < ROWS; row++) {
for (var col = 0; col < COLS; col++) {
var f = gameBoard.getFlower(row, col);
if (!f || f instanceof LicoriceBall) continue;
// Check right
var fRight = gameBoard.getFlower(row, col + 1);
if (fRight && fRight.type === f.type && !(fRight instanceof LicoriceBall)) {
newMatches.push({
row: row,
col: col
});
newMatches.push({
row: row,
col: col + 1
});
}
// Check down
var fDown = gameBoard.getFlower(row + 1, col);
if (fDown && fDown.type === f.type && !(fDown instanceof LicoriceBall)) {
newMatches.push({
row: row,
col: col
});
newMatches.push({
row: row + 1,
col: col
});
}
}
}
// Remove duplicates
var seen = {};
var uniqueMatches = [];
for (var i = 0; i < newMatches.length; i++) {
var key = newMatches[i].row + "," + newMatches[i].col;
if (!seen[key]) {
uniqueMatches.push(newMatches[i]);
seen[key] = true;
}
}
if (uniqueMatches.length > 0 && comboActive) {
comboProcessMatches(uniqueMatches);
} else {
isProcessingMatches = false;
}
}, 350); // Wait for match animation
} else {
isProcessingMatches = false;
}
}
// Override processMatches for combo mode
processMatches = comboProcessMatches;
// End combo mode after 8 seconds
LK.setTimeout(function () {
comboActive = false;
processMatches = originalProcessMatches;
isProcessingMatches = false;
}, 8000);
// After explosion, let flowers fall and fill, then check for matches
makeFlowersFall(function () {
fillEmptySpaces(function () {
var matches = findAllMatches();
if (matches.length > 0) {
processMatches(matches);
} else {
isProcessingMatches = false;
}
});
});
}