/****
* 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