/****
* 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();
;