/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { bestTime: 0, bestMoves: 0 }); /**** * Classes ****/ var Card = Container.expand(function (cardId, cardValue) { var self = Container.call(this); // Properties self.cardId = cardId; self.cardValue = cardValue; self.isFlipped = false; self.isMatched = false; // Card back - visible by default var backShape = self.attachAsset('cardBack', { anchorX: 0.5, anchorY: 0.5, width: 300, height: 300 }); // Card front - hidden by default var frontShape = self.attachAsset('card', { anchorX: 0.5, anchorY: 0.5, width: 300, height: 300, visible: false }); // Photo asset instead of text var photoAsset = self.attachAsset('photo' + cardValue, { anchorX: 0.5, anchorY: 0.5, width: 300, height: 300, visible: false }); // Event handler for card click self.down = function (x, y, obj) { if (!self.isFlipped && !self.isMatched && !gameState.isProcessing) { self.flip(); } }; // Handle hover effect self.mouseOver = function () { if (!self.isFlipped && !self.isMatched && !gameState.isProcessing) { // No scaling effect to prevent cards from growing } }; // Handle mouse out effect self.mouseOut = function () { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 150, easing: tween.easeOut }); }; // Flip the card self.flip = function () { if (self.isFlipped) { return; } self.isFlipped = true; LK.getSound('flip').play(); // Reveal the card tween(frontShape, { scaleX: 1 }, { duration: 200, easing: tween.easeOut }); frontShape.visible = true; photoAsset.visible = true; backShape.visible = false; // Process the move in the game checkForMatch(); }; // Match this card (permanently show it) self.match = function () { self.isMatched = true; // Change appearance to indicate matched state tween(frontShape, { tint: 0x33cc33 }, { duration: 300, easing: tween.easeOut }); }; // Reset the card (flip back) self.reset = function () { self.isFlipped = false; frontShape.visible = false; photoAsset.visible = false; backShape.visible = true; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xdcc2b9 }); /**** * Game Code ****/ // Initialize card photo assets - one for each pair (18 pairs) // Game constants function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) { return _arrayLikeToArray(r, a); } var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) { n[e] = r[e]; } return n; } function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) { return; } f = !1; } else { for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0) { ; } } } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) { return; } } finally { if (o) { throw n; } } } return a; } } function _arrayWithHoles(r) { if (Array.isArray(r)) { return r; } } var GRID_SIZE = 6; var CARD_COUNT = GRID_SIZE * GRID_SIZE; var CARD_WIDTH = 300; var CARD_HEIGHT = 300; var CARD_SPACING = 25; var GRID_WIDTH = GRID_SIZE * (CARD_WIDTH + CARD_SPACING) - CARD_SPACING; var GRID_HEIGHT = GRID_SIZE * (CARD_HEIGHT + CARD_SPACING) - CARD_SPACING; var GRID_START_X = (2048 - GRID_WIDTH) / 2; var GRID_START_Y = (2732 - GRID_HEIGHT) / 2; // Game state var gameState = { cards: [], flippedCards: [], isProcessing: false, moves: 0, matchesFound: 0, totalMatches: CARD_COUNT / 2, isGameOver: false, timeRemaining: 120, // 2 minutes in seconds timerActive: false }; // GUI elements var movesText = new Text2("Moves: 0", { size: 70, fill: 0xFFFFFF }); movesText.anchor.set(0.5, 0.5); LK.gui.top.addChild(movesText); movesText.y = 180; var titleText = new Text2("Photo Match Frenzy", { size: 90, fill: 0xFFFFFF }); titleText.anchor.set(0.5, 0.5); LK.gui.top.addChild(titleText); titleText.y = 80; // Create timer text var timerText = new Text2("Time: 2:00", { size: 70, fill: 0xFFFFFF }); timerText.anchor.set(0, 0.5); LK.gui.top.addChild(timerText); timerText.x = movesText.x + 300; // Position timer to the right of the moves counter timerText.y = 180; // Same y position as the moves counter // Generate card values (pairs of values from 1 to CARD_COUNT/2) function generateCardValues() { var values = []; for (var i = 1; i <= CARD_COUNT / 2; i++) { values.push(i, i); // Add each value twice (to create pairs) } // Shuffle the array for (var i = values.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var _ref = [values[j], values[i]]; values[i] = _ref[0]; values[j] = _ref[1]; } return values; } // Initialize the game board function initGame() { // Reset game state gameState.cards = []; gameState.flippedCards = []; gameState.isProcessing = false; gameState.moves = 0; gameState.matchesFound = 0; gameState.isGameOver = false; // Generate card values var cardValues = generateCardValues(); // Create cards for (var row = 0; row < GRID_SIZE; row++) { for (var col = 0; col < GRID_SIZE; col++) { var index = row * GRID_SIZE + col; var cardId = index; var cardValue = cardValues[index]; var card = new Card(cardId, cardValue); // Position the card in the grid card.x = GRID_START_X + col * (CARD_WIDTH + CARD_SPACING) + CARD_WIDTH / 2; card.y = GRID_START_Y + row * (CARD_HEIGHT + CARD_SPACING) + CARD_HEIGHT / 2; gameState.cards.push(card); game.addChild(card); } } // Update UI updateMoves(); // Reset and start the timer gameState.timeRemaining = 120; // 2 minutes gameState.timerActive = true; updateTimer(); // Play background music LK.playMusic('bgMusic'); } // Update the moves counter function updateMoves() { movesText.setText("Moves: " + gameState.moves); } // Update the timer display function updateTimer() { if (!gameState.timerActive) { return; } var minutes = Math.floor(gameState.timeRemaining / 60); var seconds = gameState.timeRemaining % 60; // Format seconds to always have two digits var secondsDisplay = seconds < 10 ? "0" + seconds : seconds; timerText.setText("Time: " + minutes + ":" + secondsDisplay); } // Check if the flipped cards match function checkForMatch() { // Find all currently flipped but not matched cards gameState.flippedCards = gameState.cards.filter(function (card) { return card.isFlipped && !card.isMatched; }); // If we have flipped 2 cards, check for a match if (gameState.flippedCards.length === 2) { gameState.moves++; updateMoves(); var _gameState$flippedCar = _slicedToArray(gameState.flippedCards, 2), card1 = _gameState$flippedCar[0], card2 = _gameState$flippedCar[1]; // Set a delay to let player see the cards (less than 1 second) gameState.isProcessing = true; LK.setTimeout(function () { if (card1.cardValue === card2.cardValue) { // Match found LK.getSound('match').play(); card1.match(); card2.match(); gameState.matchesFound++; // Check for game completion if (gameState.matchesFound === gameState.totalMatches) { endGame(); } } else { // No match LK.getSound('noMatch').play(); card1.reset(); card2.reset(); } gameState.flippedCards = []; gameState.isProcessing = false; }, 900); } } // End the game function endGame(timeout) { gameState.isGameOver = true; gameState.timerActive = false; // Check if we beat high score var isNewBestMoves = false; if (!timeout && (storage.bestMoves === 0 || gameState.moves < storage.bestMoves)) { storage.bestMoves = gameState.moves; isNewBestMoves = true; } if (timeout) { // Game over due to timeout LK.setScore(gameState.matchesFound * 100); LK.setTimeout(function () { LK.showGameOver(); }, 1000); } else { // Play victory sound LK.getSound('victory').play(); // Update the score LK.setScore(10000 - gameState.moves * 100); // Show victory photo showVictoryPhoto(); // Show the "You Win" screen after photo display LK.setTimeout(function () { LK.showYouWin(); }, 4000); } } // Function to show victory photo function showVictoryPhoto() { // Create container for photo display var photoContainer = new Container(); game.addChild(photoContainer); // Add background overlay var overlay = LK.getAsset('card', { anchorX: 0.5, anchorY: 0.5, width: 1600, height: 1600, tint: 0x000000, alpha: 0.7 }); photoContainer.addChild(overlay); // Position in center of screen photoContainer.x = 2048 / 2; photoContainer.y = 2732 / 2; // Select victory photo (using photo1 as the victory photo) var victoryPhoto = LK.getAsset('photo1', { anchorX: 0.5, anchorY: 0.5, width: 1200, height: 1200 }); photoContainer.addChild(victoryPhoto); // Add congratulations text var congratsText = new Text2("Congratulations!", { size: 100, fill: 0xFFFFFF }); congratsText.anchor.set(0.5, 0.5); congratsText.y = -700; photoContainer.addChild(congratsText); // Scale in effect photoContainer.scale.set(0); tween(photoContainer, { scaleX: 1, scaleY: 1 }, { duration: 1000, easing: tween.easeOutBack }); // Remove after 3 seconds LK.setTimeout(function () { tween(photoContainer, { scaleX: 0, scaleY: 0, alpha: 0 }, { duration: 800, easing: tween.easeIn, onComplete: function onComplete() { game.removeChild(photoContainer); } }); }, 3000); } // Game update loop game.update = function () { // Only update timer once per second if (gameState.timerActive && !gameState.isGameOver && LK.ticks % 60 === 0) { gameState.timeRemaining--; updateTimer(); // Check if time is up if (gameState.timeRemaining <= 0) { endGame(true); // End game with timeout parameter } } }; // Initialize the game initGame(); // Add hover detection to cards function setupHoverDetection() { var lastDistance = {}; var hoverThreshold = 200; // Distance in pixels to trigger hover game.move = function (x, y, obj) { // Process all cards for hover effects gameState.cards.forEach(function (card, index) { var cardPos = game.toLocal(card.position); var distance = Math.sqrt(Math.pow(x - cardPos.x, 2) + Math.pow(y - cardPos.y, 2)); // Initialize last distance if not set if (lastDistance[index] === undefined) { lastDistance[index] = distance; } // Detect when we move close to a card (crossing the threshold) if (lastDistance[index] >= hoverThreshold && distance < hoverThreshold) { card.mouseOver(); } // Detect when we move away from a card else if (lastDistance[index] < hoverThreshold && distance >= hoverThreshold) { card.mouseOut(); } // Update last distance lastDistance[index] = distance; }); }; } // Set up hover detection after initializing the game setupHoverDetection();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
bestTime: 0,
bestMoves: 0
});
/****
* Classes
****/
var Card = Container.expand(function (cardId, cardValue) {
var self = Container.call(this);
// Properties
self.cardId = cardId;
self.cardValue = cardValue;
self.isFlipped = false;
self.isMatched = false;
// Card back - visible by default
var backShape = self.attachAsset('cardBack', {
anchorX: 0.5,
anchorY: 0.5,
width: 300,
height: 300
});
// Card front - hidden by default
var frontShape = self.attachAsset('card', {
anchorX: 0.5,
anchorY: 0.5,
width: 300,
height: 300,
visible: false
});
// Photo asset instead of text
var photoAsset = self.attachAsset('photo' + cardValue, {
anchorX: 0.5,
anchorY: 0.5,
width: 300,
height: 300,
visible: false
});
// Event handler for card click
self.down = function (x, y, obj) {
if (!self.isFlipped && !self.isMatched && !gameState.isProcessing) {
self.flip();
}
};
// Handle hover effect
self.mouseOver = function () {
if (!self.isFlipped && !self.isMatched && !gameState.isProcessing) {
// No scaling effect to prevent cards from growing
}
};
// Handle mouse out effect
self.mouseOut = function () {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 150,
easing: tween.easeOut
});
};
// Flip the card
self.flip = function () {
if (self.isFlipped) {
return;
}
self.isFlipped = true;
LK.getSound('flip').play();
// Reveal the card
tween(frontShape, {
scaleX: 1
}, {
duration: 200,
easing: tween.easeOut
});
frontShape.visible = true;
photoAsset.visible = true;
backShape.visible = false;
// Process the move in the game
checkForMatch();
};
// Match this card (permanently show it)
self.match = function () {
self.isMatched = true;
// Change appearance to indicate matched state
tween(frontShape, {
tint: 0x33cc33
}, {
duration: 300,
easing: tween.easeOut
});
};
// Reset the card (flip back)
self.reset = function () {
self.isFlipped = false;
frontShape.visible = false;
photoAsset.visible = false;
backShape.visible = true;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xdcc2b9
});
/****
* Game Code
****/
// Initialize card photo assets - one for each pair (18 pairs)
// Game constants
function _slicedToArray(r, e) {
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _unsupportedIterableToArray(r, a) {
if (r) {
if ("string" == typeof r) {
return _arrayLikeToArray(r, a);
}
var t = {}.toString.call(r).slice(8, -1);
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
}
}
function _arrayLikeToArray(r, a) {
(null == a || a > r.length) && (a = r.length);
for (var e = 0, n = Array(a); e < a; e++) {
n[e] = r[e];
}
return n;
}
function _iterableToArrayLimit(r, l) {
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
if (null != t) {
var e,
n,
i,
u,
a = [],
f = !0,
o = !1;
try {
if (i = (t = t.call(r)).next, 0 === l) {
if (Object(t) !== t) {
return;
}
f = !1;
} else {
for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0) {
;
}
}
} catch (r) {
o = !0, n = r;
} finally {
try {
if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) {
return;
}
} finally {
if (o) {
throw n;
}
}
}
return a;
}
}
function _arrayWithHoles(r) {
if (Array.isArray(r)) {
return r;
}
}
var GRID_SIZE = 6;
var CARD_COUNT = GRID_SIZE * GRID_SIZE;
var CARD_WIDTH = 300;
var CARD_HEIGHT = 300;
var CARD_SPACING = 25;
var GRID_WIDTH = GRID_SIZE * (CARD_WIDTH + CARD_SPACING) - CARD_SPACING;
var GRID_HEIGHT = GRID_SIZE * (CARD_HEIGHT + CARD_SPACING) - CARD_SPACING;
var GRID_START_X = (2048 - GRID_WIDTH) / 2;
var GRID_START_Y = (2732 - GRID_HEIGHT) / 2;
// Game state
var gameState = {
cards: [],
flippedCards: [],
isProcessing: false,
moves: 0,
matchesFound: 0,
totalMatches: CARD_COUNT / 2,
isGameOver: false,
timeRemaining: 120,
// 2 minutes in seconds
timerActive: false
};
// GUI elements
var movesText = new Text2("Moves: 0", {
size: 70,
fill: 0xFFFFFF
});
movesText.anchor.set(0.5, 0.5);
LK.gui.top.addChild(movesText);
movesText.y = 180;
var titleText = new Text2("Photo Match Frenzy", {
size: 90,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
LK.gui.top.addChild(titleText);
titleText.y = 80;
// Create timer text
var timerText = new Text2("Time: 2:00", {
size: 70,
fill: 0xFFFFFF
});
timerText.anchor.set(0, 0.5);
LK.gui.top.addChild(timerText);
timerText.x = movesText.x + 300; // Position timer to the right of the moves counter
timerText.y = 180; // Same y position as the moves counter
// Generate card values (pairs of values from 1 to CARD_COUNT/2)
function generateCardValues() {
var values = [];
for (var i = 1; i <= CARD_COUNT / 2; i++) {
values.push(i, i); // Add each value twice (to create pairs)
}
// Shuffle the array
for (var i = values.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var _ref = [values[j], values[i]];
values[i] = _ref[0];
values[j] = _ref[1];
}
return values;
}
// Initialize the game board
function initGame() {
// Reset game state
gameState.cards = [];
gameState.flippedCards = [];
gameState.isProcessing = false;
gameState.moves = 0;
gameState.matchesFound = 0;
gameState.isGameOver = false;
// Generate card values
var cardValues = generateCardValues();
// Create cards
for (var row = 0; row < GRID_SIZE; row++) {
for (var col = 0; col < GRID_SIZE; col++) {
var index = row * GRID_SIZE + col;
var cardId = index;
var cardValue = cardValues[index];
var card = new Card(cardId, cardValue);
// Position the card in the grid
card.x = GRID_START_X + col * (CARD_WIDTH + CARD_SPACING) + CARD_WIDTH / 2;
card.y = GRID_START_Y + row * (CARD_HEIGHT + CARD_SPACING) + CARD_HEIGHT / 2;
gameState.cards.push(card);
game.addChild(card);
}
}
// Update UI
updateMoves();
// Reset and start the timer
gameState.timeRemaining = 120; // 2 minutes
gameState.timerActive = true;
updateTimer();
// Play background music
LK.playMusic('bgMusic');
}
// Update the moves counter
function updateMoves() {
movesText.setText("Moves: " + gameState.moves);
}
// Update the timer display
function updateTimer() {
if (!gameState.timerActive) {
return;
}
var minutes = Math.floor(gameState.timeRemaining / 60);
var seconds = gameState.timeRemaining % 60;
// Format seconds to always have two digits
var secondsDisplay = seconds < 10 ? "0" + seconds : seconds;
timerText.setText("Time: " + minutes + ":" + secondsDisplay);
}
// Check if the flipped cards match
function checkForMatch() {
// Find all currently flipped but not matched cards
gameState.flippedCards = gameState.cards.filter(function (card) {
return card.isFlipped && !card.isMatched;
});
// If we have flipped 2 cards, check for a match
if (gameState.flippedCards.length === 2) {
gameState.moves++;
updateMoves();
var _gameState$flippedCar = _slicedToArray(gameState.flippedCards, 2),
card1 = _gameState$flippedCar[0],
card2 = _gameState$flippedCar[1];
// Set a delay to let player see the cards (less than 1 second)
gameState.isProcessing = true;
LK.setTimeout(function () {
if (card1.cardValue === card2.cardValue) {
// Match found
LK.getSound('match').play();
card1.match();
card2.match();
gameState.matchesFound++;
// Check for game completion
if (gameState.matchesFound === gameState.totalMatches) {
endGame();
}
} else {
// No match
LK.getSound('noMatch').play();
card1.reset();
card2.reset();
}
gameState.flippedCards = [];
gameState.isProcessing = false;
}, 900);
}
}
// End the game
function endGame(timeout) {
gameState.isGameOver = true;
gameState.timerActive = false;
// Check if we beat high score
var isNewBestMoves = false;
if (!timeout && (storage.bestMoves === 0 || gameState.moves < storage.bestMoves)) {
storage.bestMoves = gameState.moves;
isNewBestMoves = true;
}
if (timeout) {
// Game over due to timeout
LK.setScore(gameState.matchesFound * 100);
LK.setTimeout(function () {
LK.showGameOver();
}, 1000);
} else {
// Play victory sound
LK.getSound('victory').play();
// Update the score
LK.setScore(10000 - gameState.moves * 100);
// Show victory photo
showVictoryPhoto();
// Show the "You Win" screen after photo display
LK.setTimeout(function () {
LK.showYouWin();
}, 4000);
}
}
// Function to show victory photo
function showVictoryPhoto() {
// Create container for photo display
var photoContainer = new Container();
game.addChild(photoContainer);
// Add background overlay
var overlay = LK.getAsset('card', {
anchorX: 0.5,
anchorY: 0.5,
width: 1600,
height: 1600,
tint: 0x000000,
alpha: 0.7
});
photoContainer.addChild(overlay);
// Position in center of screen
photoContainer.x = 2048 / 2;
photoContainer.y = 2732 / 2;
// Select victory photo (using photo1 as the victory photo)
var victoryPhoto = LK.getAsset('photo1', {
anchorX: 0.5,
anchorY: 0.5,
width: 1200,
height: 1200
});
photoContainer.addChild(victoryPhoto);
// Add congratulations text
var congratsText = new Text2("Congratulations!", {
size: 100,
fill: 0xFFFFFF
});
congratsText.anchor.set(0.5, 0.5);
congratsText.y = -700;
photoContainer.addChild(congratsText);
// Scale in effect
photoContainer.scale.set(0);
tween(photoContainer, {
scaleX: 1,
scaleY: 1
}, {
duration: 1000,
easing: tween.easeOutBack
});
// Remove after 3 seconds
LK.setTimeout(function () {
tween(photoContainer, {
scaleX: 0,
scaleY: 0,
alpha: 0
}, {
duration: 800,
easing: tween.easeIn,
onComplete: function onComplete() {
game.removeChild(photoContainer);
}
});
}, 3000);
}
// Game update loop
game.update = function () {
// Only update timer once per second
if (gameState.timerActive && !gameState.isGameOver && LK.ticks % 60 === 0) {
gameState.timeRemaining--;
updateTimer();
// Check if time is up
if (gameState.timeRemaining <= 0) {
endGame(true); // End game with timeout parameter
}
}
};
// Initialize the game
initGame();
// Add hover detection to cards
function setupHoverDetection() {
var lastDistance = {};
var hoverThreshold = 200; // Distance in pixels to trigger hover
game.move = function (x, y, obj) {
// Process all cards for hover effects
gameState.cards.forEach(function (card, index) {
var cardPos = game.toLocal(card.position);
var distance = Math.sqrt(Math.pow(x - cardPos.x, 2) + Math.pow(y - cardPos.y, 2));
// Initialize last distance if not set
if (lastDistance[index] === undefined) {
lastDistance[index] = distance;
}
// Detect when we move close to a card (crossing the threshold)
if (lastDistance[index] >= hoverThreshold && distance < hoverThreshold) {
card.mouseOver();
}
// Detect when we move away from a card
else if (lastDistance[index] < hoverThreshold && distance >= hoverThreshold) {
card.mouseOut();
}
// Update last distance
lastDistance[index] = distance;
});
};
}
// Set up hover detection after initializing the game
setupHoverDetection();