/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Card class: represents a single card in the memory game var Card = Container.expand(function () { var self = Container.call(this); // Card properties self.symbol = null; // Symbol or id for matching self.isFaceUp = false; self.isMatched = false; self.index = -1; // Position in the grid // Card dimensions (will be set in game code) self.cardWidth = 0; self.cardHeight = 0; // Face-down asset (back of card) var backAssetId; var frontAssetId = 'cardFront'; if (typeof currentLevel !== "undefined" && currentLevel > 45) { backAssetId = 'CardBack8'; } else if (typeof currentLevel !== "undefined" && currentLevel > 35) { backAssetId = 'Cardback7'; } else if (typeof currentLevel !== "undefined" && currentLevel > 25) { backAssetId = 'CardBack6'; } else if (typeof currentLevel !== "undefined" && currentLevel > 20) { backAssetId = 'CardBack5'; } else if (typeof currentLevel !== "undefined" && currentLevel > 15) { backAssetId = 'CardBack4'; } else if (typeof currentLevel !== "undefined" && currentLevel > 10) { backAssetId = 'CardBack3'; } else if (typeof currentLevel !== "undefined" && currentLevel > 5) { backAssetId = 'CardBack2'; } else { backAssetId = 'cardBack'; } var back = self.attachAsset(backAssetId, { anchorX: 0.5, anchorY: 0.5 }); // Face-up asset (front of card, shows symbol) var front = self.attachAsset(frontAssetId, { anchorX: 0.5, anchorY: 0.5 }); // Symbol text (shows symbol when face up) var symbolText = new Text2('', { size: 90, fill: 0x222222 }); symbolText.anchor.set(0.5, 0.5); front.addChild(symbolText); // Set card size self.setSize = function (w, h) { self.cardWidth = w; self.cardHeight = h; back.width = w; back.height = h; front.width = w; front.height = h; }; // Set the symbol for this card self.setSymbol = function (symbol) { self.symbol = symbol; symbolText.setText(symbol); }; // Flip the card to face up (with animation) self.flipUp = function (_onFinish) { if (self.isFaceUp || self.isMatched) { return; } self.isFaceUp = true; // Animate flip: scaleX 1 -> 0, swap, 0 -> 1 tween(self, { scaleX: 0 }, { duration: 120, easing: tween.cubicIn, onFinish: function onFinish() { back.visible = false; front.visible = true; tween(self, { scaleX: 1 }, { duration: 120, easing: tween.cubicOut, onFinish: function onFinish() { if (_onFinish) { _onFinish(); } } }); } }); }; // Flip the card to face down (with animation) self.flipDown = function (_onFinish2) { if (!self.isFaceUp || self.isMatched) { return; } self.isFaceUp = false; // Animate flip: scaleX 1 -> 0, swap, 0 -> 1 tween(self, { scaleX: 0 }, { duration: 120, easing: tween.cubicIn, onFinish: function onFinish() { front.visible = false; back.visible = true; tween(self, { scaleX: 1 }, { duration: 120, easing: tween.cubicOut, onFinish: function onFinish() { if (_onFinish2) { _onFinish2(); } } }); } }); }; // Instantly show face up (no animation) self.showFaceUp = function () { self.isFaceUp = true; self.scaleX = 1; back.visible = false; front.visible = true; }; // Instantly show face down (no animation) self.showFaceDown = function () { self.isFaceUp = false; self.scaleX = 1; front.visible = false; back.visible = true; }; // Mark as matched (disable interaction, highlight) self.setMatched = function () { self.isMatched = true; // Subtle highlight tween(self, { tint: 0xA0FFA0 }, { duration: 300, easing: tween.linear }); }; // Handle tap/click self.down = function (x, y, obj) { if (self.isFaceUp || self.isMatched || game.lockInput) { return; } game.onCardTapped(self); }; // Initialize: show face down self.showFaceDown(); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2e3a4f // Deep blue background }); /**** * Game Code ****/ // --- Card symbols (use simple emojis for MVP) --- // Tween plugin for card flip and reveal animations var cardSymbols = ["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐ฅ", "๐ฅ", "๐", "๐", "๐", "๐", "๐ฅฅ", "๐ ", "๐ฅญ", "๐โ๐ฉ"]; var cardSymbols2 = ["๐ถ", "๐ฑ", "๐ญ", "๐ฐ", "๐ฆ", "๐ป", "๐ผ", "๐จ", "๐ฏ", "๐ฆ", "๐ฎ", "๐ท", "๐ธ", "๐ต", "๐", "๐ง", "๐ฆ", "๐ฆโโฌ", "๐ฅ", "๐ฆ", "๐ฆ ", "๐ฆ", "๐ฆ", "๐บ", "๐ฆ", "๐", "๐ท", "๐ฆ"]; var cardSymbols3 = ["๐", "๐", "๐", "๐", "๐", "๐๏ธ", "๐", "๐", "๐", "๐", "๐ป", "๐", "๐", "๐", "๐ต", "๐๏ธ", "๐ด", "๐ฒ", "๐บ", "๐ค", "โต๏ธ", "๐ถ", "๐ฉ๏ธ", "โ๏ธ", "๐ฐ๏ธ", "๐", "๐", "๐ธ"]; var cardSymbols4 = ["๐งฐ", "๐ช", "๐ง", "๐จ", "โ๏ธ", "๐ ๏ธ", "โ๏ธ", "๐ช", "๐ช", "๐ฉ", "โ๏ธ", "๐ชค", "๐งฑ", "๐งฒ", "๐ซ", "๐ฃ", "๐งจ", "๐ช", "๐ช", "๐ก๏ธ", "โ๏ธ", "๐ก๏ธ", "๐ชฆ", "๐", "๐ช", "๐", "๐", "๐", "๐ฌ", "โ๏ธ", "๐ช", "๐น", "๐ฅ", "๐ฅ", "๐ท", "๐บ", "๐ธ", "๐น", "๐ฅ", "๐ฅ"]; // --- Game settings --- var maxLevel = 50; var currentLevel = 1; // Level configuration: grid size per level (increase difficulty) function getLevelConfig(level) { // Level 1-5: 4x4, 6-10: 5x4, 11-15: 6x5, 16-20: 6x6, 21-25: 7x6 if (level <= 5) { return { cols: 4, rows: 4 }; } if (level <= 10) { return { cols: 5, rows: 4 }; } if (level <= 15) { return { cols: 6, rows: 5 }; } if (level <= 20) { return { cols: 6, rows: 6 }; } if (level <= 35) { return { cols: 8, rows: 6 }; } if (level <= 45) { return { cols: 8, rows: 7 }; } if (level < 50) { return { cols: 8, rows: 7 }; } if (level >= 50) { return { cols: 10, rows: 8 }; } return { cols: 9, rows: 7 }; } var gridCols = getLevelConfig(currentLevel).cols; var gridRows = getLevelConfig(currentLevel).rows; var totalPairs = gridCols * gridRows / 2; var cardSpacing = 36; // px between cards // --- Card assets (simple colored rectangles) --- // --- Game state --- var cards = []; // All card objects var firstCard = null; var secondCard = null; var lockInput = false; // Prevent input during animations var moves = 0; var matchesFound = 0; var timer = null; var elapsedTime = 0; // in seconds var timerText = null; var movesText = null; // --- GUI: Moves, Timer, Level --- var levelText = new Text2('Level: 1', { size: 80, fill: 0xFFFFFF }); levelText.anchor.set(0.5, 0); LK.gui.top.addChild(levelText); timerText = new Text2('Time: 0s', { size: 80, fill: 0xFFFFFF }); timerText.anchor.set(0.5, 0); LK.gui.top.addChild(timerText); // Position GUI elements (timer right, avoid top-left 100x100) // Align level indicator to top middle using LK.gui.top levelText.x = -10; levelText.y = 20; timerText.x = 500; // --- Timer for random swap after level 35 --- var randomSwapTimer = null; var randomSwapTimer10s = null; var randomSwapTimer5s = null; var randomSwapTimer2s = null; // --- Swap queue for level 50+ to prevent overlapping swaps --- var swapQueue = []; var swapInProgress = false; // Helper: Queue a swap (for level 50+) function queueCardSwap(cardA, cardB) { swapQueue.push({ cardA: cardA, cardB: cardB }); processSwapQueue(); } // Helper: Process swap queue (for level 50+) function processSwapQueue() { if (swapInProgress || swapQueue.length === 0) { return; } swapInProgress = true; var swap = swapQueue.shift(); var cardA = swap.cardA; var cardB = swap.cardB; var tempSymbol = cardA.symbol; var tempIndex = cardA.index; cardA.setSymbol(cardB.symbol); cardB.setSymbol(tempSymbol); // Swap index property as well cardA.index = cardB.index; cardB.index = tempIndex; var tempX = cardA.x; var tempY = cardA.y; var swapsDone = 0; function onSwapDone() { swapsDone++; if (swapsDone === 2) { swapInProgress = false; // Snap to grid to avoid drift cardA.x = Math.round(cardA.x); cardA.y = Math.round(cardA.y); cardB.x = Math.round(cardB.x); cardB.y = Math.round(cardB.y); processSwapQueue(); } } tween(cardA, { x: cardB.x, y: cardB.y }, { duration: 1200, easing: tween.cubicInOut, onFinish: onSwapDone }); tween(cardB, { x: tempX, y: tempY }, { duration: 1200, easing: tween.cubicInOut, onFinish: onSwapDone }); } // --- Helper: Shuffle array (Fisher-Yates) --- function shuffleArray(arr) { for (var i = arr.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } // --- Layout cards --- function layoutCards() { // Remove old cards if any for (var i = 0; i < cards.length; i++) { cards[i].destroy(); } cards = []; // Prepare symbols (duplicate and shuffle) var symbols = []; var useSymbols; if (currentLevel === 9 || currentLevel === 41) { useSymbols = cardSymbols3; } else { useSymbols = currentLevel >= 50 ? cardSymbols4 : currentLevel > 45 ? cardSymbols3 : currentLevel > 20 ? cardSymbols2 : cardSymbols; } for (var i = 0; i < totalPairs; i++) { symbols.push(useSymbols[i]); symbols.push(useSymbols[i]); } shuffleArray(symbols); // Card size: fit grid to 2048x2732 with spacing var availableWidth = 2048 - cardSpacing * (gridCols + 1); var availableHeight = 1800 - cardSpacing * (gridRows + 1); // leave space for GUI var cardWidth = Math.floor(availableWidth / gridCols); var cardHeight = Math.floor(availableHeight / gridRows); // Center grid var gridPixelWidth = cardWidth * gridCols + cardSpacing * (gridCols - 1); var gridPixelHeight = cardHeight * gridRows + cardSpacing * (gridRows - 1); var startX = Math.floor((2048 - gridPixelWidth) / 2) + cardWidth / 2; var startY = 350 + cardHeight / 2; // leave space for GUI // Create cards for (var row = 0; row < gridRows; row++) { for (var col = 0; col < gridCols; col++) { var idx = row * gridCols + col; var card = new Card(); card.setSize(cardWidth, cardHeight); card.setSymbol(symbols[idx]); card.index = idx; card.x = startX + col * (cardWidth + cardSpacing); card.y = startY + row * (cardHeight + cardSpacing); card.showFaceDown(); game.addChild(card); cards.push(card); } } } // --- Reset game state --- function resetGame() { firstCard = null; secondCard = null; lockInput = false; moves = 0; matchesFound = 0; if (currentLevel === 16) { elapsedTime = 150; } else if (currentLevel === 21) { elapsedTime = 150; } else if (currentLevel === 26) { elapsedTime = 200; } else if (currentLevel === 31) { elapsedTime = 200; } else if (currentLevel === 36) { elapsedTime = 250; } else if (currentLevel === 41) { elapsedTime = 250; } else if (currentLevel === 46) { elapsedTime = 300; } else if (currentLevel === 50) { elapsedTime = 500; } else if (currentLevel === 1) { elapsedTime = 100; } else if (currentLevel === 6 || currentLevel === 11) { elapsedTime = 120; } else { elapsedTime = 120; } if (typeof levelText !== "undefined") { // At level 6, display Level: 2 instead of Level: 6 if (currentLevel === 6) { levelText.setText('Level: 2'); } else if (currentLevel === 11) { levelText.setText('Level: 3'); } else if (currentLevel === 16) { levelText.setText('Level: 4'); } else if (currentLevel === 21) { levelText.setText('Level: 5'); } else if (currentLevel === 26) { levelText.setText('Level: 6'); } else if (currentLevel === 31) { levelText.setText('Level: 7'); } else if (currentLevel === 36) { levelText.setText('Level: 8'); } else if (currentLevel === 41) { levelText.setText('Level: 9'); } else if (currentLevel === 46) { levelText.setText('Level: 10'); } else if (currentLevel === 50) { levelText.setText('Final Level'); } else if (currentLevel === 26) { levelText.setText('Level: 6'); } else if (currentLevel === 21) { levelText.setText('Level: 5'); } else if (currentLevel === 16) { levelText.setText('Level: 4'); } else { levelText.setText('Level: ' + currentLevel); } } // Update grid size for this level var config = getLevelConfig(currentLevel); gridCols = config.cols; gridRows = config.rows; totalPairs = gridCols * gridRows / 2; timerText.setText('Time: ' + elapsedTime + 's'); layoutCards(); // Play lastlevelmusic at level 50 if (currentLevel === 50) { LK.playMusic('lastlevelmusic'); } if (timer) { LK.clearInterval(timer); } timer = LK.setInterval(function () { elapsedTime--; if (elapsedTime <= 0) { elapsedTime = 0; timerText.setText('Time: 0s'); LK.clearInterval(timer); LK.showGameOver(); // You lose if timer gets to 0 return; } else { timerText.setText('Time: ' + elapsedTime + 's'); } }, 1000); // --- Timer-based random swap after level 35 --- if (typeof randomSwapTimer !== "undefined" && randomSwapTimer) { LK.clearInterval(randomSwapTimer); randomSwapTimer = null; } if (currentLevel >= 50) { // At level 50, swap every 2s, 5s, 10s, and 20s using the swap queue swapQueue = []; swapInProgress = false; randomSwapTimer = LK.setInterval(function () { // 20s swap var swappable = []; for (var i = 0; i < cards.length; i++) { if (!cards[i].isMatched && !cards[i].isFaceUp) { swappable.push(cards[i]); } } if (swappable.length >= 2) { var idx1 = Math.floor(Math.random() * swappable.length); var idx2 = idx1; while (idx2 === idx1) { idx2 = Math.floor(Math.random() * swappable.length); } var cardA = swappable[idx1]; var cardB = swappable[idx2]; queueCardSwap(cardA, cardB); } }, 20000); // 20 seconds // Add a second timer for 10s swap if (typeof randomSwapTimer10s !== "undefined" && randomSwapTimer10s) { LK.clearInterval(randomSwapTimer10s); randomSwapTimer10s = null; } randomSwapTimer10s = LK.setInterval(function () { var swappable = []; for (var i = 0; i < cards.length; i++) { if (!cards[i].isMatched && !cards[i].isFaceUp) { swappable.push(cards[i]); } } if (swappable.length >= 2) { var idx1 = Math.floor(Math.random() * swappable.length); var idx2 = idx1; while (idx2 === idx1) { idx2 = Math.floor(Math.random() * swappable.length); } var cardA = swappable[idx1]; var cardB = swappable[idx2]; queueCardSwap(cardA, cardB); } }, 10000); // 10 seconds // Add a third timer for 5s swap if (typeof randomSwapTimer5s !== "undefined" && randomSwapTimer5s) { LK.clearInterval(randomSwapTimer5s); randomSwapTimer5s = null; } randomSwapTimer5s = LK.setInterval(function () { var swappable = []; for (var i = 0; i < cards.length; i++) { if (!cards[i].isMatched && !cards[i].isFaceUp) { swappable.push(cards[i]); } } if (swappable.length >= 2) { var idx1 = Math.floor(Math.random() * swappable.length); var idx2 = idx1; while (idx2 === idx1) { idx2 = Math.floor(Math.random() * swappable.length); } var cardA = swappable[idx1]; var cardB = swappable[idx2]; queueCardSwap(cardA, cardB); } }, 5000); // 5 seconds // Add a fourth timer for 2s swap if (typeof randomSwapTimer2s !== "undefined" && randomSwapTimer2s) { LK.clearInterval(randomSwapTimer2s); randomSwapTimer2s = null; } randomSwapTimer2s = LK.setInterval(function () { var swappable = []; for (var i = 0; i < cards.length; i++) { if (!cards[i].isMatched && !cards[i].isFaceUp) { swappable.push(cards[i]); } } if (swappable.length >= 2) { var idx1 = Math.floor(Math.random() * swappable.length); var idx2 = idx1; while (idx2 === idx1) { idx2 = Math.floor(Math.random() * swappable.length); } var cardA = swappable[idx1]; var cardB = swappable[idx2]; queueCardSwap(cardA, cardB); } }, 2000); // 2 seconds } else if (currentLevel > 40) { // Only 20s swap randomSwapTimer = LK.setInterval(function () { var swappable = []; for (var i = 0; i < cards.length; i++) { if (!cards[i].isMatched && !cards[i].isFaceUp) { swappable.push(cards[i]); } } if (swappable.length >= 2) { var idx1 = Math.floor(Math.random() * swappable.length); var idx2 = idx1; while (idx2 === idx1) { idx2 = Math.floor(Math.random() * swappable.length); } var cardA = swappable[idx1]; var cardB = swappable[idx2]; var tempSymbol = cardA.symbol; var tempIndex = cardA.index; cardA.setSymbol(cardB.symbol); cardB.setSymbol(tempSymbol); // Swap index property as well cardA.index = cardB.index; cardB.index = tempIndex; var tempX = cardA.x; var tempY = cardA.y; tween(cardA, { x: cardB.x, y: cardB.y }, { duration: 1200, easing: tween.cubicInOut }); tween(cardB, { x: tempX, y: tempY }, { duration: 1200, easing: tween.cubicInOut }); } }, 20000); // 20 seconds // Clear 10s timer if it exists if (typeof randomSwapTimer10s !== "undefined" && randomSwapTimer10s) { LK.clearInterval(randomSwapTimer10s); randomSwapTimer10s = null; } } else { randomSwapTimer = null; if (typeof randomSwapTimer10s !== "undefined" && randomSwapTimer10s) { LK.clearInterval(randomSwapTimer10s); randomSwapTimer10s = null; } } } // --- Card tap handler --- game.onCardTapped = function (card) { if (lockInput || card.isFaceUp || card.isMatched) { return; } if (!firstCard) { firstCard = card; LK.getSound('cardchose').play(); card.flipUp(); } else if (!secondCard && card !== firstCard) { secondCard = card; lockInput = true; LK.getSound('cardchose').play(); card.flipUp(function () { // Check for match after both are face up LK.setTimeout(function () { checkMatch(); }, 150); }); } }; // --- Check for match --- function checkMatch() { moves++; // After level 40, swap 2 random cards every 2 moves; after level 30, every 4 moves; after level 25, every 5 moves; after level 20, every 10 moves var shouldSwap = false; if (currentLevel >= 50 && moves % 1 === 0 && cards.length > 1) { shouldSwap = true; } else if (currentLevel > 40 && moves % 2 === 0 && cards.length > 1) { shouldSwap = true; } else if (currentLevel > 30 && moves % 4 === 0 && cards.length > 1) { shouldSwap = true; } else if (currentLevel > 25 && moves % 5 === 0 && cards.length > 1) { shouldSwap = true; } else if (currentLevel > 20 && moves % 10 === 0 && cards.length > 1) { shouldSwap = true; } if (shouldSwap) { // Find all cards that are not matched and not currently face up var swappable = []; for (var i = 0; i < cards.length; i++) { if (!cards[i].isMatched && !cards[i].isFaceUp) { swappable.push(cards[i]); } } if (swappable.length >= 2) { var idx1 = Math.floor(Math.random() * swappable.length); var idx2 = idx1; while (idx2 === idx1) { idx2 = Math.floor(Math.random() * swappable.length); } var cardA = swappable[idx1]; var cardB = swappable[idx2]; if (currentLevel >= 50) { queueCardSwap(cardA, cardB); } else { // Old behavior for lower levels var tempSymbol = cardA.symbol; var tempIndex = cardA.index; cardA.setSymbol(cardB.symbol); cardB.setSymbol(tempSymbol); // Swap index property as well cardA.index = cardB.index; cardB.index = tempIndex; var tempX = cardA.x; var tempY = cardA.y; tween(cardA, { x: cardB.x, y: cardB.y }, { duration: 1200, easing: tween.cubicInOut }); tween(cardB, { x: tempX, y: tempY }, { duration: 1200, easing: tween.cubicInOut }); } } } if (firstCard && secondCard && firstCard.symbol === secondCard.symbol) { // Match! LK.getSound('cardmatch').play(); firstCard.setMatched(); secondCard.setMatched(); matchesFound++; // Subtle scale animation for matched cards, then disappear if (firstCard && secondCard) { var matched1 = firstCard; var matched2 = secondCard; if (matched1) { tween(matched1, { scaleX: 1.15, scaleY: 1.15 }, { duration: 80, easing: tween.cubicOut, onFinish: function onFinish() { if (matched1) { tween(matched1, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 120, onFinish: function onFinish() { if (matched1) { matched1.destroy(); } } }); } } }); } if (matched2) { tween(matched2, { scaleX: 1.15, scaleY: 1.15 }, { duration: 80, easing: tween.cubicOut, onFinish: function onFinish() { if (matched2) { tween(matched2, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 120, onFinish: function onFinish() { if (matched2) { matched2.destroy(); } } }); } } }); } } // Check for win if (matchesFound === totalPairs) { LK.clearInterval(timer); LK.setScore(moves); // Use moves as score (lower is better) if (currentLevel < maxLevel) { // Play level completed sound LK.getSound('levelcompleted').play(); // Show "Level Completed" text in the center of the screen var levelCompletedText = new Text2('Level Completed!', { size: 180, fill: 0xFFD700, font: "Impact" }); levelCompletedText.anchor.set(0.5, 0.5); levelCompletedText.x = 2048 / 2; levelCompletedText.y = 2732 / 2; game.addChild(levelCompletedText); if (currentLevel === 1) { currentLevel = 6; } else if (currentLevel === 6) { currentLevel = 11; } else if (currentLevel === 11) { currentLevel = 16; } else if (currentLevel === 16) { currentLevel = 21; } else if (currentLevel === 21) { currentLevel = 26; } else if (currentLevel === 26) { currentLevel = 31; } else if (currentLevel === 31) { currentLevel = 36; } else if (currentLevel === 36) { currentLevel = 41; } else if (currentLevel === 41) { currentLevel = 46; } else if (currentLevel === 46) { currentLevel = 50; } else { currentLevel++; } // Short delay before next level, remove text before reset LK.setTimeout(function () { if (levelCompletedText && typeof levelCompletedText.destroy === "function") { levelCompletedText.destroy(); } resetGame(); }, 1200); } else { LK.showYouWin(); } } // Reset selection firstCard = null; secondCard = null; lockInput = false; } else { // Not a match: flip both back after short delay LK.setTimeout(function () { if (firstCard && typeof firstCard.flipDown === "function") { firstCard.flipDown(); } if (secondCard && typeof secondCard.flipDown === "function") { secondCard.flipDown(function () { firstCard = null; secondCard = null; lockInput = false; }); } else { // If secondCard is missing, still reset state after delay firstCard = null; secondCard = null; lockInput = false; } }, 250); } } // --- Game update (not used for logic, but required) --- game.update = function () { // Movement-based card swap: if two cards are dragged/moved to each other's position, swap their symbol and index // (Assuming you have a drag/move system, e.g. for future features or mobile drag-to-swap) // This is a defensive implementation: checks for cards that overlap and are not already matched or face up for (var i = 0; i < cards.length; i++) { var cardA = cards[i]; if (cardA.isMatched || cardA.isFaceUp) continue; for (var j = i + 1; j < cards.length; j++) { var cardB = cards[j]; if (cardB.isMatched || cardB.isFaceUp) continue; // If cardA and cardB overlap (bounding box collision) if (Math.abs(cardA.x - cardB.x) < (cardA.cardWidth + cardB.cardWidth) / 2 && Math.abs(cardA.y - cardB.y) < (cardA.cardHeight + cardB.cardHeight) / 2) { // Only swap if not already swapped this frame if (!cardA._swappedThisFrame && !cardB._swappedThisFrame) { // Swap symbol and index var tempSymbol = cardA.symbol; var tempIndex = cardA.index; cardA.setSymbol(cardB.symbol); cardB.setSymbol(tempSymbol); cardA.index = cardB.index; cardB.index = tempIndex; cardA._swappedThisFrame = true; cardB._swappedThisFrame = true; } } } } // Reset swap flags for next frame for (var i = 0; i < cards.length; i++) { cards[i]._swappedThisFrame = false; } }; // --- DEV Button: Center bottom, always visible, go to next level --- // (Hidden for production, but function remains for later update test) var devBtn = new Text2('DEV', { size: 90, fill: 0xFF00FF, font: "Impact" }); devBtn.anchor.set(0.5, 1); // Place DEV button slightly left of center devBtn.x = -120; devBtn.y = -50; // LK.gui.bottom.addChild(devBtn); // HIDDEN: do not add to GUI, but keep logic for later test devBtn.visible = false; // Hide from view // DEV button handler: go to next level immediately (max 25) devBtn.down = function (x, y, obj) { if (matchesFound === totalPairs) { // Already won, do nothing return; } // Replicate the level pass logic from checkMatch() if (currentLevel < maxLevel) { // Play level completed sound LK.getSound('levelcompleted').play(); // Show "Level Completed" text in the center of the screen var levelCompletedText = new Text2('Level Completed!', { size: 180, fill: 0xFFD700, font: "Impact" }); levelCompletedText.anchor.set(0.5, 0.5); levelCompletedText.x = 2048 / 2; levelCompletedText.y = 2732 / 2; game.addChild(levelCompletedText); if (currentLevel === 1) { currentLevel = 6; } else if (currentLevel === 6) { currentLevel = 11; } else if (currentLevel === 11) { currentLevel = 16; } else if (currentLevel === 16) { currentLevel = 21; } else if (currentLevel === 21) { currentLevel = 26; } else if (currentLevel === 26) { levelText.setText('Level: 6'); currentLevel = 31; } else if (currentLevel === 31) { levelText.setText('Level: 7'); currentLevel = 36; } else if (currentLevel === 36) { levelText.setText('Level: 8'); levelText.setText('Level: 8'); currentLevel = 41; } else if (currentLevel === 41) { levelText.setText('Level: 9'); currentLevel = 46; } else if (currentLevel === 46) { levelText.setText('Level: 10'); currentLevel = 50; } else if (currentLevel === 50) { levelText.setText('Final Level'); currentLevel = 50; } else { currentLevel++; } LK.setTimeout(function () { if (levelCompletedText && typeof levelCompletedText.destroy === "function") { levelCompletedText.destroy(); } resetGame(); }, 1200); } else { LK.showYouWin(); } }; // --- Reveal Button: Center bottom, always visible, reveals all cards --- // (Hidden for production, but function remains for later update test) var revealBtn = new Text2('Reveal', { size: 90, fill: 0x00CCFF, font: "Impact" }); revealBtn.anchor.set(0.5, 1); // Place Reveal button slightly right of center revealBtn.x = 120; revealBtn.y = -50; // LK.gui.bottom.addChild(revealBtn); // HIDDEN: do not add to GUI, but keep logic for later test revealBtn.visible = false; // Hide from view // Reveal button handler: toggle all cards between revealed and hidden var revealState = false; // false = hidden, true = revealed revealBtn.down = function (x, y, obj) { revealState = !revealState; if (revealState) { // Reveal all cards (that are not matched) for (var i = 0; i < cards.length; i++) { var card = cards[i]; if (!card.isFaceUp && !card.isMatched) { card.flipUp(); } } revealBtn.setText('Hide'); } else { // Hide all cards (that are not matched) for (var i = 0; i < cards.length; i++) { var card = cards[i]; if (card.isFaceUp && !card.isMatched) { card.flipDown(); } } revealBtn.setText('Reveal'); } }; // --- Start game --- LK.playMusic('gamemusic'); resetGame(); // --- Game over handler (reset on game over) --- game.onGameOver = function () { if (timer) { LK.clearInterval(timer); } if (typeof randomSwapTimer !== "undefined" && randomSwapTimer) { LK.clearInterval(randomSwapTimer); randomSwapTimer = null; } if (typeof randomSwapTimer10s !== "undefined" && randomSwapTimer10s) { LK.clearInterval(randomSwapTimer10s); randomSwapTimer10s = null; } if (typeof randomSwapTimer5s !== "undefined" && randomSwapTimer5s) { LK.clearInterval(randomSwapTimer5s); randomSwapTimer5s = null; } if (typeof randomSwapTimer2s !== "undefined" && randomSwapTimer2s) { LK.clearInterval(randomSwapTimer2s); randomSwapTimer2s = null; } currentLevel = 1; LK.stopMusic(); resetGame(); }; // --- Win handler (reset on win) --- game.onYouWin = function () { if (timer) { LK.clearInterval(timer); } if (typeof randomSwapTimer !== "undefined" && randomSwapTimer) { LK.clearInterval(randomSwapTimer); randomSwapTimer = null; } if (typeof randomSwapTimer10s !== "undefined" && randomSwapTimer10s) { LK.clearInterval(randomSwapTimer10s); randomSwapTimer10s = null; } if (typeof randomSwapTimer5s !== "undefined" && randomSwapTimer5s) { LK.clearInterval(randomSwapTimer5s); randomSwapTimer5s = null; } if (typeof randomSwapTimer2s !== "undefined" && randomSwapTimer2s) { LK.clearInterval(randomSwapTimer2s); randomSwapTimer2s = null; } currentLevel = 1; LK.stopMusic(); // Game will reset automatically by LK };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Card class: represents a single card in the memory game
var Card = Container.expand(function () {
var self = Container.call(this);
// Card properties
self.symbol = null; // Symbol or id for matching
self.isFaceUp = false;
self.isMatched = false;
self.index = -1; // Position in the grid
// Card dimensions (will be set in game code)
self.cardWidth = 0;
self.cardHeight = 0;
// Face-down asset (back of card)
var backAssetId;
var frontAssetId = 'cardFront';
if (typeof currentLevel !== "undefined" && currentLevel > 45) {
backAssetId = 'CardBack8';
} else if (typeof currentLevel !== "undefined" && currentLevel > 35) {
backAssetId = 'Cardback7';
} else if (typeof currentLevel !== "undefined" && currentLevel > 25) {
backAssetId = 'CardBack6';
} else if (typeof currentLevel !== "undefined" && currentLevel > 20) {
backAssetId = 'CardBack5';
} else if (typeof currentLevel !== "undefined" && currentLevel > 15) {
backAssetId = 'CardBack4';
} else if (typeof currentLevel !== "undefined" && currentLevel > 10) {
backAssetId = 'CardBack3';
} else if (typeof currentLevel !== "undefined" && currentLevel > 5) {
backAssetId = 'CardBack2';
} else {
backAssetId = 'cardBack';
}
var back = self.attachAsset(backAssetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Face-up asset (front of card, shows symbol)
var front = self.attachAsset(frontAssetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Symbol text (shows symbol when face up)
var symbolText = new Text2('', {
size: 90,
fill: 0x222222
});
symbolText.anchor.set(0.5, 0.5);
front.addChild(symbolText);
// Set card size
self.setSize = function (w, h) {
self.cardWidth = w;
self.cardHeight = h;
back.width = w;
back.height = h;
front.width = w;
front.height = h;
};
// Set the symbol for this card
self.setSymbol = function (symbol) {
self.symbol = symbol;
symbolText.setText(symbol);
};
// Flip the card to face up (with animation)
self.flipUp = function (_onFinish) {
if (self.isFaceUp || self.isMatched) {
return;
}
self.isFaceUp = true;
// Animate flip: scaleX 1 -> 0, swap, 0 -> 1
tween(self, {
scaleX: 0
}, {
duration: 120,
easing: tween.cubicIn,
onFinish: function onFinish() {
back.visible = false;
front.visible = true;
tween(self, {
scaleX: 1
}, {
duration: 120,
easing: tween.cubicOut,
onFinish: function onFinish() {
if (_onFinish) {
_onFinish();
}
}
});
}
});
};
// Flip the card to face down (with animation)
self.flipDown = function (_onFinish2) {
if (!self.isFaceUp || self.isMatched) {
return;
}
self.isFaceUp = false;
// Animate flip: scaleX 1 -> 0, swap, 0 -> 1
tween(self, {
scaleX: 0
}, {
duration: 120,
easing: tween.cubicIn,
onFinish: function onFinish() {
front.visible = false;
back.visible = true;
tween(self, {
scaleX: 1
}, {
duration: 120,
easing: tween.cubicOut,
onFinish: function onFinish() {
if (_onFinish2) {
_onFinish2();
}
}
});
}
});
};
// Instantly show face up (no animation)
self.showFaceUp = function () {
self.isFaceUp = true;
self.scaleX = 1;
back.visible = false;
front.visible = true;
};
// Instantly show face down (no animation)
self.showFaceDown = function () {
self.isFaceUp = false;
self.scaleX = 1;
front.visible = false;
back.visible = true;
};
// Mark as matched (disable interaction, highlight)
self.setMatched = function () {
self.isMatched = true;
// Subtle highlight
tween(self, {
tint: 0xA0FFA0
}, {
duration: 300,
easing: tween.linear
});
};
// Handle tap/click
self.down = function (x, y, obj) {
if (self.isFaceUp || self.isMatched || game.lockInput) {
return;
}
game.onCardTapped(self);
};
// Initialize: show face down
self.showFaceDown();
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2e3a4f // Deep blue background
});
/****
* Game Code
****/
// --- Card symbols (use simple emojis for MVP) ---
// Tween plugin for card flip and reveal animations
var cardSymbols = ["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐ฅ", "๐ฅ", "๐", "๐", "๐", "๐", "๐ฅฅ", "๐
", "๐ฅญ", "๐โ๐ฉ"];
var cardSymbols2 = ["๐ถ", "๐ฑ", "๐ญ", "๐ฐ", "๐ฆ", "๐ป", "๐ผ", "๐จ", "๐ฏ", "๐ฆ", "๐ฎ", "๐ท", "๐ธ", "๐ต", "๐", "๐ง", "๐ฆ", "๐ฆโโฌ", "๐ฅ", "๐ฆ", "๐ฆ
", "๐ฆ", "๐ฆ", "๐บ", "๐ฆ", "๐", "๐ท", "๐ฆ"];
var cardSymbols3 = ["๐", "๐", "๐", "๐", "๐", "๐๏ธ", "๐", "๐", "๐", "๐", "๐ป", "๐", "๐", "๐", "๐ต", "๐๏ธ", "๐ด", "๐ฒ", "๐บ", "๐ค", "โต๏ธ", "๐ถ", "๐ฉ๏ธ", "โ๏ธ", "๐ฐ๏ธ", "๐", "๐", "๐ธ"];
var cardSymbols4 = ["๐งฐ", "๐ช", "๐ง", "๐จ", "โ๏ธ", "๐ ๏ธ", "โ๏ธ", "๐ช", "๐ช", "๐ฉ", "โ๏ธ", "๐ชค", "๐งฑ", "๐งฒ", "๐ซ", "๐ฃ", "๐งจ", "๐ช", "๐ช", "๐ก๏ธ", "โ๏ธ", "๐ก๏ธ", "๐ชฆ", "๐", "๐ช", "๐", "๐", "๐", "๐ฌ", "โ๏ธ", "๐ช", "๐น", "๐ฅ", "๐ฅ", "๐ท", "๐บ", "๐ธ", "๐น", "๐ฅ", "๐ฅ"];
// --- Game settings ---
var maxLevel = 50;
var currentLevel = 1;
// Level configuration: grid size per level (increase difficulty)
function getLevelConfig(level) {
// Level 1-5: 4x4, 6-10: 5x4, 11-15: 6x5, 16-20: 6x6, 21-25: 7x6
if (level <= 5) {
return {
cols: 4,
rows: 4
};
}
if (level <= 10) {
return {
cols: 5,
rows: 4
};
}
if (level <= 15) {
return {
cols: 6,
rows: 5
};
}
if (level <= 20) {
return {
cols: 6,
rows: 6
};
}
if (level <= 35) {
return {
cols: 8,
rows: 6
};
}
if (level <= 45) {
return {
cols: 8,
rows: 7
};
}
if (level < 50) {
return {
cols: 8,
rows: 7
};
}
if (level >= 50) {
return {
cols: 10,
rows: 8
};
}
return {
cols: 9,
rows: 7
};
}
var gridCols = getLevelConfig(currentLevel).cols;
var gridRows = getLevelConfig(currentLevel).rows;
var totalPairs = gridCols * gridRows / 2;
var cardSpacing = 36; // px between cards
// --- Card assets (simple colored rectangles) ---
// --- Game state ---
var cards = []; // All card objects
var firstCard = null;
var secondCard = null;
var lockInput = false; // Prevent input during animations
var moves = 0;
var matchesFound = 0;
var timer = null;
var elapsedTime = 0; // in seconds
var timerText = null;
var movesText = null;
// --- GUI: Moves, Timer, Level ---
var levelText = new Text2('Level: 1', {
size: 80,
fill: 0xFFFFFF
});
levelText.anchor.set(0.5, 0);
LK.gui.top.addChild(levelText);
timerText = new Text2('Time: 0s', {
size: 80,
fill: 0xFFFFFF
});
timerText.anchor.set(0.5, 0);
LK.gui.top.addChild(timerText);
// Position GUI elements (timer right, avoid top-left 100x100)
// Align level indicator to top middle using LK.gui.top
levelText.x = -10;
levelText.y = 20;
timerText.x = 500;
// --- Timer for random swap after level 35 ---
var randomSwapTimer = null;
var randomSwapTimer10s = null;
var randomSwapTimer5s = null;
var randomSwapTimer2s = null;
// --- Swap queue for level 50+ to prevent overlapping swaps ---
var swapQueue = [];
var swapInProgress = false;
// Helper: Queue a swap (for level 50+)
function queueCardSwap(cardA, cardB) {
swapQueue.push({
cardA: cardA,
cardB: cardB
});
processSwapQueue();
}
// Helper: Process swap queue (for level 50+)
function processSwapQueue() {
if (swapInProgress || swapQueue.length === 0) {
return;
}
swapInProgress = true;
var swap = swapQueue.shift();
var cardA = swap.cardA;
var cardB = swap.cardB;
var tempSymbol = cardA.symbol;
var tempIndex = cardA.index;
cardA.setSymbol(cardB.symbol);
cardB.setSymbol(tempSymbol);
// Swap index property as well
cardA.index = cardB.index;
cardB.index = tempIndex;
var tempX = cardA.x;
var tempY = cardA.y;
var swapsDone = 0;
function onSwapDone() {
swapsDone++;
if (swapsDone === 2) {
swapInProgress = false;
// Snap to grid to avoid drift
cardA.x = Math.round(cardA.x);
cardA.y = Math.round(cardA.y);
cardB.x = Math.round(cardB.x);
cardB.y = Math.round(cardB.y);
processSwapQueue();
}
}
tween(cardA, {
x: cardB.x,
y: cardB.y
}, {
duration: 1200,
easing: tween.cubicInOut,
onFinish: onSwapDone
});
tween(cardB, {
x: tempX,
y: tempY
}, {
duration: 1200,
easing: tween.cubicInOut,
onFinish: onSwapDone
});
}
// --- Helper: Shuffle array (Fisher-Yates) ---
function shuffleArray(arr) {
for (var i = arr.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
// --- Layout cards ---
function layoutCards() {
// Remove old cards if any
for (var i = 0; i < cards.length; i++) {
cards[i].destroy();
}
cards = [];
// Prepare symbols (duplicate and shuffle)
var symbols = [];
var useSymbols;
if (currentLevel === 9 || currentLevel === 41) {
useSymbols = cardSymbols3;
} else {
useSymbols = currentLevel >= 50 ? cardSymbols4 : currentLevel > 45 ? cardSymbols3 : currentLevel > 20 ? cardSymbols2 : cardSymbols;
}
for (var i = 0; i < totalPairs; i++) {
symbols.push(useSymbols[i]);
symbols.push(useSymbols[i]);
}
shuffleArray(symbols);
// Card size: fit grid to 2048x2732 with spacing
var availableWidth = 2048 - cardSpacing * (gridCols + 1);
var availableHeight = 1800 - cardSpacing * (gridRows + 1); // leave space for GUI
var cardWidth = Math.floor(availableWidth / gridCols);
var cardHeight = Math.floor(availableHeight / gridRows);
// Center grid
var gridPixelWidth = cardWidth * gridCols + cardSpacing * (gridCols - 1);
var gridPixelHeight = cardHeight * gridRows + cardSpacing * (gridRows - 1);
var startX = Math.floor((2048 - gridPixelWidth) / 2) + cardWidth / 2;
var startY = 350 + cardHeight / 2; // leave space for GUI
// Create cards
for (var row = 0; row < gridRows; row++) {
for (var col = 0; col < gridCols; col++) {
var idx = row * gridCols + col;
var card = new Card();
card.setSize(cardWidth, cardHeight);
card.setSymbol(symbols[idx]);
card.index = idx;
card.x = startX + col * (cardWidth + cardSpacing);
card.y = startY + row * (cardHeight + cardSpacing);
card.showFaceDown();
game.addChild(card);
cards.push(card);
}
}
}
// --- Reset game state ---
function resetGame() {
firstCard = null;
secondCard = null;
lockInput = false;
moves = 0;
matchesFound = 0;
if (currentLevel === 16) {
elapsedTime = 150;
} else if (currentLevel === 21) {
elapsedTime = 150;
} else if (currentLevel === 26) {
elapsedTime = 200;
} else if (currentLevel === 31) {
elapsedTime = 200;
} else if (currentLevel === 36) {
elapsedTime = 250;
} else if (currentLevel === 41) {
elapsedTime = 250;
} else if (currentLevel === 46) {
elapsedTime = 300;
} else if (currentLevel === 50) {
elapsedTime = 500;
} else if (currentLevel === 1) {
elapsedTime = 100;
} else if (currentLevel === 6 || currentLevel === 11) {
elapsedTime = 120;
} else {
elapsedTime = 120;
}
if (typeof levelText !== "undefined") {
// At level 6, display Level: 2 instead of Level: 6
if (currentLevel === 6) {
levelText.setText('Level: 2');
} else if (currentLevel === 11) {
levelText.setText('Level: 3');
} else if (currentLevel === 16) {
levelText.setText('Level: 4');
} else if (currentLevel === 21) {
levelText.setText('Level: 5');
} else if (currentLevel === 26) {
levelText.setText('Level: 6');
} else if (currentLevel === 31) {
levelText.setText('Level: 7');
} else if (currentLevel === 36) {
levelText.setText('Level: 8');
} else if (currentLevel === 41) {
levelText.setText('Level: 9');
} else if (currentLevel === 46) {
levelText.setText('Level: 10');
} else if (currentLevel === 50) {
levelText.setText('Final Level');
} else if (currentLevel === 26) {
levelText.setText('Level: 6');
} else if (currentLevel === 21) {
levelText.setText('Level: 5');
} else if (currentLevel === 16) {
levelText.setText('Level: 4');
} else {
levelText.setText('Level: ' + currentLevel);
}
}
// Update grid size for this level
var config = getLevelConfig(currentLevel);
gridCols = config.cols;
gridRows = config.rows;
totalPairs = gridCols * gridRows / 2;
timerText.setText('Time: ' + elapsedTime + 's');
layoutCards();
// Play lastlevelmusic at level 50
if (currentLevel === 50) {
LK.playMusic('lastlevelmusic');
}
if (timer) {
LK.clearInterval(timer);
}
timer = LK.setInterval(function () {
elapsedTime--;
if (elapsedTime <= 0) {
elapsedTime = 0;
timerText.setText('Time: 0s');
LK.clearInterval(timer);
LK.showGameOver(); // You lose if timer gets to 0
return;
} else {
timerText.setText('Time: ' + elapsedTime + 's');
}
}, 1000);
// --- Timer-based random swap after level 35 ---
if (typeof randomSwapTimer !== "undefined" && randomSwapTimer) {
LK.clearInterval(randomSwapTimer);
randomSwapTimer = null;
}
if (currentLevel >= 50) {
// At level 50, swap every 2s, 5s, 10s, and 20s using the swap queue
swapQueue = [];
swapInProgress = false;
randomSwapTimer = LK.setInterval(function () {
// 20s swap
var swappable = [];
for (var i = 0; i < cards.length; i++) {
if (!cards[i].isMatched && !cards[i].isFaceUp) {
swappable.push(cards[i]);
}
}
if (swappable.length >= 2) {
var idx1 = Math.floor(Math.random() * swappable.length);
var idx2 = idx1;
while (idx2 === idx1) {
idx2 = Math.floor(Math.random() * swappable.length);
}
var cardA = swappable[idx1];
var cardB = swappable[idx2];
queueCardSwap(cardA, cardB);
}
}, 20000); // 20 seconds
// Add a second timer for 10s swap
if (typeof randomSwapTimer10s !== "undefined" && randomSwapTimer10s) {
LK.clearInterval(randomSwapTimer10s);
randomSwapTimer10s = null;
}
randomSwapTimer10s = LK.setInterval(function () {
var swappable = [];
for (var i = 0; i < cards.length; i++) {
if (!cards[i].isMatched && !cards[i].isFaceUp) {
swappable.push(cards[i]);
}
}
if (swappable.length >= 2) {
var idx1 = Math.floor(Math.random() * swappable.length);
var idx2 = idx1;
while (idx2 === idx1) {
idx2 = Math.floor(Math.random() * swappable.length);
}
var cardA = swappable[idx1];
var cardB = swappable[idx2];
queueCardSwap(cardA, cardB);
}
}, 10000); // 10 seconds
// Add a third timer for 5s swap
if (typeof randomSwapTimer5s !== "undefined" && randomSwapTimer5s) {
LK.clearInterval(randomSwapTimer5s);
randomSwapTimer5s = null;
}
randomSwapTimer5s = LK.setInterval(function () {
var swappable = [];
for (var i = 0; i < cards.length; i++) {
if (!cards[i].isMatched && !cards[i].isFaceUp) {
swappable.push(cards[i]);
}
}
if (swappable.length >= 2) {
var idx1 = Math.floor(Math.random() * swappable.length);
var idx2 = idx1;
while (idx2 === idx1) {
idx2 = Math.floor(Math.random() * swappable.length);
}
var cardA = swappable[idx1];
var cardB = swappable[idx2];
queueCardSwap(cardA, cardB);
}
}, 5000); // 5 seconds
// Add a fourth timer for 2s swap
if (typeof randomSwapTimer2s !== "undefined" && randomSwapTimer2s) {
LK.clearInterval(randomSwapTimer2s);
randomSwapTimer2s = null;
}
randomSwapTimer2s = LK.setInterval(function () {
var swappable = [];
for (var i = 0; i < cards.length; i++) {
if (!cards[i].isMatched && !cards[i].isFaceUp) {
swappable.push(cards[i]);
}
}
if (swappable.length >= 2) {
var idx1 = Math.floor(Math.random() * swappable.length);
var idx2 = idx1;
while (idx2 === idx1) {
idx2 = Math.floor(Math.random() * swappable.length);
}
var cardA = swappable[idx1];
var cardB = swappable[idx2];
queueCardSwap(cardA, cardB);
}
}, 2000); // 2 seconds
} else if (currentLevel > 40) {
// Only 20s swap
randomSwapTimer = LK.setInterval(function () {
var swappable = [];
for (var i = 0; i < cards.length; i++) {
if (!cards[i].isMatched && !cards[i].isFaceUp) {
swappable.push(cards[i]);
}
}
if (swappable.length >= 2) {
var idx1 = Math.floor(Math.random() * swappable.length);
var idx2 = idx1;
while (idx2 === idx1) {
idx2 = Math.floor(Math.random() * swappable.length);
}
var cardA = swappable[idx1];
var cardB = swappable[idx2];
var tempSymbol = cardA.symbol;
var tempIndex = cardA.index;
cardA.setSymbol(cardB.symbol);
cardB.setSymbol(tempSymbol);
// Swap index property as well
cardA.index = cardB.index;
cardB.index = tempIndex;
var tempX = cardA.x;
var tempY = cardA.y;
tween(cardA, {
x: cardB.x,
y: cardB.y
}, {
duration: 1200,
easing: tween.cubicInOut
});
tween(cardB, {
x: tempX,
y: tempY
}, {
duration: 1200,
easing: tween.cubicInOut
});
}
}, 20000); // 20 seconds
// Clear 10s timer if it exists
if (typeof randomSwapTimer10s !== "undefined" && randomSwapTimer10s) {
LK.clearInterval(randomSwapTimer10s);
randomSwapTimer10s = null;
}
} else {
randomSwapTimer = null;
if (typeof randomSwapTimer10s !== "undefined" && randomSwapTimer10s) {
LK.clearInterval(randomSwapTimer10s);
randomSwapTimer10s = null;
}
}
}
// --- Card tap handler ---
game.onCardTapped = function (card) {
if (lockInput || card.isFaceUp || card.isMatched) {
return;
}
if (!firstCard) {
firstCard = card;
LK.getSound('cardchose').play();
card.flipUp();
} else if (!secondCard && card !== firstCard) {
secondCard = card;
lockInput = true;
LK.getSound('cardchose').play();
card.flipUp(function () {
// Check for match after both are face up
LK.setTimeout(function () {
checkMatch();
}, 150);
});
}
};
// --- Check for match ---
function checkMatch() {
moves++;
// After level 40, swap 2 random cards every 2 moves; after level 30, every 4 moves; after level 25, every 5 moves; after level 20, every 10 moves
var shouldSwap = false;
if (currentLevel >= 50 && moves % 1 === 0 && cards.length > 1) {
shouldSwap = true;
} else if (currentLevel > 40 && moves % 2 === 0 && cards.length > 1) {
shouldSwap = true;
} else if (currentLevel > 30 && moves % 4 === 0 && cards.length > 1) {
shouldSwap = true;
} else if (currentLevel > 25 && moves % 5 === 0 && cards.length > 1) {
shouldSwap = true;
} else if (currentLevel > 20 && moves % 10 === 0 && cards.length > 1) {
shouldSwap = true;
}
if (shouldSwap) {
// Find all cards that are not matched and not currently face up
var swappable = [];
for (var i = 0; i < cards.length; i++) {
if (!cards[i].isMatched && !cards[i].isFaceUp) {
swappable.push(cards[i]);
}
}
if (swappable.length >= 2) {
var idx1 = Math.floor(Math.random() * swappable.length);
var idx2 = idx1;
while (idx2 === idx1) {
idx2 = Math.floor(Math.random() * swappable.length);
}
var cardA = swappable[idx1];
var cardB = swappable[idx2];
if (currentLevel >= 50) {
queueCardSwap(cardA, cardB);
} else {
// Old behavior for lower levels
var tempSymbol = cardA.symbol;
var tempIndex = cardA.index;
cardA.setSymbol(cardB.symbol);
cardB.setSymbol(tempSymbol);
// Swap index property as well
cardA.index = cardB.index;
cardB.index = tempIndex;
var tempX = cardA.x;
var tempY = cardA.y;
tween(cardA, {
x: cardB.x,
y: cardB.y
}, {
duration: 1200,
easing: tween.cubicInOut
});
tween(cardB, {
x: tempX,
y: tempY
}, {
duration: 1200,
easing: tween.cubicInOut
});
}
}
}
if (firstCard && secondCard && firstCard.symbol === secondCard.symbol) {
// Match!
LK.getSound('cardmatch').play();
firstCard.setMatched();
secondCard.setMatched();
matchesFound++;
// Subtle scale animation for matched cards, then disappear
if (firstCard && secondCard) {
var matched1 = firstCard;
var matched2 = secondCard;
if (matched1) {
tween(matched1, {
scaleX: 1.15,
scaleY: 1.15
}, {
duration: 80,
easing: tween.cubicOut,
onFinish: function onFinish() {
if (matched1) {
tween(matched1, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 120,
onFinish: function onFinish() {
if (matched1) {
matched1.destroy();
}
}
});
}
}
});
}
if (matched2) {
tween(matched2, {
scaleX: 1.15,
scaleY: 1.15
}, {
duration: 80,
easing: tween.cubicOut,
onFinish: function onFinish() {
if (matched2) {
tween(matched2, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 120,
onFinish: function onFinish() {
if (matched2) {
matched2.destroy();
}
}
});
}
}
});
}
}
// Check for win
if (matchesFound === totalPairs) {
LK.clearInterval(timer);
LK.setScore(moves); // Use moves as score (lower is better)
if (currentLevel < maxLevel) {
// Play level completed sound
LK.getSound('levelcompleted').play();
// Show "Level Completed" text in the center of the screen
var levelCompletedText = new Text2('Level Completed!', {
size: 180,
fill: 0xFFD700,
font: "Impact"
});
levelCompletedText.anchor.set(0.5, 0.5);
levelCompletedText.x = 2048 / 2;
levelCompletedText.y = 2732 / 2;
game.addChild(levelCompletedText);
if (currentLevel === 1) {
currentLevel = 6;
} else if (currentLevel === 6) {
currentLevel = 11;
} else if (currentLevel === 11) {
currentLevel = 16;
} else if (currentLevel === 16) {
currentLevel = 21;
} else if (currentLevel === 21) {
currentLevel = 26;
} else if (currentLevel === 26) {
currentLevel = 31;
} else if (currentLevel === 31) {
currentLevel = 36;
} else if (currentLevel === 36) {
currentLevel = 41;
} else if (currentLevel === 41) {
currentLevel = 46;
} else if (currentLevel === 46) {
currentLevel = 50;
} else {
currentLevel++;
}
// Short delay before next level, remove text before reset
LK.setTimeout(function () {
if (levelCompletedText && typeof levelCompletedText.destroy === "function") {
levelCompletedText.destroy();
}
resetGame();
}, 1200);
} else {
LK.showYouWin();
}
}
// Reset selection
firstCard = null;
secondCard = null;
lockInput = false;
} else {
// Not a match: flip both back after short delay
LK.setTimeout(function () {
if (firstCard && typeof firstCard.flipDown === "function") {
firstCard.flipDown();
}
if (secondCard && typeof secondCard.flipDown === "function") {
secondCard.flipDown(function () {
firstCard = null;
secondCard = null;
lockInput = false;
});
} else {
// If secondCard is missing, still reset state after delay
firstCard = null;
secondCard = null;
lockInput = false;
}
}, 250);
}
}
// --- Game update (not used for logic, but required) ---
game.update = function () {
// Movement-based card swap: if two cards are dragged/moved to each other's position, swap their symbol and index
// (Assuming you have a drag/move system, e.g. for future features or mobile drag-to-swap)
// This is a defensive implementation: checks for cards that overlap and are not already matched or face up
for (var i = 0; i < cards.length; i++) {
var cardA = cards[i];
if (cardA.isMatched || cardA.isFaceUp) continue;
for (var j = i + 1; j < cards.length; j++) {
var cardB = cards[j];
if (cardB.isMatched || cardB.isFaceUp) continue;
// If cardA and cardB overlap (bounding box collision)
if (Math.abs(cardA.x - cardB.x) < (cardA.cardWidth + cardB.cardWidth) / 2 && Math.abs(cardA.y - cardB.y) < (cardA.cardHeight + cardB.cardHeight) / 2) {
// Only swap if not already swapped this frame
if (!cardA._swappedThisFrame && !cardB._swappedThisFrame) {
// Swap symbol and index
var tempSymbol = cardA.symbol;
var tempIndex = cardA.index;
cardA.setSymbol(cardB.symbol);
cardB.setSymbol(tempSymbol);
cardA.index = cardB.index;
cardB.index = tempIndex;
cardA._swappedThisFrame = true;
cardB._swappedThisFrame = true;
}
}
}
}
// Reset swap flags for next frame
for (var i = 0; i < cards.length; i++) {
cards[i]._swappedThisFrame = false;
}
};
// --- DEV Button: Center bottom, always visible, go to next level ---
// (Hidden for production, but function remains for later update test)
var devBtn = new Text2('DEV', {
size: 90,
fill: 0xFF00FF,
font: "Impact"
});
devBtn.anchor.set(0.5, 1);
// Place DEV button slightly left of center
devBtn.x = -120;
devBtn.y = -50;
// LK.gui.bottom.addChild(devBtn); // HIDDEN: do not add to GUI, but keep logic for later test
devBtn.visible = false; // Hide from view
// DEV button handler: go to next level immediately (max 25)
devBtn.down = function (x, y, obj) {
if (matchesFound === totalPairs) {
// Already won, do nothing
return;
}
// Replicate the level pass logic from checkMatch()
if (currentLevel < maxLevel) {
// Play level completed sound
LK.getSound('levelcompleted').play();
// Show "Level Completed" text in the center of the screen
var levelCompletedText = new Text2('Level Completed!', {
size: 180,
fill: 0xFFD700,
font: "Impact"
});
levelCompletedText.anchor.set(0.5, 0.5);
levelCompletedText.x = 2048 / 2;
levelCompletedText.y = 2732 / 2;
game.addChild(levelCompletedText);
if (currentLevel === 1) {
currentLevel = 6;
} else if (currentLevel === 6) {
currentLevel = 11;
} else if (currentLevel === 11) {
currentLevel = 16;
} else if (currentLevel === 16) {
currentLevel = 21;
} else if (currentLevel === 21) {
currentLevel = 26;
} else if (currentLevel === 26) {
levelText.setText('Level: 6');
currentLevel = 31;
} else if (currentLevel === 31) {
levelText.setText('Level: 7');
currentLevel = 36;
} else if (currentLevel === 36) {
levelText.setText('Level: 8');
levelText.setText('Level: 8');
currentLevel = 41;
} else if (currentLevel === 41) {
levelText.setText('Level: 9');
currentLevel = 46;
} else if (currentLevel === 46) {
levelText.setText('Level: 10');
currentLevel = 50;
} else if (currentLevel === 50) {
levelText.setText('Final Level');
currentLevel = 50;
} else {
currentLevel++;
}
LK.setTimeout(function () {
if (levelCompletedText && typeof levelCompletedText.destroy === "function") {
levelCompletedText.destroy();
}
resetGame();
}, 1200);
} else {
LK.showYouWin();
}
};
// --- Reveal Button: Center bottom, always visible, reveals all cards ---
// (Hidden for production, but function remains for later update test)
var revealBtn = new Text2('Reveal', {
size: 90,
fill: 0x00CCFF,
font: "Impact"
});
revealBtn.anchor.set(0.5, 1);
// Place Reveal button slightly right of center
revealBtn.x = 120;
revealBtn.y = -50;
// LK.gui.bottom.addChild(revealBtn); // HIDDEN: do not add to GUI, but keep logic for later test
revealBtn.visible = false; // Hide from view
// Reveal button handler: toggle all cards between revealed and hidden
var revealState = false; // false = hidden, true = revealed
revealBtn.down = function (x, y, obj) {
revealState = !revealState;
if (revealState) {
// Reveal all cards (that are not matched)
for (var i = 0; i < cards.length; i++) {
var card = cards[i];
if (!card.isFaceUp && !card.isMatched) {
card.flipUp();
}
}
revealBtn.setText('Hide');
} else {
// Hide all cards (that are not matched)
for (var i = 0; i < cards.length; i++) {
var card = cards[i];
if (card.isFaceUp && !card.isMatched) {
card.flipDown();
}
}
revealBtn.setText('Reveal');
}
};
// --- Start game ---
LK.playMusic('gamemusic');
resetGame();
// --- Game over handler (reset on game over) ---
game.onGameOver = function () {
if (timer) {
LK.clearInterval(timer);
}
if (typeof randomSwapTimer !== "undefined" && randomSwapTimer) {
LK.clearInterval(randomSwapTimer);
randomSwapTimer = null;
}
if (typeof randomSwapTimer10s !== "undefined" && randomSwapTimer10s) {
LK.clearInterval(randomSwapTimer10s);
randomSwapTimer10s = null;
}
if (typeof randomSwapTimer5s !== "undefined" && randomSwapTimer5s) {
LK.clearInterval(randomSwapTimer5s);
randomSwapTimer5s = null;
}
if (typeof randomSwapTimer2s !== "undefined" && randomSwapTimer2s) {
LK.clearInterval(randomSwapTimer2s);
randomSwapTimer2s = null;
}
currentLevel = 1;
LK.stopMusic();
resetGame();
};
// --- Win handler (reset on win) ---
game.onYouWin = function () {
if (timer) {
LK.clearInterval(timer);
}
if (typeof randomSwapTimer !== "undefined" && randomSwapTimer) {
LK.clearInterval(randomSwapTimer);
randomSwapTimer = null;
}
if (typeof randomSwapTimer10s !== "undefined" && randomSwapTimer10s) {
LK.clearInterval(randomSwapTimer10s);
randomSwapTimer10s = null;
}
if (typeof randomSwapTimer5s !== "undefined" && randomSwapTimer5s) {
LK.clearInterval(randomSwapTimer5s);
randomSwapTimer5s = null;
}
if (typeof randomSwapTimer2s !== "undefined" && randomSwapTimer2s) {
LK.clearInterval(randomSwapTimer2s);
randomSwapTimer2s = null;
}
currentLevel = 1;
LK.stopMusic();
// Game will reset automatically by LK
};