User prompt
Please fix the bug: 'Timeout.tick error: Promise is not a constructor' in or related to this line: 'var popPromise = new Promise(function (resolve) {' Line Number: 554
User prompt
Please fix the bug: 'Timeout.tick error: Cannot read properties of undefined (reading 'vars')' in or related to this line: 'return popTween.vars.onFinish = resolve;' Line Number: 555
User prompt
Please fix the bug: '_tween is not defined' in or related to this line: '_tween.stop(self, {' Line Number: 132 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: '_tween is not defined' in or related to this line: '_tween.stop(self, {' Line Number: 132 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'Uncaught ReferenceError: isDropping is not defined' in or related to this line: 'if (game && typeof game.handleCandyClick === 'function' && !isSwapping && !isDropping) {' Line Number: 178
Code edit (1 edits merged)
Please save this source code
User prompt
sistem umrumda değil sadece burada iyi oyun yapmak için nasıl kd yazmak gerek o önemli
User prompt
complete the game
User prompt
Candy Match Adventure
Initial prompt
Ah, anladım! Candy Crush Saga gibi bir eşleştirme oyunu istiyorsunuz. İşte bu tür bir oyun için detaylı bir istem (prompt): OYUN İSTEMİ: ŞEKER EŞLEŞTİRME MACERASI 1. Oyun Adı: Şeker Eşleştirme Macerası (veya Tatlı Patlama, Şeker Dünyası, Renkli Dokunuş) 2. Oyun Konsepti ve Teması: Konsept: Oyuncuların renkli şekerleri bitişik olanlarla yer değiştirerek aynı renkten en az üç tanesini yatay veya dikey olarak yan yana getirmeye çalıştığı, klasik bir "match-3" (üçünü eşleştir) bulmaca oyunu. Eşleşen şekerler patlar ve yerlerine yukarıdan yenileri düşer, potansiyel zincirleme reaksiyonlar yaratır. Tema: Canlı, parlak renkli şekerler, çikolatalar, jöleler ve diğer tatlılar. Arka planlar genellikle tatlı temalı manzaralar (Şeker Ormanı, Çikolata Nehri, Dondurma Dağları vb.) olabilir. Neşeli ve bağımlılık yapıcı bir atmosfer. 3. Hedef Kitle: Geniş kitle, özellikle mobil platformlardaki casual (gündelik) oyuncular. Her yaş grubuna hitap edebilir, öğrenmesi kolaydır. 4. Platform: Mobil (iOS, Android öncelikli), potansiyel olarak Web veya PC. 5. Temel Oynanış Döngüsü: Seviye Başlangıcı: Oyuncuya bir oyun alanı (ızgara), belirli sayıda hamle veya bazen süre sınırı ve bir veya daha fazla hedef sunulur. Yer Değiştirme: Oyuncu, bitişik iki şekeri (yatay veya dikey) yer değiştirir. Bu değiştirme işlemi, en az bir üçlü eşleşme (aynı renkten 3 veya daha fazla şekerin yan yana gelmesi) oluşturuyorsa geçerlidir. Eşleşme ve Patlama: Geçerli bir eşleşme oluştuğunda, eşleşen şekerler oyun alanından kaybolur (patlar, yok olur). Şelale (Cascade): Patlayan şekerlerin üzerindeki şekerler boşalan yerlere doğru aşağı kayar. Izgaranın en üstünden yeni şekerler düşerek boşlukları doldurur. Zincirleme Reaksiyonlar: Düşen şekerler yeni eşleşmeler oluşturabilir. Bu olursa, onlar da patlar ve süreç yeni eşleşme oluşmayana kadar devam eder. Zincirleme reaksiyonlar genellikle daha fazla puan kazandırır. Hedef Takibi: Oyuncu, verilen hamle/süre sınırı içinde seviyenin hedeflerini tamamlamaya çalışır. Seviye Sonu: Hedefler tamamlanırsa seviye kazanılır. Hamle/süre biterse ve hedefler tamamlanmadıysa seviye kaybedilir. 6. Oyun Mekanikleri ve Öğeleri: Oyun Alanı (Izgara): Genellikle kare veya dikdörtgen şeklinde, çeşitli boyutlarda (örn: 7x7, 8x8, 9x9) hücrelerden oluşan alan. Bazı seviyelerde ızgara şekli düzensiz olabilir veya boşluklar içerebilir. Normal Şekerler: 5-6 farklı renkte ve şekilde temel şekerler. Bunlar eşleştirilerek patlatılır. Özel Şekerler (Güçlendiriciler): Daha fazla sayıda şekeri (4'lü, 5'li, L veya T şeklinde) eşleştirerek oluşturulur: Çizgili Şeker (4'lü eşleşme): Oluşturulduğu yöne (yatay veya dikey) bağlı olarak tüm bir satırı veya sütunu temizler. Paketli Şeker (L veya T şeklinde 5'li eşleşme): Patladığında etrafındaki 3x3'lük bir alanı temizler, genellikle iki kez patlar. Renk Bombası (Düz 5'li eşleşme): Herhangi bir renkteki şekerle yer değiştirildiğinde, o renkteki tüm şekerleri oyun alanından temizler. Özel Şeker Kombinasyonları: İki özel şekerin yan yana getirilip yer değiştirilmesiyle çok daha güçlü etkiler yaratılır (örn: Çizgili + Paketli, Renk Bombası + Çizgili, iki Renk Bombası vb.). Hedef Türleri: Puan Hedefi: Belirli bir puana ulaşmak. Jöle Temizleme: Izgaradaki belirli karelerin üzerindeki jöleleri, o karelerdeki şekerleri eşleştirerek temizlemek. Malzeme Düşürme: Belirli malzemeleri (örn: kiraz, fındık) ızgaranın en alt sırasına kadar indirmek. Belirli Şekerleri Toplama: Belirli renk veya türdeki şekerlerden istenen sayıda toplamak. Engelleri Yok Etme: Çikolata, buz, kilit gibi engelleri yok etmek. Engeller: Oyunu zorlaştıran öğeler: Jöle/Çift Jöle: Üzerindeki şekeri bir veya iki kez eşleştirerek temizlenir. Buz: Üzerindeki şekeri eşleştirerek kırılır. Kilit/Zincir: Şekerin hareket etmesini engeller, yanındaki eşleşmeyle kırılır. Çikolata: Her hamlede yayılabilir, yanındaki eşleşmeyle yok edilir. Şekerleme Bombası: Belirli sayıda hamle içinde eşleştirilmezse patlar ve seviye kaybedilir. Ve daha birçok yaratıcı engel... Hamle/Süre Sınırı: Seviyelerin zorluğunu belirleyen ana kısıtlamalar. Puanlama ve Yıldızlar: Seviye sonunda performansa göre (genellikle puana göre) 1, 2 veya 3 yıldız kazanılır. Can Sistemi: Oyuncuların sınırlı sayıda canı olur. Bir seviyeyi geçemeyince bir can kaybedilir. Canlar zamanla dolar veya satın alınabilir/arkadaşlardan istenebilir. Oyun İçi Güçlendiriciler (Boosters): Seviyeye başlamadan önce veya seviye sırasında kullanılabilen, oyunu kolaylaştıran yardımcılar (örn: ekstra hamle, başlangıçta özel şekerler, belirli bir şekeri yok etme aracı). 7. Görsel Stil ve Ses: Görsel: Çok parlak, canlı, "lezzetli" görünen şeker tasarımları. Akıcı animasyonlar (şekerlerin düşmesi, patlaması, özel efektler). Çekici arka planlar. Net ve anlaşılır arayüz. Ses: Neşeli, akılda kalıcı arka plan müziği. Tatmin edici patlama, eşleşme, özel şeker aktifleşme ses efektleri. Başarı ve başarısızlık için belirgin sesli bildirimler. 8. İlerleme Sistemi: Oyuncular genellikle bir harita üzerinde ilerlerler, her durak bir seviyeyi temsil eder. Harita teması oyunun şekerleme dünyasına uygun olmalıdır. Belirli seviyelerde yeni mekanikler, şeker türleri veya engeller tanıtılır. Zorlu "boss" seviyeleri veya belirli aralıklarla farklı türde meydan okumalar eklenebilir. 9. Monetizasyon (İsteğe Bağlı): Can satın alma. Oyun içi güçlendirici (booster) satın alma. Ekstra hamle satın alma (seviye kaybedilmek üzereyken). Kozmetik öğeler (farklı piyonlar, harita görünümleri vb. - daha az yaygın). Reklam izleyerek ödül kazanma (can, booster vb.). Bu istem, popüler "match-3" şeker patlatma oyunlarının temel yapısını ve özelliklerini kapsamaktadır. Geliştirme ekibi bu temeli alıp kendi yaratıcı dokunuşlarını ekleyebilir. Başarılar!
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { level: 1, score: 0, highScore: 0 }); /**** * Classes ****/ var Candy = Container.expand(function (type, row, col) { var self = Container.call(this); self.type = type || 'redCandy'; self.row = row || 0; self.col = col || 0; self.isSpecial = false; // Flag indicating if this candy is special (e.g., striped, wrapped) // Visual representation var visual = self.attachAsset(self.type, { anchorX: 0.5, anchorY: 0.5, alpha: 1 }); // Selection overlay (hidden by default) var overlay = self.attachAsset('selectedOverlay', { anchorX: 0.5, anchorY: 0.5, alpha: 0, visible: false // Start invisible }); // Marker for special candies (optional, initially hidden) var specialMarker = null; self.setSpecial = function (special) { self.isSpecial = special; // Add visual distinction for special candies if needed if (special) { if (!specialMarker) { // Example: Add a marker instead of scaling/pulsing the base visual specialMarker = self.attachAsset('specialMarkerCandy', { anchorX: 0.5, anchorY: 0.5, alpha: 1 // Make it visible }); } // Optional: Pulse animation for special marker tween.stop(specialMarker); // Stop previous tweens on marker specialMarker.alpha = 0.6; tween(specialMarker, { alpha: 1.0 }, { duration: 800, easing: tween.easeInOut, yoyo: true, // Go back and forth loop: true // Repeat indefinitely }); } else { if (specialMarker) { tween.stop(specialMarker); // Stop animation specialMarker.destroy(); specialMarker = null; } } }; self.select = function () { overlay.visible = true; tween.stop(overlay, { alpha: true }); // Stop previous animation tween(overlay, { alpha: 0.4 }, { duration: 150 }); }; self.deselect = function () { tween.stop(overlay, { alpha: true }); // Stop previous animation tween(overlay, { alpha: 0 }, { duration: 150, onFinish: function onFinish() { overlay.visible = false; // Hide after fade out } }); }; self.moveTo = function (newRow, newCol, animate, delay, onComplete) { self.row = newRow; self.col = newCol; var newX = CELL_SIZE * self.col + CELL_SIZE / 2; var newY = CELL_SIZE * self.row + CELL_SIZE / 2; if (animate) { tween.stop(self, { x: true, y: true }); // Stop previous movement tween(self, { x: newX, y: newY }, { duration: 300, // Consistent duration delay: delay || 0, easing: tween.easeOutQuad, // Smooth easing onFinish: onComplete }); } else { tween.stop(self, { x: true, y: true }); // Ensure any running tween is stopped self.x = newX; self.y = newY; if (onComplete) { onComplete(); } } }; // Pop animation - Returns the tween instance self.pop = function (delay, onComplete) { tween.stop(self, { alpha: true, scaleX: true, scaleY: true }); // Stop other tweens return tween(self, { alpha: 0, scaleX: 0.1, // Shrink down scaleY: 0.1 }, { duration: 250, delay: delay || 0, easing: tween.easeInQuad, // Ease in for disappearance onFinish: function onFinish() { // Note: Actual destruction and removal from arrays happen elsewhere if (onComplete) { onComplete(); } } }); }; // Override destroy to clean up marker tween var baseDestroy = self.destroy; self.destroy = function () { if (specialMarker) { tween.stop(specialMarker); } baseDestroy.call(self); // Call original destroy }; self.down = function (x, y, obj) { // Delegate to the main game click handler if (game && typeof game.handleCandyClick === 'function' && !isSwapping && !isDropping) { game.handleCandyClick(self); } }; return self; }); var GameBoard = Container.expand(function (rows, cols) { var self = Container.call(this); self.rows = rows || 8; self.cols = cols || 8; // Background board var boardVisual = self.attachAsset('gameBoard', { anchorX: 0.5, anchorY: 0.5 }); boardVisual.width = CELL_SIZE * self.cols; // Match width to grid boardVisual.height = CELL_SIZE * self.rows; // Match height to grid // Cell backgrounds self.cells = []; for (var row = 0; row < self.rows; row++) { self.cells[row] = []; // Initialize row array for (var col = 0; col < self.cols; col++) { var cell = self.attachAsset('cellBackground', { anchorX: 0.5, anchorY: 0.5, x: col * CELL_SIZE + CELL_SIZE / 2, y: row * CELL_SIZE + CELL_SIZE / 2 }); self.cells[row][col] = cell; // Store cell reference if needed later } } return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 // Set background color to black }); /**** * Game Code ****/ var popPromises = []; // Initialize popPromises array to store promises for animations function handleCompletion(actions, callback) { var remaining = actions.length; if (remaining === 0) { callback(); } actions.forEach(function (action) { action.then(function () { remaining--; if (remaining === 0) { callback(); } }); }); } function resolveDropsAndFills() { handleDrops(); } function executeDoubleStripedEffect(centerCandy) { isProcessing = true; var targetRow = centerCandy.row; var targetCol = centerCandy.col; var candiesToDestroy = []; for (var c = 0; c < BOARD_SIZE; c++) { var _board$targetRow; var candy = (_board$targetRow = board[targetRow]) === null || _board$targetRow === void 0 ? void 0 : _board$targetRow[c]; if (candy && candiesToDestroy.indexOf(candy) === -1) { candiesToDestroy.push(candy); board[targetRow][c] = null; } } // Game state variables var popPromises = []; // Initialize popPromises array to store promises for animations for (var r = 0; r < BOARD_SIZE; r++) { var _board$r; var candy = (_board$r = board[r]) === null || _board$r === void 0 ? void 0 : _board$r[targetCol]; if (candy && candiesToDestroy.indexOf(candy) === -1) { candiesToDestroy.push(candy); board[r][targetCol] = null; } } // Play sound and update score LK.getSound('special').play(); score += candiesToDestroy.length * 20; updateUI(); // Prepare animations var actions = []; candiesToDestroy.forEach(function (candy) { var delay = (Math.abs(candy.row - targetRow) + Math.abs(candy.col - targetCol)) * 35; actions.push({ then: function then(resolve) { return candy.pop(delay, resolve); } }); }); // Wait for animations to complete handleCompletion(actions, function () { candiesToDestroy.forEach(function (c) { return c.destroy(); }); resolveDropsAndFills(); }); } function executeColorBombEffect(bombCandy, targetType) { isProcessing = true; // Lock the board // Find all candies of the targetType var candiesToDestroyByBomb = []; candies.forEach(function (c) { if (c.type === targetType && !c.isSpecial) { candiesToDestroyByBomb.push(c); } }); // Include the bombCandy itself candiesToDestroyByBomb.push(bombCandy); // Update board model candiesToDestroyByBomb.forEach(function (candy) { board[candy.row][candy.col] = null; }); // Prepare animations candiesToDestroyByBomb.forEach(function (candy, index) { popPromises.push({ then: function then(resolve) { candy.pop(index * 30, function () { candy.destroy(); resolve(); }); } }); }); // Play sound and update score LK.getSound('special').play(); score += candiesToDestroyByBomb.length * 10; updateUI(); // Wait for animations to complete (function (promises, callback) { var remaining = promises.length; if (remaining === 0) { callback(); } promises.forEach(function (promise) { promise.then(function () { remaining--; if (remaining === 0) { callback(); } }); }); })(popPromises, function () { // Cleanup and proceed to drop/fill candiesToDestroyByBomb.forEach(function (candy) { var index = candies.indexOf(candy); if (index > -1) { candies.splice(index, 1); } }); resolveDropsAndFills(); }); } function activateSpecialCandy(candy) { switch (candy.type) { case 'colorBomb': // Clear all candies of the color it was swapped with var targetType = candy.swappedWithType; // Assume swappedWithType is set during swap candies.forEach(function (c) { if (c.type === targetType) { popPromises.push({ then: function then(resolve) { c.pop(0, function () { c.destroy(); resolve(); }); } }); } }); break; case 'stripedH': // Clear entire row for (var col = 0; col < BOARD_SIZE; col++) { var rowCandy = board[candy.row][col]; if (rowCandy) { popPromises.push({ then: function then(resolve) { rowCandy.pop(col * 50, function () { // Add delay based on column index rowCandy.destroy(); resolve(); }); } }); } } break; case 'stripedV': // Clear entire column for (var row = 0; row < BOARD_SIZE; row++) { var colCandy = board[row][candy.col]; if (colCandy) { colCandy.pop(row * 50, function () { // Add delay based on row index colCandy.destroy(); }); } } break; default: break; } } // Sound for special candy creation/activation // Keep as is for selection // Example: Could be a different shape/texture // Renamed specialCandy shape slightly for clarity // Assets definition looks good, assuming LK.init.shape/sound works as expected. // Constants var BOARD_SIZE = 8; var CELL_SIZE = 200; // Increase size of each cell for better visibility var CANDY_TYPES = ['redCandy', 'yellowCandy', 'greenCandy', 'blueCandy', 'purpleCandy', 'orangeCandy']; var NUM_CANDY_TYPES = CANDY_TYPES.length; // Game state variables var board; // 2D array holding Candy objects or null var candies = []; // Flat list of all active Candy objects on the board var selectedCandy = null; var isSwapping = false; // True during the swap animation var isProcessing = false; // General flag for when board is resolving (matches, drops, fills) var isDropping = false; // Flag to indicate if candies are currently dropping var moves = 0; var score = 0; var targetScore = 0; var level = storage.level || 1; var highScore = storage.highScore || 0; // Game elements var gameBoardContainer; // Holds the visual board and candies var scoreTxt, movesTxt, levelTxt; // --- Core Game Logic Functions --- function setupGame() { // Create and center the game board container var background = LK.getAsset('gameBoard', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, scaleX: 2048 / 100, scaleY: 2732 / 100 }); game.addChild(background); gameBoardContainer = new GameBoard(BOARD_SIZE, BOARD_SIZE); // Center the board (assuming 2048x2732 is the stage size) gameBoardContainer.x = (2048 - gameBoardContainer.width) / 2 + 400; // Shift right by 400 pixels gameBoardContainer.y = (2732 - gameBoardContainer.height) / 2 + 200; // Shift down by 200 pixels game.addChild(gameBoardContainer); // Setup UI elements scoreTxt = new Text2('Score: 0', { size: 70, fill: 0xFFFFFF, align: 'right' }); scoreTxt.anchor.set(1, 0); // Anchor top-right LK.gui.topRight.addChild(scoreTxt); scoreTxt.x = -20; // Add some padding from the edge movesTxt = new Text2('Moves: 0', { size: 70, fill: 0xFFFFFF, align: 'left' }); movesTxt.anchor.set(0, 0); // Anchor top-left LK.gui.topLeft.addChild(movesTxt); movesTxt.x = 20; // Add some padding from the edge levelTxt = new Text2('Level: 1', { size: 70, fill: 0xFFFFFF }); levelTxt.anchor.set(0.5, 0); // Anchor top-center LK.gui.top.addChild(levelTxt); // Add "Glaud" text in small orange at the bottom center var glaudTxt = new Text2('Glaud', { size: 40, fill: 0xFF8C00 // Orange color }); glaudTxt.anchor.set(0.5, 1); // Anchor bottom-center LK.gui.bottom.addChild(glaudTxt); // Initialize the first level initializeLevel(level); // Start background music LK.playMusic('bgmusic', { loop: true, fade: { start: 0, end: 0.5, duration: 1000 } }); // Attach global click listener (if Candy.down isn't sufficient) // game.down = handleGameClick; // Use this if clicking empty cells should deselect } function initializeLevel(levelNum) { level = levelNum; score = 0; moves = 20 + (level - 1) * 2; // Example: Increase moves per level targetScore = 1000 * level; // Example: Increase target score per level // Add additional objectives like clearing jellies or bringing ingredients down var objectives = { clearJellies: Math.floor(level / 2), bringIngredients: Math.floor(level / 3) }; selectedCandy = null; isSwapping = false; isProcessing = false; updateUI(); initializeBoard(); } function initializeBoard() { // Clear existing candies visually and from arrays for (var i = 0; i < candies.length; i++) { candies[i].destroy(); } candies = []; board = []; // Create the board array and fill with candies for (var row = 0; row < BOARD_SIZE; row++) { board[row] = []; for (var col = 0; col < BOARD_SIZE; col++) { board[row][col] = null; // Ensure initialized to null // Generate candy ensuring no initial matches var attempts = 0; do { var type = getRandomCandyType(); // Temporarily place to check for matches var tempCandy = { type: type }; // Lightweight check board[row][col] = tempCandy; attempts++; if (attempts > 50) { // Safety break for rare impossible scenarios console.warn("Struggling to place initial candy without match at", row, col); break; } } while (checkMatchAt(row, col)); // If check passed or safety break, create the real candy var candy = new Candy(type, row, col); candy.moveTo(row, col, false); // Place instantly gameBoardContainer.addChild(candy); board[row][col] = candy; // Place real candy in board candies.push(candy); } } // Final check: Ensure there are possible moves if (!hasPossibleMoves()) { console.log("No initial moves, reshuffling..."); // Use LK.setTimeout to allow the initial draw frame to happen first LK.setTimeout(reshuffleBoard, 100); } } // Helper to check for matches *only* at a specific new candy's location function checkMatchAt(row, col) { var candy = board[row][col]; if (!candy) { return false; } var type = candy.type; // Check Horizontal var count = 1; if (col > 0 && board[row][col - 1] && board[row][col - 1].type === type) { count++; } if (col > 1 && board[row][col - 2] && board[row][col - 2].type === type) { count++; } if (count >= 3) { return true; } // Check Vertical count = 1; if (row > 0 && board[row - 1][col] && board[row - 1][col].type === type) { count++; } if (row > 1 && board[row - 2][col] && board[row - 2][col].type === type) { count++; } if (count >= 3) { return true; } return false; } function getRandomCandyType() { var typesAvailable = Math.min(NUM_CANDY_TYPES, 3 + Math.floor(level / 3)); // Slower difficulty ramp return CANDY_TYPES[Math.floor(Math.random() * typesAvailable)]; } function updateUI() { scoreTxt.setText('Score: ' + score); movesTxt.setText('Moves: ' + moves); levelTxt.setText('Level: ' + level); } // Renamed from handleCandyClick to be clearer game.handleCandyClick = function (candy) { if (isProcessing || isSwapping || isDropping) { return; } // Ignore clicks during processing or animations if (selectedCandy === null) { selectedCandy = candy; // Highlight the cell background gameBoardContainer.cells[candy.row][candy.col].tint = 0xffff00; // Highlight with yellow } else { // Second selection if (selectedCandy === candy) { // Deselect if same candy clicked // Revert cell background tint gameBoardContainer.cells[selectedCandy.row][selectedCandy.col].tint = 0x704f3d; // Original color gameBoardContainer.cells[selectedCandy.row][selectedCandy.col].tint = 0x704f3d; // Revert tint selectedCandy = null; } else { // Check if candies are adjacent var rowDiff = Math.abs(selectedCandy.row - candy.row); var colDiff = Math.abs(selectedCandy.col - candy.col); if (rowDiff === 1 && colDiff === 0 || rowDiff === 0 && colDiff === 1) { // Adjacent: Initiate swap selectedCandy.deselect(); // Deselect the first one visually swapCandies(selectedCandy, candy); selectedCandy = null; // Clear selection after initiating swap } else { // Not adjacent: Change selection to the new candy // Revert previous selection's cell background tint gameBoardContainer.cells[selectedCandy.row][selectedCandy.col].tint = 0x704f3d; // Original color selectedCandy = candy; // Highlight new selection's cell background gameBoardContainer.cells[candy.row][candy.col].tint = 0xffff00; // Highlight with yellow LK.getSound('invalid').play(); // Sound for invalid selection change } } } }; function swapCandies(candy1, candy2) { if (isSwapping || isProcessing) { return; } // Prevent overlapping swaps isSwapping = true; // Store original positions for potential revert var candy1Row = candy1.row; var candy1Col = candy1.col; var candy2Row = candy2.row; var candy2Col = candy2.col; // Track the type of candy a color bomb is swapped with if (candy1.type === 'colorBomb') { candy1.swappedWithType = candy2.type; } else if (candy2.type === 'colorBomb') { candy2.swappedWithType = candy1.type; } // Update board model immediately board[candy1Row][candy1Col] = candy2; board[candy2Row][candy2Col] = candy1; // Update candy objects' internal row/col candy1.row = candy2Row; candy1.col = candy2Col; candy2.row = candy1Row; candy2.col = candy1Col; // Animate the swap visually LK.getSound('swap').play(); var tween1 = candy1.moveTo(candy2Row, candy2Col, true); var tween2 = candy2.moveTo(candy1Row, candy1Col, true); // After animation finishes (wait for the longer of the two, though they should be same duration) // Using a small timeout is simpler than tracking both tweens LK.setTimeout(function () { // Check if the swap resulted in a match var matchFound = checkMatchAt(candy1.row, candy1.col) || checkMatchAt(candy2.row, candy2.col) || findMatches().length > 0; // More robust check // Check for color bomb swap if (candy1.type === 'colorBomb' && !candy2.isSpecial) { executeColorBombEffect(candy1, candy2.type); isSwapping = false; return; } else if (candy2.type === 'colorBomb' && !candy1.isSpecial) { executeColorBombEffect(candy2, candy1.type); isSwapping = false; return; } if (matchFound) { // Valid swap, deduct move and process matches moves--; updateUI(); isSwapping = false; // Allow processing now processBoard(); // Start the match -> drop -> fill cycle // Check game state later, after processing finishes } else if ((candy1.type === 'stripedH' || candy1.type === 'stripedV') && (candy2.type === 'stripedH' || candy2.type === 'stripedV')) { executeDoubleStripedEffect(candy1); isSwapping = false; return; } else { // Invalid swap, animate back LK.getSound('invalid').play(); // Update board model back board[candy1Row][candy1Col] = candy1; board[candy2Row][candy2Col] = candy2; // Update candy objects' internal row/col back candy1.row = candy1Row; candy1.col = candy1Col; candy2.row = candy2Row; candy2.col = candy2Col; // Animate back candy1.moveTo(candy1Row, candy1Col, true); candy2.moveTo(candy2Row, candy2Col, true); // After swap back animation finishes LK.setTimeout(function () { isSwapping = false; // Ready for next input gameBoardContainer.cells[candy1.row][candy1.col].tint = 0x704f3d; // Revert tint gameBoardContainer.cells[candy2.row][candy2.col].tint = 0x704f3d; // Revert tint selectedCandy = null; }, 310); // Slightly longer than moveTo duration } }, 310); // Wait for swap animation to mostly complete } // Main processing loop trigger function processBoard() { if (isProcessing) { return; } // Avoid re-entry isProcessing = true; handleMatches(); } // Step 1: Find and handle matches function handleMatches() { var matches = findMatches(); if (matches.length > 0) { LK.getSound('match').play(); var pointsEarned = 0; var specialCandiesToCreate = []; // Store {row, col, type} for special creation // Mark candies for removal and calculate score/special var candiesToRemove = []; matches.forEach(function (match) { var matchSize = match.length; var pointsPerCandy = 10 * matchSize; // Bonus for longer matches // Decide if a special candy should be created from this match var createSpecial = null; // { type: 'stripedH', row: r, col: c } etc. if (matchSize >= 5) { // 5-in-a-row -> Color Bomb (example) createSpecial = { type: 'colorBomb', row: match.row, col: match.col + (match.horizontal ? Math.floor(matchSize / 2) : 0) }; if (!match.horizontal) { createSpecial.col = match.col; } if (!match.horizontal) { createSpecial.row = match.row + Math.floor(matchSize / 2); } } else if (matchSize === 4) { // 4-in-a-row -> Striped (example) createSpecial = { type: match.horizontal ? 'stripedV' : 'stripedH', row: match.row, col: match.col + (match.horizontal ? Math.floor(matchSize / 2) : 0) }; if (!match.horizontal) { createSpecial.col = match.col; } if (!match.horizontal) { createSpecial.row = match.row + Math.floor(matchSize / 2); } } // Add logic for L/T shapes for Wrapped candies if needed var specialCreated = false; for (var j = 0; j < matchSize; j++) { var row = match.horizontal ? match.row : match.row + j; var col = match.horizontal ? match.col + j : match.col; var candy = board[row][col]; if (candy && candiesToRemove.indexOf(candy) === -1) { if (candy.isSpecial) { popPromises.push({ then: function then(resolve) { setTimeout(function () { activateSpecialCandy(candy); resolve(); }, 300); // Delay special candy activation to allow normal match processing } }); } // Ensure not already marked candiesToRemove.push(candy); pointsEarned += pointsPerCandy * (candy.isSpecial ? 2 : 1); // More points for popping special candies // If this is the spot for special creation, mark it if (createSpecial && row === createSpecial.row && col === createSpecial.col) { specialCandiesToCreate.push(createSpecial); specialCreated = true; } } } // If special should be created but wasn't assigned to a specific candy (e.g., user swipe location matters), handle here // if (createSpecial && !specialCreated) { ... } }); score += pointsEarned; updateUI(); // Animate removal candiesToRemove.forEach(function (candy, index) { // Remove from board model immediately if (board[candy.row][candy.col] === candy) { board[candy.row][candy.col] = null; } // Animate pop popPromises.push({ then: function then(resolve) { candy.pop(index * 30, resolve); // Pass resolve directly to pop as onComplete } }); // Remove from the master list var listIndex = candies.indexOf(candy); if (listIndex > -1) { candies.splice(listIndex, 1); } }); // After all pops are visually done, proceed to drop/fill (function (promises, callback) { var remaining = promises.length; if (remaining === 0) { callback(); } promises.forEach(function (promise) { promise.then(function () { remaining--; if (remaining === 0) { callback(); } }); }); })(popPromises, function () { candiesToRemove.forEach(function (candy) { return candy.destroy(); }); // Final cleanup // Now create the special candies *before* dropping specialCandiesToCreate.forEach(function (spec) { var newType = spec.type; // Use the specified special type // In a real game, 'spec.type' would determine the *kind* of special candy // For now, we just make it visually 'special' var specialCandy = new Candy(spec.type, spec.row, spec.col); specialCandy.setSpecial(true); // Make it special specialCandy.moveTo(spec.row, spec.col, false); // Place instantly gameBoardContainer.addChild(specialCandy); board[spec.row][spec.col] = specialCandy; candies.push(specialCandy); LK.getSound('special').play(); }); handleDrops(); // Proceed to next step }); } else { // No matches found, board is stable isProcessing = false; selectedCandy = null; // Clear selection // Check for possible moves ONLY if the board is stable if (!hasPossibleMoves()) { reshuffleBoard(); // This will set isProcessing again briefly } else { checkGameState(); // Check win/loss only when stable and moves exist } } } // Step 2: Handle candies dropping down function handleDrops() { var dropsOccurred = false; var dropPromises = []; for (var col = 0; col < BOARD_SIZE; col++) { var emptyRow = BOARD_SIZE - 1; // Start checking from bottom // Find the lowest empty spot for (var row = BOARD_SIZE - 1; row >= 0; row--) { if (board[row][col] === null) { emptyRow = row; // Found the lowest empty row in this column break; // No need to check further up for the initial empty spot } } // Now check candies above this empty spot for (var row = BOARD_SIZE - 1; row >= 0; row--) { if (board[row][col] !== null) { // Check if there's a gap below this candy var targetRow = row; while (targetRow + 1 < BOARD_SIZE && board[targetRow + 1][col] === null) { targetRow++; } if (targetRow !== row) { // Move in model var candy = board[row][col]; board[targetRow][col] = candy; board[row][col] = null; // Animate move var dropTween = candy.moveTo(targetRow, col, true, 0); // Drop animation dropPromises.push({ then: function then(resolve) { if (dropTween && dropTween.vars) { dropTween.vars.onFinish = resolve; } else { resolve(); // Resolve immediately if dropTween or vars is undefined } } }); dropsOccurred = true; } } } } if (dropsOccurred) { (function (promises, callback) { var remaining = promises.length; if (remaining === 0) { callback(); } promises.forEach(function (promise) { promise.then(function () { remaining--; if (remaining === 0) { callback(); } }); }); })(dropPromises, handleFills); // Wait for drops, then fill } else { handleFills(); // No drops, proceed directly to filling } } // Step 3: Fill empty spaces from the top function handleFills() { var fillsOccurred = false; var fillPromises = []; for (var col = 0; col < BOARD_SIZE; col++) { var fillRow = -1; // Start generating candies above the board for (var row = BOARD_SIZE - 1; row >= 0; row--) { if (board[row][col] === null) { // Found an empty spot var type = getRandomCandyType(); var candy = new Candy(type, fillRow, col); // Create above the board candy.moveTo(fillRow, col, false); // Place instantly above gameBoardContainer.addChild(candy); board[row][col] = candy; // Assign to target cell in model candies.push(candy); // Animate move into place var delay = (-fillRow - 1) * 50; // Stagger based on how high it starts var fillTween = candy.moveTo(row, col, true, delay); fillPromises.push({ then: function then(resolve) { if (fillTween && fillTween.vars) { fillTween.vars.onFinish = resolve; } else { resolve(); // Resolve immediately if fillTween or vars is undefined } } }); fillsOccurred = true; fillRow--; // Next candy starts even higher } } } if (fillsOccurred) { if (typeof Promise !== 'undefined' && Promise.all) { Promise.all(fillPromises).then(handleMatches); // Wait for fills, then check for new matches (cascade) } else { console.error("Promise is not supported in this environment."); handleMatches(); // Fallback to directly calling handleMatches } } else { // No fills needed, implies no matches occurred in the previous step either. Board is stable. handleMatches(); // Final check (will find no matches and trigger end processing) } } function findMatches() { var matches = []; var checked = []; // Keep track of candies already part of a found match function markChecked(candy) { if (candy && checked.indexOf(candy) === -1) { checked.push(candy); } } // Check horizontal matches for (var row = 0; row < BOARD_SIZE; row++) { for (var col = 0; col < BOARD_SIZE - 2; col++) { var candy1 = board[row][col]; if (!candy1 || checked.indexOf(candy1) > -1) { continue; } // Skip nulls or already checked var candy2 = board[row][col + 1]; var candy3 = board[row][col + 2]; if (candy2 && candy3 && candy1.type === candy2.type && candy1.type === candy3.type) { // Found a horizontal match of at least 3 var currentMatch = [candy1, candy2, candy3]; var matchLength = 3; // Check for longer match for (var k = col + 3; k < BOARD_SIZE; k++) { var nextCandy = board[row][k]; if (nextCandy && nextCandy.type === candy1.type) { currentMatch.push(nextCandy); matchLength++; } else { break; // End of match } } // Store match details matches.push({ candies: currentMatch, // Store the actual candy objects row: row, col: col, length: matchLength, horizontal: true }); // Mark all candies in this match as checked currentMatch.forEach(markChecked); col += matchLength - 1; // Skip checked candies in the outer loop } } } // Check vertical matches for (var col = 0; col < BOARD_SIZE; col++) { for (var row = 0; row < BOARD_SIZE - 2; row++) { var candy1 = board[row][col]; // Skip nulls or candies already part of a horizontal match if (!candy1 || checked.indexOf(candy1) > -1) { continue; } var candy2 = board[row + 1][col]; var candy3 = board[row + 2][col]; if (candy2 && candy3 && candy1.type === candy2.type && candy1.type === candy3.type) { // Found a vertical match of at least 3 var currentMatch = [candy1, candy2, candy3]; var matchLength = 3; // Check for longer match for (var k = row + 3; k < BOARD_SIZE; k++) { var nextCandy = board[k][col]; if (nextCandy && nextCandy.type === candy1.type) { // Avoid adding if already part of a horizontal match found earlier if (checked.indexOf(nextCandy) === -1) { currentMatch.push(nextCandy); } else { // If it was part of a horizontal match, just increase length but don't re-add } matchLength++; } else { break; // End of match } } // Store match details matches.push({ candies: currentMatch, // Store the actual candy objects row: row, col: col, length: matchLength, horizontal: false }); // Mark all candies in this match as checked currentMatch.forEach(markChecked); row += matchLength - 1; // Skip checked candies in the outer loop } } } return matches; } function hasPossibleMoves() { // Check horizontal swaps that could create a match for (var row = 0; row < BOARD_SIZE; row++) { for (var col = 0; col < BOARD_SIZE - 1; col++) { if (canSwapCreateMatch(row, col, row, col + 1)) { return true; } } } // Check vertical swaps that could create a match for (var row = 0; row < BOARD_SIZE - 1; row++) { for (var col = 0; col < BOARD_SIZE; col++) { if (canSwapCreateMatch(row, col, row + 1, col)) { return true; } } } return false; // No possible moves found } // Helper function for hasPossibleMoves function canSwapCreateMatch(r1, c1, r2, c2) { var candy1 = board[r1][c1]; var candy2 = board[r2][c2]; if (!candy1 || !candy2) { return false; } // Cannot swap with empty space // Temporarily swap types in the model (don't need full object swap) board[r1][c1] = candy2; board[r2][c2] = candy1; // Check if this swap creates a match around either position var createsMatch = checkMatchAt(r1, c1) || checkMatchAt(r2, c2) || findMatches().length > 0; // Simplified check // Swap back board[r1][c1] = candy1; board[r2][c2] = candy2; return createsMatch; } function reshuffleBoard() { if (isProcessing) { // Don't reshuffle if already processing LK.setTimeout(reshuffleBoard, 500); // Try again later return; } isProcessing = true; // Block input during reshuffle // Display reshuffling message var reshuffleTxt = new Text2('Reshuffling...', { size: 100, fill: 0xFFFFFF }); reshuffleTxt.anchor.set(0.5, 0.5); LK.gui.center.addChild(reshuffleTxt); // 1. Collect all current candy types and special status var currentCandiesInfo = []; candies.forEach(function (candy) { currentCandiesInfo.push({ type: candy.type, isSpecial: candy.isSpecial // Retain special status during reshuffle }); candy.destroy(); // Remove old visuals immediately }); candies = []; // Clear the list board = []; // Clear the board model // 2. Shuffle the collected info for (var i = currentCandiesInfo.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var _ref = [currentCandiesInfo[j], currentCandiesInfo[i]]; currentCandiesInfo[i] = _ref[0]; currentCandiesInfo[j] = _ref[1]; } // 3. Rebuild the board ensuring no initial matches AND possible moves var attempts = 0; do { attempts++; if (attempts > 10) { // Safety break if it struggles too much console.error("Failed to reshuffle into a valid state after 10 attempts."); // Could force a simple known-good layout here or reset level isProcessing = false; // Unlock reshuffleTxt.destroy(); // Maybe show a "No more moves" game over? checkGameState(true); // Force check, likely leading to game over if moves are 0 return; } // Clear board for re-attempt candies.forEach(function (c) { return c.destroy(); }); candies = []; board = []; var infoIndex = 0; for (var row = 0; row < BOARD_SIZE; row++) { board[row] = []; for (var col = 0; col < BOARD_SIZE; col++) { var info = currentCandiesInfo[infoIndex++]; board[row][col] = null; // Temp null for checkMatchAt // Ensure no immediate matches with shuffled type var placeAttempts = 0; do { var currentInfo = info; // Use the shuffled info board[row][col] = { type: currentInfo.type, isSpecial: currentInfo.isSpecial }; // Temp place placeAttempts++; if (placeAttempts > currentCandiesInfo.length) { // Avoid infinite loop if types are very limited // Swap with a later random type if stuck var swapIdx = infoIndex + Math.floor(Math.random() * (currentCandiesInfo.length - infoIndex)); if (swapIdx < currentCandiesInfo.length) { var _ref2 = [currentCandiesInfo[swapIdx], currentCandiesInfo[infoIndex - 1]]; currentCandiesInfo[infoIndex - 1] = _ref2[0]; currentCandiesInfo[swapIdx] = _ref2[1]; info = currentCandiesInfo[infoIndex - 1]; // Get the newly swapped info } board[row][col] = { type: info.type, isSpecial: info.isSpecial }; // Try again with swapped break; // Exit do-while for this cell after swap } } while (checkMatchAt(row, col)); // Check if placing this type creates a match // Create the actual candy var candy = new Candy(board[row][col].type, row, col); candy.setSpecial(board[row][col].isSpecial); candy.alpha = 0; // Start invisible candy.moveTo(row, col, false); gameBoardContainer.addChild(candy); board[row][col] = candy; // Replace temp object with real candy candies.push(candy); // Animate fade-in tween(candy, { alpha: 1 }, { duration: 300, delay: (row + col) * 20 }); } } // Loop condition: Keep reshuffling if the new board has no possible moves } while (!hasPossibleMoves() && attempts < 10); // Reshuffle successful LK.setTimeout(function () { reshuffleTxt.destroy(); isProcessing = false; // Unlock the board }, 500); // Keep message visible briefly } function checkGameState() { var forceCheck = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; // Don't check state while the board is actively processing, unless forced (e.g., reshuffle failed) if (isProcessing && !forceCheck) { return; } // Check Win Condition if (score >= targetScore) { console.log("Level " + level + " Complete!"); isProcessing = true; // Prevent further input // Update storage level++; storage.level = level; if (score > highScore) { highScore = score; storage.highScore = highScore; } storage.score = score; // Save current score too, maybe for win screen display LK.setTimeout(function () { LK.showYouWin({ level: level - 1, score: score, highScore: highScore }); }, 1000); // Delay for effect } // Check Lose Condition else if (moves <= 0) { console.log("Game Over - Out of Moves!"); isProcessing = true; // Prevent further input // Update high score if needed if (score > highScore) { highScore = score; storage.highScore = highScore; } storage.score = score; // Save final score LK.setTimeout(function () { LK.showGameOver({ level: level, score: score, highScore: highScore }); }, 1000); // Delay for effect } // Note: The "no possible moves" scenario is now handled by reshuffleBoard, // but if reshuffling fails repeatedly, it might also lead to a game over state here. } // --- Game Start --- setupGame();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
level: 1,
score: 0,
highScore: 0
});
/****
* Classes
****/
var Candy = Container.expand(function (type, row, col) {
var self = Container.call(this);
self.type = type || 'redCandy';
self.row = row || 0;
self.col = col || 0;
self.isSpecial = false; // Flag indicating if this candy is special (e.g., striped, wrapped)
// Visual representation
var visual = self.attachAsset(self.type, {
anchorX: 0.5,
anchorY: 0.5,
alpha: 1
});
// Selection overlay (hidden by default)
var overlay = self.attachAsset('selectedOverlay', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0,
visible: false // Start invisible
});
// Marker for special candies (optional, initially hidden)
var specialMarker = null;
self.setSpecial = function (special) {
self.isSpecial = special;
// Add visual distinction for special candies if needed
if (special) {
if (!specialMarker) {
// Example: Add a marker instead of scaling/pulsing the base visual
specialMarker = self.attachAsset('specialMarkerCandy', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 1 // Make it visible
});
}
// Optional: Pulse animation for special marker
tween.stop(specialMarker); // Stop previous tweens on marker
specialMarker.alpha = 0.6;
tween(specialMarker, {
alpha: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
yoyo: true,
// Go back and forth
loop: true // Repeat indefinitely
});
} else {
if (specialMarker) {
tween.stop(specialMarker); // Stop animation
specialMarker.destroy();
specialMarker = null;
}
}
};
self.select = function () {
overlay.visible = true;
tween.stop(overlay, {
alpha: true
}); // Stop previous animation
tween(overlay, {
alpha: 0.4
}, {
duration: 150
});
};
self.deselect = function () {
tween.stop(overlay, {
alpha: true
}); // Stop previous animation
tween(overlay, {
alpha: 0
}, {
duration: 150,
onFinish: function onFinish() {
overlay.visible = false; // Hide after fade out
}
});
};
self.moveTo = function (newRow, newCol, animate, delay, onComplete) {
self.row = newRow;
self.col = newCol;
var newX = CELL_SIZE * self.col + CELL_SIZE / 2;
var newY = CELL_SIZE * self.row + CELL_SIZE / 2;
if (animate) {
tween.stop(self, {
x: true,
y: true
}); // Stop previous movement
tween(self, {
x: newX,
y: newY
}, {
duration: 300,
// Consistent duration
delay: delay || 0,
easing: tween.easeOutQuad,
// Smooth easing
onFinish: onComplete
});
} else {
tween.stop(self, {
x: true,
y: true
}); // Ensure any running tween is stopped
self.x = newX;
self.y = newY;
if (onComplete) {
onComplete();
}
}
};
// Pop animation - Returns the tween instance
self.pop = function (delay, onComplete) {
tween.stop(self, {
alpha: true,
scaleX: true,
scaleY: true
}); // Stop other tweens
return tween(self, {
alpha: 0,
scaleX: 0.1,
// Shrink down
scaleY: 0.1
}, {
duration: 250,
delay: delay || 0,
easing: tween.easeInQuad,
// Ease in for disappearance
onFinish: function onFinish() {
// Note: Actual destruction and removal from arrays happen elsewhere
if (onComplete) {
onComplete();
}
}
});
};
// Override destroy to clean up marker tween
var baseDestroy = self.destroy;
self.destroy = function () {
if (specialMarker) {
tween.stop(specialMarker);
}
baseDestroy.call(self); // Call original destroy
};
self.down = function (x, y, obj) {
// Delegate to the main game click handler
if (game && typeof game.handleCandyClick === 'function' && !isSwapping && !isDropping) {
game.handleCandyClick(self);
}
};
return self;
});
var GameBoard = Container.expand(function (rows, cols) {
var self = Container.call(this);
self.rows = rows || 8;
self.cols = cols || 8;
// Background board
var boardVisual = self.attachAsset('gameBoard', {
anchorX: 0.5,
anchorY: 0.5
});
boardVisual.width = CELL_SIZE * self.cols; // Match width to grid
boardVisual.height = CELL_SIZE * self.rows; // Match height to grid
// Cell backgrounds
self.cells = [];
for (var row = 0; row < self.rows; row++) {
self.cells[row] = []; // Initialize row array
for (var col = 0; col < self.cols; col++) {
var cell = self.attachAsset('cellBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: col * CELL_SIZE + CELL_SIZE / 2,
y: row * CELL_SIZE + CELL_SIZE / 2
});
self.cells[row][col] = cell; // Store cell reference if needed later
}
}
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000 // Set background color to black
});
/****
* Game Code
****/
var popPromises = []; // Initialize popPromises array to store promises for animations
function handleCompletion(actions, callback) {
var remaining = actions.length;
if (remaining === 0) {
callback();
}
actions.forEach(function (action) {
action.then(function () {
remaining--;
if (remaining === 0) {
callback();
}
});
});
}
function resolveDropsAndFills() {
handleDrops();
}
function executeDoubleStripedEffect(centerCandy) {
isProcessing = true;
var targetRow = centerCandy.row;
var targetCol = centerCandy.col;
var candiesToDestroy = [];
for (var c = 0; c < BOARD_SIZE; c++) {
var _board$targetRow;
var candy = (_board$targetRow = board[targetRow]) === null || _board$targetRow === void 0 ? void 0 : _board$targetRow[c];
if (candy && candiesToDestroy.indexOf(candy) === -1) {
candiesToDestroy.push(candy);
board[targetRow][c] = null;
}
}
// Game state variables
var popPromises = []; // Initialize popPromises array to store promises for animations
for (var r = 0; r < BOARD_SIZE; r++) {
var _board$r;
var candy = (_board$r = board[r]) === null || _board$r === void 0 ? void 0 : _board$r[targetCol];
if (candy && candiesToDestroy.indexOf(candy) === -1) {
candiesToDestroy.push(candy);
board[r][targetCol] = null;
}
}
// Play sound and update score
LK.getSound('special').play();
score += candiesToDestroy.length * 20;
updateUI();
// Prepare animations
var actions = [];
candiesToDestroy.forEach(function (candy) {
var delay = (Math.abs(candy.row - targetRow) + Math.abs(candy.col - targetCol)) * 35;
actions.push({
then: function then(resolve) {
return candy.pop(delay, resolve);
}
});
});
// Wait for animations to complete
handleCompletion(actions, function () {
candiesToDestroy.forEach(function (c) {
return c.destroy();
});
resolveDropsAndFills();
});
}
function executeColorBombEffect(bombCandy, targetType) {
isProcessing = true; // Lock the board
// Find all candies of the targetType
var candiesToDestroyByBomb = [];
candies.forEach(function (c) {
if (c.type === targetType && !c.isSpecial) {
candiesToDestroyByBomb.push(c);
}
});
// Include the bombCandy itself
candiesToDestroyByBomb.push(bombCandy);
// Update board model
candiesToDestroyByBomb.forEach(function (candy) {
board[candy.row][candy.col] = null;
});
// Prepare animations
candiesToDestroyByBomb.forEach(function (candy, index) {
popPromises.push({
then: function then(resolve) {
candy.pop(index * 30, function () {
candy.destroy();
resolve();
});
}
});
});
// Play sound and update score
LK.getSound('special').play();
score += candiesToDestroyByBomb.length * 10;
updateUI();
// Wait for animations to complete
(function (promises, callback) {
var remaining = promises.length;
if (remaining === 0) {
callback();
}
promises.forEach(function (promise) {
promise.then(function () {
remaining--;
if (remaining === 0) {
callback();
}
});
});
})(popPromises, function () {
// Cleanup and proceed to drop/fill
candiesToDestroyByBomb.forEach(function (candy) {
var index = candies.indexOf(candy);
if (index > -1) {
candies.splice(index, 1);
}
});
resolveDropsAndFills();
});
}
function activateSpecialCandy(candy) {
switch (candy.type) {
case 'colorBomb':
// Clear all candies of the color it was swapped with
var targetType = candy.swappedWithType; // Assume swappedWithType is set during swap
candies.forEach(function (c) {
if (c.type === targetType) {
popPromises.push({
then: function then(resolve) {
c.pop(0, function () {
c.destroy();
resolve();
});
}
});
}
});
break;
case 'stripedH':
// Clear entire row
for (var col = 0; col < BOARD_SIZE; col++) {
var rowCandy = board[candy.row][col];
if (rowCandy) {
popPromises.push({
then: function then(resolve) {
rowCandy.pop(col * 50, function () {
// Add delay based on column index
rowCandy.destroy();
resolve();
});
}
});
}
}
break;
case 'stripedV':
// Clear entire column
for (var row = 0; row < BOARD_SIZE; row++) {
var colCandy = board[row][candy.col];
if (colCandy) {
colCandy.pop(row * 50, function () {
// Add delay based on row index
colCandy.destroy();
});
}
}
break;
default:
break;
}
}
// Sound for special candy creation/activation
// Keep as is for selection
// Example: Could be a different shape/texture
// Renamed specialCandy shape slightly for clarity
// Assets definition looks good, assuming LK.init.shape/sound works as expected.
// Constants
var BOARD_SIZE = 8;
var CELL_SIZE = 200; // Increase size of each cell for better visibility
var CANDY_TYPES = ['redCandy', 'yellowCandy', 'greenCandy', 'blueCandy', 'purpleCandy', 'orangeCandy'];
var NUM_CANDY_TYPES = CANDY_TYPES.length;
// Game state variables
var board; // 2D array holding Candy objects or null
var candies = []; // Flat list of all active Candy objects on the board
var selectedCandy = null;
var isSwapping = false; // True during the swap animation
var isProcessing = false; // General flag for when board is resolving (matches, drops, fills)
var isDropping = false; // Flag to indicate if candies are currently dropping
var moves = 0;
var score = 0;
var targetScore = 0;
var level = storage.level || 1;
var highScore = storage.highScore || 0;
// Game elements
var gameBoardContainer; // Holds the visual board and candies
var scoreTxt, movesTxt, levelTxt;
// --- Core Game Logic Functions ---
function setupGame() {
// Create and center the game board container
var background = LK.getAsset('gameBoard', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 2048 / 100,
scaleY: 2732 / 100
});
game.addChild(background);
gameBoardContainer = new GameBoard(BOARD_SIZE, BOARD_SIZE);
// Center the board (assuming 2048x2732 is the stage size)
gameBoardContainer.x = (2048 - gameBoardContainer.width) / 2 + 400; // Shift right by 400 pixels
gameBoardContainer.y = (2732 - gameBoardContainer.height) / 2 + 200; // Shift down by 200 pixels
game.addChild(gameBoardContainer);
// Setup UI elements
scoreTxt = new Text2('Score: 0', {
size: 70,
fill: 0xFFFFFF,
align: 'right'
});
scoreTxt.anchor.set(1, 0); // Anchor top-right
LK.gui.topRight.addChild(scoreTxt);
scoreTxt.x = -20; // Add some padding from the edge
movesTxt = new Text2('Moves: 0', {
size: 70,
fill: 0xFFFFFF,
align: 'left'
});
movesTxt.anchor.set(0, 0); // Anchor top-left
LK.gui.topLeft.addChild(movesTxt);
movesTxt.x = 20; // Add some padding from the edge
levelTxt = new Text2('Level: 1', {
size: 70,
fill: 0xFFFFFF
});
levelTxt.anchor.set(0.5, 0); // Anchor top-center
LK.gui.top.addChild(levelTxt);
// Add "Glaud" text in small orange at the bottom center
var glaudTxt = new Text2('Glaud', {
size: 40,
fill: 0xFF8C00 // Orange color
});
glaudTxt.anchor.set(0.5, 1); // Anchor bottom-center
LK.gui.bottom.addChild(glaudTxt);
// Initialize the first level
initializeLevel(level);
// Start background music
LK.playMusic('bgmusic', {
loop: true,
fade: {
start: 0,
end: 0.5,
duration: 1000
}
});
// Attach global click listener (if Candy.down isn't sufficient)
// game.down = handleGameClick; // Use this if clicking empty cells should deselect
}
function initializeLevel(levelNum) {
level = levelNum;
score = 0;
moves = 20 + (level - 1) * 2; // Example: Increase moves per level
targetScore = 1000 * level; // Example: Increase target score per level
// Add additional objectives like clearing jellies or bringing ingredients down
var objectives = {
clearJellies: Math.floor(level / 2),
bringIngredients: Math.floor(level / 3)
};
selectedCandy = null;
isSwapping = false;
isProcessing = false;
updateUI();
initializeBoard();
}
function initializeBoard() {
// Clear existing candies visually and from arrays
for (var i = 0; i < candies.length; i++) {
candies[i].destroy();
}
candies = [];
board = [];
// Create the board array and fill with candies
for (var row = 0; row < BOARD_SIZE; row++) {
board[row] = [];
for (var col = 0; col < BOARD_SIZE; col++) {
board[row][col] = null; // Ensure initialized to null
// Generate candy ensuring no initial matches
var attempts = 0;
do {
var type = getRandomCandyType();
// Temporarily place to check for matches
var tempCandy = {
type: type
}; // Lightweight check
board[row][col] = tempCandy;
attempts++;
if (attempts > 50) {
// Safety break for rare impossible scenarios
console.warn("Struggling to place initial candy without match at", row, col);
break;
}
} while (checkMatchAt(row, col));
// If check passed or safety break, create the real candy
var candy = new Candy(type, row, col);
candy.moveTo(row, col, false); // Place instantly
gameBoardContainer.addChild(candy);
board[row][col] = candy; // Place real candy in board
candies.push(candy);
}
}
// Final check: Ensure there are possible moves
if (!hasPossibleMoves()) {
console.log("No initial moves, reshuffling...");
// Use LK.setTimeout to allow the initial draw frame to happen first
LK.setTimeout(reshuffleBoard, 100);
}
}
// Helper to check for matches *only* at a specific new candy's location
function checkMatchAt(row, col) {
var candy = board[row][col];
if (!candy) {
return false;
}
var type = candy.type;
// Check Horizontal
var count = 1;
if (col > 0 && board[row][col - 1] && board[row][col - 1].type === type) {
count++;
}
if (col > 1 && board[row][col - 2] && board[row][col - 2].type === type) {
count++;
}
if (count >= 3) {
return true;
}
// Check Vertical
count = 1;
if (row > 0 && board[row - 1][col] && board[row - 1][col].type === type) {
count++;
}
if (row > 1 && board[row - 2][col] && board[row - 2][col].type === type) {
count++;
}
if (count >= 3) {
return true;
}
return false;
}
function getRandomCandyType() {
var typesAvailable = Math.min(NUM_CANDY_TYPES, 3 + Math.floor(level / 3)); // Slower difficulty ramp
return CANDY_TYPES[Math.floor(Math.random() * typesAvailable)];
}
function updateUI() {
scoreTxt.setText('Score: ' + score);
movesTxt.setText('Moves: ' + moves);
levelTxt.setText('Level: ' + level);
}
// Renamed from handleCandyClick to be clearer
game.handleCandyClick = function (candy) {
if (isProcessing || isSwapping || isDropping) {
return;
} // Ignore clicks during processing or animations
if (selectedCandy === null) {
selectedCandy = candy;
// Highlight the cell background
gameBoardContainer.cells[candy.row][candy.col].tint = 0xffff00; // Highlight with yellow
} else {
// Second selection
if (selectedCandy === candy) {
// Deselect if same candy clicked
// Revert cell background tint
gameBoardContainer.cells[selectedCandy.row][selectedCandy.col].tint = 0x704f3d; // Original color
gameBoardContainer.cells[selectedCandy.row][selectedCandy.col].tint = 0x704f3d; // Revert tint
selectedCandy = null;
} else {
// Check if candies are adjacent
var rowDiff = Math.abs(selectedCandy.row - candy.row);
var colDiff = Math.abs(selectedCandy.col - candy.col);
if (rowDiff === 1 && colDiff === 0 || rowDiff === 0 && colDiff === 1) {
// Adjacent: Initiate swap
selectedCandy.deselect(); // Deselect the first one visually
swapCandies(selectedCandy, candy);
selectedCandy = null; // Clear selection after initiating swap
} else {
// Not adjacent: Change selection to the new candy
// Revert previous selection's cell background tint
gameBoardContainer.cells[selectedCandy.row][selectedCandy.col].tint = 0x704f3d; // Original color
selectedCandy = candy;
// Highlight new selection's cell background
gameBoardContainer.cells[candy.row][candy.col].tint = 0xffff00; // Highlight with yellow
LK.getSound('invalid').play(); // Sound for invalid selection change
}
}
}
};
function swapCandies(candy1, candy2) {
if (isSwapping || isProcessing) {
return;
} // Prevent overlapping swaps
isSwapping = true;
// Store original positions for potential revert
var candy1Row = candy1.row;
var candy1Col = candy1.col;
var candy2Row = candy2.row;
var candy2Col = candy2.col;
// Track the type of candy a color bomb is swapped with
if (candy1.type === 'colorBomb') {
candy1.swappedWithType = candy2.type;
} else if (candy2.type === 'colorBomb') {
candy2.swappedWithType = candy1.type;
}
// Update board model immediately
board[candy1Row][candy1Col] = candy2;
board[candy2Row][candy2Col] = candy1;
// Update candy objects' internal row/col
candy1.row = candy2Row;
candy1.col = candy2Col;
candy2.row = candy1Row;
candy2.col = candy1Col;
// Animate the swap visually
LK.getSound('swap').play();
var tween1 = candy1.moveTo(candy2Row, candy2Col, true);
var tween2 = candy2.moveTo(candy1Row, candy1Col, true);
// After animation finishes (wait for the longer of the two, though they should be same duration)
// Using a small timeout is simpler than tracking both tweens
LK.setTimeout(function () {
// Check if the swap resulted in a match
var matchFound = checkMatchAt(candy1.row, candy1.col) || checkMatchAt(candy2.row, candy2.col) || findMatches().length > 0; // More robust check
// Check for color bomb swap
if (candy1.type === 'colorBomb' && !candy2.isSpecial) {
executeColorBombEffect(candy1, candy2.type);
isSwapping = false;
return;
} else if (candy2.type === 'colorBomb' && !candy1.isSpecial) {
executeColorBombEffect(candy2, candy1.type);
isSwapping = false;
return;
}
if (matchFound) {
// Valid swap, deduct move and process matches
moves--;
updateUI();
isSwapping = false; // Allow processing now
processBoard(); // Start the match -> drop -> fill cycle
// Check game state later, after processing finishes
} else if ((candy1.type === 'stripedH' || candy1.type === 'stripedV') && (candy2.type === 'stripedH' || candy2.type === 'stripedV')) {
executeDoubleStripedEffect(candy1);
isSwapping = false;
return;
} else {
// Invalid swap, animate back
LK.getSound('invalid').play();
// Update board model back
board[candy1Row][candy1Col] = candy1;
board[candy2Row][candy2Col] = candy2;
// Update candy objects' internal row/col back
candy1.row = candy1Row;
candy1.col = candy1Col;
candy2.row = candy2Row;
candy2.col = candy2Col;
// Animate back
candy1.moveTo(candy1Row, candy1Col, true);
candy2.moveTo(candy2Row, candy2Col, true);
// After swap back animation finishes
LK.setTimeout(function () {
isSwapping = false; // Ready for next input
gameBoardContainer.cells[candy1.row][candy1.col].tint = 0x704f3d; // Revert tint
gameBoardContainer.cells[candy2.row][candy2.col].tint = 0x704f3d; // Revert tint
selectedCandy = null;
}, 310); // Slightly longer than moveTo duration
}
}, 310); // Wait for swap animation to mostly complete
}
// Main processing loop trigger
function processBoard() {
if (isProcessing) {
return;
} // Avoid re-entry
isProcessing = true;
handleMatches();
}
// Step 1: Find and handle matches
function handleMatches() {
var matches = findMatches();
if (matches.length > 0) {
LK.getSound('match').play();
var pointsEarned = 0;
var specialCandiesToCreate = []; // Store {row, col, type} for special creation
// Mark candies for removal and calculate score/special
var candiesToRemove = [];
matches.forEach(function (match) {
var matchSize = match.length;
var pointsPerCandy = 10 * matchSize; // Bonus for longer matches
// Decide if a special candy should be created from this match
var createSpecial = null; // { type: 'stripedH', row: r, col: c } etc.
if (matchSize >= 5) {
// 5-in-a-row -> Color Bomb (example)
createSpecial = {
type: 'colorBomb',
row: match.row,
col: match.col + (match.horizontal ? Math.floor(matchSize / 2) : 0)
};
if (!match.horizontal) {
createSpecial.col = match.col;
}
if (!match.horizontal) {
createSpecial.row = match.row + Math.floor(matchSize / 2);
}
} else if (matchSize === 4) {
// 4-in-a-row -> Striped (example)
createSpecial = {
type: match.horizontal ? 'stripedV' : 'stripedH',
row: match.row,
col: match.col + (match.horizontal ? Math.floor(matchSize / 2) : 0)
};
if (!match.horizontal) {
createSpecial.col = match.col;
}
if (!match.horizontal) {
createSpecial.row = match.row + Math.floor(matchSize / 2);
}
} // Add logic for L/T shapes for Wrapped candies if needed
var specialCreated = false;
for (var j = 0; j < matchSize; j++) {
var row = match.horizontal ? match.row : match.row + j;
var col = match.horizontal ? match.col + j : match.col;
var candy = board[row][col];
if (candy && candiesToRemove.indexOf(candy) === -1) {
if (candy.isSpecial) {
popPromises.push({
then: function then(resolve) {
setTimeout(function () {
activateSpecialCandy(candy);
resolve();
}, 300); // Delay special candy activation to allow normal match processing
}
});
}
// Ensure not already marked
candiesToRemove.push(candy);
pointsEarned += pointsPerCandy * (candy.isSpecial ? 2 : 1); // More points for popping special candies
// If this is the spot for special creation, mark it
if (createSpecial && row === createSpecial.row && col === createSpecial.col) {
specialCandiesToCreate.push(createSpecial);
specialCreated = true;
}
}
}
// If special should be created but wasn't assigned to a specific candy (e.g., user swipe location matters), handle here
// if (createSpecial && !specialCreated) { ... }
});
score += pointsEarned;
updateUI();
// Animate removal
candiesToRemove.forEach(function (candy, index) {
// Remove from board model immediately
if (board[candy.row][candy.col] === candy) {
board[candy.row][candy.col] = null;
}
// Animate pop
popPromises.push({
then: function then(resolve) {
candy.pop(index * 30, resolve); // Pass resolve directly to pop as onComplete
}
});
// Remove from the master list
var listIndex = candies.indexOf(candy);
if (listIndex > -1) {
candies.splice(listIndex, 1);
}
});
// After all pops are visually done, proceed to drop/fill
(function (promises, callback) {
var remaining = promises.length;
if (remaining === 0) {
callback();
}
promises.forEach(function (promise) {
promise.then(function () {
remaining--;
if (remaining === 0) {
callback();
}
});
});
})(popPromises, function () {
candiesToRemove.forEach(function (candy) {
return candy.destroy();
}); // Final cleanup
// Now create the special candies *before* dropping
specialCandiesToCreate.forEach(function (spec) {
var newType = spec.type; // Use the specified special type
// In a real game, 'spec.type' would determine the *kind* of special candy
// For now, we just make it visually 'special'
var specialCandy = new Candy(spec.type, spec.row, spec.col);
specialCandy.setSpecial(true); // Make it special
specialCandy.moveTo(spec.row, spec.col, false); // Place instantly
gameBoardContainer.addChild(specialCandy);
board[spec.row][spec.col] = specialCandy;
candies.push(specialCandy);
LK.getSound('special').play();
});
handleDrops(); // Proceed to next step
});
} else {
// No matches found, board is stable
isProcessing = false;
selectedCandy = null; // Clear selection
// Check for possible moves ONLY if the board is stable
if (!hasPossibleMoves()) {
reshuffleBoard(); // This will set isProcessing again briefly
} else {
checkGameState(); // Check win/loss only when stable and moves exist
}
}
}
// Step 2: Handle candies dropping down
function handleDrops() {
var dropsOccurred = false;
var dropPromises = [];
for (var col = 0; col < BOARD_SIZE; col++) {
var emptyRow = BOARD_SIZE - 1; // Start checking from bottom
// Find the lowest empty spot
for (var row = BOARD_SIZE - 1; row >= 0; row--) {
if (board[row][col] === null) {
emptyRow = row; // Found the lowest empty row in this column
break; // No need to check further up for the initial empty spot
}
}
// Now check candies above this empty spot
for (var row = BOARD_SIZE - 1; row >= 0; row--) {
if (board[row][col] !== null) {
// Check if there's a gap below this candy
var targetRow = row;
while (targetRow + 1 < BOARD_SIZE && board[targetRow + 1][col] === null) {
targetRow++;
}
if (targetRow !== row) {
// Move in model
var candy = board[row][col];
board[targetRow][col] = candy;
board[row][col] = null;
// Animate move
var dropTween = candy.moveTo(targetRow, col, true, 0); // Drop animation
dropPromises.push({
then: function then(resolve) {
if (dropTween && dropTween.vars) {
dropTween.vars.onFinish = resolve;
} else {
resolve(); // Resolve immediately if dropTween or vars is undefined
}
}
});
dropsOccurred = true;
}
}
}
}
if (dropsOccurred) {
(function (promises, callback) {
var remaining = promises.length;
if (remaining === 0) {
callback();
}
promises.forEach(function (promise) {
promise.then(function () {
remaining--;
if (remaining === 0) {
callback();
}
});
});
})(dropPromises, handleFills); // Wait for drops, then fill
} else {
handleFills(); // No drops, proceed directly to filling
}
}
// Step 3: Fill empty spaces from the top
function handleFills() {
var fillsOccurred = false;
var fillPromises = [];
for (var col = 0; col < BOARD_SIZE; col++) {
var fillRow = -1; // Start generating candies above the board
for (var row = BOARD_SIZE - 1; row >= 0; row--) {
if (board[row][col] === null) {
// Found an empty spot
var type = getRandomCandyType();
var candy = new Candy(type, fillRow, col); // Create above the board
candy.moveTo(fillRow, col, false); // Place instantly above
gameBoardContainer.addChild(candy);
board[row][col] = candy; // Assign to target cell in model
candies.push(candy);
// Animate move into place
var delay = (-fillRow - 1) * 50; // Stagger based on how high it starts
var fillTween = candy.moveTo(row, col, true, delay);
fillPromises.push({
then: function then(resolve) {
if (fillTween && fillTween.vars) {
fillTween.vars.onFinish = resolve;
} else {
resolve(); // Resolve immediately if fillTween or vars is undefined
}
}
});
fillsOccurred = true;
fillRow--; // Next candy starts even higher
}
}
}
if (fillsOccurred) {
if (typeof Promise !== 'undefined' && Promise.all) {
Promise.all(fillPromises).then(handleMatches); // Wait for fills, then check for new matches (cascade)
} else {
console.error("Promise is not supported in this environment.");
handleMatches(); // Fallback to directly calling handleMatches
}
} else {
// No fills needed, implies no matches occurred in the previous step either. Board is stable.
handleMatches(); // Final check (will find no matches and trigger end processing)
}
}
function findMatches() {
var matches = [];
var checked = []; // Keep track of candies already part of a found match
function markChecked(candy) {
if (candy && checked.indexOf(candy) === -1) {
checked.push(candy);
}
}
// Check horizontal matches
for (var row = 0; row < BOARD_SIZE; row++) {
for (var col = 0; col < BOARD_SIZE - 2; col++) {
var candy1 = board[row][col];
if (!candy1 || checked.indexOf(candy1) > -1) {
continue;
} // Skip nulls or already checked
var candy2 = board[row][col + 1];
var candy3 = board[row][col + 2];
if (candy2 && candy3 && candy1.type === candy2.type && candy1.type === candy3.type) {
// Found a horizontal match of at least 3
var currentMatch = [candy1, candy2, candy3];
var matchLength = 3;
// Check for longer match
for (var k = col + 3; k < BOARD_SIZE; k++) {
var nextCandy = board[row][k];
if (nextCandy && nextCandy.type === candy1.type) {
currentMatch.push(nextCandy);
matchLength++;
} else {
break; // End of match
}
}
// Store match details
matches.push({
candies: currentMatch,
// Store the actual candy objects
row: row,
col: col,
length: matchLength,
horizontal: true
});
// Mark all candies in this match as checked
currentMatch.forEach(markChecked);
col += matchLength - 1; // Skip checked candies in the outer loop
}
}
}
// Check vertical matches
for (var col = 0; col < BOARD_SIZE; col++) {
for (var row = 0; row < BOARD_SIZE - 2; row++) {
var candy1 = board[row][col];
// Skip nulls or candies already part of a horizontal match
if (!candy1 || checked.indexOf(candy1) > -1) {
continue;
}
var candy2 = board[row + 1][col];
var candy3 = board[row + 2][col];
if (candy2 && candy3 && candy1.type === candy2.type && candy1.type === candy3.type) {
// Found a vertical match of at least 3
var currentMatch = [candy1, candy2, candy3];
var matchLength = 3;
// Check for longer match
for (var k = row + 3; k < BOARD_SIZE; k++) {
var nextCandy = board[k][col];
if (nextCandy && nextCandy.type === candy1.type) {
// Avoid adding if already part of a horizontal match found earlier
if (checked.indexOf(nextCandy) === -1) {
currentMatch.push(nextCandy);
} else {
// If it was part of a horizontal match, just increase length but don't re-add
}
matchLength++;
} else {
break; // End of match
}
}
// Store match details
matches.push({
candies: currentMatch,
// Store the actual candy objects
row: row,
col: col,
length: matchLength,
horizontal: false
});
// Mark all candies in this match as checked
currentMatch.forEach(markChecked);
row += matchLength - 1; // Skip checked candies in the outer loop
}
}
}
return matches;
}
function hasPossibleMoves() {
// Check horizontal swaps that could create a match
for (var row = 0; row < BOARD_SIZE; row++) {
for (var col = 0; col < BOARD_SIZE - 1; col++) {
if (canSwapCreateMatch(row, col, row, col + 1)) {
return true;
}
}
}
// Check vertical swaps that could create a match
for (var row = 0; row < BOARD_SIZE - 1; row++) {
for (var col = 0; col < BOARD_SIZE; col++) {
if (canSwapCreateMatch(row, col, row + 1, col)) {
return true;
}
}
}
return false; // No possible moves found
}
// Helper function for hasPossibleMoves
function canSwapCreateMatch(r1, c1, r2, c2) {
var candy1 = board[r1][c1];
var candy2 = board[r2][c2];
if (!candy1 || !candy2) {
return false;
} // Cannot swap with empty space
// Temporarily swap types in the model (don't need full object swap)
board[r1][c1] = candy2;
board[r2][c2] = candy1;
// Check if this swap creates a match around either position
var createsMatch = checkMatchAt(r1, c1) || checkMatchAt(r2, c2) || findMatches().length > 0; // Simplified check
// Swap back
board[r1][c1] = candy1;
board[r2][c2] = candy2;
return createsMatch;
}
function reshuffleBoard() {
if (isProcessing) {
// Don't reshuffle if already processing
LK.setTimeout(reshuffleBoard, 500); // Try again later
return;
}
isProcessing = true; // Block input during reshuffle
// Display reshuffling message
var reshuffleTxt = new Text2('Reshuffling...', {
size: 100,
fill: 0xFFFFFF
});
reshuffleTxt.anchor.set(0.5, 0.5);
LK.gui.center.addChild(reshuffleTxt);
// 1. Collect all current candy types and special status
var currentCandiesInfo = [];
candies.forEach(function (candy) {
currentCandiesInfo.push({
type: candy.type,
isSpecial: candy.isSpecial // Retain special status during reshuffle
});
candy.destroy(); // Remove old visuals immediately
});
candies = []; // Clear the list
board = []; // Clear the board model
// 2. Shuffle the collected info
for (var i = currentCandiesInfo.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var _ref = [currentCandiesInfo[j], currentCandiesInfo[i]];
currentCandiesInfo[i] = _ref[0];
currentCandiesInfo[j] = _ref[1];
}
// 3. Rebuild the board ensuring no initial matches AND possible moves
var attempts = 0;
do {
attempts++;
if (attempts > 10) {
// Safety break if it struggles too much
console.error("Failed to reshuffle into a valid state after 10 attempts.");
// Could force a simple known-good layout here or reset level
isProcessing = false; // Unlock
reshuffleTxt.destroy();
// Maybe show a "No more moves" game over?
checkGameState(true); // Force check, likely leading to game over if moves are 0
return;
}
// Clear board for re-attempt
candies.forEach(function (c) {
return c.destroy();
});
candies = [];
board = [];
var infoIndex = 0;
for (var row = 0; row < BOARD_SIZE; row++) {
board[row] = [];
for (var col = 0; col < BOARD_SIZE; col++) {
var info = currentCandiesInfo[infoIndex++];
board[row][col] = null; // Temp null for checkMatchAt
// Ensure no immediate matches with shuffled type
var placeAttempts = 0;
do {
var currentInfo = info; // Use the shuffled info
board[row][col] = {
type: currentInfo.type,
isSpecial: currentInfo.isSpecial
}; // Temp place
placeAttempts++;
if (placeAttempts > currentCandiesInfo.length) {
// Avoid infinite loop if types are very limited
// Swap with a later random type if stuck
var swapIdx = infoIndex + Math.floor(Math.random() * (currentCandiesInfo.length - infoIndex));
if (swapIdx < currentCandiesInfo.length) {
var _ref2 = [currentCandiesInfo[swapIdx], currentCandiesInfo[infoIndex - 1]];
currentCandiesInfo[infoIndex - 1] = _ref2[0];
currentCandiesInfo[swapIdx] = _ref2[1];
info = currentCandiesInfo[infoIndex - 1]; // Get the newly swapped info
}
board[row][col] = {
type: info.type,
isSpecial: info.isSpecial
}; // Try again with swapped
break; // Exit do-while for this cell after swap
}
} while (checkMatchAt(row, col)); // Check if placing this type creates a match
// Create the actual candy
var candy = new Candy(board[row][col].type, row, col);
candy.setSpecial(board[row][col].isSpecial);
candy.alpha = 0; // Start invisible
candy.moveTo(row, col, false);
gameBoardContainer.addChild(candy);
board[row][col] = candy; // Replace temp object with real candy
candies.push(candy);
// Animate fade-in
tween(candy, {
alpha: 1
}, {
duration: 300,
delay: (row + col) * 20
});
}
}
// Loop condition: Keep reshuffling if the new board has no possible moves
} while (!hasPossibleMoves() && attempts < 10);
// Reshuffle successful
LK.setTimeout(function () {
reshuffleTxt.destroy();
isProcessing = false; // Unlock the board
}, 500); // Keep message visible briefly
}
function checkGameState() {
var forceCheck = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
// Don't check state while the board is actively processing, unless forced (e.g., reshuffle failed)
if (isProcessing && !forceCheck) {
return;
}
// Check Win Condition
if (score >= targetScore) {
console.log("Level " + level + " Complete!");
isProcessing = true; // Prevent further input
// Update storage
level++;
storage.level = level;
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
}
storage.score = score; // Save current score too, maybe for win screen display
LK.setTimeout(function () {
LK.showYouWin({
level: level - 1,
score: score,
highScore: highScore
});
}, 1000); // Delay for effect
}
// Check Lose Condition
else if (moves <= 0) {
console.log("Game Over - Out of Moves!");
isProcessing = true; // Prevent further input
// Update high score if needed
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
}
storage.score = score; // Save final score
LK.setTimeout(function () {
LK.showGameOver({
level: level,
score: score,
highScore: highScore
});
}, 1000); // Delay for effect
}
// Note: The "no possible moves" scenario is now handled by reshuffleBoard,
// but if reshuffling fails repeatedly, it might also lead to a game over state here.
}
// --- Game Start ---
setupGame();
Generate a high-quality icon asset of the specific **blue candy** shown in the provided Candy Crush screenshot. * **Shape:** A smooth, **round, slightly flattened sphere or thick disc** shape with perfectly curved edges. It should look plump and solid. * **Color:** A bright, **vibrant, medium blue**. Clear and saturated, avoiding overly dark (navy) or light (sky blue) tones. * **Surface & Finish:** **Highly glossy** and reflective, like polished hard candy or a glass marble. The surface should look perfectly smooth. * **Lighting & Highlights:** Features a **prominent, distinct, curved white specular highlight** positioned near the **top-left edge**, following the candy's spherical contour. Additional subtle, broader highlights should be visible across the top surface, giving it dimension. Clear shading should be present on the bottom and right sides to emphasize its **3D, spherical volume**. * **Style:** Clean, **stylized 3D render**, matching the cheerful, polished, and sli. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Basic Prompt: Bright green candy, emerald green tones, crystallized texture, covered in small sugar granules, looking sweet and delicious. More Detailed Prompt (Optional): Bright green candy, with hints of emerald green and light lime green sparkles, crystallized texture, covered in small sugar granules, looking fresh and delicious as if it was just made. There is a faint halo of light around the candy, and the background is blurred. Additional Details to Add to the Prompt: Shape: Round, square, heart-shaped, etc. Material: Glass, frosted glass, sugar crystal, etc. Lighting: Soft, hard, dramatic, etc. Background: Solid color, patterned, blurred, themed (e.g., candy store), etc. Additional Objects: Other candies, paper packaging, ribbon, etc. Example Combined Prompt: Round, bright green candy with emerald green and lime green tones, crystallized texture, covered in small sugar granules, looking fresh and delicious. Soft lighting, blurred background.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Basic Prompt: Bright orange candy, in shades of orange, with a smooth and glossy surface, sweet and appealing looking confection. More Detailed Prompt (Optional): Vibrant orange candy, luminous as if lit by sunlight, with hints of orange and light tangerine tones, possessing a smooth and flawless surface, high-quality candy. The candy has light reflections and the background is softly blurred. Additional Details to Add to the Prompt: Shape: Sphere, cube, star, etc. Texture: Smooth, matte, slightly grainy, etc. Lighting: Natural light, studio light, warm light, cool light, etc. Background: White, colored, patterned, candy store, kitchen counter, etc. Additional Objects: Candy wrapper, glass jar, candy stand, etc. Example Combined Prompt: Sphere-shaped, bright orange candy, luminous as if lit by sunlight, with hints of orange and light tangerine tones, possessing a smooth and flawless surface, high-quality candy. There are distinct light reflections on the candy. The background is white. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Basic Prompt: Bright purple candy, in shades of lavender and violet, with a smooth surface, sweet and enticing looking confection. More Detailed Prompt (Optional): Deep purple in color, with hints of lavender and light lilac where the light hits it, possessing a smooth and glossy surface, crystal clear like glass, high-quality purple candy. There's a subtle halo of light around the candy, and the background is softly blurred. Additional Details to Add to the Prompt: Shape: Crystal, drop, heart, round, etc. Texture: Glossy, matte, frosted, slightly rough, etc. Lighting: Soft, dramatic, natural light, artificial light, etc. Background: Solid color, patterned, candy store, countertop, etc. Additional Objects: Other candies, transparent packaging, ribbon, etc. Basic Prompt: Bright purple candy, in shades of lavender and violet, with a smooth surface, sweet and enticing looking confection. More Detailed Prompt (Optional): Deep purple in color, with hints of lavender and light lilac where th. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Basic Prompt: Bright yellow candy, lemon yellow color, smooth surface, sweet and tempting looking treat. More Detailed Prompt (Optional): Vibrant, sunny yellow candy, with subtle hints of lemon and gold. It has a smooth, glossy surface, almost like glass. The light catches it just right, creating a small sparkle. The background is slightly blurred. More Variations to add (Optional): Shape: Sphere, star, gumball, square, etc. Texture: Gummy, hard candy, crystalline, etc. Lighting: Soft, harsh, natural, artificial, etc. Background: Plain, gradient, candy store, etc. Additions: Candy wrapper, other candies, etc. Example Combination Prompt: Round, bright yellow candy like a gumball. Has a smooth, glossy surface with soft lighting. Hints of lemon and gold colors, p. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Basic Prompt: Bright red candy, vibrant crimson color, smooth surface, sweet and appealing treat. More Detailed Prompt (Optional): A glistening, ruby-red candy, with a smooth, reflective surface. The color is a rich, deep crimson, almost like a precious gem. The light catches it beautifully, creating highlights and shadows. The background is soft and blurred. Possible Variations to Add (Optional): Shape: Heart, sphere, cube, star, twisted, etc. Texture: Glossy, matte, gummy, hard, crystalline, etc. Lighting: Soft, harsh, natural, artificial, dramatic, etc. Background: Solid color, patterned, candy store, blurred, bokeh, etc. Additions: Candy wrapper, other candies, sprinkles, sugar coating, etc. Example Combination Prompt: A heart-shaped, glistening, ruby-red candy with a smooth, reflective surface. Rich, deep crimson color. Soft lighting, blurred background.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Generate a high-quality asset representing a **single square background tile** for a match-3 game cell, inspired by the cell interiors in the provided Candy Crush screenshot, but made **significantly more transparent**. * **Shape:** A perfect **square**. * **Base Appearance:** * **Color:** A **muted, desaturated, cool grey-blue** or **dark teal-grey**. * **Texture:** Contains an **extremely subtle, fine-grained texture** (like faint diagonal lines or uniform digital noise) integrated into the color. * **Transparency:** The key feature is **increased translucency**. The tile should be **moderately to significantly see-through**, allowing layers placed underneath it to be clearly visible. It should *not* be fully opaque like the original screenshot implies, nor fully transparent (invisible). Aim for roughly **40-60% opacity**. * **Lighting:** Maintain **soft, even, ambient lighting** across the surface of the square. No internal highlights or shadows within the tile. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows