/**** * 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 states: 'hidden', 'revealed', 'matched' self.state = 'hidden'; self.fruitId = null; // Will be set on creation // Card back (hidden state) var back = self.attachAsset('cardBack', { anchorX: 0.5, anchorY: 0.5 }); self.back = back; // Card front (fruit image, revealed state) // Default to watermelon image, will be replaced by setFruit var front = self.attachAsset('Watermelon', { anchorX: 0.5, anchorY: 0.5 }); self.front = front; self.front.visible = false; // Set fruit image for this card self.setFruit = function (fruitId) { self.fruitId = fruitId; // Map fruitId to asset name in assets section var assetMap = { 'banana': 'Banana', 'cherry': 'Cherry', 'orange': 'Orange', 'grape': 'grape', 'lemon': 'lemon', 'peach': 'Peach', 'strawberry': 'strawberry', 'watermelon': 'Watermelon', 'Coconut': 'Coconut', 'Kiwi': 'Kiwi', 'apple': 'apple', 'Pear': 'Pear', 'Pineapple': 'Pineapple', 'Avocado': 'Avocado', 'Daisy': 'Daisy', 'Rock': 'Rock', 'Water': 'Water' }; var assetName = assetMap[fruitId] || 'fruit'; var fruitAsset = LK.getAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); // Remove old front if present if (self.front && self.front.parent) self.removeChild(self.front); self.front = fruitAsset; self.addChild(self.front); self.front.visible = false; self.front.x = 0; self.front.y = 0; self.back.x = 0; self.back.y = 0; }; // Reveal the card (show fruit) self.reveal = function () { if (self.state !== 'hidden') return; self.state = 'revealed'; self.back.visible = false; self.front.visible = true; // Optional: flip animation tween(self, { scaleX: 0 }, { duration: 80, easing: tween.cubicIn, onFinish: function onFinish() { self.back.visible = false; self.front.visible = true; tween(self, { scaleX: 1 }, { duration: 80, easing: tween.cubicOut }); } }); }; // Hide the card (show back) self.hide = function () { if (self.state !== 'revealed') return; self.state = 'hidden'; // Optional: flip animation tween(self, { scaleX: 0 }, { duration: 80, easing: tween.cubicIn, onFinish: function onFinish() { self.front.visible = false; self.back.visible = true; tween(self, { scaleX: 1 }, { duration: 80, easing: tween.cubicOut }); } }); }; // Mark as matched (permanently revealed) self.match = function () { self.state = 'matched'; self.back.visible = false; self.front.visible = true; // Optional: small scale bounce tween(self, { scaleX: 1.15, scaleY: 1.15 }, { duration: 100, easing: tween.bounceOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 100 }); } }); }; // Handle tap self.down = function (x, y, obj) { if (self.state === 'hidden' && !game.lockInput) { game.onCardTapped(self); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Fruit pool (all fruits with images in assets) var fruitIds = ['banana', 'cherry', 'orange', 'grape', 'lemon', 'peach', 'strawberry', 'watermelon', 'Coconut', 'Kiwi', 'apple', 'Pear', 'Pineapple', 'Avocado', 'Daisy', 'Rock', 'Water']; // No need to dynamically create fruit image assets, all are defined in assets section // Game variables var level = 1; var maxLevel = 10; var cards = []; var revealedCards = []; var matchedPairs = 0; var totalPairs = 0; var lives = 0; var maxLives = 0; var lockInput = false; var levelText, messageText; // Heart/lives display var heartNodes = []; var heartAssetName = 'heart'; // Use a heart image asset (add to assets if not present) var heartSize = 80; var heartSpacing = 20; var heartMargin = 30; // --- MENU SYSTEM --- // Menu container var menuContainer = new Container(); LK.gui.center.addChild(menuContainer); // Play button with ButtonBG var playBtnContainer = new Container(); var playBtn = new Text2('Play', { size: 140, fill: "#fff", font: "Impact" }); playBtn.anchor.set(0.5, 0.5); playBtn.y = 0; var playBtnBG = LK.getAsset('ButtonBG', { anchorX: 0.5, anchorY: 0.5 }); playBtnBG.width = playBtn.width + 80; playBtnBG.height = playBtn.height + 60; playBtnBG.x = 0; playBtnBG.y = 0; playBtnContainer.addChild(playBtnBG); playBtnContainer.addChild(playBtn); playBtnContainer.y = -120; menuContainer.addChild(playBtnContainer); // Settings button with ButtonBG var settingsBtnContainer = new Container(); var settingsBtn = new Text2('Settings', { size: 100, fill: "#fff", font: "Impact" }); settingsBtn.anchor.set(0.5, 0.5); settingsBtn.y = 0; var settingsBtnBG = LK.getAsset('ButtonBG', { anchorX: 0.5, anchorY: 0.5 }); settingsBtnBG.width = settingsBtn.width + 80; settingsBtnBG.height = settingsBtn.height + 60; settingsBtnBG.x = 0; settingsBtnBG.y = 0; settingsBtnContainer.addChild(settingsBtnBG); settingsBtnContainer.addChild(settingsBtn); settingsBtnContainer.y = 80; menuContainer.addChild(settingsBtnContainer); // Settings panel (hidden by default) var settingsPanel = new Container(); settingsPanel.visible = false; settingsPanel.y = 250; menuContainer.addChild(settingsPanel); // Sound toggle button with ButtonBG var soundOn = true; var soundBtnContainer = new Container(); var soundBtn = new Text2('Sound: On', { size: 80, fill: "#fff", font: "Impact" }); soundBtn.anchor.set(0.5, 0.5); soundBtn.y = 0; var soundBtnBG = LK.getAsset('ButtonBG', { anchorX: 0.5, anchorY: 0.5 }); soundBtnBG.width = soundBtn.width + 80; soundBtnBG.height = soundBtn.height + 60; soundBtnBG.x = 0; soundBtnBG.y = 0; soundBtnContainer.addChild(soundBtnBG); soundBtnContainer.addChild(soundBtn); soundBtnContainer.y = 0; settingsPanel.addChild(soundBtnContainer); // Music toggle button with ButtonBG var musicOn = true; var musicBtnContainer = new Container(); var musicBtn = new Text2('Music: On', { size: 80, fill: "#fff", font: "Impact" }); musicBtn.anchor.set(0.5, 0.5); musicBtn.y = 0; var musicBtnBG = LK.getAsset('ButtonBG', { anchorX: 0.5, anchorY: 0.5 }); musicBtnBG.width = musicBtn.width + 80; musicBtnBG.height = musicBtn.height + 60; musicBtnBG.x = 0; musicBtnBG.y = 0; musicBtnContainer.addChild(musicBtnBG); musicBtnContainer.addChild(musicBtn); musicBtnContainer.y = 120; settingsPanel.addChild(musicBtnContainer); // Menu logic var menuActive = true; function showMenu() { menuContainer.visible = true; levelText.visible = false; messageText.visible = false; pauseBtnContainer.visible = false; pauseMenuContainer.visible = false; // Hide all cards and hearts if present for (var i = 0; i < cards.length; i++) { cards[i].visible = false; } for (var i = 0; i < heartNodes.length; i++) { heartNodes[i].visible = false; } menuActive = true; lockInput = true; game.lockInput = true; } function hideMenu() { menuContainer.visible = false; levelText.visible = true; pauseBtnContainer.visible = true; pauseMenuContainer.visible = false; for (var i = 0; i < cards.length; i++) { cards[i].visible = true; } for (var i = 0; i < heartNodes.length; i++) { heartNodes[i].visible = true; } menuActive = false; lockInput = false; game.lockInput = false; } // Play button handler playBtnContainer.down = function (x, y, obj) { if (soundOn) LK.getSound('Click').play(); hideMenu(); // Start/restart game startLevel(1); }; // Settings button handler settingsBtnContainer.down = function (x, y, obj) { if (soundOn) LK.getSound('Click').play(); settingsPanel.visible = !settingsPanel.visible; }; // Sound toggle handler soundBtnContainer.down = function (x, y, obj) { if (soundOn) LK.getSound('Click').play(); soundOn = !soundOn; soundBtn.setText('Sound: ' + (soundOn ? 'On' : 'Off')); LK.setSoundEnabled && LK.setSoundEnabled(soundOn); }; // Music toggle handler musicBtnContainer.down = function (x, y, obj) { if (soundOn) LK.getSound('Click').play(); musicOn = !musicOn; musicBtn.setText('Music: ' + (musicOn ? 'On' : 'Off')); LK.setMusicEnabled && LK.setMusicEnabled(musicOn); }; // --- END MENU SYSTEM --- // GUI setup levelText = new Text2('Level 1', { size: 90, fill: "#fff" }); levelText.anchor.set(0.5, 0); // We'll position these below the cards after the board is created, so just add to game for now game.addChild(levelText); // Message text (centered, for "Level X", "You Win", etc) messageText = new Text2('', { size: 140, fill: "#fff" }); messageText.anchor.set(0.5, 0.5); LK.gui.center.addChild(messageText); messageText.visible = false; // --- PAUSE BUTTON & PAUSE MENU --- // Create a container for the pause button and its background var pauseBtnContainer = new Container(); // Add ButtonBG image as background var pauseBtnBG = LK.getAsset('ButtonBG', { anchorX: 0.5, anchorY: 0.5 }); pauseBtnContainer.addChild(pauseBtnBG); // Create the pause button text var pauseBtn = new Text2('Pause', { size: 80, fill: "#fff", font: "Impact" }); pauseBtn.anchor.set(0.5, 0.5); pauseBtnContainer.addChild(pauseBtn); // Place pause button just below the Level text, even further to the right of center pauseBtnContainer.x = 2048 / 2 + 260; // Move 260px to the right pauseBtnContainer.y = levelText.y + levelText.height + 10; // 10px below the Level text LK.gui.addChild(pauseBtnContainer); pauseBtnContainer.visible = false; // Pause menu container var pauseMenuContainer = new Container(); pauseMenuContainer.visible = false; LK.gui.center.addChild(pauseMenuContainer); // Resume button with ButtonBG var resumeBtnContainer = new Container(); var resumeBtn = new Text2('Resume', { size: 120, fill: "#fff", font: "Impact" }); resumeBtn.anchor.set(0.5, 0.5); resumeBtn.y = 0; var resumeBtnBG = LK.getAsset('ButtonBG', { anchorX: 0.5, anchorY: 0.5 }); resumeBtnBG.width = resumeBtn.width + 80; resumeBtnBG.height = resumeBtn.height + 60; resumeBtnBG.x = 0; resumeBtnBG.y = 0; resumeBtnContainer.addChild(resumeBtnBG); resumeBtnContainer.addChild(resumeBtn); resumeBtnContainer.y = -100; pauseMenuContainer.addChild(resumeBtnContainer); // Menu button (in pause menu) with ButtonBG var pauseMenuBtnContainer = new Container(); var pauseMenuBtn = new Text2('Menu', { size: 120, fill: "#fff", font: "Impact" }); pauseMenuBtn.anchor.set(0.5, 0.5); pauseMenuBtn.y = 0; var pauseMenuBtnBG = LK.getAsset('ButtonBG', { anchorX: 0.5, anchorY: 0.5 }); pauseMenuBtnBG.width = pauseMenuBtn.width + 80; pauseMenuBtnBG.height = pauseMenuBtn.height + 60; pauseMenuBtnBG.x = 0; pauseMenuBtnBG.y = 0; pauseMenuBtnContainer.addChild(pauseMenuBtnBG); pauseMenuBtnContainer.addChild(pauseMenuBtn); pauseMenuBtnContainer.y = 100; pauseMenuContainer.addChild(pauseMenuBtnContainer); // Pause button handler pauseBtn.down = function (x, y, obj) { if (soundOn) LK.getSound('Click').play(); pauseBtnContainer.visible = false; pauseMenuContainer.visible = true; lockInput = true; game.lockInput = true; // Hide cards and hearts visually, but keep state for (var i = 0; i < cards.length; i++) { cards[i].visible = false; } for (var i = 0; i < heartNodes.length; i++) { heartNodes[i].visible = false; } levelText.visible = false; messageText.visible = false; }; // Resume button handler resumeBtnContainer.down = function (x, y, obj) { if (soundOn) LK.getSound('Click').play(); pauseBtnContainer.visible = true; pauseMenuContainer.visible = false; lockInput = false; game.lockInput = false; for (var i = 0; i < cards.length; i++) { cards[i].visible = true; } for (var i = 0; i < heartNodes.length; i++) { heartNodes[i].visible = true; } levelText.visible = true; messageText.visible = false; }; // Menu button in pause menu handler pauseMenuBtnContainer.down = function (x, y, obj) { if (soundOn) LK.getSound('Click').play(); pauseMenuContainer.visible = false; showMenu(); pauseBtnContainer.visible = false; }; // Show menu at start showMenu(); playNextMusic(); // Layout parameters var boardMargin = 80; var cardSpacingX = 40; var cardSpacingY = 40; // Helper: shuffle array function shuffle(array) { for (var i = array.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var t = array[i]; array[i] = array[j]; array[j] = t; } return array; } // Helper: get grid size for level function getGridForLevel(lvl) { // Level 1: 2x2 (4 cards, 2 pairs) // Level 2: 2x3 (6 cards, 3 pairs) // Level 3: 2x4 (8 cards, 4 pairs) // Level 4: 3x4 (12 cards, 6 pairs) // Level 5: 4x4 (16 cards, 8 pairs) // Level 6: 4x5 (20 cards, 10 pairs) // Level 7: 5x6 (30 cards, 15 pairs) // Level 8: 6x6 (36 cards, 18 pairs) // Level 9: 6x7 (42 cards, 21 pairs) // Level 10: 7x8 (56 cards, 28 pairs) var grids = [[2, 2], [2, 3], [2, 4], [3, 4], [4, 4], [4, 5], [5, 6], [6, 6], [6, 7], [7, 8]]; return grids[Math.min(lvl - 1, grids.length - 1)]; } // Helper: get lives for level function getLivesForLevel(lvl) { // Start with 3, increase by 1 every 2 levels, max 10 return Math.min(10, 3 + Math.floor((lvl - 1) / 2)); } // Start a new level function startLevel(lvl) { // Clean up for (var i = 0; i < cards.length; i++) { cards[i].destroy(); } cards = []; revealedCards = []; matchedPairs = 0; lockInput = true; game.lockInput = true; // Set level/lives level = lvl; var grid = getGridForLevel(level); var rows = grid[0], cols = grid[1]; totalPairs = Math.floor(rows * cols / 2); maxLives = getLivesForLevel(level); lives = maxLives; // Update GUI levelText.setText('Level ' + level); // Remove old hearts for (var i = 0; i < heartNodes.length; i++) { if (heartNodes[i].parent) heartNodes[i].parent.removeChild(heartNodes[i]); heartNodes[i].destroy && heartNodes[i].destroy(); } heartNodes = []; // Draw new hearts for current lives for (var i = 0; i < lives; i++) { var heart = LK.getAsset(heartAssetName, { anchorX: 0, anchorY: 1 }); // Place hearts at the bottom left, leaving a margin from the left and bottom heart.x = heartMargin + i * (heartSize + heartSpacing); heart.y = 2732 - heartMargin; // Add directly to the game (not GUI overlay) so it stays at the bottom left of the play area game.addChild(heart); heartNodes.push(heart); } // Prepare fruit pairs var fruitPool = []; for (var i = 0; i < totalPairs; i++) { var fruit = fruitIds[i % fruitIds.length]; fruitPool.push(fruit, fruit); } shuffle(fruitPool); // Card size var cardW = LK.getAsset('cardBack', {}).width; var cardH = LK.getAsset('cardBack', {}).height; // Board size var boardW = cols * cardW + (cols - 1) * cardSpacingX; var boardH = rows * cardH + (rows - 1) * cardSpacingY; var startX = (2048 - boardW) / 2 + cardW / 2; var startY = (2732 - boardH) / 2 + cardH / 2 + 60; // Position levelText and livesText below the cards levelText.x = 2048 / 2; levelText.y = startY + boardH / 2 + cardH / 2 + 40; // Create cards var idx = 0; for (var r = 0; r < rows; r++) { for (var c = 0; c < cols; c++) { if (idx >= fruitPool.length) continue; var card = new Card(); card.setFruit(fruitPool[idx]); card.x = startX + c * (cardW + cardSpacingX); card.y = startY + r * (cardH + cardSpacingY); card.scaleX = 1; card.scaleY = 1; game.addChild(card); cards.push(card); idx++; } } // Show all cards for a few seconds, increasing with level var showDuration = 1200 + (level - 1) * 500; // e.g. 1.2s + 0.5s per level for (var i = 0; i < cards.length; i++) { cards[i].front.visible = true; cards[i].back.visible = false; cards[i].state = 'revealed'; cards[i].scaleX = 1; cards[i].scaleY = 1; } // Move messageText below the cards, centered messageText.x = 2048 / 2; messageText.y = levelText.y + levelText.height + 40; messageText.setText('Level ' + level); messageText.visible = true; lockInput = true; game.lockInput = true; // After showDuration, hide all cards and allow input LK.setTimeout(function () { for (var i = 0; i < cards.length; i++) { cards[i].front.visible = false; cards[i].back.visible = true; cards[i].state = 'hidden'; cards[i].scaleX = 1; cards[i].scaleY = 1; } messageText.visible = false; lockInput = false; game.lockInput = false; }, showDuration); } // Card tap handler game.onCardTapped = function (card) { if (lockInput || card.state !== 'hidden') return; card.reveal(); revealedCards.push(card); if (revealedCards.length === 2) { lockInput = true; game.lockInput = true; var c1 = revealedCards[0], c2 = revealedCards[1]; if (c1.fruitId === c2.fruitId) { // Match! LK.setTimeout(function () { c1.match(); c2.match(); matchedPairs++; revealedCards = []; lockInput = false; game.lockInput = false; // Check win if (matchedPairs === totalPairs) { if (level === maxLevel) { // Game completed! messageText.setText('You Win!'); messageText.visible = true; LK.setTimeout(function () { messageText.visible = false; LK.showYouWin(); }, 1200); } else { // Next level messageText.setText('Level Complete!'); messageText.visible = true; LK.setTimeout(function () { messageText.visible = false; startLevel(level + 1); }, 1200); } } }, 350); } else { // Not a match LK.setTimeout(function () { c1.hide(); c2.hide(); revealedCards = []; lives--; // Update hearts: hide one for each lost life for (var i = 0; i < heartNodes.length; i++) { heartNodes[i].visible = i < lives; } lockInput = false; game.lockInput = false; if (lives <= 0) { // Game over messageText.setText('Game Over'); messageText.visible = true; LK.setTimeout(function () { messageText.visible = false; LK.showGameOver(); }, 1200); } }, 650); } } }; // Prevent interaction when locked game.down = function (x, y, obj) { // No-op: all input handled by Card.down }; game.move = function (x, y, obj) {}; game.up = function (x, y, obj) {}; // Start first level only after Play is clicked // (Do not start here; handled by playBtn.down) // No need for game.update, all logic is event-driven; // --- MUSIC LOOP LOGIC --- var musicTracks = ['BGMusic1', 'BGMusic2']; var currentMusicIndex = 0; var musicLoopTimeout = null; // Play the next music track in the sequence, looping back to start function playNextMusic() { if (!musicOn) return; // Defensive: check musicTracks is array and currentMusicIndex is valid if (!musicTracks || !Array.isArray(musicTracks) || musicTracks.length === 0) return; if (typeof currentMusicIndex !== "number" || currentMusicIndex < 0 || currentMusicIndex >= musicTracks.length) { currentMusicIndex = 0; } var track = musicTracks[currentMusicIndex]; if (!track) return; // Stop any currently playing music LK.stopMusic && LK.stopMusic(); // Play the current track, not looping LK.playMusic(track, { loop: false }); // Get duration in ms (LK.init.music uses start/end as 0-1, so we can't get duration directly, so hardcode fallback) // If you have access to duration, use it. Otherwise, use a safe default (e.g. 60000ms) var durations = { 'BGMusic1': 60000, 'BGMusic2': 60000 }; var duration = durations[track] || 60000; // Schedule next track musicLoopTimeout = LK.setTimeout(function () { currentMusicIndex = (currentMusicIndex + 1) % musicTracks.length; playNextMusic(); }, duration - 100); // Start next track just before current ends } // Stop the music loop and any scheduled next track function stopMusicLoop() { if (musicLoopTimeout) { LK.clearTimeout(musicLoopTimeout); musicLoopTimeout = null; } LK.stopMusic && LK.stopMusic(); } // When music is toggled, start/stop music loop accordingly musicBtnContainer.down = function (x, y, obj) { if (soundOn) LK.getSound('Click').play(); musicOn = !musicOn; musicBtn.setText('Music: ' + (musicOn ? 'On' : 'Off')); LK.setMusicEnabled && LK.setMusicEnabled(musicOn); if (musicOn) { playNextMusic(); } else { stopMusicLoop(); } }; // When menu is shown, stop music loop
/****
* 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 states: 'hidden', 'revealed', 'matched'
self.state = 'hidden';
self.fruitId = null; // Will be set on creation
// Card back (hidden state)
var back = self.attachAsset('cardBack', {
anchorX: 0.5,
anchorY: 0.5
});
self.back = back;
// Card front (fruit image, revealed state)
// Default to watermelon image, will be replaced by setFruit
var front = self.attachAsset('Watermelon', {
anchorX: 0.5,
anchorY: 0.5
});
self.front = front;
self.front.visible = false;
// Set fruit image for this card
self.setFruit = function (fruitId) {
self.fruitId = fruitId;
// Map fruitId to asset name in assets section
var assetMap = {
'banana': 'Banana',
'cherry': 'Cherry',
'orange': 'Orange',
'grape': 'grape',
'lemon': 'lemon',
'peach': 'Peach',
'strawberry': 'strawberry',
'watermelon': 'Watermelon',
'Coconut': 'Coconut',
'Kiwi': 'Kiwi',
'apple': 'apple',
'Pear': 'Pear',
'Pineapple': 'Pineapple',
'Avocado': 'Avocado',
'Daisy': 'Daisy',
'Rock': 'Rock',
'Water': 'Water'
};
var assetName = assetMap[fruitId] || 'fruit';
var fruitAsset = LK.getAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
// Remove old front if present
if (self.front && self.front.parent) self.removeChild(self.front);
self.front = fruitAsset;
self.addChild(self.front);
self.front.visible = false;
self.front.x = 0;
self.front.y = 0;
self.back.x = 0;
self.back.y = 0;
};
// Reveal the card (show fruit)
self.reveal = function () {
if (self.state !== 'hidden') return;
self.state = 'revealed';
self.back.visible = false;
self.front.visible = true;
// Optional: flip animation
tween(self, {
scaleX: 0
}, {
duration: 80,
easing: tween.cubicIn,
onFinish: function onFinish() {
self.back.visible = false;
self.front.visible = true;
tween(self, {
scaleX: 1
}, {
duration: 80,
easing: tween.cubicOut
});
}
});
};
// Hide the card (show back)
self.hide = function () {
if (self.state !== 'revealed') return;
self.state = 'hidden';
// Optional: flip animation
tween(self, {
scaleX: 0
}, {
duration: 80,
easing: tween.cubicIn,
onFinish: function onFinish() {
self.front.visible = false;
self.back.visible = true;
tween(self, {
scaleX: 1
}, {
duration: 80,
easing: tween.cubicOut
});
}
});
};
// Mark as matched (permanently revealed)
self.match = function () {
self.state = 'matched';
self.back.visible = false;
self.front.visible = true;
// Optional: small scale bounce
tween(self, {
scaleX: 1.15,
scaleY: 1.15
}, {
duration: 100,
easing: tween.bounceOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
}
});
};
// Handle tap
self.down = function (x, y, obj) {
if (self.state === 'hidden' && !game.lockInput) {
game.onCardTapped(self);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Fruit pool (all fruits with images in assets)
var fruitIds = ['banana', 'cherry', 'orange', 'grape', 'lemon', 'peach', 'strawberry', 'watermelon', 'Coconut', 'Kiwi', 'apple', 'Pear', 'Pineapple', 'Avocado', 'Daisy', 'Rock', 'Water'];
// No need to dynamically create fruit image assets, all are defined in assets section
// Game variables
var level = 1;
var maxLevel = 10;
var cards = [];
var revealedCards = [];
var matchedPairs = 0;
var totalPairs = 0;
var lives = 0;
var maxLives = 0;
var lockInput = false;
var levelText, messageText;
// Heart/lives display
var heartNodes = [];
var heartAssetName = 'heart';
// Use a heart image asset (add to assets if not present)
var heartSize = 80;
var heartSpacing = 20;
var heartMargin = 30;
// --- MENU SYSTEM ---
// Menu container
var menuContainer = new Container();
LK.gui.center.addChild(menuContainer);
// Play button with ButtonBG
var playBtnContainer = new Container();
var playBtn = new Text2('Play', {
size: 140,
fill: "#fff",
font: "Impact"
});
playBtn.anchor.set(0.5, 0.5);
playBtn.y = 0;
var playBtnBG = LK.getAsset('ButtonBG', {
anchorX: 0.5,
anchorY: 0.5
});
playBtnBG.width = playBtn.width + 80;
playBtnBG.height = playBtn.height + 60;
playBtnBG.x = 0;
playBtnBG.y = 0;
playBtnContainer.addChild(playBtnBG);
playBtnContainer.addChild(playBtn);
playBtnContainer.y = -120;
menuContainer.addChild(playBtnContainer);
// Settings button with ButtonBG
var settingsBtnContainer = new Container();
var settingsBtn = new Text2('Settings', {
size: 100,
fill: "#fff",
font: "Impact"
});
settingsBtn.anchor.set(0.5, 0.5);
settingsBtn.y = 0;
var settingsBtnBG = LK.getAsset('ButtonBG', {
anchorX: 0.5,
anchorY: 0.5
});
settingsBtnBG.width = settingsBtn.width + 80;
settingsBtnBG.height = settingsBtn.height + 60;
settingsBtnBG.x = 0;
settingsBtnBG.y = 0;
settingsBtnContainer.addChild(settingsBtnBG);
settingsBtnContainer.addChild(settingsBtn);
settingsBtnContainer.y = 80;
menuContainer.addChild(settingsBtnContainer);
// Settings panel (hidden by default)
var settingsPanel = new Container();
settingsPanel.visible = false;
settingsPanel.y = 250;
menuContainer.addChild(settingsPanel);
// Sound toggle button with ButtonBG
var soundOn = true;
var soundBtnContainer = new Container();
var soundBtn = new Text2('Sound: On', {
size: 80,
fill: "#fff",
font: "Impact"
});
soundBtn.anchor.set(0.5, 0.5);
soundBtn.y = 0;
var soundBtnBG = LK.getAsset('ButtonBG', {
anchorX: 0.5,
anchorY: 0.5
});
soundBtnBG.width = soundBtn.width + 80;
soundBtnBG.height = soundBtn.height + 60;
soundBtnBG.x = 0;
soundBtnBG.y = 0;
soundBtnContainer.addChild(soundBtnBG);
soundBtnContainer.addChild(soundBtn);
soundBtnContainer.y = 0;
settingsPanel.addChild(soundBtnContainer);
// Music toggle button with ButtonBG
var musicOn = true;
var musicBtnContainer = new Container();
var musicBtn = new Text2('Music: On', {
size: 80,
fill: "#fff",
font: "Impact"
});
musicBtn.anchor.set(0.5, 0.5);
musicBtn.y = 0;
var musicBtnBG = LK.getAsset('ButtonBG', {
anchorX: 0.5,
anchorY: 0.5
});
musicBtnBG.width = musicBtn.width + 80;
musicBtnBG.height = musicBtn.height + 60;
musicBtnBG.x = 0;
musicBtnBG.y = 0;
musicBtnContainer.addChild(musicBtnBG);
musicBtnContainer.addChild(musicBtn);
musicBtnContainer.y = 120;
settingsPanel.addChild(musicBtnContainer);
// Menu logic
var menuActive = true;
function showMenu() {
menuContainer.visible = true;
levelText.visible = false;
messageText.visible = false;
pauseBtnContainer.visible = false;
pauseMenuContainer.visible = false;
// Hide all cards and hearts if present
for (var i = 0; i < cards.length; i++) {
cards[i].visible = false;
}
for (var i = 0; i < heartNodes.length; i++) {
heartNodes[i].visible = false;
}
menuActive = true;
lockInput = true;
game.lockInput = true;
}
function hideMenu() {
menuContainer.visible = false;
levelText.visible = true;
pauseBtnContainer.visible = true;
pauseMenuContainer.visible = false;
for (var i = 0; i < cards.length; i++) {
cards[i].visible = true;
}
for (var i = 0; i < heartNodes.length; i++) {
heartNodes[i].visible = true;
}
menuActive = false;
lockInput = false;
game.lockInput = false;
}
// Play button handler
playBtnContainer.down = function (x, y, obj) {
if (soundOn) LK.getSound('Click').play();
hideMenu();
// Start/restart game
startLevel(1);
};
// Settings button handler
settingsBtnContainer.down = function (x, y, obj) {
if (soundOn) LK.getSound('Click').play();
settingsPanel.visible = !settingsPanel.visible;
};
// Sound toggle handler
soundBtnContainer.down = function (x, y, obj) {
if (soundOn) LK.getSound('Click').play();
soundOn = !soundOn;
soundBtn.setText('Sound: ' + (soundOn ? 'On' : 'Off'));
LK.setSoundEnabled && LK.setSoundEnabled(soundOn);
};
// Music toggle handler
musicBtnContainer.down = function (x, y, obj) {
if (soundOn) LK.getSound('Click').play();
musicOn = !musicOn;
musicBtn.setText('Music: ' + (musicOn ? 'On' : 'Off'));
LK.setMusicEnabled && LK.setMusicEnabled(musicOn);
};
// --- END MENU SYSTEM ---
// GUI setup
levelText = new Text2('Level 1', {
size: 90,
fill: "#fff"
});
levelText.anchor.set(0.5, 0);
// We'll position these below the cards after the board is created, so just add to game for now
game.addChild(levelText);
// Message text (centered, for "Level X", "You Win", etc)
messageText = new Text2('', {
size: 140,
fill: "#fff"
});
messageText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(messageText);
messageText.visible = false;
// --- PAUSE BUTTON & PAUSE MENU ---
// Create a container for the pause button and its background
var pauseBtnContainer = new Container();
// Add ButtonBG image as background
var pauseBtnBG = LK.getAsset('ButtonBG', {
anchorX: 0.5,
anchorY: 0.5
});
pauseBtnContainer.addChild(pauseBtnBG);
// Create the pause button text
var pauseBtn = new Text2('Pause', {
size: 80,
fill: "#fff",
font: "Impact"
});
pauseBtn.anchor.set(0.5, 0.5);
pauseBtnContainer.addChild(pauseBtn);
// Place pause button just below the Level text, even further to the right of center
pauseBtnContainer.x = 2048 / 2 + 260; // Move 260px to the right
pauseBtnContainer.y = levelText.y + levelText.height + 10; // 10px below the Level text
LK.gui.addChild(pauseBtnContainer);
pauseBtnContainer.visible = false;
// Pause menu container
var pauseMenuContainer = new Container();
pauseMenuContainer.visible = false;
LK.gui.center.addChild(pauseMenuContainer);
// Resume button with ButtonBG
var resumeBtnContainer = new Container();
var resumeBtn = new Text2('Resume', {
size: 120,
fill: "#fff",
font: "Impact"
});
resumeBtn.anchor.set(0.5, 0.5);
resumeBtn.y = 0;
var resumeBtnBG = LK.getAsset('ButtonBG', {
anchorX: 0.5,
anchorY: 0.5
});
resumeBtnBG.width = resumeBtn.width + 80;
resumeBtnBG.height = resumeBtn.height + 60;
resumeBtnBG.x = 0;
resumeBtnBG.y = 0;
resumeBtnContainer.addChild(resumeBtnBG);
resumeBtnContainer.addChild(resumeBtn);
resumeBtnContainer.y = -100;
pauseMenuContainer.addChild(resumeBtnContainer);
// Menu button (in pause menu) with ButtonBG
var pauseMenuBtnContainer = new Container();
var pauseMenuBtn = new Text2('Menu', {
size: 120,
fill: "#fff",
font: "Impact"
});
pauseMenuBtn.anchor.set(0.5, 0.5);
pauseMenuBtn.y = 0;
var pauseMenuBtnBG = LK.getAsset('ButtonBG', {
anchorX: 0.5,
anchorY: 0.5
});
pauseMenuBtnBG.width = pauseMenuBtn.width + 80;
pauseMenuBtnBG.height = pauseMenuBtn.height + 60;
pauseMenuBtnBG.x = 0;
pauseMenuBtnBG.y = 0;
pauseMenuBtnContainer.addChild(pauseMenuBtnBG);
pauseMenuBtnContainer.addChild(pauseMenuBtn);
pauseMenuBtnContainer.y = 100;
pauseMenuContainer.addChild(pauseMenuBtnContainer);
// Pause button handler
pauseBtn.down = function (x, y, obj) {
if (soundOn) LK.getSound('Click').play();
pauseBtnContainer.visible = false;
pauseMenuContainer.visible = true;
lockInput = true;
game.lockInput = true;
// Hide cards and hearts visually, but keep state
for (var i = 0; i < cards.length; i++) {
cards[i].visible = false;
}
for (var i = 0; i < heartNodes.length; i++) {
heartNodes[i].visible = false;
}
levelText.visible = false;
messageText.visible = false;
};
// Resume button handler
resumeBtnContainer.down = function (x, y, obj) {
if (soundOn) LK.getSound('Click').play();
pauseBtnContainer.visible = true;
pauseMenuContainer.visible = false;
lockInput = false;
game.lockInput = false;
for (var i = 0; i < cards.length; i++) {
cards[i].visible = true;
}
for (var i = 0; i < heartNodes.length; i++) {
heartNodes[i].visible = true;
}
levelText.visible = true;
messageText.visible = false;
};
// Menu button in pause menu handler
pauseMenuBtnContainer.down = function (x, y, obj) {
if (soundOn) LK.getSound('Click').play();
pauseMenuContainer.visible = false;
showMenu();
pauseBtnContainer.visible = false;
};
// Show menu at start
showMenu();
playNextMusic();
// Layout parameters
var boardMargin = 80;
var cardSpacingX = 40;
var cardSpacingY = 40;
// Helper: shuffle array
function shuffle(array) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var t = array[i];
array[i] = array[j];
array[j] = t;
}
return array;
}
// Helper: get grid size for level
function getGridForLevel(lvl) {
// Level 1: 2x2 (4 cards, 2 pairs)
// Level 2: 2x3 (6 cards, 3 pairs)
// Level 3: 2x4 (8 cards, 4 pairs)
// Level 4: 3x4 (12 cards, 6 pairs)
// Level 5: 4x4 (16 cards, 8 pairs)
// Level 6: 4x5 (20 cards, 10 pairs)
// Level 7: 5x6 (30 cards, 15 pairs)
// Level 8: 6x6 (36 cards, 18 pairs)
// Level 9: 6x7 (42 cards, 21 pairs)
// Level 10: 7x8 (56 cards, 28 pairs)
var grids = [[2, 2], [2, 3], [2, 4], [3, 4], [4, 4], [4, 5], [5, 6], [6, 6], [6, 7], [7, 8]];
return grids[Math.min(lvl - 1, grids.length - 1)];
}
// Helper: get lives for level
function getLivesForLevel(lvl) {
// Start with 3, increase by 1 every 2 levels, max 10
return Math.min(10, 3 + Math.floor((lvl - 1) / 2));
}
// Start a new level
function startLevel(lvl) {
// Clean up
for (var i = 0; i < cards.length; i++) {
cards[i].destroy();
}
cards = [];
revealedCards = [];
matchedPairs = 0;
lockInput = true;
game.lockInput = true;
// Set level/lives
level = lvl;
var grid = getGridForLevel(level);
var rows = grid[0],
cols = grid[1];
totalPairs = Math.floor(rows * cols / 2);
maxLives = getLivesForLevel(level);
lives = maxLives;
// Update GUI
levelText.setText('Level ' + level);
// Remove old hearts
for (var i = 0; i < heartNodes.length; i++) {
if (heartNodes[i].parent) heartNodes[i].parent.removeChild(heartNodes[i]);
heartNodes[i].destroy && heartNodes[i].destroy();
}
heartNodes = [];
// Draw new hearts for current lives
for (var i = 0; i < lives; i++) {
var heart = LK.getAsset(heartAssetName, {
anchorX: 0,
anchorY: 1
});
// Place hearts at the bottom left, leaving a margin from the left and bottom
heart.x = heartMargin + i * (heartSize + heartSpacing);
heart.y = 2732 - heartMargin;
// Add directly to the game (not GUI overlay) so it stays at the bottom left of the play area
game.addChild(heart);
heartNodes.push(heart);
}
// Prepare fruit pairs
var fruitPool = [];
for (var i = 0; i < totalPairs; i++) {
var fruit = fruitIds[i % fruitIds.length];
fruitPool.push(fruit, fruit);
}
shuffle(fruitPool);
// Card size
var cardW = LK.getAsset('cardBack', {}).width;
var cardH = LK.getAsset('cardBack', {}).height;
// Board size
var boardW = cols * cardW + (cols - 1) * cardSpacingX;
var boardH = rows * cardH + (rows - 1) * cardSpacingY;
var startX = (2048 - boardW) / 2 + cardW / 2;
var startY = (2732 - boardH) / 2 + cardH / 2 + 60;
// Position levelText and livesText below the cards
levelText.x = 2048 / 2;
levelText.y = startY + boardH / 2 + cardH / 2 + 40;
// Create cards
var idx = 0;
for (var r = 0; r < rows; r++) {
for (var c = 0; c < cols; c++) {
if (idx >= fruitPool.length) continue;
var card = new Card();
card.setFruit(fruitPool[idx]);
card.x = startX + c * (cardW + cardSpacingX);
card.y = startY + r * (cardH + cardSpacingY);
card.scaleX = 1;
card.scaleY = 1;
game.addChild(card);
cards.push(card);
idx++;
}
}
// Show all cards for a few seconds, increasing with level
var showDuration = 1200 + (level - 1) * 500; // e.g. 1.2s + 0.5s per level
for (var i = 0; i < cards.length; i++) {
cards[i].front.visible = true;
cards[i].back.visible = false;
cards[i].state = 'revealed';
cards[i].scaleX = 1;
cards[i].scaleY = 1;
}
// Move messageText below the cards, centered
messageText.x = 2048 / 2;
messageText.y = levelText.y + levelText.height + 40;
messageText.setText('Level ' + level);
messageText.visible = true;
lockInput = true;
game.lockInput = true;
// After showDuration, hide all cards and allow input
LK.setTimeout(function () {
for (var i = 0; i < cards.length; i++) {
cards[i].front.visible = false;
cards[i].back.visible = true;
cards[i].state = 'hidden';
cards[i].scaleX = 1;
cards[i].scaleY = 1;
}
messageText.visible = false;
lockInput = false;
game.lockInput = false;
}, showDuration);
}
// Card tap handler
game.onCardTapped = function (card) {
if (lockInput || card.state !== 'hidden') return;
card.reveal();
revealedCards.push(card);
if (revealedCards.length === 2) {
lockInput = true;
game.lockInput = true;
var c1 = revealedCards[0],
c2 = revealedCards[1];
if (c1.fruitId === c2.fruitId) {
// Match!
LK.setTimeout(function () {
c1.match();
c2.match();
matchedPairs++;
revealedCards = [];
lockInput = false;
game.lockInput = false;
// Check win
if (matchedPairs === totalPairs) {
if (level === maxLevel) {
// Game completed!
messageText.setText('You Win!');
messageText.visible = true;
LK.setTimeout(function () {
messageText.visible = false;
LK.showYouWin();
}, 1200);
} else {
// Next level
messageText.setText('Level Complete!');
messageText.visible = true;
LK.setTimeout(function () {
messageText.visible = false;
startLevel(level + 1);
}, 1200);
}
}
}, 350);
} else {
// Not a match
LK.setTimeout(function () {
c1.hide();
c2.hide();
revealedCards = [];
lives--;
// Update hearts: hide one for each lost life
for (var i = 0; i < heartNodes.length; i++) {
heartNodes[i].visible = i < lives;
}
lockInput = false;
game.lockInput = false;
if (lives <= 0) {
// Game over
messageText.setText('Game Over');
messageText.visible = true;
LK.setTimeout(function () {
messageText.visible = false;
LK.showGameOver();
}, 1200);
}
}, 650);
}
}
};
// Prevent interaction when locked
game.down = function (x, y, obj) {
// No-op: all input handled by Card.down
};
game.move = function (x, y, obj) {};
game.up = function (x, y, obj) {};
// Start first level only after Play is clicked
// (Do not start here; handled by playBtn.down)
// No need for game.update, all logic is event-driven;
// --- MUSIC LOOP LOGIC ---
var musicTracks = ['BGMusic1', 'BGMusic2'];
var currentMusicIndex = 0;
var musicLoopTimeout = null;
// Play the next music track in the sequence, looping back to start
function playNextMusic() {
if (!musicOn) return;
// Defensive: check musicTracks is array and currentMusicIndex is valid
if (!musicTracks || !Array.isArray(musicTracks) || musicTracks.length === 0) return;
if (typeof currentMusicIndex !== "number" || currentMusicIndex < 0 || currentMusicIndex >= musicTracks.length) {
currentMusicIndex = 0;
}
var track = musicTracks[currentMusicIndex];
if (!track) return;
// Stop any currently playing music
LK.stopMusic && LK.stopMusic();
// Play the current track, not looping
LK.playMusic(track, {
loop: false
});
// Get duration in ms (LK.init.music uses start/end as 0-1, so we can't get duration directly, so hardcode fallback)
// If you have access to duration, use it. Otherwise, use a safe default (e.g. 60000ms)
var durations = {
'BGMusic1': 60000,
'BGMusic2': 60000
};
var duration = durations[track] || 60000;
// Schedule next track
musicLoopTimeout = LK.setTimeout(function () {
currentMusicIndex = (currentMusicIndex + 1) % musicTracks.length;
playNextMusic();
}, duration - 100); // Start next track just before current ends
}
// Stop the music loop and any scheduled next track
function stopMusicLoop() {
if (musicLoopTimeout) {
LK.clearTimeout(musicLoopTimeout);
musicLoopTimeout = null;
}
LK.stopMusic && LK.stopMusic();
}
// When music is toggled, start/stop music loop accordingly
musicBtnContainer.down = function (x, y, obj) {
if (soundOn) LK.getSound('Click').play();
musicOn = !musicOn;
musicBtn.setText('Music: ' + (musicOn ? 'On' : 'Off'));
LK.setMusicEnabled && LK.setMusicEnabled(musicOn);
if (musicOn) {
playNextMusic();
} else {
stopMusicLoop();
}
};
// When menu is shown, stop music loop
watermelon. In-Game asset. 2d. High contrast. No shadows
strawberry. In-Game asset. 2d. High contrast. No shadows
banana. In-Game asset. 2d. High contrast. No shadows
lemon. In-Game asset. 2d. High contrast. No shadows
Orange. In-Game asset. 2d. High contrast. No shadows
grape. In-Game asset. 2d. High contrast. No shadows
peach. In-Game asset. 2d. High contrast. No shadows
Cherry. In-Game asset. 2d. High contrast. No shadows
Heart. In-Game asset. 2d. High contrast. No shadows
avocado. In-Game asset. 2d. High contrast. No shadows
Coconut. In-Game asset. 2d. High contrast. No shadows
Pineapple. In-Game asset. 2d. High contrast. No shadows
Kiwi. In-Game asset. 2d. High contrast. No shadows
Apple. In-Game asset. 2d. High contrast. No shadows
Pear. In-Game asset. 2d. High contrast. No shadows
Daisy. In-Game asset. 2d. High contrast. No shadows
Bottle a water. In-Game asset. 2d. High contrast. No shadows
Rock (Gray). In-Game asset. 2d. High contrast. No shadows
Let it be a pink card, the corners are dark pink, getting lighter towards the middle. In-Game asset. 2d. High contrast. No shadows