/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
useSpecialCards: false
});
/****
* Classes
****/
var Card = Container.expand(function (pairId) {
var self = Container.call(this);
self.pairId = pairId;
self.isFlipped = false;
self.isMatched = false;
self.back = self.attachAsset('card_back', {
anchorX: 0.5,
anchorY: 0.5
});
var faceAssetId;
if (pairId === -1) {
// Dud Card
faceAssetId = 'dud_card';
} else if (pairId === -2) {
// Shifter Card
faceAssetId = 'shifter_card';
} else {
// Normal Card
faceAssetId = 'card_face_' + pairId;
}
self.face = self.attachAsset(faceAssetId, {
anchorX: 0.5,
anchorY: 0.5
});
self.face.visible = false;
// Public methods must be defined before they are called.
self.flipUp = function (onComplete) {
if (self.isFlipped) return;
self.isFlipped = true;
LK.getSound('flip').play();
tween(self, {
scaleX: 0
}, {
duration: 150,
easing: tween.easeIn,
onFinish: function onFinish() {
self.back.visible = false;
self.face.visible = true;
tween(self, {
scaleX: 1
}, {
duration: 150,
easing: tween.easeOut,
onFinish: onComplete
});
}
});
};
self.flipDown = function (onComplete) {
if (!self.isFlipped) return;
self.isFlipped = false;
tween(self, {
scaleX: 0
}, {
duration: 150,
easing: tween.easeIn,
onFinish: function onFinish() {
self.face.visible = false;
self.back.visible = true;
tween(self, {
scaleX: 1
}, {
duration: 150,
easing: tween.easeOut,
onFinish: onComplete
});
}
});
};
self.matchFound = function (onComplete) {
self.isMatched = true;
tween(self, {
alpha: 0,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 400,
easing: tween.easeIn,
onFinish: onComplete
});
};
self.down = function () {
handleCardFlip(self);
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2c3e50 // Dark slate blue background
});
/****
* Game Code
****/
//LK Engine will automatically create assets based on usage.
var menuBackground = game.addChild(LK.getAsset('menu_background', {
x: 1024,
y: 1366,
anchorX: 0.5,
anchorY: 0.5
}));
var boardBackground = game.addChild(LK.getAsset('board_background', {
x: 1024,
y: 1366,
anchorX: 0.5,
anchorY: 0.5
}));
//Defining 8 pairs of cards + 1 card back.
// Red
// Blue
// Green
// Yellow
// Magenta
// Cyan
// Orange
// Purple
var scoreTxt = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
LK.setScore(0); // Initialize score
// --- Game Constants ---
var COLS = 4;
var ROWS = 4;
var TOTAL_PAIRS = (COLS * ROWS - 2) / 2; // Subtract 2 for the Shifter and Dud cards
var TOTAL_CARD_FACES = 15;
var CARD_WIDTH = 350;
var CARD_HEIGHT = 350;
var MARGIN = 60;
var gridWidth = COLS * (CARD_WIDTH + MARGIN) - MARGIN;
var gridHeight = ROWS * (CARD_HEIGHT + MARGIN) - MARGIN;
var startX = (2048 - gridWidth) / 2 + CARD_WIDTH / 2;
var startY = (2732 - gridHeight) / 2 + CARD_HEIGHT / 2;
// --- Global UI and Game State Variables ---
var startScreenContainer, boardSizeContainer, leaderboardContainer, gameContainer, victoryScreenContainer;
var specialCardsCheckbox;
var cards = [];
var flippedCards = [];
var matchedPairs = 0;
var canPlay = true;
var streakCount = 0;
var maxStreak = 0;
// --- Screen Management ---
function showStartScreen() {
if (boardSizeContainer) boardSizeContainer.visible = false;
if (leaderboardContainer) leaderboardContainer.visible = false;
if (gameContainer) gameContainer.visible = false;
if (victoryScreenContainer) victoryScreenContainer.visible = false;
menuBackground.visible = true;
boardBackground.visible = false;
if (!startScreenContainer) {
startScreenContainer = game.addChild(new Container());
var title = LK.getAsset('logo', {
anchorX: 0.5,
anchorY: 0.5,
width: 1200,
height: 300
});
title.x = 2048 / 2;
title.y = 2732 / 2 - 400;
startScreenContainer.addChild(title);
var playButton = LK.getAsset('play_button', {
anchorX: 0.5,
anchorY: 0.5,
width: 600,
height: 200
});
playButton.x = 2048 / 2;
playButton.y = 2732 / 2;
playButton.down = function () {
showBoardSizeSelection();
};
startScreenContainer.addChild(playButton);
var leaderboardButton = LK.getAsset('leaderboard_button', {
anchorX: 0.5,
anchorY: 0.5,
width: 600,
height: 200
});
leaderboardButton.x = 2048 / 2;
leaderboardButton.y = 2732 / 2 + 250;
leaderboardButton.down = function () {
showLeaderboard();
};
startScreenContainer.addChild(leaderboardButton);
}
startScreenContainer.visible = true;
scoreTxt.visible = false;
}
function updateSpecialCardsButtonAppearance() {
if (storage.useSpecialCards) {
// Add white sheen when enabled (tint to a lighter color)
tween(specialCardsCheckbox, {
tint: 0xFFFFFF
}, {
duration: 200,
easing: tween.easeOut
});
} else {
// Remove sheen when disabled (tint to normal, e.g. light gray or no tint)
tween(specialCardsCheckbox, {
tint: 0xCCCCCC
}, {
duration: 200,
easing: tween.easeOut
});
}
}
function showBoardSizeSelection() {
if (startScreenContainer) startScreenContainer.visible = false;
if (leaderboardContainer) leaderboardContainer.visible = false;
if (gameContainer) gameContainer.visible = false;
if (victoryScreenContainer) victoryScreenContainer.visible = false;
menuBackground.visible = true;
boardBackground.visible = false;
if (!boardSizeContainer) {
boardSizeContainer = game.addChild(new Container());
var dialogBox = LK.getAsset('dialog_box', {
x: 2048 / 2,
y: 2732 / 2,
anchorX: 0.5,
anchorY: 0.5
});
boardSizeContainer.addChild(dialogBox);
var content = [];
var title = new Text2('Select Board Size', {
size: 120,
fill: 0x000000,
fontWeight: 'bold'
});
title.anchor.set(0.5, 0.5);
title.x = 2048 / 2;
title.y = 2732 / 2 - 400;
boardSizeContainer.addChild(title);
content.push(title);
var size4x4Button = new Text2('4x4', {
size: 100,
fill: 0x000000,
fontWeight: 'bold'
});
size4x4Button.anchor.set(0.5, 0.5);
size4x4Button.x = 2048 / 2;
size4x4Button.y = 2732 / 2 - 100;
size4x4Button.down = function () {
setBoardSize(4, 4);
startGame();
};
boardSizeContainer.addChild(size4x4Button);
content.push(size4x4Button);
var size4x5Button = new Text2('4x5', {
size: 100,
fill: 0x000000,
fontWeight: 'bold'
});
size4x5Button.anchor.set(0.5, 0.5);
size4x5Button.x = 2048 / 2;
size4x5Button.y = 2732 / 2 + 50;
size4x5Button.down = function () {
setBoardSize(4, 5);
startGame();
};
boardSizeContainer.addChild(size4x5Button);
content.push(size4x5Button);
var size4x6Button = new Text2('4x6', {
size: 100,
fill: 0x000000,
fontWeight: 'bold'
});
size4x6Button.anchor.set(0.5, 0.5);
size4x6Button.x = 2048 / 2;
size4x6Button.y = 2732 / 2 + 200;
size4x6Button.down = function () {
setBoardSize(4, 6);
startGame();
};
boardSizeContainer.addChild(size4x6Button);
content.push(size4x6Button);
specialCardsCheckbox = LK.getAsset('jumble_tile', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 300
});
specialCardsCheckbox.down = function () {
storage.useSpecialCards = !storage.useSpecialCards;
updateSpecialCardsButtonAppearance();
};
boardSizeContainer.addChild(specialCardsCheckbox);
content.push(specialCardsCheckbox);
// Set initial tint to match state before animating
if (storage.useSpecialCards) {
specialCardsCheckbox.tint = 0xFFFFFF;
} else {
specialCardsCheckbox.tint = 0xCCCCCC;
}
// Initialize button appearance based on current setting
updateSpecialCardsButtonAppearance();
// Add help button for special cards
var helpButton = new Text2('?', {
size: 80,
fill: 0x000000,
fontWeight: 'bold'
});
helpButton.anchor.set(0.0, 0.5);
// Position the help button at the end of the jumble tile button
helpButton.x = specialCardsCheckbox.x + specialCardsCheckbox.width / 2 + 30;
helpButton.y = specialCardsCheckbox.y;
helpButton.down = function () {
// Show a simple popup with instructions
if (boardSizeContainer.helpPopup) {
boardSizeContainer.helpPopup.visible = true;
return;
}
var popup = new Container();
var popupBg = LK.getAsset('dialog_box', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
width: 1200,
height: 600
});
popup.addChild(popupBg);
var helpText = new Text2("Special Cards:\n- The Shifter card reveals and shuffles all remaining cards before disappearing.\n- The Dud card disappears when flipped and does not give points or require a match.", {
size: 60,
fill: 0x000000,
wordWrap: true,
wordWrapWidth: 1000
});
helpText.anchor.set(0.5, 0.5);
helpText.x = 2048 / 2;
helpText.y = 2732 / 2 - 60;
popup.addChild(helpText);
var closeBtn = new Text2('Close', {
size: 80,
fill: 0x000000,
fontWeight: 'bold'
});
closeBtn.anchor.set(0.5, 0.5);
closeBtn.x = 2048 / 2;
closeBtn.y = 2732 / 2 + 180;
closeBtn.down = function () {
popup.visible = false;
};
popup.addChild(closeBtn);
boardSizeContainer.addChild(popup);
boardSizeContainer.helpPopup = popup;
};
boardSizeContainer.addChild(helpButton);
content.push(helpButton);
var backButton = new Text2('Back', {
size: 80,
fill: 0x000000,
fontWeight: 'bold'
});
backButton.anchor.set(0.5, 0.5);
backButton.x = 2048 / 2;
backButton.y = 2732 / 2 + 450;
backButton.down = function () {
showStartScreen();
};
boardSizeContainer.addChild(backButton);
content.push(backButton);
var minY = Infinity;
var maxY = -Infinity;
var maxWidth = 0;
for (var i = 0; i < content.length; i++) {
var text = content[i];
var top = text.y - text.height * text.anchor.y;
var bottom = text.y + text.height * (1 - text.anchor.y);
if (top < minY) minY = top;
if (bottom > maxY) maxY = bottom;
if (text.width > maxWidth) maxWidth = text.width;
}
var PADDING_Y = 100;
var PADDING_X = 150;
dialogBox.height = (maxY - minY + PADDING_Y * 2) * 1.1;
dialogBox.width = maxWidth + PADDING_X * 2;
dialogBox.y = minY + (maxY - minY) / 2;
}
// Special cards checkbox is now an image button - no text to update
boardSizeContainer.visible = true;
scoreTxt.visible = false;
}
function setBoardSize(cols, rows) {
COLS = cols;
ROWS = rows;
if (storage.useSpecialCards === true) {
TOTAL_PAIRS = (COLS * ROWS - 2) / 2;
} else {
TOTAL_PAIRS = COLS * ROWS / 2;
}
gridWidth = COLS * (CARD_WIDTH + MARGIN) - MARGIN;
gridHeight = ROWS * (CARD_HEIGHT + MARGIN) - MARGIN;
startX = (2048 - gridWidth) / 2 + CARD_WIDTH / 2;
startY = (2732 - gridHeight) / 2 + CARD_HEIGHT / 2;
}
function showLeaderboard() {
if (startScreenContainer) startScreenContainer.visible = false;
if (boardSizeContainer) boardSizeContainer.visible = false;
menuBackground.visible = true;
boardBackground.visible = false;
if (!leaderboardContainer) {
leaderboardContainer = game.addChild(new Container());
var dialogBox = LK.getAsset('dialog_box', {
x: 2048 / 2,
y: 2732 / 2,
anchorX: 0.5,
anchorY: 0.5
});
leaderboardContainer.addChild(dialogBox);
var content = [];
var title = new Text2('Leaderboard', {
size: 150,
fill: 0x000000,
fontWeight: 'bold'
});
title.anchor.set(0.5, 0.5);
title.x = 2048 / 2;
title.y = 2732 / 2 - 600;
leaderboardContainer.addChild(title);
content.push(title);
var header = new Text2('RANK USER SCORE STREAK', {
size: 70,
fill: 0x000000
});
header.anchor.set(0.5, 0.5);
header.x = 2048 / 2;
header.y = 2732 / 2 - 300;
leaderboardContainer.addChild(header);
content.push(header);
var highScore = storage.highScore || 0;
var highScoreStreak = storage.highScoreStreak || 0;
var userLine = new Text2('1 You ' + highScore + ' ' + highScoreStreak, {
size: 90,
fill: 0x000000,
fontWeight: 'bold'
});
userLine.anchor.set(0.5, 0.5);
userLine.x = 2048 / 2;
userLine.y = 2732 / 2 - 150;
leaderboardContainer.addChild(userLine);
leaderboardContainer.userLine = userLine; // Store reference
content.push(userLine);
var backButton = new Text2('Back', {
size: 100,
fill: 0x000000,
fontWeight: 'bold'
});
backButton.anchor.set(0.5, 0.5);
backButton.x = 2048 / 2;
backButton.y = 2732 / 2 + 300;
backButton.down = function () {
showStartScreen();
};
leaderboardContainer.addChild(backButton);
content.push(backButton);
var minY = Infinity;
var maxY = -Infinity;
var maxWidth = 0;
for (var i = 0; i < content.length; i++) {
var text = content[i];
var top = text.y - text.height * text.anchor.y;
var bottom = text.y + text.height * (1 - text.anchor.y);
if (top < minY) minY = top;
if (bottom > maxY) maxY = bottom;
if (text.width > maxWidth) maxWidth = text.width;
}
var PADDING_Y = 100;
var PADDING_X = 150;
dialogBox.height = (maxY - minY + PADDING_Y * 2) * 1.1;
dialogBox.width = maxWidth + PADDING_X * 2;
dialogBox.y = minY + (maxY - minY) / 2;
} else {
var highScore = storage.highScore || 0;
var highScoreStreak = storage.highScoreStreak || 0;
leaderboardContainer.userLine.setText('1 You ' + highScore + ' ' + highScoreStreak);
}
leaderboardContainer.visible = true;
scoreTxt.visible = false;
}
function showVictoryScreen() {
if (gameContainer) gameContainer.visible = false;
if (boardSizeContainer) boardSizeContainer.visible = false;
scoreTxt.visible = false;
menuBackground.visible = true;
boardBackground.visible = false;
if (!victoryScreenContainer) {
victoryScreenContainer = game.addChild(new Container());
var title = new Text2('You Win!', {
size: 150,
fill: 0xFFD700
});
title.anchor.set(0.5, 0.5);
title.x = 2048 / 2;
title.y = 2732 / 2 - 400;
victoryScreenContainer.addChild(title);
var finalScoreText = new Text2('Final Score: ' + LK.getScore(), {
size: 100,
fill: 0xFFFFFF,
fontWeight: 'bold',
stroke: 0x000000,
strokeThickness: 8
});
finalScoreText.anchor.set(0.5, 0.5);
finalScoreText.x = 2048 / 2;
finalScoreText.y = 2732 / 2 - 200;
victoryScreenContainer.addChild(finalScoreText);
victoryScreenContainer.finalScoreText = finalScoreText; // To update it later
var playAgainButton = new Text2('Play Again', {
size: 100,
fill: 0xFFFFFF,
fontWeight: 'bold',
stroke: 0x000000,
strokeThickness: 8
});
playAgainButton.anchor.set(0.5, 0.5);
playAgainButton.x = 2048 / 2;
playAgainButton.y = 2732 / 2;
playAgainButton.down = function () {
startGame();
};
victoryScreenContainer.addChild(playAgainButton);
var mainMenuButton = new Text2('Main Menu', {
size: 100,
fill: 0xFFFFFF,
fontWeight: 'bold',
stroke: 0x000000,
strokeThickness: 8
});
mainMenuButton.anchor.set(0.5, 0.5);
mainMenuButton.x = 2048 / 2;
mainMenuButton.y = 2732 / 2 + 200;
mainMenuButton.down = function () {
showStartScreen();
};
victoryScreenContainer.addChild(mainMenuButton);
} else {
victoryScreenContainer.finalScoreText.setText('Final Score: ' + LK.getScore());
}
victoryScreenContainer.visible = true;
}
function startGame() {
if (startScreenContainer) startScreenContainer.visible = false;
if (boardSizeContainer) boardSizeContainer.visible = false;
if (leaderboardContainer) leaderboardContainer.visible = false;
if (victoryScreenContainer) victoryScreenContainer.visible = false;
menuBackground.visible = false;
boardBackground.visible = true;
if (!gameContainer) {
gameContainer = game.addChild(new Container());
}
gameContainer.visible = true;
// Clear old cards
for (var i = 0; i < cards.length; i++) {
cards[i].destroy();
}
// Reset game state
cards = [];
flippedCards = [];
matchedPairs = 0;
canPlay = true;
streakCount = 0;
maxStreak = 0;
LK.setScore(0);
scoreTxt.setText('Score: ' + LK.getScore());
scoreTxt.visible = true;
setupBoard();
}
// --- Game Logic Functions ---
function showStreakAnimation(bonusPoints) {
var bonusContainer = new Container();
bonusContainer.x = 2048 / 2;
bonusContainer.y = 2732 / 2;
var bonusText = new Text2('BONUS!!!', {
size: 120,
fill: 0xFFD700
});
bonusText.anchor.set(0.5, 0.5);
bonusText.y = -70;
bonusContainer.addChild(bonusText);
var pointsText = new Text2('+' + bonusPoints, {
size: 100,
fill: 0xFFFFFF
});
pointsText.anchor.set(0.5, 0.5);
pointsText.y = 70;
bonusContainer.addChild(pointsText);
gameContainer.addChild(bonusContainer);
// Animate upward movement
tween(bonusContainer, {
y: bonusContainer.y - 200
}, {
duration: 1000,
easing: tween.easeOut
});
// After 0.5s, start fading out
LK.setTimeout(function () {
if (bonusContainer.parent) {
tween(bonusContainer, {
alpha: 0
}, {
duration: 500,
// Fades over the last 0.5s
easing: tween.easeOut,
onFinish: function onFinish() {
// Self-destruct after animation. Check parent to avoid errors
// if the game state was cleared during the animation.
if (bonusContainer.parent) {
bonusContainer.destroy();
}
}
});
}
}, 500);
}
function checkWinCondition() {
var remainingCards = 0;
for (var i = 0; i < cards.length; i++) {
if (!cards[i].isMatched) {
remainingCards++;
}
}
if (remainingCards === 0) {
var highScore = storage.highScore || 0;
var highScoreStreak = storage.highScoreStreak || 0;
if (LK.getScore() > highScore) {
storage.highScore = LK.getScore();
storage.highScoreStreak = maxStreak;
}
LK.getSound('win').play();
LK.setTimeout(function () {
showVictoryScreen();
}, 200);
}
}
function handleDudCard(card) {
card.isMatched = true; // Mark as handled
// Match the flip down delay of the other card (1000ms)
LK.setTimeout(function () {
tween(card, {
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
checkWinCondition();
canPlay = true;
}
});
}, 1000);
}
function flipAllDownAndEndTurn(cardsToFlip) {
var flipCount = 0;
if (cardsToFlip.length === 0) {
checkWinCondition();
canPlay = true;
return;
}
cardsToFlip.forEach(function (c) {
c.flipDown(function () {
flipCount++;
if (flipCount === cardsToFlip.length) {
checkWinCondition();
canPlay = true;
}
});
});
}
function startShuffleAnimation(shifterCard, otherCards, originalPositions) {
var newPositions = shuffle(originalPositions);
tween(shifterCard, {
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
// Card is visually gone and marked as matched.
}
});
var moveCount = 0;
if (otherCards.length === 0) {
flipAllDownAndEndTurn([]);
return;
}
otherCards.forEach(function (c, i) {
tween(c, {
x: newPositions[i].x,
y: newPositions[i].y
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
moveCount++;
if (moveCount === otherCards.length) {
// All cards moved, now flip them back down
flipAllDownAndEndTurn(otherCards);
}
}
});
});
}
function handleShifterCard(card) {
card.isMatched = true; // Mark as handled
var otherCards = cards.filter(function (c) {
return c !== card && !c.isMatched;
});
if (otherCards.length === 0) {
handleDudCard(card); // Behaves like a dud card if no other cards are on board
return;
}
var originalPositions = [];
otherCards.forEach(function (c) {
originalPositions.push({
x: c.x,
y: c.y
});
});
var revealCount = 0;
otherCards.forEach(function (c) {
c.flipUp(function () {
revealCount++;
if (revealCount === otherCards.length) {
LK.setTimeout(function () {
startShuffleAnimation(card, otherCards, originalPositions);
}, 800); // Pause to let player see cards
}
});
});
}
function handleCardFlip(card) {
if (!canPlay || card.isFlipped || card.isMatched) {
return;
}
// Handle special cards immediately
if (card.pairId < 0) {
canPlay = false;
// If a normal card is already flipped, flip it back down before handling the special card
if (flippedCards.length === 1) {
var otherCard = flippedCards[0];
otherCard.flipDown(function () {
flippedCards = [];
card.flipUp(function () {
if (card.pairId === -1) {
handleDudCard(card);
} else if (card.pairId === -2) {
handleShifterCard(card);
}
});
});
} else {
flippedCards = []; // Clear any single-flipped normal card
card.flipUp(function () {
if (card.pairId === -1) {
handleDudCard(card);
} else if (card.pairId === -2) {
handleShifterCard(card);
}
});
}
return;
}
// Prevent flipping a third card
if (flippedCards.length >= 2) {
return;
}
flippedCards.push(card);
card.flipUp(function () {
if (flippedCards.length === 2) {
canPlay = false;
checkMatch();
}
});
}
function checkMatch() {
var card1 = flippedCards[0];
var card2 = flippedCards[1];
if (card1.pairId === card2.pairId) {
matchedPairs++;
streakCount++;
if (streakCount > maxStreak) {
maxStreak = streakCount;
}
// Calculate points based on streak
var points = 10;
var bonusPoints = 0;
if (streakCount === 2) {
points = 20;
bonusPoints = 10;
} else if (streakCount >= 3) {
points = 30;
bonusPoints = 20;
}
if (bonusPoints > 0) {
showStreakAnimation(bonusPoints);
}
LK.setScore(LK.getScore() + points);
scoreTxt.setText('Score: ' + LK.getScore());
LK.setTimeout(function () {
LK.getSound('match').play();
card1.matchFound();
card2.matchFound(function () {
resetTurn();
checkWinCondition();
});
}, 750);
} else {
// Reset streak on miss
streakCount = 0;
LK.setTimeout(function () {
card1.flipDown();
card2.flipDown(function () {
resetTurn();
});
}, 1000);
}
}
function resetTurn() {
flippedCards = [];
canPlay = true;
}
function shuffle(array) {
var currentIndex = array.length,
randomIndex;
while (currentIndex !== 0) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
var _ref = [array[randomIndex], array[currentIndex]];
array[currentIndex] = _ref[0];
array[randomIndex] = _ref[1];
}
return array;
}
function isLayoutValid(grid) {
for (var row = 0; row < ROWS; row++) {
for (var col = 0; col < COLS; col++) {
var currentPairId = grid[row][col];
// Check all 8 neighbors
for (var dr = -1; dr <= 1; dr++) {
for (var dc = -1; dc <= 1; dc++) {
if (dr === 0 && dc === 0) continue; // Don't check self
var neighborRow = row + dr;
var neighborCol = col + dc;
if (neighborRow >= 0 && neighborRow < ROWS && neighborCol >= 0 && neighborCol < COLS) {
if (grid[neighborRow][neighborCol] === currentPairId) {
return false; // Found an adjacent match
}
}
}
}
}
}
return true; // No adjacent matches found
}
// --- Board Setup ---
function setupBoard() {
var pairIds = [];
// Create a list of all available card face IDs (0 to TOTAL_CARD_FACES-1)
var allFaceIndices = [];
for (var i = 0; i < TOTAL_CARD_FACES; i++) {
allFaceIndices.push(i);
}
// Shuffle the available faces and select enough for the board
shuffle(allFaceIndices);
var gameFaceIndices = allFaceIndices.slice(0, TOTAL_PAIRS);
// Create pairs from the selected faces
for (var i = 0; i < TOTAL_PAIRS; i++) {
pairIds.push(gameFaceIndices[i], gameFaceIndices[i]);
}
// Add the special cards if enabled
if (storage.useSpecialCards === true) {
pairIds.push(-1); // Dud Card
pairIds.push(-2); // Shifter Card
}
var grid = [];
var attempts = 0;
var maxAttempts = 100; // Safeguard to prevent infinite loop
do {
shuffle(pairIds);
// Create a 2D grid from the shuffled IDs
for (var r = 0; r < ROWS; r++) {
grid[r] = [];
for (var c = 0; c < COLS; c++) {
grid[r][c] = pairIds[r * COLS + c];
}
}
attempts++;
} while (!isLayoutValid(grid) && attempts < maxAttempts);
// Create cards from the final grid layout
for (var row = 0; row < ROWS; row++) {
for (var col = 0; col < COLS; col++) {
var pairId = grid[row][col];
var card = new Card(pairId);
card.x = startX + col * (CARD_WIDTH + MARGIN);
card.y = startY + row * (CARD_HEIGHT + MARGIN);
cards.push(card);
gameContainer.addChild(card);
}
}
}
// --- Initial Call ---
showStartScreen(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
useSpecialCards: false
});
/****
* Classes
****/
var Card = Container.expand(function (pairId) {
var self = Container.call(this);
self.pairId = pairId;
self.isFlipped = false;
self.isMatched = false;
self.back = self.attachAsset('card_back', {
anchorX: 0.5,
anchorY: 0.5
});
var faceAssetId;
if (pairId === -1) {
// Dud Card
faceAssetId = 'dud_card';
} else if (pairId === -2) {
// Shifter Card
faceAssetId = 'shifter_card';
} else {
// Normal Card
faceAssetId = 'card_face_' + pairId;
}
self.face = self.attachAsset(faceAssetId, {
anchorX: 0.5,
anchorY: 0.5
});
self.face.visible = false;
// Public methods must be defined before they are called.
self.flipUp = function (onComplete) {
if (self.isFlipped) return;
self.isFlipped = true;
LK.getSound('flip').play();
tween(self, {
scaleX: 0
}, {
duration: 150,
easing: tween.easeIn,
onFinish: function onFinish() {
self.back.visible = false;
self.face.visible = true;
tween(self, {
scaleX: 1
}, {
duration: 150,
easing: tween.easeOut,
onFinish: onComplete
});
}
});
};
self.flipDown = function (onComplete) {
if (!self.isFlipped) return;
self.isFlipped = false;
tween(self, {
scaleX: 0
}, {
duration: 150,
easing: tween.easeIn,
onFinish: function onFinish() {
self.face.visible = false;
self.back.visible = true;
tween(self, {
scaleX: 1
}, {
duration: 150,
easing: tween.easeOut,
onFinish: onComplete
});
}
});
};
self.matchFound = function (onComplete) {
self.isMatched = true;
tween(self, {
alpha: 0,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 400,
easing: tween.easeIn,
onFinish: onComplete
});
};
self.down = function () {
handleCardFlip(self);
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2c3e50 // Dark slate blue background
});
/****
* Game Code
****/
//LK Engine will automatically create assets based on usage.
var menuBackground = game.addChild(LK.getAsset('menu_background', {
x: 1024,
y: 1366,
anchorX: 0.5,
anchorY: 0.5
}));
var boardBackground = game.addChild(LK.getAsset('board_background', {
x: 1024,
y: 1366,
anchorX: 0.5,
anchorY: 0.5
}));
//Defining 8 pairs of cards + 1 card back.
// Red
// Blue
// Green
// Yellow
// Magenta
// Cyan
// Orange
// Purple
var scoreTxt = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
LK.setScore(0); // Initialize score
// --- Game Constants ---
var COLS = 4;
var ROWS = 4;
var TOTAL_PAIRS = (COLS * ROWS - 2) / 2; // Subtract 2 for the Shifter and Dud cards
var TOTAL_CARD_FACES = 15;
var CARD_WIDTH = 350;
var CARD_HEIGHT = 350;
var MARGIN = 60;
var gridWidth = COLS * (CARD_WIDTH + MARGIN) - MARGIN;
var gridHeight = ROWS * (CARD_HEIGHT + MARGIN) - MARGIN;
var startX = (2048 - gridWidth) / 2 + CARD_WIDTH / 2;
var startY = (2732 - gridHeight) / 2 + CARD_HEIGHT / 2;
// --- Global UI and Game State Variables ---
var startScreenContainer, boardSizeContainer, leaderboardContainer, gameContainer, victoryScreenContainer;
var specialCardsCheckbox;
var cards = [];
var flippedCards = [];
var matchedPairs = 0;
var canPlay = true;
var streakCount = 0;
var maxStreak = 0;
// --- Screen Management ---
function showStartScreen() {
if (boardSizeContainer) boardSizeContainer.visible = false;
if (leaderboardContainer) leaderboardContainer.visible = false;
if (gameContainer) gameContainer.visible = false;
if (victoryScreenContainer) victoryScreenContainer.visible = false;
menuBackground.visible = true;
boardBackground.visible = false;
if (!startScreenContainer) {
startScreenContainer = game.addChild(new Container());
var title = LK.getAsset('logo', {
anchorX: 0.5,
anchorY: 0.5,
width: 1200,
height: 300
});
title.x = 2048 / 2;
title.y = 2732 / 2 - 400;
startScreenContainer.addChild(title);
var playButton = LK.getAsset('play_button', {
anchorX: 0.5,
anchorY: 0.5,
width: 600,
height: 200
});
playButton.x = 2048 / 2;
playButton.y = 2732 / 2;
playButton.down = function () {
showBoardSizeSelection();
};
startScreenContainer.addChild(playButton);
var leaderboardButton = LK.getAsset('leaderboard_button', {
anchorX: 0.5,
anchorY: 0.5,
width: 600,
height: 200
});
leaderboardButton.x = 2048 / 2;
leaderboardButton.y = 2732 / 2 + 250;
leaderboardButton.down = function () {
showLeaderboard();
};
startScreenContainer.addChild(leaderboardButton);
}
startScreenContainer.visible = true;
scoreTxt.visible = false;
}
function updateSpecialCardsButtonAppearance() {
if (storage.useSpecialCards) {
// Add white sheen when enabled (tint to a lighter color)
tween(specialCardsCheckbox, {
tint: 0xFFFFFF
}, {
duration: 200,
easing: tween.easeOut
});
} else {
// Remove sheen when disabled (tint to normal, e.g. light gray or no tint)
tween(specialCardsCheckbox, {
tint: 0xCCCCCC
}, {
duration: 200,
easing: tween.easeOut
});
}
}
function showBoardSizeSelection() {
if (startScreenContainer) startScreenContainer.visible = false;
if (leaderboardContainer) leaderboardContainer.visible = false;
if (gameContainer) gameContainer.visible = false;
if (victoryScreenContainer) victoryScreenContainer.visible = false;
menuBackground.visible = true;
boardBackground.visible = false;
if (!boardSizeContainer) {
boardSizeContainer = game.addChild(new Container());
var dialogBox = LK.getAsset('dialog_box', {
x: 2048 / 2,
y: 2732 / 2,
anchorX: 0.5,
anchorY: 0.5
});
boardSizeContainer.addChild(dialogBox);
var content = [];
var title = new Text2('Select Board Size', {
size: 120,
fill: 0x000000,
fontWeight: 'bold'
});
title.anchor.set(0.5, 0.5);
title.x = 2048 / 2;
title.y = 2732 / 2 - 400;
boardSizeContainer.addChild(title);
content.push(title);
var size4x4Button = new Text2('4x4', {
size: 100,
fill: 0x000000,
fontWeight: 'bold'
});
size4x4Button.anchor.set(0.5, 0.5);
size4x4Button.x = 2048 / 2;
size4x4Button.y = 2732 / 2 - 100;
size4x4Button.down = function () {
setBoardSize(4, 4);
startGame();
};
boardSizeContainer.addChild(size4x4Button);
content.push(size4x4Button);
var size4x5Button = new Text2('4x5', {
size: 100,
fill: 0x000000,
fontWeight: 'bold'
});
size4x5Button.anchor.set(0.5, 0.5);
size4x5Button.x = 2048 / 2;
size4x5Button.y = 2732 / 2 + 50;
size4x5Button.down = function () {
setBoardSize(4, 5);
startGame();
};
boardSizeContainer.addChild(size4x5Button);
content.push(size4x5Button);
var size4x6Button = new Text2('4x6', {
size: 100,
fill: 0x000000,
fontWeight: 'bold'
});
size4x6Button.anchor.set(0.5, 0.5);
size4x6Button.x = 2048 / 2;
size4x6Button.y = 2732 / 2 + 200;
size4x6Button.down = function () {
setBoardSize(4, 6);
startGame();
};
boardSizeContainer.addChild(size4x6Button);
content.push(size4x6Button);
specialCardsCheckbox = LK.getAsset('jumble_tile', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 300
});
specialCardsCheckbox.down = function () {
storage.useSpecialCards = !storage.useSpecialCards;
updateSpecialCardsButtonAppearance();
};
boardSizeContainer.addChild(specialCardsCheckbox);
content.push(specialCardsCheckbox);
// Set initial tint to match state before animating
if (storage.useSpecialCards) {
specialCardsCheckbox.tint = 0xFFFFFF;
} else {
specialCardsCheckbox.tint = 0xCCCCCC;
}
// Initialize button appearance based on current setting
updateSpecialCardsButtonAppearance();
// Add help button for special cards
var helpButton = new Text2('?', {
size: 80,
fill: 0x000000,
fontWeight: 'bold'
});
helpButton.anchor.set(0.0, 0.5);
// Position the help button at the end of the jumble tile button
helpButton.x = specialCardsCheckbox.x + specialCardsCheckbox.width / 2 + 30;
helpButton.y = specialCardsCheckbox.y;
helpButton.down = function () {
// Show a simple popup with instructions
if (boardSizeContainer.helpPopup) {
boardSizeContainer.helpPopup.visible = true;
return;
}
var popup = new Container();
var popupBg = LK.getAsset('dialog_box', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
width: 1200,
height: 600
});
popup.addChild(popupBg);
var helpText = new Text2("Special Cards:\n- The Shifter card reveals and shuffles all remaining cards before disappearing.\n- The Dud card disappears when flipped and does not give points or require a match.", {
size: 60,
fill: 0x000000,
wordWrap: true,
wordWrapWidth: 1000
});
helpText.anchor.set(0.5, 0.5);
helpText.x = 2048 / 2;
helpText.y = 2732 / 2 - 60;
popup.addChild(helpText);
var closeBtn = new Text2('Close', {
size: 80,
fill: 0x000000,
fontWeight: 'bold'
});
closeBtn.anchor.set(0.5, 0.5);
closeBtn.x = 2048 / 2;
closeBtn.y = 2732 / 2 + 180;
closeBtn.down = function () {
popup.visible = false;
};
popup.addChild(closeBtn);
boardSizeContainer.addChild(popup);
boardSizeContainer.helpPopup = popup;
};
boardSizeContainer.addChild(helpButton);
content.push(helpButton);
var backButton = new Text2('Back', {
size: 80,
fill: 0x000000,
fontWeight: 'bold'
});
backButton.anchor.set(0.5, 0.5);
backButton.x = 2048 / 2;
backButton.y = 2732 / 2 + 450;
backButton.down = function () {
showStartScreen();
};
boardSizeContainer.addChild(backButton);
content.push(backButton);
var minY = Infinity;
var maxY = -Infinity;
var maxWidth = 0;
for (var i = 0; i < content.length; i++) {
var text = content[i];
var top = text.y - text.height * text.anchor.y;
var bottom = text.y + text.height * (1 - text.anchor.y);
if (top < minY) minY = top;
if (bottom > maxY) maxY = bottom;
if (text.width > maxWidth) maxWidth = text.width;
}
var PADDING_Y = 100;
var PADDING_X = 150;
dialogBox.height = (maxY - minY + PADDING_Y * 2) * 1.1;
dialogBox.width = maxWidth + PADDING_X * 2;
dialogBox.y = minY + (maxY - minY) / 2;
}
// Special cards checkbox is now an image button - no text to update
boardSizeContainer.visible = true;
scoreTxt.visible = false;
}
function setBoardSize(cols, rows) {
COLS = cols;
ROWS = rows;
if (storage.useSpecialCards === true) {
TOTAL_PAIRS = (COLS * ROWS - 2) / 2;
} else {
TOTAL_PAIRS = COLS * ROWS / 2;
}
gridWidth = COLS * (CARD_WIDTH + MARGIN) - MARGIN;
gridHeight = ROWS * (CARD_HEIGHT + MARGIN) - MARGIN;
startX = (2048 - gridWidth) / 2 + CARD_WIDTH / 2;
startY = (2732 - gridHeight) / 2 + CARD_HEIGHT / 2;
}
function showLeaderboard() {
if (startScreenContainer) startScreenContainer.visible = false;
if (boardSizeContainer) boardSizeContainer.visible = false;
menuBackground.visible = true;
boardBackground.visible = false;
if (!leaderboardContainer) {
leaderboardContainer = game.addChild(new Container());
var dialogBox = LK.getAsset('dialog_box', {
x: 2048 / 2,
y: 2732 / 2,
anchorX: 0.5,
anchorY: 0.5
});
leaderboardContainer.addChild(dialogBox);
var content = [];
var title = new Text2('Leaderboard', {
size: 150,
fill: 0x000000,
fontWeight: 'bold'
});
title.anchor.set(0.5, 0.5);
title.x = 2048 / 2;
title.y = 2732 / 2 - 600;
leaderboardContainer.addChild(title);
content.push(title);
var header = new Text2('RANK USER SCORE STREAK', {
size: 70,
fill: 0x000000
});
header.anchor.set(0.5, 0.5);
header.x = 2048 / 2;
header.y = 2732 / 2 - 300;
leaderboardContainer.addChild(header);
content.push(header);
var highScore = storage.highScore || 0;
var highScoreStreak = storage.highScoreStreak || 0;
var userLine = new Text2('1 You ' + highScore + ' ' + highScoreStreak, {
size: 90,
fill: 0x000000,
fontWeight: 'bold'
});
userLine.anchor.set(0.5, 0.5);
userLine.x = 2048 / 2;
userLine.y = 2732 / 2 - 150;
leaderboardContainer.addChild(userLine);
leaderboardContainer.userLine = userLine; // Store reference
content.push(userLine);
var backButton = new Text2('Back', {
size: 100,
fill: 0x000000,
fontWeight: 'bold'
});
backButton.anchor.set(0.5, 0.5);
backButton.x = 2048 / 2;
backButton.y = 2732 / 2 + 300;
backButton.down = function () {
showStartScreen();
};
leaderboardContainer.addChild(backButton);
content.push(backButton);
var minY = Infinity;
var maxY = -Infinity;
var maxWidth = 0;
for (var i = 0; i < content.length; i++) {
var text = content[i];
var top = text.y - text.height * text.anchor.y;
var bottom = text.y + text.height * (1 - text.anchor.y);
if (top < minY) minY = top;
if (bottom > maxY) maxY = bottom;
if (text.width > maxWidth) maxWidth = text.width;
}
var PADDING_Y = 100;
var PADDING_X = 150;
dialogBox.height = (maxY - minY + PADDING_Y * 2) * 1.1;
dialogBox.width = maxWidth + PADDING_X * 2;
dialogBox.y = minY + (maxY - minY) / 2;
} else {
var highScore = storage.highScore || 0;
var highScoreStreak = storage.highScoreStreak || 0;
leaderboardContainer.userLine.setText('1 You ' + highScore + ' ' + highScoreStreak);
}
leaderboardContainer.visible = true;
scoreTxt.visible = false;
}
function showVictoryScreen() {
if (gameContainer) gameContainer.visible = false;
if (boardSizeContainer) boardSizeContainer.visible = false;
scoreTxt.visible = false;
menuBackground.visible = true;
boardBackground.visible = false;
if (!victoryScreenContainer) {
victoryScreenContainer = game.addChild(new Container());
var title = new Text2('You Win!', {
size: 150,
fill: 0xFFD700
});
title.anchor.set(0.5, 0.5);
title.x = 2048 / 2;
title.y = 2732 / 2 - 400;
victoryScreenContainer.addChild(title);
var finalScoreText = new Text2('Final Score: ' + LK.getScore(), {
size: 100,
fill: 0xFFFFFF,
fontWeight: 'bold',
stroke: 0x000000,
strokeThickness: 8
});
finalScoreText.anchor.set(0.5, 0.5);
finalScoreText.x = 2048 / 2;
finalScoreText.y = 2732 / 2 - 200;
victoryScreenContainer.addChild(finalScoreText);
victoryScreenContainer.finalScoreText = finalScoreText; // To update it later
var playAgainButton = new Text2('Play Again', {
size: 100,
fill: 0xFFFFFF,
fontWeight: 'bold',
stroke: 0x000000,
strokeThickness: 8
});
playAgainButton.anchor.set(0.5, 0.5);
playAgainButton.x = 2048 / 2;
playAgainButton.y = 2732 / 2;
playAgainButton.down = function () {
startGame();
};
victoryScreenContainer.addChild(playAgainButton);
var mainMenuButton = new Text2('Main Menu', {
size: 100,
fill: 0xFFFFFF,
fontWeight: 'bold',
stroke: 0x000000,
strokeThickness: 8
});
mainMenuButton.anchor.set(0.5, 0.5);
mainMenuButton.x = 2048 / 2;
mainMenuButton.y = 2732 / 2 + 200;
mainMenuButton.down = function () {
showStartScreen();
};
victoryScreenContainer.addChild(mainMenuButton);
} else {
victoryScreenContainer.finalScoreText.setText('Final Score: ' + LK.getScore());
}
victoryScreenContainer.visible = true;
}
function startGame() {
if (startScreenContainer) startScreenContainer.visible = false;
if (boardSizeContainer) boardSizeContainer.visible = false;
if (leaderboardContainer) leaderboardContainer.visible = false;
if (victoryScreenContainer) victoryScreenContainer.visible = false;
menuBackground.visible = false;
boardBackground.visible = true;
if (!gameContainer) {
gameContainer = game.addChild(new Container());
}
gameContainer.visible = true;
// Clear old cards
for (var i = 0; i < cards.length; i++) {
cards[i].destroy();
}
// Reset game state
cards = [];
flippedCards = [];
matchedPairs = 0;
canPlay = true;
streakCount = 0;
maxStreak = 0;
LK.setScore(0);
scoreTxt.setText('Score: ' + LK.getScore());
scoreTxt.visible = true;
setupBoard();
}
// --- Game Logic Functions ---
function showStreakAnimation(bonusPoints) {
var bonusContainer = new Container();
bonusContainer.x = 2048 / 2;
bonusContainer.y = 2732 / 2;
var bonusText = new Text2('BONUS!!!', {
size: 120,
fill: 0xFFD700
});
bonusText.anchor.set(0.5, 0.5);
bonusText.y = -70;
bonusContainer.addChild(bonusText);
var pointsText = new Text2('+' + bonusPoints, {
size: 100,
fill: 0xFFFFFF
});
pointsText.anchor.set(0.5, 0.5);
pointsText.y = 70;
bonusContainer.addChild(pointsText);
gameContainer.addChild(bonusContainer);
// Animate upward movement
tween(bonusContainer, {
y: bonusContainer.y - 200
}, {
duration: 1000,
easing: tween.easeOut
});
// After 0.5s, start fading out
LK.setTimeout(function () {
if (bonusContainer.parent) {
tween(bonusContainer, {
alpha: 0
}, {
duration: 500,
// Fades over the last 0.5s
easing: tween.easeOut,
onFinish: function onFinish() {
// Self-destruct after animation. Check parent to avoid errors
// if the game state was cleared during the animation.
if (bonusContainer.parent) {
bonusContainer.destroy();
}
}
});
}
}, 500);
}
function checkWinCondition() {
var remainingCards = 0;
for (var i = 0; i < cards.length; i++) {
if (!cards[i].isMatched) {
remainingCards++;
}
}
if (remainingCards === 0) {
var highScore = storage.highScore || 0;
var highScoreStreak = storage.highScoreStreak || 0;
if (LK.getScore() > highScore) {
storage.highScore = LK.getScore();
storage.highScoreStreak = maxStreak;
}
LK.getSound('win').play();
LK.setTimeout(function () {
showVictoryScreen();
}, 200);
}
}
function handleDudCard(card) {
card.isMatched = true; // Mark as handled
// Match the flip down delay of the other card (1000ms)
LK.setTimeout(function () {
tween(card, {
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
checkWinCondition();
canPlay = true;
}
});
}, 1000);
}
function flipAllDownAndEndTurn(cardsToFlip) {
var flipCount = 0;
if (cardsToFlip.length === 0) {
checkWinCondition();
canPlay = true;
return;
}
cardsToFlip.forEach(function (c) {
c.flipDown(function () {
flipCount++;
if (flipCount === cardsToFlip.length) {
checkWinCondition();
canPlay = true;
}
});
});
}
function startShuffleAnimation(shifterCard, otherCards, originalPositions) {
var newPositions = shuffle(originalPositions);
tween(shifterCard, {
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
// Card is visually gone and marked as matched.
}
});
var moveCount = 0;
if (otherCards.length === 0) {
flipAllDownAndEndTurn([]);
return;
}
otherCards.forEach(function (c, i) {
tween(c, {
x: newPositions[i].x,
y: newPositions[i].y
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
moveCount++;
if (moveCount === otherCards.length) {
// All cards moved, now flip them back down
flipAllDownAndEndTurn(otherCards);
}
}
});
});
}
function handleShifterCard(card) {
card.isMatched = true; // Mark as handled
var otherCards = cards.filter(function (c) {
return c !== card && !c.isMatched;
});
if (otherCards.length === 0) {
handleDudCard(card); // Behaves like a dud card if no other cards are on board
return;
}
var originalPositions = [];
otherCards.forEach(function (c) {
originalPositions.push({
x: c.x,
y: c.y
});
});
var revealCount = 0;
otherCards.forEach(function (c) {
c.flipUp(function () {
revealCount++;
if (revealCount === otherCards.length) {
LK.setTimeout(function () {
startShuffleAnimation(card, otherCards, originalPositions);
}, 800); // Pause to let player see cards
}
});
});
}
function handleCardFlip(card) {
if (!canPlay || card.isFlipped || card.isMatched) {
return;
}
// Handle special cards immediately
if (card.pairId < 0) {
canPlay = false;
// If a normal card is already flipped, flip it back down before handling the special card
if (flippedCards.length === 1) {
var otherCard = flippedCards[0];
otherCard.flipDown(function () {
flippedCards = [];
card.flipUp(function () {
if (card.pairId === -1) {
handleDudCard(card);
} else if (card.pairId === -2) {
handleShifterCard(card);
}
});
});
} else {
flippedCards = []; // Clear any single-flipped normal card
card.flipUp(function () {
if (card.pairId === -1) {
handleDudCard(card);
} else if (card.pairId === -2) {
handleShifterCard(card);
}
});
}
return;
}
// Prevent flipping a third card
if (flippedCards.length >= 2) {
return;
}
flippedCards.push(card);
card.flipUp(function () {
if (flippedCards.length === 2) {
canPlay = false;
checkMatch();
}
});
}
function checkMatch() {
var card1 = flippedCards[0];
var card2 = flippedCards[1];
if (card1.pairId === card2.pairId) {
matchedPairs++;
streakCount++;
if (streakCount > maxStreak) {
maxStreak = streakCount;
}
// Calculate points based on streak
var points = 10;
var bonusPoints = 0;
if (streakCount === 2) {
points = 20;
bonusPoints = 10;
} else if (streakCount >= 3) {
points = 30;
bonusPoints = 20;
}
if (bonusPoints > 0) {
showStreakAnimation(bonusPoints);
}
LK.setScore(LK.getScore() + points);
scoreTxt.setText('Score: ' + LK.getScore());
LK.setTimeout(function () {
LK.getSound('match').play();
card1.matchFound();
card2.matchFound(function () {
resetTurn();
checkWinCondition();
});
}, 750);
} else {
// Reset streak on miss
streakCount = 0;
LK.setTimeout(function () {
card1.flipDown();
card2.flipDown(function () {
resetTurn();
});
}, 1000);
}
}
function resetTurn() {
flippedCards = [];
canPlay = true;
}
function shuffle(array) {
var currentIndex = array.length,
randomIndex;
while (currentIndex !== 0) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
var _ref = [array[randomIndex], array[currentIndex]];
array[currentIndex] = _ref[0];
array[randomIndex] = _ref[1];
}
return array;
}
function isLayoutValid(grid) {
for (var row = 0; row < ROWS; row++) {
for (var col = 0; col < COLS; col++) {
var currentPairId = grid[row][col];
// Check all 8 neighbors
for (var dr = -1; dr <= 1; dr++) {
for (var dc = -1; dc <= 1; dc++) {
if (dr === 0 && dc === 0) continue; // Don't check self
var neighborRow = row + dr;
var neighborCol = col + dc;
if (neighborRow >= 0 && neighborRow < ROWS && neighborCol >= 0 && neighborCol < COLS) {
if (grid[neighborRow][neighborCol] === currentPairId) {
return false; // Found an adjacent match
}
}
}
}
}
}
return true; // No adjacent matches found
}
// --- Board Setup ---
function setupBoard() {
var pairIds = [];
// Create a list of all available card face IDs (0 to TOTAL_CARD_FACES-1)
var allFaceIndices = [];
for (var i = 0; i < TOTAL_CARD_FACES; i++) {
allFaceIndices.push(i);
}
// Shuffle the available faces and select enough for the board
shuffle(allFaceIndices);
var gameFaceIndices = allFaceIndices.slice(0, TOTAL_PAIRS);
// Create pairs from the selected faces
for (var i = 0; i < TOTAL_PAIRS; i++) {
pairIds.push(gameFaceIndices[i], gameFaceIndices[i]);
}
// Add the special cards if enabled
if (storage.useSpecialCards === true) {
pairIds.push(-1); // Dud Card
pairIds.push(-2); // Shifter Card
}
var grid = [];
var attempts = 0;
var maxAttempts = 100; // Safeguard to prevent infinite loop
do {
shuffle(pairIds);
// Create a 2D grid from the shuffled IDs
for (var r = 0; r < ROWS; r++) {
grid[r] = [];
for (var c = 0; c < COLS; c++) {
grid[r][c] = pairIds[r * COLS + c];
}
}
attempts++;
} while (!isLayoutValid(grid) && attempts < maxAttempts);
// Create cards from the final grid layout
for (var row = 0; row < ROWS; row++) {
for (var col = 0; col < COLS; col++) {
var pairId = grid[row][col];
var card = new Card(pairId);
card.x = startX + col * (CARD_WIDTH + MARGIN);
card.y = startY + row * (CARD_HEIGHT + MARGIN);
cards.push(card);
gameContainer.addChild(card);
}
}
}
// --- Initial Call ---
showStartScreen();
a cartoon style 3d image of a snowman in a field with pine trees in the background. no people.. In-Game asset. 2d. High contrast. No shadows
a cartoon style 3d image of a cozy christmas living room with a cozy fire in a fireplace.. In-Game asset. 2d. High contrast. No shadows
a cartoon style 3d image of a memory match board game with some cards face up and some cards face down.. In-Game asset. 2d. High contrast. No shadows
a cartoon style 3d button that says Play.. In-Game asset. 2d. High contrast. No shadows
a cartoon style 3d button with the word Leader Board on it.. In-Game asset. 2d. High contrast. No shadows
A cartoon style 3d button with the words Match Maker all on 1 line. In-Game asset. 2d. High contrast. No shadows
a cartoon style 3d image of a green meadow with trees on the edges and a clearing in the middle.. In-Game asset. 2d. High contrast. No shadows
A tan dialog box background with a dark brown border.. In-Game asset. 2d. High contrast. No shadows. Cartoon style. 3D image
A table with the game Memory on it that got jostled so the card are flying in the air.. In-Game asset. 2d. High contrast. No shadows. Cartoon style. 3D image
Big TNT firecracker that is a dud with smoke wafting out of the fuse hole. In-Game asset. 2d. High contrast. No shadows. Cartoon style. 3D image
A small button that says Jumble. No shadow.