User prompt
yanıp sönme olayı biraz daha yavaş olsun
User prompt
kristaller patlamadan önce iki defa yanıp sönsün ve patlayacağını daha iyi belli etsin
User prompt
her spinin benden 100 coin kesmesi lazım
User prompt
şimdi adam akıllı görünür bir şekilde kristallerin altında yeni bir spin butonu yap
User prompt
sil spin butonunu
User prompt
ya yap artık
User prompt
tamam kontrol et hepsini ve spin butonunu kristallerin hemen altında görünür yap. Lütfen yap artık.
User prompt
spin butonunu ekranın altında görünür yap
User prompt
spin butonuna yeni bir asset ata ve görünür yap
User prompt
ya kristalleri biraz yukarı kaydır. spin butonunu da hemen altına koy.
User prompt
hala görünmyor
User prompt
hala görünmüyor
User prompt
şimdi de yüz kere spin butonunu ekranın altın getir dememe reğmen getirmediğin için spin butonuna basamıyorum ve oyun ilerlemiyor
User prompt
nereye tıklarsam tıklayayım oyun yenileniyor
User prompt
tamam ekrana en alta ekle o zaman. Sadece ona basınca yeni oyuna geçsin
User prompt
spin butonu oluşturur musun lütfen. ayrıca her spinde 100 puan düşmesi lazım ana puanımdan
User prompt
spin butonu oluştur aşağıda. Ona tıklayınca yeniden başlat oyunu. Ayrııca her spin 100 puan. oyuncu 2000 puan ile başlasın oyuna
User prompt
joker kristali nadir bir şey olsun. Çıkma ihtimali %10 felan olsun her tur
User prompt
bağlı olmak zorunda değil.
User prompt
jokerler ile beraber herhangi bir kristal 10 ya da 10'u geçerse de patlasın ve yerine yenileri aksın yukarıdan ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Karıştır butonu olsun altta. Ben ona basınca yeniden insin kristaller. Bir de kristallere 3 saniyelik yukarıdan inme efekti uygulayabilirsin. Aşağı akıyor gibi ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
Crystal Cascade: 4x6 Slot Frenzy
Initial prompt
Bu 2D slot oyunu, 4x6 boyutlarında, yani toplamda 24 kristalin aynı anda göründüğü dikdörtgen bir oyun alanında geçer. Oyunun temel görsel öğeleri beş farklı renkte kristallerden oluşur: mavi, sarı, kırmızı, mor ve beyaz. Her biri kendine özgü bir puan değerine sahiptir. Mavi kristaller oyuncuya adet başına 2 puan kazandırırken, sarılar 5, kırmızılar 10, morlar 20 ve en değerli olan beyaz kristaller 50 puan verir. Bunların dışında bir de joker kristal yer alır. Joker kristal, herhangi bir rengi temsil edebilir; yani bir patlama kombinasyonunun içinde eksik olan herhangi bir rengi tamamlamak için kullanılabilir. Görünüm olarak diğerlerinden kolayca ayırt edilecek şekilde tasarlanır ve özel bir ışıltıya sahip olur. Oyun başladığında ekran rastgele kristallerle dolar ve oyuncu herhangi bir şey yapmadan önce sistem otomatik olarak aynı renkten en az 10 kristal olup olmadığını kontrol eder. Eğer varsa bu kristaller patlar ve oyuncuya toplam puanı kazandırılır. Patlayan kristaller yukarıdan gelen yenileriyle doldurulur, tıpkı klasik “cascading” (akma) slot oyunlarında olduğu gibi. Bu akma sistemi, yeni kombinasyonlar oluşturabilecek şekilde ilerler. Örneğin mavi kristalden ekranda 8 tane varsa ve yukarıdan inen 2 mavi kristalle sayı 10’a tamamlanırsa, bu yeni kombinasyon da patlayarak aynı süreç tekrar eder. Böylece tek bir döngüde zincirleme patlamalarla daha fazla puan kazanma imkanı doğar. Patlamalar sırasında kullanılan jokerler de, puan hesaba katılırken kombinasyonda temsil ettikleri kristalin puanı üzerinden değerlendirilir. Oyunun akışı hızlı ve akıcıdır. Her döngüde ekranın üstünden kristaller düşerken hafif titreşim efektleri ve patlama animasyonları ile görsel bir tatmin sağlanır. Arka planda düşük tempolu bir elektronik müzik çalarken, patlamalarla beraber ses efektleri de tetiklenir ve oyuncuya her başarılı kombinasyonda pozitif geri bildirim verir. Oyunun temel amacı yüksek puan elde etmek ve zincirleme patlamalarla ekranı olabildiğince sık temizlemektir. Oyuncuların strateji geliştirmesine gerek kalmadan tamamen şansa dayalı bir sistem işlemesine rağmen, görsel tatmin, puan artışı ve bonuslarla oyuncunun dikkatini uzun süre çekebilir. Bu nedenle oyun, mobil ya da tarayıcı üzerinden rahatlıkla oynanabilecek bağımlılık yapıcı bir yapıya sahiptir.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Passive Bug class (background animation) var Bug = Container.expand(function () { var self = Container.call(this); // Pick a random bug type var bugTypes = [{ id: 'bocek_orumcek', name: 'örümcek' }, { id: 'bocek_kirkayak', name: 'kırkayak' }, { id: 'bocek_karafatma', name: 'karafatma' }, { id: 'bocek_hamamb', name: 'hamamböceği' }]; var bugType = bugTypes[Math.floor(Math.random() * bugTypes.length)]; self.bugType = bugType.name; // Attach bug asset self.asset = self.attachAsset(bugType.id, { anchorX: 0.5, anchorY: 0.5 }); // State self.active = false; self.targetX = 0; self.targetY = 0; self.speed = 2 + Math.random() * 2; // px per frame self.hideTimeout = null; self.showTimeout = null; // Helper: set random position (offscreen or edge) self.setRandomStart = function () { // Appear from a random edge var edge = Math.floor(Math.random() * 4); // 0: left, 1: right, 2: top, 3: bottom var margin = 100; if (edge === 0) { // left self.x = -margin; self.y = margin + Math.random() * (2732 - 2 * margin); } else if (edge === 1) { // right self.x = 2048 + margin; self.y = margin + Math.random() * (2732 - 2 * margin); } else if (edge === 2) { // top self.x = margin + Math.random() * (2048 - 2 * margin); self.y = -margin; } else { // bottom self.x = margin + Math.random() * (2048 - 2 * margin); self.y = 2732 + margin; } }; // Helper: set random target inside screen self.setRandomTarget = function () { var margin = 200; self.targetX = margin + Math.random() * (2048 - 2 * margin); self.targetY = margin + Math.random() * (2732 - 2 * margin); }; // Show bug: appear and start moving self.showBug = function () { self.setRandomStart(); self.setRandomTarget(); self.visible = true; self.active = true; self.alpha = 0.7 + Math.random() * 0.3; self.scaleX = self.scaleY = 0.7 + Math.random() * 0.5; // Set random speed self.speed = 2 + Math.random() * 2; // Set random zIndex so bugs can overlap self.zIndex = 0; // Set random rotation self.rotation = Math.random() * Math.PI * 2; // Set how long bug will stay visible var minShow = 2000, maxShow = 5000; if (self.hideTimeout) LK.clearTimeout(self.hideTimeout); self.hideTimeout = LK.setTimeout(function () { self.hideBug(); }, minShow + Math.random() * (maxShow - minShow)); }; // Hide bug: disappear and schedule next appearance self.hideBug = function () { self.visible = false; self.active = false; // Schedule next appearance var minDelay = 1500, maxDelay = 4000; if (self.showTimeout) LK.clearTimeout(self.showTimeout); self.showTimeout = LK.setTimeout(function () { self.showBug(); }, minDelay + Math.random() * (maxDelay - minDelay)); }; // Per-frame update self.update = function () { if (!self.active) return; // --- Jitter: bugs shake a little as they move --- // Add a small random offset to position for jitter var jitterAmount = 2 + Math.random() * 2; var jitterAngle = Math.random() * Math.PI * 2; var jitterX = Math.cos(jitterAngle) * jitterAmount; var jitterY = Math.sin(jitterAngle) * jitterAmount; // --- Sudden direction change: sometimes pick a new target suddenly --- if (typeof self._framesToNextTurn === "undefined") { self._framesToNextTurn = 30 + Math.floor(Math.random() * 60); } self._framesToNextTurn--; if (self._framesToNextTurn <= 0) { // 40% chance to pick a new random target (simulate sudden turn) if (Math.random() < 0.4) { self.setRandomTarget(); // Also randomize speed and rotation for realism self.speed = 2 + Math.random() * 2; self.rotation = Math.random() * Math.PI * 2; } self._framesToNextTurn = 30 + Math.floor(Math.random() * 60); } // Move towards target var dx = self.targetX - self.x; var dy = self.targetY - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < self.speed) { // Arrived at target, pick a new target self.setRandomTarget(); } else { // Move with a little jitter self.x += dx / dist * self.speed + jitterX; self.y += dy / dist * self.speed + jitterY; // Optionally, face the direction of movement self.rotation = Math.atan2(dy, dx) + (Math.random() - 0.5) * 0.2; } }; // Start hidden, then schedule first appearance self.visible = false; self.active = false; self.setRandomStart(); self.setRandomTarget(); // Schedule first appearance self.showTimeout = LK.setTimeout(function () { self.showBug(); }, 1000 + Math.random() * 2000); return self; }); // Crystal class var Crystal = Container.expand(function () { var self = Container.call(this); // type: 'blue', 'yellow', 'red', 'purple', 'white', 'joker' self.type = 'blue'; self.row = 0; self.col = 0; self.popping = false; // Attach correct asset function setAsset() { if (self.asset) self.removeChild(self.asset); var assetId = 'crystal_' + self.type; self.asset = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); } self.setType = function (type) { self.type = type; setAsset(); }; // Animate pop self.pop = function (_onFinish) { if (self.popping) return; self.popping = true; tween(self, { scaleX: 1.4, scaleY: 1.4, alpha: 0 }, { duration: 350, easing: tween.easeOut, onFinish: function onFinish() { if (_onFinish) _onFinish(); } }); LK.getSound('crystal_pop').play(); }; // Reset visual state self.resetVisual = function () { self.scaleX = 1; self.scaleY = 1; self.alpha = 1; self.popping = false; // Remove freeze frame if present if (self.freezeFrame) { self.removeChild(self.freezeFrame); self.freezeFrame = null; } }; // Add or remove freeze frame self.setFreezeFrame = function (show) { if (show) { if (!self.freezeFrame) { // Create a square frame using a box shape, slightly larger than the crystal var frame = LK.getAsset('spin_btn_shape', { anchorX: 0.5, anchorY: 0.5 }); frame.width = self.asset.width * 1.18; frame.height = self.asset.height * 1.18; frame.alpha = 0.7; frame.tint = 0x00e6b8; frame.zIndex = -1; self.addChildAt(frame, 0); self.freezeFrame = frame; } if (self.freezeFrame) self.freezeFrame.visible = true; } else { if (self.freezeFrame) { self.removeChild(self.freezeFrame); self.freezeFrame = null; } } }; setAsset(); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181c2c }); /**** * Game Code ****/ // Böcek görselleri (örümcek, kırkayak, karafatma, hamamböceği) // Add background image behind all elements var backgroundImage = LK.getAsset('background_main', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); game.addChildAt(backgroundImage, 0); // Always at the back // --- Passive Bugs (background animation) --- var bugs = []; // Ensure each bug type is present and moving var bugTypeIds = ['bocek_orumcek', 'bocek_kirkayak', 'bocek_karafatma', 'bocek_hamamb']; for (var i = 0; i < bugTypeIds.length; i++) { var bug = new Bug(); // Force the bug to use a specific type for each instance var bugTypes = [{ id: 'bocek_orumcek', name: 'örümcek' }, { id: 'bocek_kirkayak', name: 'kırkayak' }, { id: 'bocek_karafatma', name: 'karafatma' }, { id: 'bocek_hamamb', name: 'hamamböceği' }]; // Set bug type and asset bug.bugType = bugTypes[i].name; if (bug.asset) bug.removeChild(bug.asset); bug.asset = bug.attachAsset(bugTypeIds[i], { anchorX: 0.5, anchorY: 0.5 }); // Place bugs behind all game elements but above background game.addChildAt(bug, 1); bugs.push(bug); } // --- Game Constants --- // Crystal shapes (box for simplicity, different colors for each type) // Sound effects (placeholders, actual sound assets will be loaded by LK) var BOARD_COLS = 4; var BOARD_ROWS = 6; var CRYSTAL_SIZE = 300; // px var CRYSTAL_SPACING = 24; // px var BOARD_WIDTH = BOARD_COLS * CRYSTAL_SIZE + (BOARD_COLS - 1) * CRYSTAL_SPACING; var BOARD_HEIGHT = BOARD_ROWS * CRYSTAL_SIZE + (BOARD_ROWS - 1) * CRYSTAL_SPACING; var BOARD_X = (2048 - BOARD_WIDTH) / 2; var BOARD_Y = (2732 - BOARD_HEIGHT) / 2 - 180; // move board up to make space for spin button var CRYSTAL_TYPES = ['blue', 'yellow', 'red', 'purple', 'white', 'joker']; var CRYSTAL_COLORS = { blue: 0x3a8ee6, yellow: 0xf7e14a, red: 0xe64a3a, purple: 0xb44ae6, white: 0xffffff, joker: 0x00e6b8 }; // Score values per crystal type var CRYSTAL_SCORES = { blue: 10, yellow: 12, red: 14, purple: 16, white: 18, joker: 25 }; // --- Game State --- var board = []; // 2D array [row][col] of Crystal or null var poppingCrystals = []; // Crystals currently popping var isCascading = false; var scoreTxt; var cascadeTimeout = null; // --- Level and Joker Probability State --- var level = 1; var jokerProbability = 0.10; // Start at 10% // --- UI: Score --- scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // --- Board Initialization --- function createEmptyBoard() { board = []; for (var row = 0; row < BOARD_ROWS; row++) { var rowArr = []; for (var col = 0; col < BOARD_COLS; col++) { rowArr.push(null); } board.push(rowArr); } } function randomCrystalType() { // Use current jokerProbability (decreases as level increases) var r = Math.random(); if (r < jokerProbability) return 'joker'; var idx = Math.floor(Math.random() * 5); return CRYSTAL_TYPES[idx]; } function spawnCrystal(row, col, type) { var crystal = new Crystal(); crystal.setType(type); crystal.row = row; crystal.col = col; // Position var x = BOARD_X + col * (CRYSTAL_SIZE + CRYSTAL_SPACING) + CRYSTAL_SIZE / 2; var y = BOARD_Y + row * (CRYSTAL_SIZE + CRYSTAL_SPACING) + CRYSTAL_SIZE / 2; crystal.x = x; crystal.y = y; crystal.resetVisual(); game.addChild(crystal); // Make crystal fixed: prevent any movement or change // If this crystal is frozen, make sure it cannot be replaced or destroyed if (crystal.frozen) { crystal.interactive = false; crystal.buttonMode = false; crystal.down = function () {}; crystal.pop = function () {}; crystal.setType = function (type) {}; crystal.resetVisual = function () {}; crystal.setFreezeFrame = function () {}; } return crystal; } function fillBoardRandom() { for (var row = 0; row < BOARD_ROWS; row++) { for (var col = 0; col < BOARD_COLS; col++) { var type = randomCrystalType(); var crystal = spawnCrystal(row, col, type); board[row][col] = crystal; } } refreshCrystalInteractivity(); } // --- Board Utility --- function getNeighbors(row, col) { // 4-directional neighbors var n = []; if (row > 0) n.push([row - 1, col]); if (row < BOARD_ROWS - 1) n.push([row + 1, col]); if (col > 0) n.push([row, col - 1]); if (col < BOARD_COLS - 1) n.push([row, col + 1]); return n; } // --- Find Groups for Popping --- function findPopGroups() { // Returns: [{type, crystals: [Crystal, ...], jokerCount: n}] var groups = []; // Count all crystals by type (excluding jokers) var typeCounts = {}; var typeCrystals = {}; var jokerCrystals = []; for (var i = 0; i < CRYSTAL_TYPES.length; i++) { var t = CRYSTAL_TYPES[i]; if (t !== 'joker') { typeCounts[t] = 0; typeCrystals[t] = []; } } for (var row = 0; row < BOARD_ROWS; row++) { for (var col = 0; col < BOARD_COLS; col++) { var crystal = board[row][col]; if (!crystal) continue; if (crystal.type === 'joker') { // Frozen jokers can pop if they are part of a valid pop group, so always count them jokerCrystals.push(crystal); } else { // Frozen crystals can pop if they are part of a valid pop group, so always count them typeCounts[crystal.type]++; typeCrystals[crystal.type].push(crystal); } } } // For each color, see if color+joker >= 10, if so, pop all of that color and enough jokers for (var t in typeCounts) { var total = typeCounts[t] + jokerCrystals.length; if (total >= 10) { // Add all of this color var groupCrystals = []; for (var i = 0; i < typeCrystals[t].length; i++) { groupCrystals.push(typeCrystals[t][i]); } // Add jokers for (var j = 0; j < jokerCrystals.length; j++) { groupCrystals.push(jokerCrystals[j]); } groups.push({ type: t, crystals: groupCrystals, jokerCount: jokerCrystals.length }); } } return groups; } // --- Pop Groups --- function popGroups(groups, onFinish) { if (groups.length === 0) { if (onFinish) onFinish(); return; } isCascading = true; poppingCrystals = []; var popped = {}; // --- Combo Multiplier Logic --- if (typeof comboMultiplier === "undefined") { comboMultiplier = 1; } if (typeof lastCascadeMoveId === "undefined") { lastCascadeMoveId = 0; } if (typeof currentCascadeMoveId === "undefined") { currentCascadeMoveId = 0; } // If this is a new cascade move, reset comboMultiplier if (typeof cascadeMoveActive === "undefined" || !cascadeMoveActive) { comboMultiplier = 1; currentCascadeMoveId = Date.now(); lastCascadeMoveId = currentCascadeMoveId; cascadeMoveActive = true; } else { // If still in the same move, increment comboMultiplier if (currentCascadeMoveId === lastCascadeMoveId) { comboMultiplier++; } else { comboMultiplier = 1; currentCascadeMoveId = Date.now(); lastCascadeMoveId = currentCascadeMoveId; } } // Mark all crystals to pop (avoid double pop) for (var g = 0; g < groups.length; g++) { var group = groups[g]; for (var i = 0; i < group.crystals.length; i++) { var c = group.crystals[i]; var key = c.row + ',' + c.col; if (!popped[key]) { popped[key] = true; poppingCrystals.push(c); } } } // Animate pop with 2s shake before popping var popCount = poppingCrystals.length; var finished = 0; for (var i = 0; i < poppingCrystals.length; i++) { var c = poppingCrystals[i]; (function (crystal) { // Save original position var origX = crystal.x; var origY = crystal.y; var shakeDuration = 2000; var shakeInterval = 30; var elapsed = 0; var shakeTimer = null; function startShake() { shakeTimer = LK.setInterval(function () { // Random shake offset, smaller amplitude for subtle effect var dx = (Math.random() - 0.5) * 24; var dy = (Math.random() - 0.5) * 24; crystal.x = origX + dx; crystal.y = origY + dy; elapsed += shakeInterval; if (elapsed >= shakeDuration) { // End shake, restore position LK.clearInterval(shakeTimer); crystal.x = origX; crystal.y = origY; // Now pop crystal.pop(function () { finished++; if (finished === popCount) { // Remove from board for (var j = 0; j < poppingCrystals.length; j++) { var pc = poppingCrystals[j]; if (board[pc.row][pc.col] === pc) { board[pc.row][pc.col] = null; pc.destroy(); } } poppingCrystals = []; if (onFinish) onFinish(); } }); } }, shakeInterval); } startShake(); })(c); } // Score with combo multiplier for (var g = 0; g < groups.length; g++) { var group = groups[g]; var baseScore = 0; for (var i = 0; i < group.crystals.length; i++) { var c = group.crystals[i]; var t = c.type === 'joker' ? group.type : c.type; baseScore += CRYSTAL_SCORES[t]; } LK.setScore(LK.getScore() + baseScore * comboMultiplier); } scoreTxt.setText(LK.getScore()); LK.getSound('cascade').play(); LK.effects.flashObject(scoreTxt, 0xffff00, 400); // --- Level Up Logic --- var newLevel = Math.floor(LK.getScore() / 1000) + 1; if (newLevel > level) { level = newLevel; // Decrease jokerProbability by 1% per level up, but not below 1% jokerProbability = Math.max(0.01, 0.10 - (level - 1) * 0.01); // Optional: flash score text or give feedback for level up LK.effects.flashObject(scoreTxt, 0x00ff00, 600); } } // --- Cascade: Drop Crystals Down and Fill --- function cascadeBoard(onFinish) { // For each column, drop crystals down to fill empty spaces var changed = false; for (var col = 0; col < BOARD_COLS; col++) { var emptyRows = []; for (var row = BOARD_ROWS - 1; row >= 0; row--) { if (!board[row][col]) { emptyRows.push(row); } else if (emptyRows.length > 0) { // Move crystal down var targetRow = emptyRows.shift(); var crystal = board[row][col]; board[targetRow][col] = crystal; board[row][col] = null; crystal.row = targetRow; crystal.col = col; // Animate drop var newY = BOARD_Y + targetRow * (CRYSTAL_SIZE + CRYSTAL_SPACING) + CRYSTAL_SIZE / 2; tween(crystal, { y: newY }, { duration: 220, easing: tween.cubicOut }); emptyRows.push(row); changed = true; } } } // Fill empty spaces at the top for (var col = 0; col < BOARD_COLS; col++) { for (var row = 0; row < BOARD_ROWS; row++) { if (!board[row][col]) { var type = randomCrystalType(); var crystal = spawnCrystal(row, col, type); board[row][col] = crystal; // Drop from above var startY = BOARD_Y - CRYSTAL_SIZE; crystal.y = startY; var targetY = BOARD_Y + row * (CRYSTAL_SIZE + CRYSTAL_SPACING) + CRYSTAL_SIZE / 2; tween(crystal, { y: targetY }, { duration: 220, easing: tween.cubicOut }); changed = true; } } } // Wait for animation, then call onFinish LK.setTimeout(function () { refreshCrystalInteractivity(); if (onFinish) onFinish(); }, 250); } // --- Main Game Loop: Cascade, Pop, Repeat --- function startCascadeLoop() { if (cascadeTimeout) { LK.clearTimeout(cascadeTimeout); cascadeTimeout = null; } var groups = findPopGroups(); if (groups.length === 0) { isCascading = false; return; } popGroups(groups, function () { cascadeBoard(function () { // Chain reaction! cascadeTimeout = LK.setTimeout(function () { startCascadeLoop(); }, 120); }); }); } // --- Start New Game --- function startGame() { LK.setScore(2000); scoreTxt.setText('2000'); // Reset combo multiplier state for new game comboMultiplier = 1; cascadeMoveActive = false; // Reset bomb usage for new game bombUsed = false; // Reset swap usage for new game swapUsedCount = 0; if (swapBtnText) swapBtnText.setText('Swap (' + (swapMaxCount - swapUsedCount) + ')'); if (swapBtnBg) swapBtnBg.alpha = 1; if (swapBtnContainer) { swapBtnContainer.interactive = true; swapBtnContainer.buttonMode = true; } // Reset freeze usage for new game freezeUsedCount = 0; freezeBtnText.setText('Freeze (' + (freezeMaxCount - freezeUsedCount) + ')'); freezeBtnBg.alpha = 1; freezeBtnContainer.interactive = true; freezeBtnContainer.buttonMode = true; // Reset level and jokerProbability level = 1; jokerProbability = 0.10; // Remove all crystals for (var row = 0; row < BOARD_ROWS; row++) { for (var col = 0; col < BOARD_COLS; col++) { if (board[row] && board[row][col]) { board[row][col].frozen = false; board[row][col].alpha = 1; // Remove freeze frame if present if (typeof board[row][col].setFreezeFrame === "function") { board[row][col].setFreezeFrame(false); } board[row][col].destroy(); } } } createEmptyBoard(); fillBoardRandom(); refreshCrystalInteractivity(); LK.setTimeout(function () { startCascadeLoop(); }, 400); } // --- Karıştır Button --- var shuffleBtn = new Text2('Karıştır', { size: 110, fill: 0x00E6B8, font: "Arial Black" }); shuffleBtn.anchor.set(0.5, 1); shuffleBtn.x = LK.gui.width / 2; shuffleBtn.y = LK.gui.height - 60 - 140; // Move up to make space for spin button shuffleBtn.interactive = true; shuffleBtn.buttonMode = true; LK.gui.bottom.addChild(shuffleBtn); function reshuffleBoardWithFall() { if (isCascading) return; // Reset combo multiplier state for new move comboMultiplier = 1; cascadeMoveActive = false; // Remove all crystals for (var row = 0; row < BOARD_ROWS; row++) { for (var col = 0; col < BOARD_COLS; col++) { if (board[row][col]) { // Only destroy if not a frozen crystal if (!board[row][col].frozen) { board[row][col].frozen = false; board[row][col].alpha = 1; // Remove freeze frame if present if (typeof board[row][col].setFreezeFrame === "function") { board[row][col].setFreezeFrame(false); } board[row][col].destroy(); board[row][col] = null; } } } } // Do not recreate the board, just refill non-frozen spots // Fill board with new crystals, but spawn them above and animate falling for (var row = 0; row < BOARD_ROWS; row++) { for (var col = 0; col < BOARD_COLS; col++) { // Eğer kristal donmuş ve justFrozen ise, bu turda silme, sadece işaretle (removeNextTurn) if (board[row][col] && board[row][col].frozen && board[row][col].justFrozen) { // Bir tur bekle, bu turda sadece işaretle board[row][col].justFrozen = false; board[row][col].removeNextTurn = true; if (typeof board[row][col].setFreezeFrame === "function") { board[row][col].setFreezeFrame(false); } tween(board[row][col], { alpha: 1 }, { duration: 300, easing: tween.easeOut }); } // Eğer kristal donmuş ve removeNextTurn işaretli ise, bu turda sil else if (board[row][col] && board[row][col].frozen && board[row][col].removeNextTurn) { board[row][col].frozen = false; board[row][col].removeNextTurn = false; if (typeof board[row][col].setFreezeFrame === "function") { board[row][col].setFreezeFrame(false); } // Kristali sil board[row][col].destroy(); board[row][col] = null; } // Eğer kristal donmuş ve justFrozen değilse, donmayı kaldır (yani ikinci turda donma kalksın) else if (board[row][col] && board[row][col].frozen && !board[row][col].justFrozen) { board[row][col].frozen = false; if (typeof board[row][col].setFreezeFrame === "function") { board[row][col].setFreezeFrame(false); } tween(board[row][col], { alpha: 1 }, { duration: 300, easing: tween.easeOut }); } // If the cell is empty, fill with a new crystal if (!board[row][col]) { var type = randomCrystalType(); var crystal = spawnCrystal(row, col, type); board[row][col] = crystal; // Start above the board var targetY = crystal.y; crystal.y = BOARD_Y - CRYSTAL_SIZE * 2; tween(crystal, { y: targetY }, { duration: 3000, easing: tween.cubicOut }); } else if (board[row][col].frozen) { // Animate frozen crystal to its own position for effect var targetY = BOARD_Y + row * (CRYSTAL_SIZE + CRYSTAL_SPACING) + CRYSTAL_SIZE / 2; tween(board[row][col], { y: targetY }, { duration: 3000, easing: tween.cubicOut }); // Ensure freeze frame is visible if (typeof board[row][col].setFreezeFrame === "function") { board[row][col].setFreezeFrame(true); } board[row][col].alpha = 0.5; } else if (!(board[row][col].type === "joker" && board[row][col].frozen)) { // For all non-frozen crystals (including non-jokers and non-frozen jokers), replace with a new random crystal var prev = board[row][col]; // If this is a frozen joker, skip replacing it if (prev.type === "joker" && prev.frozen) { // Animate frozen joker to its own position for effect var targetY = BOARD_Y + row * (CRYSTAL_SIZE + CRYSTAL_SPACING) + CRYSTAL_SIZE / 2; tween(prev, { y: targetY }, { duration: 3000, easing: tween.cubicOut }); // Ensure freeze frame is visible if (typeof prev.setFreezeFrame === "function") { prev.setFreezeFrame(true); } prev.alpha = 0.5; continue; } var type = randomCrystalType(); // Remove old crystal prev.destroy(); // Spawn new crystal var crystal = spawnCrystal(row, col, type); board[row][col] = crystal; // Start above the board var targetY = crystal.y; crystal.y = BOARD_Y - CRYSTAL_SIZE * 2; tween(crystal, { y: targetY }, { duration: 3000, easing: tween.cubicOut }); } } } refreshCrystalInteractivity(); LK.setTimeout(function () { startCascadeLoop(); }, 3100); } // Button event shuffleBtn.down = function (x, y, obj) { reshuffleBoardWithFall(); }; // --- Spin Button (visible, below crystals) --- var spinBtnContainer = new Container(); var spinBtnBg = LK.getAsset('spin_btn_shape', { anchorX: 0.5, anchorY: 0.5 }); spinBtnContainer.addChild(spinBtnBg); var spinBtnText = new Text2('Spin', { size: 90, fill: 0xffffff, font: "Arial Black" }); spinBtnText.anchor.set(0.5, 0.5); spinBtnText.x = 0; spinBtnText.y = 0; spinBtnContainer.addChild(spinBtnText); // Position: just below the board, centered horizontally spinBtnContainer.x = 2048 / 2; spinBtnContainer.y = BOARD_Y + BOARD_HEIGHT + 100 + spinBtnBg.height / 2; // Make interactive spinBtnContainer.interactive = true; spinBtnContainer.buttonMode = true; spinBtnContainer.down = function (x, y, obj) { if (isCascading) return; var currentCoins = LK.getScore(); if (currentCoins < 100) { // Not enough coins, flash red for feedback LK.effects.flashObject(spinBtnBg, 0xff0000, 300); return; } // Reset combo multiplier state for new move comboMultiplier = 1; cascadeMoveActive = false; LK.setScore(currentCoins - 100); scoreTxt.setText(LK.getScore()); reshuffleBoardWithFall(); // Optional: flash the button for feedback LK.effects.flashObject(spinBtnBg, 0xffffff, 200); }; // Add to game scene (not GUI, so it stays relative to board) game.addChild(spinBtnContainer); // --- Special Power Buttons --- // State for power selection var powerMode = null; // "bomb", "freeze", "swap" or null // --- Swap Button State --- var swapBtnContainer; var swapBtnBg; var swapBtnText; var swapUsedCount = 0; var swapMaxCount = 5; // Helper to clear power mode UI state function clearPowerMode() { powerMode = null; swapFirstCrystal = null; bombBtnBg.alpha = 1; freezeBtnBg.alpha = 1; if (swapBtnBg) swapBtnBg.alpha = 1; } // --- Bomb Button --- var bombBtnContainer = new Container(); var bombBtnBg = LK.getAsset('spin_btn_shape', { anchorX: 0.5, anchorY: 0.5 }); bombBtnBg.tint = 0xff4444; bombBtnContainer.addChild(bombBtnBg); var bombBtnText = new Text2('Bomb (1)', { size: 70, fill: 0xffffff, font: "Arial Black" }); bombBtnText.anchor.set(0.5, 0.5); bombBtnText.x = 0; bombBtnText.y = 0; bombBtnContainer.addChild(bombBtnText); bombBtnContainer.x = spinBtnContainer.x - 500; bombBtnContainer.y = spinBtnContainer.y; bombBtnContainer.interactive = true; bombBtnContainer.buttonMode = true; bombBtnContainer.down = function () { clearPowerMode(); powerMode = "bomb"; bombBtnBg.alpha = 0.7; }; // --- Freeze Button --- var freezeBtnContainer = new Container(); var freezeBtnBg = LK.getAsset('spin_btn_shape', { anchorX: 0.5, anchorY: 0.5 }); freezeBtnBg.tint = 0x44ffd0; freezeBtnContainer.addChild(freezeBtnBg); var freezeBtnText = new Text2('Freeze (' + (freezeMaxCount - freezeUsedCount) + ')', { size: 70, fill: 0xffffff, font: "Arial Black" }); freezeBtnText.anchor.set(0.5, 0.5); freezeBtnText.x = 0; freezeBtnText.y = 0; freezeBtnContainer.addChild(freezeBtnText); freezeBtnContainer.x = spinBtnContainer.x + 500; freezeBtnContainer.y = spinBtnContainer.y; freezeBtnContainer.interactive = true; freezeBtnContainer.buttonMode = true; // Track freeze usage if (typeof freezeUsedCount === "undefined") { var freezeUsedCount = 0; } var freezeMaxCount = 3; freezeBtnContainer.down = function () { clearPowerMode(); if (freezeUsedCount >= freezeMaxCount) { // Visually disable freeze button if limit reached freezeBtnBg.alpha = 0.3; freezeBtnContainer.interactive = false; freezeBtnContainer.buttonMode = false; return; } powerMode = "freeze"; freezeBtnBg.alpha = 0.7; }; // Add to game game.addChild(bombBtnContainer); game.addChild(freezeBtnContainer); // --- Swap Button --- swapBtnContainer = new Container(); swapBtnBg = LK.getAsset('spin_btn_shape', { anchorX: 0.5, anchorY: 0.5 }); swapBtnBg.tint = 0x4488ff; swapBtnContainer.addChild(swapBtnBg); swapBtnText = new Text2('Swap (' + (swapMaxCount - swapUsedCount) + ')', { size: 70, fill: 0xffffff, font: "Arial Black" }); swapBtnText.anchor.set(0.5, 0.5); swapBtnText.x = 0; swapBtnText.y = 0; swapBtnContainer.addChild(swapBtnText); // Position: just below the spin button, centered horizontally swapBtnContainer.x = spinBtnContainer.x; swapBtnContainer.y = spinBtnContainer.y + spinBtnBg.height + 40; swapBtnContainer.interactive = true; swapBtnContainer.buttonMode = true; swapBtnContainer.down = function () { clearPowerMode(); if (swapUsedCount >= swapMaxCount) { swapBtnBg.alpha = 0.3; swapBtnContainer.interactive = false; swapBtnContainer.buttonMode = false; return; } powerMode = "swap"; swapBtnBg.alpha = 0.7; }; // Add to game game.addChild(swapBtnContainer); // --- Power Button Crystal Selection Logic --- // Track bomb usage if (typeof bombUsed === "undefined") { var bombUsed = false; } // Helper to re-apply crystal interactivity for powers function applyPowerCrystalInteractivity() { for (var row = 0; row < BOARD_ROWS; row++) { if (!board[row]) continue; for (var col = 0; col < BOARD_COLS; col++) { (function (r, c) { if (!board[r]) return; var crystal = board[r][c]; if (!crystal) return; crystal.interactive = true; crystal.buttonMode = true; crystal.down = function () { if (isCascading) return; // Bomb: destroy selected and neighbors, only if not used yet if (powerMode === "bomb" && !bombUsed) { var finishBombPop = function finishBombPop() { clearPowerMode(); LK.setTimeout(function () { startCascadeLoop(); }, 400); }; bombUsed = true; bombBtnText.setText('Bomb (0)'); var targets = [[r, c]]; var neighbors = getNeighbors(r, c); for (var i = 0; i < neighbors.length; i++) { var nr = neighbors[i][0], nc = neighbors[i][1]; if (board[nr][nc]) targets.push([nr, nc]); } var poppedCount = 0; var toPop = []; for (var i = 0; i < targets.length; i++) { var tr = targets[i][0], tc = targets[i][1]; if (board[tr][tc]) { toPop.push([tr, tc]); } } if (toPop.length === 0) { finishBombPop(); return; } var finished = 0; for (var i = 0; i < toPop.length; i++) { var tr = toPop[i][0], tc = toPop[i][1]; if (board[tr][tc]) { board[tr][tc].pop(function () { finished++; if (finished === toPop.length) { // Remove all popped for (var j = 0; j < toPop.length; j++) { var rr = toPop[j][0], cc = toPop[j][1]; if (board[rr][cc]) { board[rr][cc].destroy(); board[rr][cc] = null; } } // After bomb pop, trigger cascadeBoard to fill empty spaces cascadeBoard(function () { finishBombPop(); }); } }); } } // Visually disable bomb button bombBtnBg.alpha = 0.3; bombBtnContainer.interactive = false; bombBtnContainer.buttonMode = false; return; } // Freeze: after clicking Freeze, the clicked crystal stays fixed in place and is not replaced on spin/reshuffle else if (powerMode === "freeze") { var crystal = board[r][c]; if (crystal && !crystal.frozen && freezeUsedCount < freezeMaxCount) { crystal.frozen = true; crystal.justFrozen = true; // Sadece bir tur için işaretle // Visually indicate frozen state tween(crystal, { alpha: 0.5 }, { duration: 300, easing: tween.easeOut }); // Show freeze frame if (typeof crystal.setFreezeFrame === "function") { crystal.setFreezeFrame(true); } freezeUsedCount++; freezeBtnText.setText('Freeze (' + (freezeMaxCount - freezeUsedCount) + ')'); // If reached max, disable freeze button if (freezeUsedCount >= freezeMaxCount) { freezeBtnBg.alpha = 0.3; freezeBtnContainer.interactive = false; freezeBtnContainer.buttonMode = false; } // Always clear selection and power mode after one freeze clearPowerMode(); } } // --- Swap Power Logic --- else if (powerMode === "swap") { var crystal = board[r][c]; if (crystal && !crystal.frozen && swapUsedCount < swapMaxCount) { // Only allow swap on non-frozen crystals var oldType = crystal.type; // Pick a new type different from current var possibleTypes = []; for (var i = 0; i < CRYSTAL_TYPES.length; i++) { var t = CRYSTAL_TYPES[i]; if (t !== oldType) possibleTypes.push(t); } var newType = possibleTypes[Math.floor(Math.random() * possibleTypes.length)]; // Animate pop, then replace crystal.pop(function () { // Remove old crystal from board and scene if (board[r][c] === crystal) { crystal.destroy(); // Spawn new crystal of newType at same position var newCrystal = spawnCrystal(r, c, newType); board[r][c] = newCrystal; newCrystal.x = crystal.x; newCrystal.y = crystal.y; newCrystal.resetVisual(); // Re-apply interactivity refreshCrystalInteractivity(); } }); swapUsedCount++; swapBtnText.setText('Swap (' + (swapMaxCount - swapUsedCount) + ')'); // If reached max, disable swap button if (swapUsedCount >= swapMaxCount) { swapBtnBg.alpha = 0.3; swapBtnContainer.interactive = false; swapBtnContainer.buttonMode = false; } // Always clear selection and power mode after one swap clearPowerMode(); } } }; })(row, col); } } } // Call this after board is filled or refreshed applyPowerCrystalInteractivity(); // --- Patch: When filling board, re-apply crystal interactivity for powers --- function refreshCrystalInteractivity() { for (var row = 0; row < BOARD_ROWS; row++) { if (!board[row]) continue; for (var col = 0; col < BOARD_COLS; col++) { var crystal = board[row][col]; if (!crystal) continue; crystal.interactive = true; crystal.buttonMode = true; crystal.down = function () {}; // Restore freeze frame if frozen if (crystal.type === "joker" && crystal.frozen && typeof crystal.setFreezeFrame === "function") { crystal.setFreezeFrame(true); crystal.alpha = 0.5; } else if (typeof crystal.setFreezeFrame === "function") { crystal.setFreezeFrame(false); crystal.alpha = 1; } } } // Re-apply power crystal interactivity applyPowerCrystalInteractivity(); } // --- Touch to Spin/Reshuffle (tap anywhere else, fallback for desktop) --- // Removed global tap-to-reshuffle so only Spin button restarts the game // --- Game Over/Win (not used, slot is endless) --- // --- Game Update (not needed, all logic is event/cascade driven) --- game.update = function () { // Update bugs (background animation) for (var i = 0; i < bugs.length; i++) { if (bugs[i] && typeof bugs[i].update === "function") { bugs[i].update(); } } }; // --- Start the game --- startGame(); ;
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Passive Bug class (background animation)
var Bug = Container.expand(function () {
var self = Container.call(this);
// Pick a random bug type
var bugTypes = [{
id: 'bocek_orumcek',
name: 'örümcek'
}, {
id: 'bocek_kirkayak',
name: 'kırkayak'
}, {
id: 'bocek_karafatma',
name: 'karafatma'
}, {
id: 'bocek_hamamb',
name: 'hamamböceği'
}];
var bugType = bugTypes[Math.floor(Math.random() * bugTypes.length)];
self.bugType = bugType.name;
// Attach bug asset
self.asset = self.attachAsset(bugType.id, {
anchorX: 0.5,
anchorY: 0.5
});
// State
self.active = false;
self.targetX = 0;
self.targetY = 0;
self.speed = 2 + Math.random() * 2; // px per frame
self.hideTimeout = null;
self.showTimeout = null;
// Helper: set random position (offscreen or edge)
self.setRandomStart = function () {
// Appear from a random edge
var edge = Math.floor(Math.random() * 4); // 0: left, 1: right, 2: top, 3: bottom
var margin = 100;
if (edge === 0) {
// left
self.x = -margin;
self.y = margin + Math.random() * (2732 - 2 * margin);
} else if (edge === 1) {
// right
self.x = 2048 + margin;
self.y = margin + Math.random() * (2732 - 2 * margin);
} else if (edge === 2) {
// top
self.x = margin + Math.random() * (2048 - 2 * margin);
self.y = -margin;
} else {
// bottom
self.x = margin + Math.random() * (2048 - 2 * margin);
self.y = 2732 + margin;
}
};
// Helper: set random target inside screen
self.setRandomTarget = function () {
var margin = 200;
self.targetX = margin + Math.random() * (2048 - 2 * margin);
self.targetY = margin + Math.random() * (2732 - 2 * margin);
};
// Show bug: appear and start moving
self.showBug = function () {
self.setRandomStart();
self.setRandomTarget();
self.visible = true;
self.active = true;
self.alpha = 0.7 + Math.random() * 0.3;
self.scaleX = self.scaleY = 0.7 + Math.random() * 0.5;
// Set random speed
self.speed = 2 + Math.random() * 2;
// Set random zIndex so bugs can overlap
self.zIndex = 0;
// Set random rotation
self.rotation = Math.random() * Math.PI * 2;
// Set how long bug will stay visible
var minShow = 2000,
maxShow = 5000;
if (self.hideTimeout) LK.clearTimeout(self.hideTimeout);
self.hideTimeout = LK.setTimeout(function () {
self.hideBug();
}, minShow + Math.random() * (maxShow - minShow));
};
// Hide bug: disappear and schedule next appearance
self.hideBug = function () {
self.visible = false;
self.active = false;
// Schedule next appearance
var minDelay = 1500,
maxDelay = 4000;
if (self.showTimeout) LK.clearTimeout(self.showTimeout);
self.showTimeout = LK.setTimeout(function () {
self.showBug();
}, minDelay + Math.random() * (maxDelay - minDelay));
};
// Per-frame update
self.update = function () {
if (!self.active) return;
// --- Jitter: bugs shake a little as they move ---
// Add a small random offset to position for jitter
var jitterAmount = 2 + Math.random() * 2;
var jitterAngle = Math.random() * Math.PI * 2;
var jitterX = Math.cos(jitterAngle) * jitterAmount;
var jitterY = Math.sin(jitterAngle) * jitterAmount;
// --- Sudden direction change: sometimes pick a new target suddenly ---
if (typeof self._framesToNextTurn === "undefined") {
self._framesToNextTurn = 30 + Math.floor(Math.random() * 60);
}
self._framesToNextTurn--;
if (self._framesToNextTurn <= 0) {
// 40% chance to pick a new random target (simulate sudden turn)
if (Math.random() < 0.4) {
self.setRandomTarget();
// Also randomize speed and rotation for realism
self.speed = 2 + Math.random() * 2;
self.rotation = Math.random() * Math.PI * 2;
}
self._framesToNextTurn = 30 + Math.floor(Math.random() * 60);
}
// Move towards target
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < self.speed) {
// Arrived at target, pick a new target
self.setRandomTarget();
} else {
// Move with a little jitter
self.x += dx / dist * self.speed + jitterX;
self.y += dy / dist * self.speed + jitterY;
// Optionally, face the direction of movement
self.rotation = Math.atan2(dy, dx) + (Math.random() - 0.5) * 0.2;
}
};
// Start hidden, then schedule first appearance
self.visible = false;
self.active = false;
self.setRandomStart();
self.setRandomTarget();
// Schedule first appearance
self.showTimeout = LK.setTimeout(function () {
self.showBug();
}, 1000 + Math.random() * 2000);
return self;
});
// Crystal class
var Crystal = Container.expand(function () {
var self = Container.call(this);
// type: 'blue', 'yellow', 'red', 'purple', 'white', 'joker'
self.type = 'blue';
self.row = 0;
self.col = 0;
self.popping = false;
// Attach correct asset
function setAsset() {
if (self.asset) self.removeChild(self.asset);
var assetId = 'crystal_' + self.type;
self.asset = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
}
self.setType = function (type) {
self.type = type;
setAsset();
};
// Animate pop
self.pop = function (_onFinish) {
if (self.popping) return;
self.popping = true;
tween(self, {
scaleX: 1.4,
scaleY: 1.4,
alpha: 0
}, {
duration: 350,
easing: tween.easeOut,
onFinish: function onFinish() {
if (_onFinish) _onFinish();
}
});
LK.getSound('crystal_pop').play();
};
// Reset visual state
self.resetVisual = function () {
self.scaleX = 1;
self.scaleY = 1;
self.alpha = 1;
self.popping = false;
// Remove freeze frame if present
if (self.freezeFrame) {
self.removeChild(self.freezeFrame);
self.freezeFrame = null;
}
};
// Add or remove freeze frame
self.setFreezeFrame = function (show) {
if (show) {
if (!self.freezeFrame) {
// Create a square frame using a box shape, slightly larger than the crystal
var frame = LK.getAsset('spin_btn_shape', {
anchorX: 0.5,
anchorY: 0.5
});
frame.width = self.asset.width * 1.18;
frame.height = self.asset.height * 1.18;
frame.alpha = 0.7;
frame.tint = 0x00e6b8;
frame.zIndex = -1;
self.addChildAt(frame, 0);
self.freezeFrame = frame;
}
if (self.freezeFrame) self.freezeFrame.visible = true;
} else {
if (self.freezeFrame) {
self.removeChild(self.freezeFrame);
self.freezeFrame = null;
}
}
};
setAsset();
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181c2c
});
/****
* Game Code
****/
// Böcek görselleri (örümcek, kırkayak, karafatma, hamamböceği)
// Add background image behind all elements
var backgroundImage = LK.getAsset('background_main', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
game.addChildAt(backgroundImage, 0); // Always at the back
// --- Passive Bugs (background animation) ---
var bugs = [];
// Ensure each bug type is present and moving
var bugTypeIds = ['bocek_orumcek', 'bocek_kirkayak', 'bocek_karafatma', 'bocek_hamamb'];
for (var i = 0; i < bugTypeIds.length; i++) {
var bug = new Bug();
// Force the bug to use a specific type for each instance
var bugTypes = [{
id: 'bocek_orumcek',
name: 'örümcek'
}, {
id: 'bocek_kirkayak',
name: 'kırkayak'
}, {
id: 'bocek_karafatma',
name: 'karafatma'
}, {
id: 'bocek_hamamb',
name: 'hamamböceği'
}];
// Set bug type and asset
bug.bugType = bugTypes[i].name;
if (bug.asset) bug.removeChild(bug.asset);
bug.asset = bug.attachAsset(bugTypeIds[i], {
anchorX: 0.5,
anchorY: 0.5
});
// Place bugs behind all game elements but above background
game.addChildAt(bug, 1);
bugs.push(bug);
}
// --- Game Constants ---
// Crystal shapes (box for simplicity, different colors for each type)
// Sound effects (placeholders, actual sound assets will be loaded by LK)
var BOARD_COLS = 4;
var BOARD_ROWS = 6;
var CRYSTAL_SIZE = 300; // px
var CRYSTAL_SPACING = 24; // px
var BOARD_WIDTH = BOARD_COLS * CRYSTAL_SIZE + (BOARD_COLS - 1) * CRYSTAL_SPACING;
var BOARD_HEIGHT = BOARD_ROWS * CRYSTAL_SIZE + (BOARD_ROWS - 1) * CRYSTAL_SPACING;
var BOARD_X = (2048 - BOARD_WIDTH) / 2;
var BOARD_Y = (2732 - BOARD_HEIGHT) / 2 - 180; // move board up to make space for spin button
var CRYSTAL_TYPES = ['blue', 'yellow', 'red', 'purple', 'white', 'joker'];
var CRYSTAL_COLORS = {
blue: 0x3a8ee6,
yellow: 0xf7e14a,
red: 0xe64a3a,
purple: 0xb44ae6,
white: 0xffffff,
joker: 0x00e6b8
};
// Score values per crystal type
var CRYSTAL_SCORES = {
blue: 10,
yellow: 12,
red: 14,
purple: 16,
white: 18,
joker: 25
};
// --- Game State ---
var board = []; // 2D array [row][col] of Crystal or null
var poppingCrystals = []; // Crystals currently popping
var isCascading = false;
var scoreTxt;
var cascadeTimeout = null;
// --- Level and Joker Probability State ---
var level = 1;
var jokerProbability = 0.10; // Start at 10%
// --- UI: Score ---
scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// --- Board Initialization ---
function createEmptyBoard() {
board = [];
for (var row = 0; row < BOARD_ROWS; row++) {
var rowArr = [];
for (var col = 0; col < BOARD_COLS; col++) {
rowArr.push(null);
}
board.push(rowArr);
}
}
function randomCrystalType() {
// Use current jokerProbability (decreases as level increases)
var r = Math.random();
if (r < jokerProbability) return 'joker';
var idx = Math.floor(Math.random() * 5);
return CRYSTAL_TYPES[idx];
}
function spawnCrystal(row, col, type) {
var crystal = new Crystal();
crystal.setType(type);
crystal.row = row;
crystal.col = col;
// Position
var x = BOARD_X + col * (CRYSTAL_SIZE + CRYSTAL_SPACING) + CRYSTAL_SIZE / 2;
var y = BOARD_Y + row * (CRYSTAL_SIZE + CRYSTAL_SPACING) + CRYSTAL_SIZE / 2;
crystal.x = x;
crystal.y = y;
crystal.resetVisual();
game.addChild(crystal);
// Make crystal fixed: prevent any movement or change
// If this crystal is frozen, make sure it cannot be replaced or destroyed
if (crystal.frozen) {
crystal.interactive = false;
crystal.buttonMode = false;
crystal.down = function () {};
crystal.pop = function () {};
crystal.setType = function (type) {};
crystal.resetVisual = function () {};
crystal.setFreezeFrame = function () {};
}
return crystal;
}
function fillBoardRandom() {
for (var row = 0; row < BOARD_ROWS; row++) {
for (var col = 0; col < BOARD_COLS; col++) {
var type = randomCrystalType();
var crystal = spawnCrystal(row, col, type);
board[row][col] = crystal;
}
}
refreshCrystalInteractivity();
}
// --- Board Utility ---
function getNeighbors(row, col) {
// 4-directional neighbors
var n = [];
if (row > 0) n.push([row - 1, col]);
if (row < BOARD_ROWS - 1) n.push([row + 1, col]);
if (col > 0) n.push([row, col - 1]);
if (col < BOARD_COLS - 1) n.push([row, col + 1]);
return n;
}
// --- Find Groups for Popping ---
function findPopGroups() {
// Returns: [{type, crystals: [Crystal, ...], jokerCount: n}]
var groups = [];
// Count all crystals by type (excluding jokers)
var typeCounts = {};
var typeCrystals = {};
var jokerCrystals = [];
for (var i = 0; i < CRYSTAL_TYPES.length; i++) {
var t = CRYSTAL_TYPES[i];
if (t !== 'joker') {
typeCounts[t] = 0;
typeCrystals[t] = [];
}
}
for (var row = 0; row < BOARD_ROWS; row++) {
for (var col = 0; col < BOARD_COLS; col++) {
var crystal = board[row][col];
if (!crystal) continue;
if (crystal.type === 'joker') {
// Frozen jokers can pop if they are part of a valid pop group, so always count them
jokerCrystals.push(crystal);
} else {
// Frozen crystals can pop if they are part of a valid pop group, so always count them
typeCounts[crystal.type]++;
typeCrystals[crystal.type].push(crystal);
}
}
}
// For each color, see if color+joker >= 10, if so, pop all of that color and enough jokers
for (var t in typeCounts) {
var total = typeCounts[t] + jokerCrystals.length;
if (total >= 10) {
// Add all of this color
var groupCrystals = [];
for (var i = 0; i < typeCrystals[t].length; i++) {
groupCrystals.push(typeCrystals[t][i]);
}
// Add jokers
for (var j = 0; j < jokerCrystals.length; j++) {
groupCrystals.push(jokerCrystals[j]);
}
groups.push({
type: t,
crystals: groupCrystals,
jokerCount: jokerCrystals.length
});
}
}
return groups;
}
// --- Pop Groups ---
function popGroups(groups, onFinish) {
if (groups.length === 0) {
if (onFinish) onFinish();
return;
}
isCascading = true;
poppingCrystals = [];
var popped = {};
// --- Combo Multiplier Logic ---
if (typeof comboMultiplier === "undefined") {
comboMultiplier = 1;
}
if (typeof lastCascadeMoveId === "undefined") {
lastCascadeMoveId = 0;
}
if (typeof currentCascadeMoveId === "undefined") {
currentCascadeMoveId = 0;
}
// If this is a new cascade move, reset comboMultiplier
if (typeof cascadeMoveActive === "undefined" || !cascadeMoveActive) {
comboMultiplier = 1;
currentCascadeMoveId = Date.now();
lastCascadeMoveId = currentCascadeMoveId;
cascadeMoveActive = true;
} else {
// If still in the same move, increment comboMultiplier
if (currentCascadeMoveId === lastCascadeMoveId) {
comboMultiplier++;
} else {
comboMultiplier = 1;
currentCascadeMoveId = Date.now();
lastCascadeMoveId = currentCascadeMoveId;
}
}
// Mark all crystals to pop (avoid double pop)
for (var g = 0; g < groups.length; g++) {
var group = groups[g];
for (var i = 0; i < group.crystals.length; i++) {
var c = group.crystals[i];
var key = c.row + ',' + c.col;
if (!popped[key]) {
popped[key] = true;
poppingCrystals.push(c);
}
}
}
// Animate pop with 2s shake before popping
var popCount = poppingCrystals.length;
var finished = 0;
for (var i = 0; i < poppingCrystals.length; i++) {
var c = poppingCrystals[i];
(function (crystal) {
// Save original position
var origX = crystal.x;
var origY = crystal.y;
var shakeDuration = 2000;
var shakeInterval = 30;
var elapsed = 0;
var shakeTimer = null;
function startShake() {
shakeTimer = LK.setInterval(function () {
// Random shake offset, smaller amplitude for subtle effect
var dx = (Math.random() - 0.5) * 24;
var dy = (Math.random() - 0.5) * 24;
crystal.x = origX + dx;
crystal.y = origY + dy;
elapsed += shakeInterval;
if (elapsed >= shakeDuration) {
// End shake, restore position
LK.clearInterval(shakeTimer);
crystal.x = origX;
crystal.y = origY;
// Now pop
crystal.pop(function () {
finished++;
if (finished === popCount) {
// Remove from board
for (var j = 0; j < poppingCrystals.length; j++) {
var pc = poppingCrystals[j];
if (board[pc.row][pc.col] === pc) {
board[pc.row][pc.col] = null;
pc.destroy();
}
}
poppingCrystals = [];
if (onFinish) onFinish();
}
});
}
}, shakeInterval);
}
startShake();
})(c);
}
// Score with combo multiplier
for (var g = 0; g < groups.length; g++) {
var group = groups[g];
var baseScore = 0;
for (var i = 0; i < group.crystals.length; i++) {
var c = group.crystals[i];
var t = c.type === 'joker' ? group.type : c.type;
baseScore += CRYSTAL_SCORES[t];
}
LK.setScore(LK.getScore() + baseScore * comboMultiplier);
}
scoreTxt.setText(LK.getScore());
LK.getSound('cascade').play();
LK.effects.flashObject(scoreTxt, 0xffff00, 400);
// --- Level Up Logic ---
var newLevel = Math.floor(LK.getScore() / 1000) + 1;
if (newLevel > level) {
level = newLevel;
// Decrease jokerProbability by 1% per level up, but not below 1%
jokerProbability = Math.max(0.01, 0.10 - (level - 1) * 0.01);
// Optional: flash score text or give feedback for level up
LK.effects.flashObject(scoreTxt, 0x00ff00, 600);
}
}
// --- Cascade: Drop Crystals Down and Fill ---
function cascadeBoard(onFinish) {
// For each column, drop crystals down to fill empty spaces
var changed = false;
for (var col = 0; col < BOARD_COLS; col++) {
var emptyRows = [];
for (var row = BOARD_ROWS - 1; row >= 0; row--) {
if (!board[row][col]) {
emptyRows.push(row);
} else if (emptyRows.length > 0) {
// Move crystal down
var targetRow = emptyRows.shift();
var crystal = board[row][col];
board[targetRow][col] = crystal;
board[row][col] = null;
crystal.row = targetRow;
crystal.col = col;
// Animate drop
var newY = BOARD_Y + targetRow * (CRYSTAL_SIZE + CRYSTAL_SPACING) + CRYSTAL_SIZE / 2;
tween(crystal, {
y: newY
}, {
duration: 220,
easing: tween.cubicOut
});
emptyRows.push(row);
changed = true;
}
}
}
// Fill empty spaces at the top
for (var col = 0; col < BOARD_COLS; col++) {
for (var row = 0; row < BOARD_ROWS; row++) {
if (!board[row][col]) {
var type = randomCrystalType();
var crystal = spawnCrystal(row, col, type);
board[row][col] = crystal;
// Drop from above
var startY = BOARD_Y - CRYSTAL_SIZE;
crystal.y = startY;
var targetY = BOARD_Y + row * (CRYSTAL_SIZE + CRYSTAL_SPACING) + CRYSTAL_SIZE / 2;
tween(crystal, {
y: targetY
}, {
duration: 220,
easing: tween.cubicOut
});
changed = true;
}
}
}
// Wait for animation, then call onFinish
LK.setTimeout(function () {
refreshCrystalInteractivity();
if (onFinish) onFinish();
}, 250);
}
// --- Main Game Loop: Cascade, Pop, Repeat ---
function startCascadeLoop() {
if (cascadeTimeout) {
LK.clearTimeout(cascadeTimeout);
cascadeTimeout = null;
}
var groups = findPopGroups();
if (groups.length === 0) {
isCascading = false;
return;
}
popGroups(groups, function () {
cascadeBoard(function () {
// Chain reaction!
cascadeTimeout = LK.setTimeout(function () {
startCascadeLoop();
}, 120);
});
});
}
// --- Start New Game ---
function startGame() {
LK.setScore(2000);
scoreTxt.setText('2000');
// Reset combo multiplier state for new game
comboMultiplier = 1;
cascadeMoveActive = false;
// Reset bomb usage for new game
bombUsed = false;
// Reset swap usage for new game
swapUsedCount = 0;
if (swapBtnText) swapBtnText.setText('Swap (' + (swapMaxCount - swapUsedCount) + ')');
if (swapBtnBg) swapBtnBg.alpha = 1;
if (swapBtnContainer) {
swapBtnContainer.interactive = true;
swapBtnContainer.buttonMode = true;
}
// Reset freeze usage for new game
freezeUsedCount = 0;
freezeBtnText.setText('Freeze (' + (freezeMaxCount - freezeUsedCount) + ')');
freezeBtnBg.alpha = 1;
freezeBtnContainer.interactive = true;
freezeBtnContainer.buttonMode = true;
// Reset level and jokerProbability
level = 1;
jokerProbability = 0.10;
// Remove all crystals
for (var row = 0; row < BOARD_ROWS; row++) {
for (var col = 0; col < BOARD_COLS; col++) {
if (board[row] && board[row][col]) {
board[row][col].frozen = false;
board[row][col].alpha = 1;
// Remove freeze frame if present
if (typeof board[row][col].setFreezeFrame === "function") {
board[row][col].setFreezeFrame(false);
}
board[row][col].destroy();
}
}
}
createEmptyBoard();
fillBoardRandom();
refreshCrystalInteractivity();
LK.setTimeout(function () {
startCascadeLoop();
}, 400);
}
// --- Karıştır Button ---
var shuffleBtn = new Text2('Karıştır', {
size: 110,
fill: 0x00E6B8,
font: "Arial Black"
});
shuffleBtn.anchor.set(0.5, 1);
shuffleBtn.x = LK.gui.width / 2;
shuffleBtn.y = LK.gui.height - 60 - 140; // Move up to make space for spin button
shuffleBtn.interactive = true;
shuffleBtn.buttonMode = true;
LK.gui.bottom.addChild(shuffleBtn);
function reshuffleBoardWithFall() {
if (isCascading) return;
// Reset combo multiplier state for new move
comboMultiplier = 1;
cascadeMoveActive = false;
// Remove all crystals
for (var row = 0; row < BOARD_ROWS; row++) {
for (var col = 0; col < BOARD_COLS; col++) {
if (board[row][col]) {
// Only destroy if not a frozen crystal
if (!board[row][col].frozen) {
board[row][col].frozen = false;
board[row][col].alpha = 1;
// Remove freeze frame if present
if (typeof board[row][col].setFreezeFrame === "function") {
board[row][col].setFreezeFrame(false);
}
board[row][col].destroy();
board[row][col] = null;
}
}
}
}
// Do not recreate the board, just refill non-frozen spots
// Fill board with new crystals, but spawn them above and animate falling
for (var row = 0; row < BOARD_ROWS; row++) {
for (var col = 0; col < BOARD_COLS; col++) {
// Eğer kristal donmuş ve justFrozen ise, bu turda silme, sadece işaretle (removeNextTurn)
if (board[row][col] && board[row][col].frozen && board[row][col].justFrozen) {
// Bir tur bekle, bu turda sadece işaretle
board[row][col].justFrozen = false;
board[row][col].removeNextTurn = true;
if (typeof board[row][col].setFreezeFrame === "function") {
board[row][col].setFreezeFrame(false);
}
tween(board[row][col], {
alpha: 1
}, {
duration: 300,
easing: tween.easeOut
});
}
// Eğer kristal donmuş ve removeNextTurn işaretli ise, bu turda sil
else if (board[row][col] && board[row][col].frozen && board[row][col].removeNextTurn) {
board[row][col].frozen = false;
board[row][col].removeNextTurn = false;
if (typeof board[row][col].setFreezeFrame === "function") {
board[row][col].setFreezeFrame(false);
}
// Kristali sil
board[row][col].destroy();
board[row][col] = null;
}
// Eğer kristal donmuş ve justFrozen değilse, donmayı kaldır (yani ikinci turda donma kalksın)
else if (board[row][col] && board[row][col].frozen && !board[row][col].justFrozen) {
board[row][col].frozen = false;
if (typeof board[row][col].setFreezeFrame === "function") {
board[row][col].setFreezeFrame(false);
}
tween(board[row][col], {
alpha: 1
}, {
duration: 300,
easing: tween.easeOut
});
}
// If the cell is empty, fill with a new crystal
if (!board[row][col]) {
var type = randomCrystalType();
var crystal = spawnCrystal(row, col, type);
board[row][col] = crystal;
// Start above the board
var targetY = crystal.y;
crystal.y = BOARD_Y - CRYSTAL_SIZE * 2;
tween(crystal, {
y: targetY
}, {
duration: 3000,
easing: tween.cubicOut
});
} else if (board[row][col].frozen) {
// Animate frozen crystal to its own position for effect
var targetY = BOARD_Y + row * (CRYSTAL_SIZE + CRYSTAL_SPACING) + CRYSTAL_SIZE / 2;
tween(board[row][col], {
y: targetY
}, {
duration: 3000,
easing: tween.cubicOut
});
// Ensure freeze frame is visible
if (typeof board[row][col].setFreezeFrame === "function") {
board[row][col].setFreezeFrame(true);
}
board[row][col].alpha = 0.5;
} else if (!(board[row][col].type === "joker" && board[row][col].frozen)) {
// For all non-frozen crystals (including non-jokers and non-frozen jokers), replace with a new random crystal
var prev = board[row][col];
// If this is a frozen joker, skip replacing it
if (prev.type === "joker" && prev.frozen) {
// Animate frozen joker to its own position for effect
var targetY = BOARD_Y + row * (CRYSTAL_SIZE + CRYSTAL_SPACING) + CRYSTAL_SIZE / 2;
tween(prev, {
y: targetY
}, {
duration: 3000,
easing: tween.cubicOut
});
// Ensure freeze frame is visible
if (typeof prev.setFreezeFrame === "function") {
prev.setFreezeFrame(true);
}
prev.alpha = 0.5;
continue;
}
var type = randomCrystalType();
// Remove old crystal
prev.destroy();
// Spawn new crystal
var crystal = spawnCrystal(row, col, type);
board[row][col] = crystal;
// Start above the board
var targetY = crystal.y;
crystal.y = BOARD_Y - CRYSTAL_SIZE * 2;
tween(crystal, {
y: targetY
}, {
duration: 3000,
easing: tween.cubicOut
});
}
}
}
refreshCrystalInteractivity();
LK.setTimeout(function () {
startCascadeLoop();
}, 3100);
}
// Button event
shuffleBtn.down = function (x, y, obj) {
reshuffleBoardWithFall();
};
// --- Spin Button (visible, below crystals) ---
var spinBtnContainer = new Container();
var spinBtnBg = LK.getAsset('spin_btn_shape', {
anchorX: 0.5,
anchorY: 0.5
});
spinBtnContainer.addChild(spinBtnBg);
var spinBtnText = new Text2('Spin', {
size: 90,
fill: 0xffffff,
font: "Arial Black"
});
spinBtnText.anchor.set(0.5, 0.5);
spinBtnText.x = 0;
spinBtnText.y = 0;
spinBtnContainer.addChild(spinBtnText);
// Position: just below the board, centered horizontally
spinBtnContainer.x = 2048 / 2;
spinBtnContainer.y = BOARD_Y + BOARD_HEIGHT + 100 + spinBtnBg.height / 2;
// Make interactive
spinBtnContainer.interactive = true;
spinBtnContainer.buttonMode = true;
spinBtnContainer.down = function (x, y, obj) {
if (isCascading) return;
var currentCoins = LK.getScore();
if (currentCoins < 100) {
// Not enough coins, flash red for feedback
LK.effects.flashObject(spinBtnBg, 0xff0000, 300);
return;
}
// Reset combo multiplier state for new move
comboMultiplier = 1;
cascadeMoveActive = false;
LK.setScore(currentCoins - 100);
scoreTxt.setText(LK.getScore());
reshuffleBoardWithFall();
// Optional: flash the button for feedback
LK.effects.flashObject(spinBtnBg, 0xffffff, 200);
};
// Add to game scene (not GUI, so it stays relative to board)
game.addChild(spinBtnContainer);
// --- Special Power Buttons ---
// State for power selection
var powerMode = null; // "bomb", "freeze", "swap" or null
// --- Swap Button State ---
var swapBtnContainer;
var swapBtnBg;
var swapBtnText;
var swapUsedCount = 0;
var swapMaxCount = 5;
// Helper to clear power mode UI state
function clearPowerMode() {
powerMode = null;
swapFirstCrystal = null;
bombBtnBg.alpha = 1;
freezeBtnBg.alpha = 1;
if (swapBtnBg) swapBtnBg.alpha = 1;
}
// --- Bomb Button ---
var bombBtnContainer = new Container();
var bombBtnBg = LK.getAsset('spin_btn_shape', {
anchorX: 0.5,
anchorY: 0.5
});
bombBtnBg.tint = 0xff4444;
bombBtnContainer.addChild(bombBtnBg);
var bombBtnText = new Text2('Bomb (1)', {
size: 70,
fill: 0xffffff,
font: "Arial Black"
});
bombBtnText.anchor.set(0.5, 0.5);
bombBtnText.x = 0;
bombBtnText.y = 0;
bombBtnContainer.addChild(bombBtnText);
bombBtnContainer.x = spinBtnContainer.x - 500;
bombBtnContainer.y = spinBtnContainer.y;
bombBtnContainer.interactive = true;
bombBtnContainer.buttonMode = true;
bombBtnContainer.down = function () {
clearPowerMode();
powerMode = "bomb";
bombBtnBg.alpha = 0.7;
};
// --- Freeze Button ---
var freezeBtnContainer = new Container();
var freezeBtnBg = LK.getAsset('spin_btn_shape', {
anchorX: 0.5,
anchorY: 0.5
});
freezeBtnBg.tint = 0x44ffd0;
freezeBtnContainer.addChild(freezeBtnBg);
var freezeBtnText = new Text2('Freeze (' + (freezeMaxCount - freezeUsedCount) + ')', {
size: 70,
fill: 0xffffff,
font: "Arial Black"
});
freezeBtnText.anchor.set(0.5, 0.5);
freezeBtnText.x = 0;
freezeBtnText.y = 0;
freezeBtnContainer.addChild(freezeBtnText);
freezeBtnContainer.x = spinBtnContainer.x + 500;
freezeBtnContainer.y = spinBtnContainer.y;
freezeBtnContainer.interactive = true;
freezeBtnContainer.buttonMode = true;
// Track freeze usage
if (typeof freezeUsedCount === "undefined") {
var freezeUsedCount = 0;
}
var freezeMaxCount = 3;
freezeBtnContainer.down = function () {
clearPowerMode();
if (freezeUsedCount >= freezeMaxCount) {
// Visually disable freeze button if limit reached
freezeBtnBg.alpha = 0.3;
freezeBtnContainer.interactive = false;
freezeBtnContainer.buttonMode = false;
return;
}
powerMode = "freeze";
freezeBtnBg.alpha = 0.7;
};
// Add to game
game.addChild(bombBtnContainer);
game.addChild(freezeBtnContainer);
// --- Swap Button ---
swapBtnContainer = new Container();
swapBtnBg = LK.getAsset('spin_btn_shape', {
anchorX: 0.5,
anchorY: 0.5
});
swapBtnBg.tint = 0x4488ff;
swapBtnContainer.addChild(swapBtnBg);
swapBtnText = new Text2('Swap (' + (swapMaxCount - swapUsedCount) + ')', {
size: 70,
fill: 0xffffff,
font: "Arial Black"
});
swapBtnText.anchor.set(0.5, 0.5);
swapBtnText.x = 0;
swapBtnText.y = 0;
swapBtnContainer.addChild(swapBtnText);
// Position: just below the spin button, centered horizontally
swapBtnContainer.x = spinBtnContainer.x;
swapBtnContainer.y = spinBtnContainer.y + spinBtnBg.height + 40;
swapBtnContainer.interactive = true;
swapBtnContainer.buttonMode = true;
swapBtnContainer.down = function () {
clearPowerMode();
if (swapUsedCount >= swapMaxCount) {
swapBtnBg.alpha = 0.3;
swapBtnContainer.interactive = false;
swapBtnContainer.buttonMode = false;
return;
}
powerMode = "swap";
swapBtnBg.alpha = 0.7;
};
// Add to game
game.addChild(swapBtnContainer);
// --- Power Button Crystal Selection Logic ---
// Track bomb usage
if (typeof bombUsed === "undefined") {
var bombUsed = false;
}
// Helper to re-apply crystal interactivity for powers
function applyPowerCrystalInteractivity() {
for (var row = 0; row < BOARD_ROWS; row++) {
if (!board[row]) continue;
for (var col = 0; col < BOARD_COLS; col++) {
(function (r, c) {
if (!board[r]) return;
var crystal = board[r][c];
if (!crystal) return;
crystal.interactive = true;
crystal.buttonMode = true;
crystal.down = function () {
if (isCascading) return;
// Bomb: destroy selected and neighbors, only if not used yet
if (powerMode === "bomb" && !bombUsed) {
var finishBombPop = function finishBombPop() {
clearPowerMode();
LK.setTimeout(function () {
startCascadeLoop();
}, 400);
};
bombUsed = true;
bombBtnText.setText('Bomb (0)');
var targets = [[r, c]];
var neighbors = getNeighbors(r, c);
for (var i = 0; i < neighbors.length; i++) {
var nr = neighbors[i][0],
nc = neighbors[i][1];
if (board[nr][nc]) targets.push([nr, nc]);
}
var poppedCount = 0;
var toPop = [];
for (var i = 0; i < targets.length; i++) {
var tr = targets[i][0],
tc = targets[i][1];
if (board[tr][tc]) {
toPop.push([tr, tc]);
}
}
if (toPop.length === 0) {
finishBombPop();
return;
}
var finished = 0;
for (var i = 0; i < toPop.length; i++) {
var tr = toPop[i][0],
tc = toPop[i][1];
if (board[tr][tc]) {
board[tr][tc].pop(function () {
finished++;
if (finished === toPop.length) {
// Remove all popped
for (var j = 0; j < toPop.length; j++) {
var rr = toPop[j][0],
cc = toPop[j][1];
if (board[rr][cc]) {
board[rr][cc].destroy();
board[rr][cc] = null;
}
}
// After bomb pop, trigger cascadeBoard to fill empty spaces
cascadeBoard(function () {
finishBombPop();
});
}
});
}
}
// Visually disable bomb button
bombBtnBg.alpha = 0.3;
bombBtnContainer.interactive = false;
bombBtnContainer.buttonMode = false;
return;
}
// Freeze: after clicking Freeze, the clicked crystal stays fixed in place and is not replaced on spin/reshuffle
else if (powerMode === "freeze") {
var crystal = board[r][c];
if (crystal && !crystal.frozen && freezeUsedCount < freezeMaxCount) {
crystal.frozen = true;
crystal.justFrozen = true; // Sadece bir tur için işaretle
// Visually indicate frozen state
tween(crystal, {
alpha: 0.5
}, {
duration: 300,
easing: tween.easeOut
});
// Show freeze frame
if (typeof crystal.setFreezeFrame === "function") {
crystal.setFreezeFrame(true);
}
freezeUsedCount++;
freezeBtnText.setText('Freeze (' + (freezeMaxCount - freezeUsedCount) + ')');
// If reached max, disable freeze button
if (freezeUsedCount >= freezeMaxCount) {
freezeBtnBg.alpha = 0.3;
freezeBtnContainer.interactive = false;
freezeBtnContainer.buttonMode = false;
}
// Always clear selection and power mode after one freeze
clearPowerMode();
}
}
// --- Swap Power Logic ---
else if (powerMode === "swap") {
var crystal = board[r][c];
if (crystal && !crystal.frozen && swapUsedCount < swapMaxCount) {
// Only allow swap on non-frozen crystals
var oldType = crystal.type;
// Pick a new type different from current
var possibleTypes = [];
for (var i = 0; i < CRYSTAL_TYPES.length; i++) {
var t = CRYSTAL_TYPES[i];
if (t !== oldType) possibleTypes.push(t);
}
var newType = possibleTypes[Math.floor(Math.random() * possibleTypes.length)];
// Animate pop, then replace
crystal.pop(function () {
// Remove old crystal from board and scene
if (board[r][c] === crystal) {
crystal.destroy();
// Spawn new crystal of newType at same position
var newCrystal = spawnCrystal(r, c, newType);
board[r][c] = newCrystal;
newCrystal.x = crystal.x;
newCrystal.y = crystal.y;
newCrystal.resetVisual();
// Re-apply interactivity
refreshCrystalInteractivity();
}
});
swapUsedCount++;
swapBtnText.setText('Swap (' + (swapMaxCount - swapUsedCount) + ')');
// If reached max, disable swap button
if (swapUsedCount >= swapMaxCount) {
swapBtnBg.alpha = 0.3;
swapBtnContainer.interactive = false;
swapBtnContainer.buttonMode = false;
}
// Always clear selection and power mode after one swap
clearPowerMode();
}
}
};
})(row, col);
}
}
}
// Call this after board is filled or refreshed
applyPowerCrystalInteractivity();
// --- Patch: When filling board, re-apply crystal interactivity for powers ---
function refreshCrystalInteractivity() {
for (var row = 0; row < BOARD_ROWS; row++) {
if (!board[row]) continue;
for (var col = 0; col < BOARD_COLS; col++) {
var crystal = board[row][col];
if (!crystal) continue;
crystal.interactive = true;
crystal.buttonMode = true;
crystal.down = function () {};
// Restore freeze frame if frozen
if (crystal.type === "joker" && crystal.frozen && typeof crystal.setFreezeFrame === "function") {
crystal.setFreezeFrame(true);
crystal.alpha = 0.5;
} else if (typeof crystal.setFreezeFrame === "function") {
crystal.setFreezeFrame(false);
crystal.alpha = 1;
}
}
}
// Re-apply power crystal interactivity
applyPowerCrystalInteractivity();
}
// --- Touch to Spin/Reshuffle (tap anywhere else, fallback for desktop) ---
// Removed global tap-to-reshuffle so only Spin button restarts the game
// --- Game Over/Win (not used, slot is endless) ---
// --- Game Update (not needed, all logic is event/cascade driven) ---
game.update = function () {
// Update bugs (background animation)
for (var i = 0; i < bugs.length; i++) {
if (bugs[i] && typeof bugs[i].update === "function") {
bugs[i].update();
}
}
};
// --- Start the game ---
startGame();
;