User prompt
Increase max level number to 50
User prompt
Use โcardback3โ asset for cards after level10
User prompt
Add rveal button near to dev button. This button reveals all cards at the level
User prompt
Use โcardback2โ asset for card after level5
User prompt
Remove moves gui
Code edit (13 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Timeout.tick error: null is not an object (evaluating 'firstCard.symbol')' in or related to this line: 'if (firstCard.symbol === secondCard.symbol) {' Line Number: 342
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
User prompt
Add DEV button to the game. This button must be at center bottom and always visible. This button take you to next level immediately
User prompt
Next button always appear at bottom
User prompt
Add next button to the game. This button take you to next level
User prompt
After matching cards, matching cards must disappear
User prompt
Please fix the bug: 'TypeError: Cannot use 'in' operator to search for 'scaleX' in null' in or related to this line: 'tween(firstCard, {' Line Number: 356
User prompt
Please fix the bug: 'TypeError: Cannot use 'in' operator to search for 'scaleX' in null' in or related to this line: 'tween(firstCard, {' Line Number: 355
User prompt
Align level indicator to top middle
User prompt
There must be level system for the game. If you match all cards you will go next level. Maximum level number must be 25.
Code edit (1 edits merged)
Please save this source code
User prompt
Card Match Mania
Initial prompt
I want card match game
/**** * 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
};