/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // Floating text for win popups var FloatingText = Container.expand(function () { var self = Container.call(this); self.txt = new Text2('', { size: 90, fill: '#fff', font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); self.txt.anchor.set(0.5, 0.5); self.addChild(self.txt); self.show = function (text, x, y, color) { self.txt.setText(text); // Defensive: Only set fill if style exists if (self.txt && self.txt.style) { self.txt.style.fill = color || '#fff'; } self.x = x; self.y = y; self.alpha = 1; tween(self, { y: y - 120, alpha: 0 }, { duration: 900, easing: tween.easeOut, onFinish: function onFinish() { self.visible = false; } }); self.visible = true; }; self.visible = false; return self; }); // Symbol class for grid symbols var _Symbol = Container.expand(function () { var self = Container.call(this); self.type = null; // e.g. 'candy_red', 'scatter_lollipop', 'bomb_multi' self.asset = null; self.row = 0; self.col = 0; self.multiplier = 0; // for bomb_multi only self.setSymbol = function (type, multiplier) { self.type = type; if (self.asset) { self.removeChild(self.asset); } var anchor = 0.5; // For bomb_multi, always use the 'bomb_multi' asset, which can be an image or shape as configured in Assets self.asset = self.attachAsset(type, { anchorX: anchor, anchorY: anchor }); if (type === 'bomb_multi') { self.multiplier = multiplier || 2; if (!self.multiTxt) { self.multiTxt = new Text2('x' + self.multiplier, { size: 60, fill: '#fff' }); self.multiTxt.anchor.set(0.5, 0.5); self.addChild(self.multiTxt); } self.multiTxt.setText('x' + self.multiplier); self.multiTxt.visible = true; } else { if (self.multiTxt) self.multiTxt.visible = false; self.multiplier = 0; } }; self.flash = function () { tween(self.asset, { scaleX: 1.2, scaleY: 1.2 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { tween(self.asset, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeIn }); } }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2d1a3a }); /**** * Game Code ****/ // --- Game constants --- // Candy symbols (6 types), lollipop scatter, multiplier bomb // You can swap the image id here to change the bomb multiplier symbol's appearance // Wallpaper image asset var GRID_COLS = 6; var GRID_ROWS = 5; var SYMBOL_SIZE = 300; // px, including margin var SYMBOL_MARGIN = 20; var SYMBOLS = ['candy_red', 'candy_blue', 'candy_green', 'candy_purple', 'fruit_banana', 'fruit_grape']; var SCATTER = 'scatter_lollipop'; var BOMB = 'bomb_multi'; var BOMB_MULTIPLIERS = [2, 4, 6, 8, 10, 12, 15, 20, 25, 50, 100]; // --- Game state --- var grid = []; // 2D array [row][col] of Symbol var gridContainer = null; var floatingText = null; var spinBtn = null; var buyBtn = null; var anteBtn = null; var balanceTxt = null; var betTxt = null; var winTxt = null; var freeSpinTxt = null; var isSpinning = false; var isTumbling = false; var isFreeSpins = false; var freeSpinsLeft = 0; var totalFreeSpinWin = 0; var bet = 20; var balance = 1000; var anteBet = false; var lastWin = 0; var scatterCount = 0; var bombs = []; var totalBombMultiplier = 0; // Add wallpaper image to the background var wallpaper = LK.getAsset('wallpaper', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732, alpha: 0.18 // Set transparency for the wallpaper (more transparent) }); game.addChild(wallpaper); // Add a small wallpaper image to the top left corner (do not overlap menu, so offset by 10px) var wallpaperSmall = LK.getAsset('wallpaper', { anchorX: 0, anchorY: 0, x: 10, y: 10, scaleX: 0.08, scaleY: 0.08 }); game.addChild(wallpaperSmall); // Add a semi-transparent panel behind the spin grid var gridPanel = LK.getAsset('bomb_multi', { anchorX: 0.5, anchorY: 0.5, scaleX: 8.5, scaleY: 4.2, alpha: 0.22 }); game.addChild(gridPanel); // --- UI setup --- function setupUI() { // Balance balanceTxt = new Text2('Balance: ' + balance, { size: 70, fill: '#fff' }); balanceTxt.anchor.set(0, 0); LK.gui.top.addChild(balanceTxt); balanceTxt.x = 120; balanceTxt.y = 10; // Bet betTxt = new Text2('Bet: ' + bet, { size: 70, fill: '#fff' }); betTxt.anchor.set(0, 0); LK.gui.top.addChild(betTxt); betTxt.x = 220; betTxt.y = 90; // Bet decrease button (top left, vertical layout) var betDecBtn = new Container(); // Use the bomb_multi asset, which can be swapped to an image in the Assets section var betDecBg = betDecBtn.attachAsset('bomb_multi', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.25, scaleY: 0.25 }); var betDecTxt = new Text2('-', { size: 80, fill: '#fff' }); betDecTxt.anchor.set(0.5, 0.5); betDecBtn.addChild(betDecTxt); // Place at top left, with margin from edge, vertical stack betDecBtn.x = 120; betDecBtn.y = 120; betDecBtn.interactive = true; betDecBtn.buttonMode = true; betDecBtn.down = function (x, y, obj) { if (isSpinning || isTumbling) return; var minBet = 10; if (bet > minBet) { bet -= 10; updateBalance(); } }; // Bet increase button (top left, above dec) var betIncBtn = new Container(); // Use the bomb_multi asset, which can be swapped to an image in the Assets section var betIncBg = betIncBtn.attachAsset('bomb_multi', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.25, scaleY: 0.25 }); var betIncTxt = new Text2('+', { size: 80, fill: '#fff' }); betIncTxt.anchor.set(0.5, 0.5); betIncBtn.addChild(betIncTxt); // Place directly above dec button, with same x, y offset by -120 betIncBtn.x = 120; betIncBtn.y = 0; betIncBtn.interactive = true; betIncBtn.buttonMode = true; betIncBtn.down = function (x, y, obj) { if (isSpinning || isTumbling) return; var maxBet = 1000; if (bet < maxBet && balance >= bet + 10) { bet += 10; updateBalance(); } }; // Add to top left GUI LK.gui.top.addChild(betDecBtn); LK.gui.top.addChild(betIncBtn); // Win winTxt = new Text2('', { size: 90, fill: '#ffe066' }); winTxt.anchor.set(0.5, 0); LK.gui.top.addChild(winTxt); winTxt.x = LK.gui.top.width / 2; winTxt.y = 10; // Free Spin freeSpinTxt = new Text2('', { size: 80, fill: '#ffb3e6' }); freeSpinTxt.anchor.set(0.5, 0); LK.gui.top.addChild(freeSpinTxt); freeSpinTxt.x = LK.gui.top.width / 2; freeSpinTxt.y = 110; // Spin button spinBtn = new Text2('SPIN', { size: 120, fill: '#fff', font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); spinBtn.anchor.set(0.5, 0.5); LK.gui.bottom.addChild(spinBtn); spinBtn.x = LK.gui.bottom.width / 2; spinBtn.y = -180; spinBtn.interactive = true; spinBtn.buttonMode = true; spinBtn.down = function (x, y, obj) { // Block tap if waiting for free spin overlay tap if (game._freeSpinAwaitingTap) return; if (!isSpinning && !isTumbling) { startSpin(); } }; // Buy Free Spins button buyBtn = new Text2('BUY 10 FS (' + bet * 100 + ')', { size: 70, fill: '#ffb3e6' }); buyBtn.anchor.set(0.5, 0.5); LK.gui.bottom.addChild(buyBtn); buyBtn.x = LK.gui.bottom.width / 2 + 350; buyBtn.y = -120; buyBtn.interactive = true; buyBtn.buttonMode = true; buyBtn.down = function (x, y, obj) { var price = bet * 100; if (!isSpinning && !isTumbling && balance >= price) { balance -= price; updateBalance(); triggerFreeSpins(10); } }; // Ante Bet button anteBtn = new Text2('ANTE OFF', { size: 70, fill: '#b3e6ff' }); anteBtn.anchor.set(0.5, 0.5); LK.gui.bottom.addChild(anteBtn); anteBtn.x = LK.gui.bottom.width / 2 - 350; anteBtn.y = -120; anteBtn.interactive = true; anteBtn.buttonMode = true; anteBtn.down = function (x, y, obj) { anteBet = !anteBet; anteBtn.setText(anteBet ? 'ANTE ON' : 'ANTE OFF'); // Defensive: Only set fill if style exists if (anteBtn && anteBtn.style) { anteBtn.style.fill = anteBet ? '#ffe066' : '#b3e6ff'; } }; // Floating win text floatingText = new FloatingText(); game.addChild(floatingText); } // --- Grid setup --- function setupGrid() { if (gridContainer) { game.removeChild(gridContainer); } gridContainer = new Container(); game.addChild(gridContainer); // Center grid var gridW = GRID_COLS * SYMBOL_SIZE + (GRID_COLS - 1) * SYMBOL_MARGIN; var gridH = GRID_ROWS * SYMBOL_SIZE + (GRID_ROWS - 1) * SYMBOL_MARGIN; gridContainer.x = (2048 - gridW) / 2 + SYMBOL_SIZE / 2; gridContainer.y = (2732 - gridH) / 2 + SYMBOL_SIZE / 2 + 60; // Position the panel behind the grid if (typeof gridPanel !== "undefined" && gridPanel) { gridPanel.x = gridContainer.x + gridW / 2 - SYMBOL_SIZE / 2; gridPanel.y = gridContainer.y + gridH / 2 - SYMBOL_SIZE / 2; // Ensure panel is behind grid game.setChildIndex(gridPanel, 0); } // Create grid grid = []; for (var r = 0; r < GRID_ROWS; r++) { grid[r] = []; for (var c = 0; c < GRID_COLS; c++) { var sym = new _Symbol(); sym.row = r; sym.col = c; sym.x = c * (SYMBOL_SIZE + SYMBOL_MARGIN); sym.y = r * (SYMBOL_SIZE + SYMBOL_MARGIN); gridContainer.addChild(sym); grid[r][c] = sym; } } } // --- Utility: random symbol --- function getRandomSymbol(scatterChance, bombChance) { var roll = Math.random(); // 20% chance for bomb if (bombChance && roll < 0.20) { // 5% of bombs are 20x or higher var highMultiRoll = Math.random(); var multi; if (highMultiRoll < 0.05) { // Only pick from multipliers >= 20 var highMultis = []; for (var i = 0; i < BOMB_MULTIPLIERS.length; i++) { if (BOMB_MULTIPLIERS[i] >= 20) highMultis.push(BOMB_MULTIPLIERS[i]); } // Defensive: fallback to max if none found if (highMultis.length > 0) { multi = highMultis[Math.floor(Math.random() * highMultis.length)]; } else { multi = 20; } } else { // Pick from multipliers < 20 var lowMultis = []; for (var i = 0; i < BOMB_MULTIPLIERS.length; i++) { if (BOMB_MULTIPLIERS[i] < 20) lowMultis.push(BOMB_MULTIPLIERS[i]); } // Defensive: fallback to min if none found if (lowMultis.length > 0) { multi = lowMultis[Math.floor(Math.random() * lowMultis.length)]; } else { multi = 2; } } return { type: BOMB, multiplier: multi }; } if (scatterChance && roll < scatterChance) { return { type: SCATTER }; } var idx = Math.floor(Math.random() * SYMBOLS.length); return { type: SYMBOLS[idx] }; } // --- Utility: count symbol types --- function countSymbols(flatGrid) { var counts = {}; for (var i = 0; i < flatGrid.length; i++) { var t = flatGrid[i].type; if (!counts[t]) counts[t] = 0; counts[t]++; } return counts; } // --- Utility: flatten grid --- function flattenGrid() { var arr = []; for (var r = 0; r < GRID_ROWS; r++) { for (var c = 0; c < GRID_COLS; c++) { arr.push(grid[r][c]); } } return arr; } // --- Utility: find all matching symbols (8+) --- function findMatches() { var flat = flattenGrid(); var counts = countSymbols(flat); var matches = []; for (var t in counts) { if (t === SCATTER || t === BOMB) continue; if (counts[t] >= 8) { // collect all symbols of this type for (var i = 0; i < flat.length; i++) { if (flat[i].type === t) matches.push(flat[i]); } } } return matches; } // --- Utility: find scatters --- function findScatters() { var flat = flattenGrid(); var scatters = []; for (var i = 0; i < flat.length; i++) { if (flat[i].type === SCATTER) scatters.push(flat[i]); } return scatters; } // --- Utility: find bombs --- function findBombs() { var flat = flattenGrid(); var bombs = []; for (var i = 0; i < flat.length; i++) { if (flat[i].type === BOMB) bombs.push(flat[i]); } return bombs; } // --- Utility: payout table --- function getPayout(type, count) { // Example payout table (per symbol, per count) // (values are for MVP, can be tuned) var table = { 'candy_red': [0, 0, 0, 0, 0, 0, 0, 0, 10, 25, 50, 100, 200, 400, 800, 1600, 3200, 6400, 12800, 25600, 51200], 'candy_blue': [0, 0, 0, 0, 0, 0, 0, 0, 8, 20, 40, 80, 160, 320, 640, 1280, 2560, 5120, 10240, 20480, 40960], 'candy_green': [0, 0, 0, 0, 0, 0, 0, 0, 6, 15, 30, 60, 120, 240, 480, 960, 1920, 3840, 7680, 15360, 30720], 'candy_purple': [0, 0, 0, 0, 0, 0, 0, 0, 5, 12, 24, 48, 96, 192, 384, 768, 1536, 3072, 6144, 12288, 24576], 'fruit_banana': [0, 0, 0, 0, 0, 0, 0, 0, 4, 10, 20, 40, 80, 160, 320, 640, 1280, 2560, 5120, 10240, 20480], 'fruit_grape': [0, 0, 0, 0, 0, 0, 0, 0, 3, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384] }; if (!table[type]) return 0; if (count >= table[type].length) count = table[type].length - 1; return table[type][count]; } // --- Utility: update balance display --- function updateBalance() { balanceTxt.setText('Balance: ' + balance); betTxt.setText('Bet: ' + bet * (anteBet ? 1.25 : 1)); if (buyBtn) { buyBtn.setText('BUY 10 FS (' + bet * 100 + ')'); } } // --- Utility: update win display --- function updateWin(win) { if (win > 0) { winTxt.setText('WIN: ' + win); } else { winTxt.setText(''); } } // --- Utility: update free spin display --- function updateFreeSpinTxt() { if (isFreeSpins) { freeSpinTxt.setText('FREE SPINS: ' + freeSpinsLeft); } else { freeSpinTxt.setText(''); } } // --- Start a spin --- function startSpin() { if (isSpinning || isTumbling) return; if (!isFreeSpins && balance < bet * (anteBet ? 1.25 : 1)) return; isSpinning = true; updateWin(0); lastWin = 0; scatterCount = 0; bombs = []; totalBombMultiplier = 0; if (!isFreeSpins) { balance -= bet * (anteBet ? 1.25 : 1); updateBalance(); } LK.getSound('spin').play(); // Fill grid with new random symbols for (var r = 0; r < GRID_ROWS; r++) { for (var c = 0; c < GRID_COLS; c++) { var scatterChance = isFreeSpins ? 0 : anteBet ? 0.045 : 0.03; // ~3% or 4.5% chance per cell var bombChance = isFreeSpins ? 0.08 : 0; var sym = getRandomSymbol(scatterChance, bombChance); grid[r][c].setSymbol(sym.type, sym.multiplier); grid[r][c].alpha = 1; grid[r][c].visible = true; } } // Animate grid in for (var r = 0; r < GRID_ROWS; r++) { for (var c = 0; c < GRID_COLS; c++) { grid[r][c].y = r * (SYMBOL_SIZE + SYMBOL_MARGIN) - 400; tween(grid[r][c], { y: r * (SYMBOL_SIZE + SYMBOL_MARGIN) }, { duration: 220 + Math.random() * 120, easing: tween.bounceOut }); } } LK.setTimeout(function () { isSpinning = false; checkWinCascade(); }, 420); } // --- Check for wins and cascade --- function checkWinCascade() { if (isSpinning || isTumbling) return; var matches = findMatches(); var scatters = findScatters(); var bombsHere = findBombs(); // Handle scatters if (!isFreeSpins && scatters.length >= 4) { triggerFreeSpins(10); LK.getSound('freespin').play(); } // Handle bombs (only in free spins) if (isFreeSpins && bombsHere.length > 0) { for (var i = 0; i < bombsHere.length; i++) { bombs.push(bombsHere[i]); totalBombMultiplier += bombsHere[i].multiplier; LK.getSound('bomb').play(); bombsHere[i].flash(); } } if (matches.length > 0) { isTumbling = true; LK.getSound('win').play(); // Flash and float win var matchType = matches[0].type; var payout = getPayout(matchType, matches.length); var winAmount = payout * bet / 20; lastWin += winAmount; // Show floating text at center of grid var gridW = GRID_COLS * SYMBOL_SIZE + (GRID_COLS - 1) * SYMBOL_MARGIN; var gridH = GRID_ROWS * SYMBOL_SIZE + (GRID_ROWS - 1) * SYMBOL_MARGIN; floatingText.show('+' + winAmount, gridContainer.x + gridW / 2 - SYMBOL_SIZE / 2, gridContainer.y + gridH / 2 - SYMBOL_SIZE / 2, '#ffe066'); // Animate matched symbols out for (var i = 0; i < matches.length; i++) { matches[i].flash(); tween(matches[i], { alpha: 0 }, { duration: 180, easing: tween.linear }); } // After animation, remove matched symbols and tumble LK.setTimeout(function () { for (var i = 0; i < matches.length; i++) { matches[i].visible = false; } tumbleSymbols(function () { // After tumble, check for more matches isTumbling = false; checkWinCascade(); }); }, 220); updateWin(lastWin); } else { // No more matches, finish spin if (lastWin > 0) { // --- Win overlay --- // Remove previous win overlay if exists if (game.winOverlay) { game.removeChild(game.winOverlay); game.winOverlay = null; } // Create overlay container var overlay = new Container(); // Transparent background var overlayBg = LK.getAsset('bomb_multi', { anchorX: 0.5, anchorY: 0.5, scaleX: 7, scaleY: 2.5, alpha: 0.38 }); overlay.addChild(overlayBg); // Win text var overlayTxt = new Text2('WIN: ' + lastWin, { size: 180, fill: '#ffe066', font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); overlayTxt.anchor.set(0.5, 0.5); overlay.addChild(overlayTxt); // Center overlay above grid var gridW = GRID_COLS * SYMBOL_SIZE + (GRID_COLS - 1) * SYMBOL_MARGIN; var gridH = GRID_ROWS * SYMBOL_SIZE + (GRID_ROWS - 1) * SYMBOL_MARGIN; overlay.x = gridContainer.x + gridW / 2 - SYMBOL_SIZE / 2; overlay.y = gridContainer.y + gridH / 2 - SYMBOL_SIZE / 2; overlay.zIndex = 9999; game.addChild(overlay); game.winOverlay = overlay; // Fade out overlay after 1.5s tween(overlay, { alpha: 0 }, { duration: 1500, onFinish: function onFinish() { if (game.winOverlay) { game.removeChild(game.winOverlay); game.winOverlay = null; } } }); // Apply bomb multipliers (free spins only) if (isFreeSpins && totalBombMultiplier > 0) { lastWin = Math.floor(lastWin * (1 + totalBombMultiplier / 100)); floatingText.show('x' + (1 + totalBombMultiplier / 100), gridContainer.x + GRID_COLS * SYMBOL_SIZE / 2, gridContainer.y + GRID_ROWS * SYMBOL_SIZE / 2, '#ffb3e6'); LK.getSound('bomb').play(); } balance += lastWin; updateBalance(); updateWin(lastWin); if (isFreeSpins) { totalFreeSpinWin += lastWin; } } if (isFreeSpins) { freeSpinsLeft--; updateFreeSpinTxt(); if (freeSpinsLeft > 0) { LK.setTimeout(function () { startSpin(); }, 900); } else { // End free spins isFreeSpins = false; updateFreeSpinTxt(); if (totalFreeSpinWin > 0) { floatingText.show('FS WIN: ' + totalFreeSpinWin, gridContainer.x + GRID_COLS * SYMBOL_SIZE / 2, gridContainer.y + GRID_ROWS * SYMBOL_SIZE / 2, '#ffe066'); } } } } } // --- Tumble (cascade) symbols --- function tumbleSymbols(onFinish) { LK.getSound('tumble').play(); // For each column, move down visible symbols to fill gaps, then add new symbols at top for (var c = 0; c < GRID_COLS; c++) { var colSyms = []; for (var r = 0; r < GRID_ROWS; r++) { if (grid[r][c].visible) colSyms.push(grid[r][c]); } // Fill from bottom up for (var r = GRID_ROWS - 1; r >= 0; r--) { if (colSyms.length > 0) { var sym = colSyms.pop(); grid[r][c].setSymbol(sym.type, sym.multiplier); grid[r][c].visible = true; grid[r][c].alpha = 1; } else { // New symbol var scatterChance = isFreeSpins ? 0 : anteBet ? 0.045 : 0.03; var bombChance = isFreeSpins ? 0.08 : 0; var symData = getRandomSymbol(scatterChance, bombChance); grid[r][c].setSymbol(symData.type, symData.multiplier); grid[r][c].alpha = 0; grid[r][c].visible = true; tween(grid[r][c], { alpha: 1 }, { duration: 180, easing: tween.linear }); } } } LK.setTimeout(function () { if (onFinish) onFinish(); }, 220); } // --- Trigger free spins --- function triggerFreeSpins(count) { isFreeSpins = true; freeSpinsLeft = count; totalFreeSpinWin = 0; updateFreeSpinTxt(); LK.getSound('freespin').play(); // --- Animate all lollipops (scatter_lollipop) to shake --- var flat = flattenGrid(); for (var i = 0; i < flat.length; i++) { if (flat[i].type === SCATTER) { // Shake animation: quick left-right movement, repeat a few times (function (sym) { var shakeTimes = 6; var shakeDist = 24; var origX = sym.x; var seq = []; for (var s = 0; s < shakeTimes; s++) { seq.push({ x: origX + (s % 2 === 0 ? shakeDist : -shakeDist), duration: 40 }); } seq.push({ x: origX, duration: 40 }); // Chain tweens var _doTween = function doTween(idx) { if (idx >= seq.length) return; tween(sym, { x: seq[idx].x }, { duration: seq[idx].duration, onFinish: function onFinish() { _doTween(idx + 1); } }); }; _doTween(0); })(flat[i]); } } // --- Show overlay text: "FREE SPINS! Tap to continue" --- // Remove previous overlay if exists if (game.freeSpinOverlay) { game.removeChild(game.freeSpinOverlay); game.freeSpinOverlay = null; } var overlay = new Container(); // Transparent background var overlayBg = LK.getAsset('bomb_multi', { anchorX: 0.5, anchorY: 0.5, scaleX: 7, scaleY: 2.5, alpha: 0.38 }); overlay.addChild(overlayBg); // Overlay text var overlayTxt = new Text2('FREE SPINS!\nTap to continue', { size: 140, fill: '#ffb3e6', font: "'GillSans-Bold',Impact,'Arial Black',Tahoma", align: 'center' }); overlayTxt.anchor.set(0.5, 0.5); overlay.addChild(overlayTxt); // Center overlay above grid var gridW = GRID_COLS * SYMBOL_SIZE + (GRID_COLS - 1) * SYMBOL_MARGIN; var gridH = GRID_ROWS * SYMBOL_SIZE + (GRID_ROWS - 1) * SYMBOL_MARGIN; overlay.x = gridContainer.x + gridW / 2 - SYMBOL_SIZE / 2; overlay.y = gridContainer.y + gridH / 2 - SYMBOL_SIZE / 2; overlay.zIndex = 9999; game.addChild(overlay); game.freeSpinOverlay = overlay; // Block game.down and spinBtn while overlay is up game._freeSpinAwaitingTap = true; // Add a one-time tap handler to remove overlay and start spins overlay.interactive = true; overlay.buttonMode = true; overlay.down = function (x, y, obj) { if (!game._freeSpinAwaitingTap) return; game._freeSpinAwaitingTap = false; if (game.freeSpinOverlay) { game.removeChild(game.freeSpinOverlay); game.freeSpinOverlay = null; } // Start free spins after tap startSpin(); }; } // --- Game update loop (not used for logic, but can be used for future animations) --- game.update = function () { // No per-frame logic needed for MVP }; // --- Game start --- setupUI(); setupGrid(); updateBalance(); updateWin(0); updateFreeSpinTxt(); LK.playMusic('bgm', { fade: { start: 0, end: 1, duration: 1200 } }); // --- Touch anywhere on grid to spin (for mobile) --- game.down = function (x, y, obj) { // Block tap if waiting for free spin overlay tap if (game._freeSpinAwaitingTap) return; if (!isSpinning && !isTumbling) { startSpin(); } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Floating text for win popups
var FloatingText = Container.expand(function () {
var self = Container.call(this);
self.txt = new Text2('', {
size: 90,
fill: '#fff',
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
self.txt.anchor.set(0.5, 0.5);
self.addChild(self.txt);
self.show = function (text, x, y, color) {
self.txt.setText(text);
// Defensive: Only set fill if style exists
if (self.txt && self.txt.style) {
self.txt.style.fill = color || '#fff';
}
self.x = x;
self.y = y;
self.alpha = 1;
tween(self, {
y: y - 120,
alpha: 0
}, {
duration: 900,
easing: tween.easeOut,
onFinish: function onFinish() {
self.visible = false;
}
});
self.visible = true;
};
self.visible = false;
return self;
});
// Symbol class for grid symbols
var _Symbol = Container.expand(function () {
var self = Container.call(this);
self.type = null; // e.g. 'candy_red', 'scatter_lollipop', 'bomb_multi'
self.asset = null;
self.row = 0;
self.col = 0;
self.multiplier = 0; // for bomb_multi only
self.setSymbol = function (type, multiplier) {
self.type = type;
if (self.asset) {
self.removeChild(self.asset);
}
var anchor = 0.5;
// For bomb_multi, always use the 'bomb_multi' asset, which can be an image or shape as configured in Assets
self.asset = self.attachAsset(type, {
anchorX: anchor,
anchorY: anchor
});
if (type === 'bomb_multi') {
self.multiplier = multiplier || 2;
if (!self.multiTxt) {
self.multiTxt = new Text2('x' + self.multiplier, {
size: 60,
fill: '#fff'
});
self.multiTxt.anchor.set(0.5, 0.5);
self.addChild(self.multiTxt);
}
self.multiTxt.setText('x' + self.multiplier);
self.multiTxt.visible = true;
} else {
if (self.multiTxt) self.multiTxt.visible = false;
self.multiplier = 0;
}
};
self.flash = function () {
tween(self.asset, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self.asset, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeIn
});
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2d1a3a
});
/****
* Game Code
****/
// --- Game constants ---
// Candy symbols (6 types), lollipop scatter, multiplier bomb
// You can swap the image id here to change the bomb multiplier symbol's appearance
// Wallpaper image asset
var GRID_COLS = 6;
var GRID_ROWS = 5;
var SYMBOL_SIZE = 300; // px, including margin
var SYMBOL_MARGIN = 20;
var SYMBOLS = ['candy_red', 'candy_blue', 'candy_green', 'candy_purple', 'fruit_banana', 'fruit_grape'];
var SCATTER = 'scatter_lollipop';
var BOMB = 'bomb_multi';
var BOMB_MULTIPLIERS = [2, 4, 6, 8, 10, 12, 15, 20, 25, 50, 100];
// --- Game state ---
var grid = []; // 2D array [row][col] of Symbol
var gridContainer = null;
var floatingText = null;
var spinBtn = null;
var buyBtn = null;
var anteBtn = null;
var balanceTxt = null;
var betTxt = null;
var winTxt = null;
var freeSpinTxt = null;
var isSpinning = false;
var isTumbling = false;
var isFreeSpins = false;
var freeSpinsLeft = 0;
var totalFreeSpinWin = 0;
var bet = 20;
var balance = 1000;
var anteBet = false;
var lastWin = 0;
var scatterCount = 0;
var bombs = [];
var totalBombMultiplier = 0;
// Add wallpaper image to the background
var wallpaper = LK.getAsset('wallpaper', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732,
alpha: 0.18 // Set transparency for the wallpaper (more transparent)
});
game.addChild(wallpaper);
// Add a small wallpaper image to the top left corner (do not overlap menu, so offset by 10px)
var wallpaperSmall = LK.getAsset('wallpaper', {
anchorX: 0,
anchorY: 0,
x: 10,
y: 10,
scaleX: 0.08,
scaleY: 0.08
});
game.addChild(wallpaperSmall);
// Add a semi-transparent panel behind the spin grid
var gridPanel = LK.getAsset('bomb_multi', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 8.5,
scaleY: 4.2,
alpha: 0.22
});
game.addChild(gridPanel);
// --- UI setup ---
function setupUI() {
// Balance
balanceTxt = new Text2('Balance: ' + balance, {
size: 70,
fill: '#fff'
});
balanceTxt.anchor.set(0, 0);
LK.gui.top.addChild(balanceTxt);
balanceTxt.x = 120;
balanceTxt.y = 10;
// Bet
betTxt = new Text2('Bet: ' + bet, {
size: 70,
fill: '#fff'
});
betTxt.anchor.set(0, 0);
LK.gui.top.addChild(betTxt);
betTxt.x = 220;
betTxt.y = 90;
// Bet decrease button (top left, vertical layout)
var betDecBtn = new Container();
// Use the bomb_multi asset, which can be swapped to an image in the Assets section
var betDecBg = betDecBtn.attachAsset('bomb_multi', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.25,
scaleY: 0.25
});
var betDecTxt = new Text2('-', {
size: 80,
fill: '#fff'
});
betDecTxt.anchor.set(0.5, 0.5);
betDecBtn.addChild(betDecTxt);
// Place at top left, with margin from edge, vertical stack
betDecBtn.x = 120;
betDecBtn.y = 120;
betDecBtn.interactive = true;
betDecBtn.buttonMode = true;
betDecBtn.down = function (x, y, obj) {
if (isSpinning || isTumbling) return;
var minBet = 10;
if (bet > minBet) {
bet -= 10;
updateBalance();
}
};
// Bet increase button (top left, above dec)
var betIncBtn = new Container();
// Use the bomb_multi asset, which can be swapped to an image in the Assets section
var betIncBg = betIncBtn.attachAsset('bomb_multi', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.25,
scaleY: 0.25
});
var betIncTxt = new Text2('+', {
size: 80,
fill: '#fff'
});
betIncTxt.anchor.set(0.5, 0.5);
betIncBtn.addChild(betIncTxt);
// Place directly above dec button, with same x, y offset by -120
betIncBtn.x = 120;
betIncBtn.y = 0;
betIncBtn.interactive = true;
betIncBtn.buttonMode = true;
betIncBtn.down = function (x, y, obj) {
if (isSpinning || isTumbling) return;
var maxBet = 1000;
if (bet < maxBet && balance >= bet + 10) {
bet += 10;
updateBalance();
}
};
// Add to top left GUI
LK.gui.top.addChild(betDecBtn);
LK.gui.top.addChild(betIncBtn);
// Win
winTxt = new Text2('', {
size: 90,
fill: '#ffe066'
});
winTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(winTxt);
winTxt.x = LK.gui.top.width / 2;
winTxt.y = 10;
// Free Spin
freeSpinTxt = new Text2('', {
size: 80,
fill: '#ffb3e6'
});
freeSpinTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(freeSpinTxt);
freeSpinTxt.x = LK.gui.top.width / 2;
freeSpinTxt.y = 110;
// Spin button
spinBtn = new Text2('SPIN', {
size: 120,
fill: '#fff',
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
spinBtn.anchor.set(0.5, 0.5);
LK.gui.bottom.addChild(spinBtn);
spinBtn.x = LK.gui.bottom.width / 2;
spinBtn.y = -180;
spinBtn.interactive = true;
spinBtn.buttonMode = true;
spinBtn.down = function (x, y, obj) {
// Block tap if waiting for free spin overlay tap
if (game._freeSpinAwaitingTap) return;
if (!isSpinning && !isTumbling) {
startSpin();
}
};
// Buy Free Spins button
buyBtn = new Text2('BUY 10 FS (' + bet * 100 + ')', {
size: 70,
fill: '#ffb3e6'
});
buyBtn.anchor.set(0.5, 0.5);
LK.gui.bottom.addChild(buyBtn);
buyBtn.x = LK.gui.bottom.width / 2 + 350;
buyBtn.y = -120;
buyBtn.interactive = true;
buyBtn.buttonMode = true;
buyBtn.down = function (x, y, obj) {
var price = bet * 100;
if (!isSpinning && !isTumbling && balance >= price) {
balance -= price;
updateBalance();
triggerFreeSpins(10);
}
};
// Ante Bet button
anteBtn = new Text2('ANTE OFF', {
size: 70,
fill: '#b3e6ff'
});
anteBtn.anchor.set(0.5, 0.5);
LK.gui.bottom.addChild(anteBtn);
anteBtn.x = LK.gui.bottom.width / 2 - 350;
anteBtn.y = -120;
anteBtn.interactive = true;
anteBtn.buttonMode = true;
anteBtn.down = function (x, y, obj) {
anteBet = !anteBet;
anteBtn.setText(anteBet ? 'ANTE ON' : 'ANTE OFF');
// Defensive: Only set fill if style exists
if (anteBtn && anteBtn.style) {
anteBtn.style.fill = anteBet ? '#ffe066' : '#b3e6ff';
}
};
// Floating win text
floatingText = new FloatingText();
game.addChild(floatingText);
}
// --- Grid setup ---
function setupGrid() {
if (gridContainer) {
game.removeChild(gridContainer);
}
gridContainer = new Container();
game.addChild(gridContainer);
// Center grid
var gridW = GRID_COLS * SYMBOL_SIZE + (GRID_COLS - 1) * SYMBOL_MARGIN;
var gridH = GRID_ROWS * SYMBOL_SIZE + (GRID_ROWS - 1) * SYMBOL_MARGIN;
gridContainer.x = (2048 - gridW) / 2 + SYMBOL_SIZE / 2;
gridContainer.y = (2732 - gridH) / 2 + SYMBOL_SIZE / 2 + 60;
// Position the panel behind the grid
if (typeof gridPanel !== "undefined" && gridPanel) {
gridPanel.x = gridContainer.x + gridW / 2 - SYMBOL_SIZE / 2;
gridPanel.y = gridContainer.y + gridH / 2 - SYMBOL_SIZE / 2;
// Ensure panel is behind grid
game.setChildIndex(gridPanel, 0);
}
// Create grid
grid = [];
for (var r = 0; r < GRID_ROWS; r++) {
grid[r] = [];
for (var c = 0; c < GRID_COLS; c++) {
var sym = new _Symbol();
sym.row = r;
sym.col = c;
sym.x = c * (SYMBOL_SIZE + SYMBOL_MARGIN);
sym.y = r * (SYMBOL_SIZE + SYMBOL_MARGIN);
gridContainer.addChild(sym);
grid[r][c] = sym;
}
}
}
// --- Utility: random symbol ---
function getRandomSymbol(scatterChance, bombChance) {
var roll = Math.random();
// 20% chance for bomb
if (bombChance && roll < 0.20) {
// 5% of bombs are 20x or higher
var highMultiRoll = Math.random();
var multi;
if (highMultiRoll < 0.05) {
// Only pick from multipliers >= 20
var highMultis = [];
for (var i = 0; i < BOMB_MULTIPLIERS.length; i++) {
if (BOMB_MULTIPLIERS[i] >= 20) highMultis.push(BOMB_MULTIPLIERS[i]);
}
// Defensive: fallback to max if none found
if (highMultis.length > 0) {
multi = highMultis[Math.floor(Math.random() * highMultis.length)];
} else {
multi = 20;
}
} else {
// Pick from multipliers < 20
var lowMultis = [];
for (var i = 0; i < BOMB_MULTIPLIERS.length; i++) {
if (BOMB_MULTIPLIERS[i] < 20) lowMultis.push(BOMB_MULTIPLIERS[i]);
}
// Defensive: fallback to min if none found
if (lowMultis.length > 0) {
multi = lowMultis[Math.floor(Math.random() * lowMultis.length)];
} else {
multi = 2;
}
}
return {
type: BOMB,
multiplier: multi
};
}
if (scatterChance && roll < scatterChance) {
return {
type: SCATTER
};
}
var idx = Math.floor(Math.random() * SYMBOLS.length);
return {
type: SYMBOLS[idx]
};
}
// --- Utility: count symbol types ---
function countSymbols(flatGrid) {
var counts = {};
for (var i = 0; i < flatGrid.length; i++) {
var t = flatGrid[i].type;
if (!counts[t]) counts[t] = 0;
counts[t]++;
}
return counts;
}
// --- Utility: flatten grid ---
function flattenGrid() {
var arr = [];
for (var r = 0; r < GRID_ROWS; r++) {
for (var c = 0; c < GRID_COLS; c++) {
arr.push(grid[r][c]);
}
}
return arr;
}
// --- Utility: find all matching symbols (8+) ---
function findMatches() {
var flat = flattenGrid();
var counts = countSymbols(flat);
var matches = [];
for (var t in counts) {
if (t === SCATTER || t === BOMB) continue;
if (counts[t] >= 8) {
// collect all symbols of this type
for (var i = 0; i < flat.length; i++) {
if (flat[i].type === t) matches.push(flat[i]);
}
}
}
return matches;
}
// --- Utility: find scatters ---
function findScatters() {
var flat = flattenGrid();
var scatters = [];
for (var i = 0; i < flat.length; i++) {
if (flat[i].type === SCATTER) scatters.push(flat[i]);
}
return scatters;
}
// --- Utility: find bombs ---
function findBombs() {
var flat = flattenGrid();
var bombs = [];
for (var i = 0; i < flat.length; i++) {
if (flat[i].type === BOMB) bombs.push(flat[i]);
}
return bombs;
}
// --- Utility: payout table ---
function getPayout(type, count) {
// Example payout table (per symbol, per count)
// (values are for MVP, can be tuned)
var table = {
'candy_red': [0, 0, 0, 0, 0, 0, 0, 0, 10, 25, 50, 100, 200, 400, 800, 1600, 3200, 6400, 12800, 25600, 51200],
'candy_blue': [0, 0, 0, 0, 0, 0, 0, 0, 8, 20, 40, 80, 160, 320, 640, 1280, 2560, 5120, 10240, 20480, 40960],
'candy_green': [0, 0, 0, 0, 0, 0, 0, 0, 6, 15, 30, 60, 120, 240, 480, 960, 1920, 3840, 7680, 15360, 30720],
'candy_purple': [0, 0, 0, 0, 0, 0, 0, 0, 5, 12, 24, 48, 96, 192, 384, 768, 1536, 3072, 6144, 12288, 24576],
'fruit_banana': [0, 0, 0, 0, 0, 0, 0, 0, 4, 10, 20, 40, 80, 160, 320, 640, 1280, 2560, 5120, 10240, 20480],
'fruit_grape': [0, 0, 0, 0, 0, 0, 0, 0, 3, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384]
};
if (!table[type]) return 0;
if (count >= table[type].length) count = table[type].length - 1;
return table[type][count];
}
// --- Utility: update balance display ---
function updateBalance() {
balanceTxt.setText('Balance: ' + balance);
betTxt.setText('Bet: ' + bet * (anteBet ? 1.25 : 1));
if (buyBtn) {
buyBtn.setText('BUY 10 FS (' + bet * 100 + ')');
}
}
// --- Utility: update win display ---
function updateWin(win) {
if (win > 0) {
winTxt.setText('WIN: ' + win);
} else {
winTxt.setText('');
}
}
// --- Utility: update free spin display ---
function updateFreeSpinTxt() {
if (isFreeSpins) {
freeSpinTxt.setText('FREE SPINS: ' + freeSpinsLeft);
} else {
freeSpinTxt.setText('');
}
}
// --- Start a spin ---
function startSpin() {
if (isSpinning || isTumbling) return;
if (!isFreeSpins && balance < bet * (anteBet ? 1.25 : 1)) return;
isSpinning = true;
updateWin(0);
lastWin = 0;
scatterCount = 0;
bombs = [];
totalBombMultiplier = 0;
if (!isFreeSpins) {
balance -= bet * (anteBet ? 1.25 : 1);
updateBalance();
}
LK.getSound('spin').play();
// Fill grid with new random symbols
for (var r = 0; r < GRID_ROWS; r++) {
for (var c = 0; c < GRID_COLS; c++) {
var scatterChance = isFreeSpins ? 0 : anteBet ? 0.045 : 0.03; // ~3% or 4.5% chance per cell
var bombChance = isFreeSpins ? 0.08 : 0;
var sym = getRandomSymbol(scatterChance, bombChance);
grid[r][c].setSymbol(sym.type, sym.multiplier);
grid[r][c].alpha = 1;
grid[r][c].visible = true;
}
}
// Animate grid in
for (var r = 0; r < GRID_ROWS; r++) {
for (var c = 0; c < GRID_COLS; c++) {
grid[r][c].y = r * (SYMBOL_SIZE + SYMBOL_MARGIN) - 400;
tween(grid[r][c], {
y: r * (SYMBOL_SIZE + SYMBOL_MARGIN)
}, {
duration: 220 + Math.random() * 120,
easing: tween.bounceOut
});
}
}
LK.setTimeout(function () {
isSpinning = false;
checkWinCascade();
}, 420);
}
// --- Check for wins and cascade ---
function checkWinCascade() {
if (isSpinning || isTumbling) return;
var matches = findMatches();
var scatters = findScatters();
var bombsHere = findBombs();
// Handle scatters
if (!isFreeSpins && scatters.length >= 4) {
triggerFreeSpins(10);
LK.getSound('freespin').play();
}
// Handle bombs (only in free spins)
if (isFreeSpins && bombsHere.length > 0) {
for (var i = 0; i < bombsHere.length; i++) {
bombs.push(bombsHere[i]);
totalBombMultiplier += bombsHere[i].multiplier;
LK.getSound('bomb').play();
bombsHere[i].flash();
}
}
if (matches.length > 0) {
isTumbling = true;
LK.getSound('win').play();
// Flash and float win
var matchType = matches[0].type;
var payout = getPayout(matchType, matches.length);
var winAmount = payout * bet / 20;
lastWin += winAmount;
// Show floating text at center of grid
var gridW = GRID_COLS * SYMBOL_SIZE + (GRID_COLS - 1) * SYMBOL_MARGIN;
var gridH = GRID_ROWS * SYMBOL_SIZE + (GRID_ROWS - 1) * SYMBOL_MARGIN;
floatingText.show('+' + winAmount, gridContainer.x + gridW / 2 - SYMBOL_SIZE / 2, gridContainer.y + gridH / 2 - SYMBOL_SIZE / 2, '#ffe066');
// Animate matched symbols out
for (var i = 0; i < matches.length; i++) {
matches[i].flash();
tween(matches[i], {
alpha: 0
}, {
duration: 180,
easing: tween.linear
});
}
// After animation, remove matched symbols and tumble
LK.setTimeout(function () {
for (var i = 0; i < matches.length; i++) {
matches[i].visible = false;
}
tumbleSymbols(function () {
// After tumble, check for more matches
isTumbling = false;
checkWinCascade();
});
}, 220);
updateWin(lastWin);
} else {
// No more matches, finish spin
if (lastWin > 0) {
// --- Win overlay ---
// Remove previous win overlay if exists
if (game.winOverlay) {
game.removeChild(game.winOverlay);
game.winOverlay = null;
}
// Create overlay container
var overlay = new Container();
// Transparent background
var overlayBg = LK.getAsset('bomb_multi', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 7,
scaleY: 2.5,
alpha: 0.38
});
overlay.addChild(overlayBg);
// Win text
var overlayTxt = new Text2('WIN: ' + lastWin, {
size: 180,
fill: '#ffe066',
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
overlayTxt.anchor.set(0.5, 0.5);
overlay.addChild(overlayTxt);
// Center overlay above grid
var gridW = GRID_COLS * SYMBOL_SIZE + (GRID_COLS - 1) * SYMBOL_MARGIN;
var gridH = GRID_ROWS * SYMBOL_SIZE + (GRID_ROWS - 1) * SYMBOL_MARGIN;
overlay.x = gridContainer.x + gridW / 2 - SYMBOL_SIZE / 2;
overlay.y = gridContainer.y + gridH / 2 - SYMBOL_SIZE / 2;
overlay.zIndex = 9999;
game.addChild(overlay);
game.winOverlay = overlay;
// Fade out overlay after 1.5s
tween(overlay, {
alpha: 0
}, {
duration: 1500,
onFinish: function onFinish() {
if (game.winOverlay) {
game.removeChild(game.winOverlay);
game.winOverlay = null;
}
}
});
// Apply bomb multipliers (free spins only)
if (isFreeSpins && totalBombMultiplier > 0) {
lastWin = Math.floor(lastWin * (1 + totalBombMultiplier / 100));
floatingText.show('x' + (1 + totalBombMultiplier / 100), gridContainer.x + GRID_COLS * SYMBOL_SIZE / 2, gridContainer.y + GRID_ROWS * SYMBOL_SIZE / 2, '#ffb3e6');
LK.getSound('bomb').play();
}
balance += lastWin;
updateBalance();
updateWin(lastWin);
if (isFreeSpins) {
totalFreeSpinWin += lastWin;
}
}
if (isFreeSpins) {
freeSpinsLeft--;
updateFreeSpinTxt();
if (freeSpinsLeft > 0) {
LK.setTimeout(function () {
startSpin();
}, 900);
} else {
// End free spins
isFreeSpins = false;
updateFreeSpinTxt();
if (totalFreeSpinWin > 0) {
floatingText.show('FS WIN: ' + totalFreeSpinWin, gridContainer.x + GRID_COLS * SYMBOL_SIZE / 2, gridContainer.y + GRID_ROWS * SYMBOL_SIZE / 2, '#ffe066');
}
}
}
}
}
// --- Tumble (cascade) symbols ---
function tumbleSymbols(onFinish) {
LK.getSound('tumble').play();
// For each column, move down visible symbols to fill gaps, then add new symbols at top
for (var c = 0; c < GRID_COLS; c++) {
var colSyms = [];
for (var r = 0; r < GRID_ROWS; r++) {
if (grid[r][c].visible) colSyms.push(grid[r][c]);
}
// Fill from bottom up
for (var r = GRID_ROWS - 1; r >= 0; r--) {
if (colSyms.length > 0) {
var sym = colSyms.pop();
grid[r][c].setSymbol(sym.type, sym.multiplier);
grid[r][c].visible = true;
grid[r][c].alpha = 1;
} else {
// New symbol
var scatterChance = isFreeSpins ? 0 : anteBet ? 0.045 : 0.03;
var bombChance = isFreeSpins ? 0.08 : 0;
var symData = getRandomSymbol(scatterChance, bombChance);
grid[r][c].setSymbol(symData.type, symData.multiplier);
grid[r][c].alpha = 0;
grid[r][c].visible = true;
tween(grid[r][c], {
alpha: 1
}, {
duration: 180,
easing: tween.linear
});
}
}
}
LK.setTimeout(function () {
if (onFinish) onFinish();
}, 220);
}
// --- Trigger free spins ---
function triggerFreeSpins(count) {
isFreeSpins = true;
freeSpinsLeft = count;
totalFreeSpinWin = 0;
updateFreeSpinTxt();
LK.getSound('freespin').play();
// --- Animate all lollipops (scatter_lollipop) to shake ---
var flat = flattenGrid();
for (var i = 0; i < flat.length; i++) {
if (flat[i].type === SCATTER) {
// Shake animation: quick left-right movement, repeat a few times
(function (sym) {
var shakeTimes = 6;
var shakeDist = 24;
var origX = sym.x;
var seq = [];
for (var s = 0; s < shakeTimes; s++) {
seq.push({
x: origX + (s % 2 === 0 ? shakeDist : -shakeDist),
duration: 40
});
}
seq.push({
x: origX,
duration: 40
});
// Chain tweens
var _doTween = function doTween(idx) {
if (idx >= seq.length) return;
tween(sym, {
x: seq[idx].x
}, {
duration: seq[idx].duration,
onFinish: function onFinish() {
_doTween(idx + 1);
}
});
};
_doTween(0);
})(flat[i]);
}
}
// --- Show overlay text: "FREE SPINS! Tap to continue" ---
// Remove previous overlay if exists
if (game.freeSpinOverlay) {
game.removeChild(game.freeSpinOverlay);
game.freeSpinOverlay = null;
}
var overlay = new Container();
// Transparent background
var overlayBg = LK.getAsset('bomb_multi', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 7,
scaleY: 2.5,
alpha: 0.38
});
overlay.addChild(overlayBg);
// Overlay text
var overlayTxt = new Text2('FREE SPINS!\nTap to continue', {
size: 140,
fill: '#ffb3e6',
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma",
align: 'center'
});
overlayTxt.anchor.set(0.5, 0.5);
overlay.addChild(overlayTxt);
// Center overlay above grid
var gridW = GRID_COLS * SYMBOL_SIZE + (GRID_COLS - 1) * SYMBOL_MARGIN;
var gridH = GRID_ROWS * SYMBOL_SIZE + (GRID_ROWS - 1) * SYMBOL_MARGIN;
overlay.x = gridContainer.x + gridW / 2 - SYMBOL_SIZE / 2;
overlay.y = gridContainer.y + gridH / 2 - SYMBOL_SIZE / 2;
overlay.zIndex = 9999;
game.addChild(overlay);
game.freeSpinOverlay = overlay;
// Block game.down and spinBtn while overlay is up
game._freeSpinAwaitingTap = true;
// Add a one-time tap handler to remove overlay and start spins
overlay.interactive = true;
overlay.buttonMode = true;
overlay.down = function (x, y, obj) {
if (!game._freeSpinAwaitingTap) return;
game._freeSpinAwaitingTap = false;
if (game.freeSpinOverlay) {
game.removeChild(game.freeSpinOverlay);
game.freeSpinOverlay = null;
}
// Start free spins after tap
startSpin();
};
}
// --- Game update loop (not used for logic, but can be used for future animations) ---
game.update = function () {
// No per-frame logic needed for MVP
};
// --- Game start ---
setupUI();
setupGrid();
updateBalance();
updateWin(0);
updateFreeSpinTxt();
LK.playMusic('bgm', {
fade: {
start: 0,
end: 1,
duration: 1200
}
});
// --- Touch anywhere on grid to spin (for mobile) ---
game.down = function (x, y, obj) {
// Block tap if waiting for free spin overlay tap
if (game._freeSpinAwaitingTap) return;
if (!isSpinning && !isTumbling) {
startSpin();
}
};
scatter_lollipop. In-Game asset. 2d. High contrast. No shadows
fruit_grape. In-Game asset. 2d. High contrast. No shadows
fruit_banana. In-Game asset. 2d. High contrast. No shadows
candy_red. In-Game asset. 2d. High contrast. No shadows
candy_purple. In-Game asset. 2d. High contrast. No shadows
candy_green. In-Game asset. 2d. High contrast. No shadows
candy_blue. In-Game asset. 2d. High contrast. No shadows
bomb_multi. In-Game asset. 2d. High contrast. No shadows
"Meyve Ul Cima" text logo. In-Game asset. 2d. High contrast. No shadows