/****
* 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();