User prompt
collect cardsd
User prompt
level restart with pause
User prompt
put little space between pause and restart
User prompt
Please fix the bug: 'dealBtn is not defined' in or related to this line: 'dealBtn.down = function (x, y, obj) {' Line Number: 935
User prompt
remove deal button on top right
User prompt
Please fix the bug: 'Cannot read properties of null (reading 'cards')' in or related to this line: 'dealBtn.y = GAME_HEIGHT - CARD_HEIGHT / 2 - 40 - (Math.floor(stockPile.cards.length / TABLEAU_COUNT) - 1) * 6 - 30;' Line Number: 606
User prompt
put deal button on top of stack count
User prompt
collect finished
User prompt
show stackcount near deal button
User prompt
show stack count on deal button
User prompt
move stack count to the left of deal buttun
User prompt
put stack count near deal button
User prompt
put stack count on top right
User prompt
put resart on top lesft
User prompt
put restart near pause button
User prompt
Please fix the bug: 'Uncaught ReferenceError: undoStack is not defined' in or related to this line: 'if (undoStack.length > 50) {' Line Number: 525
User prompt
collect sets automatically
User prompt
move stack count to center bottom
User prompt
put stack count over stack button
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Card class
var Card = Container.expand(function () {
var self = Container.call(this);
// Card properties
self.rank = 1; // 1=Ace, 13=King
self.suit = 0; // 0=spades (for MVP, only one suit)
self.faceUp = false;
self.selected = false;
// Card graphics
// Add a drop shadow and rounded rectangle background for realism
// Scale card assets to fit calculated CARD_WIDTH and CARD_HEIGHT
var scaleX = typeof CARD_WIDTH !== "undefined" ? CARD_WIDTH / BASE_CARD_WIDTH : 1;
var scaleY = typeof CARD_HEIGHT !== "undefined" ? CARD_HEIGHT / BASE_CARD_HEIGHT : 1;
var cardShadow = LK.getAsset('cardFace', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x222222,
alpha: 0.18,
scaleX: 1.04 * scaleX,
scaleY: 1.04 * scaleY
});
self.addChild(cardShadow);
var cardFace = self.attachAsset('cardFace', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: scaleX,
scaleY: scaleY
});
var cardBack = self.attachAsset('cardBack', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: scaleX,
scaleY: scaleY
});
cardBack.visible = true;
cardFace.visible = false;
// Corner labels (rank + suit) for top-left and bottom-right
var labelTL = new Text2('', {
size: Math.floor(cardFace.height * 0.22),
fill: 0xffffff,
align: "left"
});
labelTL.anchor.set(0, 0);
labelTL.x = -cardFace.width / 2 + Math.floor(cardFace.width * 0.08);
labelTL.y = -cardFace.height / 2 + Math.floor(cardFace.height * 0.08);
self.addChild(labelTL);
var labelBR = new Text2('', {
size: Math.floor(cardFace.height * 0.22),
fill: 0xffffff,
align: "right"
});
labelBR.anchor.set(1, 1);
labelBR.x = cardFace.width / 2 - Math.floor(cardFace.width * 0.08);
labelBR.y = cardFace.height / 2 - Math.floor(cardFace.height * 0.08);
self.addChild(labelBR);
// Set card data
self.setCard = function (rank, suit, faceUp) {
self.rank = rank;
self.suit = suit;
self.faceUp = faceUp;
self.updateFace();
};
// Update card face/back and label
self.updateFace = function () {
cardFace.visible = self.faceUp;
cardBack.visible = !self.faceUp;
// Only spades for MVP
var suitChar = '♠';
var rankStr = '';
if (self.rank === 1) rankStr = 'A';else if (self.rank === 11) rankStr = 'J';else if (self.rank === 12) rankStr = 'Q';else if (self.rank === 13) rankStr = 'K';else rankStr = '' + self.rank;
// Show only in corners
if (typeof labelTL !== "undefined" && typeof labelBR !== "undefined") {
var labelText = rankStr + suitChar;
labelTL.setText(labelText);
labelBR.setText(labelText);
labelTL.visible = true;
labelBR.visible = true;
if (self.faceUp) {
labelTL.setStyle({
fill: 0xffffff,
alpha: 1
});
labelBR.setStyle({
fill: 0xffffff,
alpha: 1
});
} else {
labelTL.setStyle({
fill: 0xcccccc,
alpha: 0.45
});
labelBR.setStyle({
fill: 0xcccccc,
alpha: 0.45
});
}
}
// Optionally, show a faint spider on the back for realism
self.updateSelected();
};
// Visual feedback for selection
self.updateSelected = function () {
if (self.selected) {
cardFace.tint = 0x99ccff;
cardBack.tint = 0x99ccff;
} else {
cardFace.tint = 0xffffff;
cardBack.tint = 0xffffff;
}
};
// Flip card
self.flip = function (faceUp) {
self.faceUp = faceUp;
self.updateFace();
};
// Touch events
self.down = function (x, y, obj) {
if (self.faceUp) {
// Let game handle selection
if (game) game.onCardDown(self, x, y, obj);
}
};
return self;
});
// Stock pile class (for dealing new rows)
var StockPile = Container.expand(function () {
var self = Container.call(this);
self.cards = [];
// Add card to stock
self.addCard = function (card) {
self.cards.push(card);
self.addChild(card);
card.x = 0;
card.y = 0;
};
// Deal one card per tableau pile
self.dealToTableau = function (tableauPiles) {
if (self.cards.length < tableauPiles.length) return false;
for (var i = 0; i < tableauPiles.length; i++) {
var card = self.cards.shift();
card.flip(true);
tableauPiles[i].addCard(card);
}
return true;
};
// Is empty?
self.isEmpty = function () {
return self.cards.length === 0;
};
return self;
});
// Tableau pile class
var TableauPile = Container.expand(function () {
var self = Container.call(this);
self.cards = [];
// Add card to pile
self.addCard = function (card) {
self.cards.push(card);
self.addChild(card);
self.layout();
};
// Remove card(s) from pile starting at index
self.removeCardsFrom = function (idx) {
var removed = [];
while (self.cards.length > idx) {
var c = self.cards.pop();
c.parent.removeChild(c);
removed.unshift(c);
}
self.layout();
return removed;
};
// Get top card
self.topCard = function () {
if (self.cards.length === 0) return null;
return self.cards[self.cards.length - 1];
};
// Layout cards in pile
self.layout = function () {
// Increase vertical spacing between cards for more space in each column
var CARD_VERTICAL_SPACING = 110;
for (var i = 0; i < self.cards.length; i++) {
var c = self.cards[i];
// Animate to new position using tween
tween.stop(c, {
x: true,
y: true
}); // Stop any previous tweens on x/y
tween(c, {
x: 0,
y: i * CARD_VERTICAL_SPACING
}, {
duration: 400,
easing: tween.cubicOut
});
c.zIndex = i;
}
};
// Reveal top card if needed
self.revealTop = function () {
var top = self.topCard();
if (top && !top.faceUp) {
top.flip(true);
}
};
// Get index of card in pile
self.indexOf = function (card) {
for (var i = 0; i < self.cards.length; i++) {
if (self.cards[i] === card) return i;
}
return -1;
};
// Can move sequence starting at idx?
self.canMoveSequence = function (idx) {
// All cards from idx to end must be face up and in descending order
for (var i = idx; i < self.cards.length - 1; i++) {
var c1 = self.cards[i];
var c2 = self.cards[i + 1];
if (!c1.faceUp || !c2.faceUp) return false;
if (c1.rank !== c2.rank + 1) return false;
}
return self.cards[idx].faceUp;
};
// Can accept a sequence of cards (cardsArr)?
self.canAccept = function (cardsArr) {
if (self.cards.length === 0) {
// Only King can be placed on empty pile
return cardsArr[0].rank === 13;
} else {
var top = self.topCard();
var moving = cardsArr[0];
return top.faceUp && top.rank === moving.rank + 1;
}
};
// Remove completed set (K-A)
self.removeCompleteSet = function () {
// Check if last 13 cards are a complete set
if (self.cards.length < 13) return false;
for (var i = 0; i < 13; i++) {
var c = self.cards[self.cards.length - 1 - i];
if (!c.faceUp || c.rank !== 13 - i) return false;
}
// Remove them
var removed = [];
for (var i = 0; i < 13; i++) {
var c = self.cards.pop();
if (c.parent) c.parent.removeChild(c);
removed.unshift(c);
}
self.layout();
// Add to collected area (handled by animation in game logic)
return removed; // Return the collected set for animation/cleanup
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xcccccc
});
/****
* Game Code
****/
// (Removed stock label)
// Show number of stacks left as card decks on stock pile
var stockDecks = [];
function updateStockDecks() {
// Remove old
for (var i = 0; i < stockDecks.length; i++) {
if (stockDecks[i].parent) stockDecks[i].parent.removeChild(stockDecks[i]);
}
stockDecks = [];
// Each "stack" is 10 cards (8 stacks of 10 = 80, but Spider uses 50 in stock: 5 stacks of 10)
var stacksLeft = Math.floor(stockPile.cards.length / TABLEAU_COUNT);
for (var i = 0; i < stacksLeft; i++) {
var deckImg = LK.getAsset('cardBack', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: CARD_WIDTH / BASE_CARD_WIDTH * 0.9,
scaleY: CARD_HEIGHT / BASE_CARD_HEIGHT * 0.9
});
// Center bottom: horizontally centered, stacked with slight offset
var centerX = GAME_WIDTH / 2;
var bottomY = GAME_HEIGHT - CARD_HEIGHT / 2 - 40;
deckImg.x = centerX + (i - (stacksLeft - 1) / 2) * 24;
deckImg.y = bottomY - i * 6;
// Do not blur or fade the stack count
deckImg.alpha = 1;
game.addChild(deckImg);
stockDecks.push(deckImg);
}
}
// Add a large faded spider icon to the background, centered and scaled for portrait
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
// Spider background: scale to fit width, center
var backgroundSpider = LK.getAsset('spider', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 10,
scaleY: 10
});
backgroundSpider.x = GAME_WIDTH / 2;
backgroundSpider.y = GAME_HEIGHT / 2;
backgroundSpider.alpha = 0.08;
game.addChildAt(backgroundSpider, 0); // Ensure it's behind all other elements
// Card and layout constants for portrait
var TABLEAU_COUNT = 8;
var GAME_MARGIN_X = 40;
var GAME_MARGIN_Y = 120;
// Get original card asset size
var tempCard = LK.getAsset('cardFace', {
anchorX: 0.5,
anchorY: 0.5
});
var BASE_CARD_WIDTH = tempCard.width;
var BASE_CARD_HEIGHT = tempCard.height;
// Calculate max card width to fit 8 columns with margin
var availableWidth = GAME_WIDTH - 2 * GAME_MARGIN_X;
var CARD_WIDTH = Math.floor(availableWidth / TABLEAU_COUNT);
var CARD_HEIGHT = Math.floor(BASE_CARD_HEIGHT * (CARD_WIDTH / BASE_CARD_WIDTH));
// No spacing between columns
var TABLEAU_SPACING = 0;
// Top offset for vertical layout (fit 2 rows of cards + margin)
var TABLEAU_TOP = GAME_MARGIN_Y + CARD_HEIGHT + 40;
// Center columns horizontally
var totalTableauWidth = TABLEAU_COUNT * CARD_WIDTH + (TABLEAU_COUNT - 1) * TABLEAU_SPACING;
var TABLEAU_LEFT = Math.round((GAME_WIDTH - totalTableauWidth) / 2);
// Stock and completed set positions (fit to portrait)
var STOCK_X = GAME_WIDTH - CARD_WIDTH - GAME_MARGIN_X;
var STOCK_Y = GAME_MARGIN_Y;
var COMPLETED_X = GAME_MARGIN_X + CARD_WIDTH / 2;
var COMPLETED_Y = GAME_MARGIN_Y;
// Clean up tempCard
if (tempCard.parent) tempCard.parent.removeChild(tempCard);
// Game state
var tableauPiles = [];
var stockPile = null;
var completedSets = 0;
var movesCount = 0; // Moves counter
var draggingCards = null;
var draggingFromPile = null;
var dragOffsetX = 0;
var dragOffsetY = 0;
var dragStartX = 0;
var dragStartY = 0;
var dragValidTarget = null;
var dragTargetPile = null;
var canDeal = true;
// Undo stack
var undoStack = [];
// Helper: auto-collect completed sets from all tableau piles
function autoCollectSets() {
var collected = false;
for (var auto_i = 0; auto_i < tableauPiles.length; auto_i++) {
var pile = tableauPiles[auto_i];
var removed;
while (removed = pile.removeCompleteSet()) {
LK.getSound('card_set').play();
completedSets += 1;
updateScore();
updateCompletedSpiders();
var spider = LK.getAsset('spider', {
anchorX: 0.5,
anchorY: 0.5
});
spider.x = pile.x + CARD_WIDTH / 2;
spider.y = pile.y + pile.cards.length * 40 + CARD_HEIGHT / 2;
game.addChild(spider);
(function (spider, setIndex, removedCards) {
tween(spider, {
y: COMPLETED_Y,
x: COMPLETED_X + setIndex * (CARD_WIDTH * 0.7)
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
if (spider.parent) spider.parent.removeChild(spider);
updateCompletedSpiders();
// Remove finished set cards from game
for (var rc = 0; rc < removedCards.length; rc++) {
if (removedCards[rc].parent) removedCards[rc].parent.removeChild(removedCards[rc]);
}
}
});
})(spider, completedSets - 1, removed);
collected = true;
}
}
// Win condition: 8 sets
if (completedSets >= 8) {
LK.showYouWin();
return;
}
}
// Helper: clone game state for undo
function cloneGameState() {
// Deep copy of tableau piles, stock, completedSets, movesCount
var tableau = [];
for (var i = 0; i < tableauPiles.length; i++) {
var pile = tableauPiles[i];
var pileArr = [];
for (var j = 0; j < pile.cards.length; j++) {
var c = pile.cards[j];
pileArr.push({
rank: c.rank,
suit: c.suit,
faceUp: c.faceUp
});
}
tableau.push(pileArr);
}
var stock = [];
for (var i = 0; i < stockPile.cards.length; i++) {
var c = stockPile.cards[i];
stock.push({
rank: c.rank,
suit: c.suit,
faceUp: c.faceUp
});
}
return {
tableau: tableau,
stock: stock,
completedSets: completedSets,
movesCount: movesCount
};
}
// Helper: restore game state from undo
function restoreGameState(state) {
// Remove all cards
for (var i = 0; i < tableauPiles.length; i++) {
var pile = tableauPiles[i];
while (pile.cards.length > 0) {
var c = pile.cards.pop();
if (c.parent) c.parent.removeChild(c);
}
}
while (stockPile.cards.length > 0) {
var c = stockPile.cards.pop();
if (c.parent) c.parent.removeChild(c);
}
// Restore tableau
for (var i = 0; i < tableauPiles.length; i++) {
var pileArr = state.tableau[i];
for (var j = 0; j < pileArr.length; j++) {
var cdata = pileArr[j];
var card = new Card();
card.setCard(cdata.rank, cdata.suit, cdata.faceUp);
tableauPiles[i].addCard(card);
}
}
// Restore stock
for (var i = 0; i < state.stock.length; i++) {
var cdata = state.stock[i];
var card = new Card();
card.setCard(cdata.rank, cdata.suit, cdata.faceUp);
stockPile.addCard(card);
}
completedSets = state.completedSets;
movesCount = state.movesCount;
updateScore();
updateCompletedSpiders();
canDeal = true;
updateStockDecks();
draggingCards = null;
draggingFromPile = null;
dragValidTarget = null;
dragTargetPile = null;
}
// Push undo state
function pushUndoState() {
// Limit undo stack to 50
if (undoStack.length > 50) undoStack.shift();
undoStack.push(cloneGameState());
}
// GUI
var scoreTxt = new Text2('0', {
size: 90,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var movesTxt = new Text2('Moves: 0', {
size: 70,
fill: 0xffffff
});
movesTxt.anchor.set(0.5, 0);
// Place moves counter under the sets counter (scoreTxt)
movesTxt.x = GAME_WIDTH / 2;
movesTxt.y = scoreTxt.y + scoreTxt.height + 10;
LK.gui.top.addChild(movesTxt);
// Undo button (now shown as an arrow icon)
var undoBtn = new Text2('↶', {
size: 180,
fill: 0xffffff
});
undoBtn.anchor.set(1, 1);
// Place at the bottom right corner of the screen (using LK.gui.bottomRight)
undoBtn.x = -40;
undoBtn.y = -40;
LK.gui.bottomRight.addChild(undoBtn);
// Moves counter next to undo button
var movesTxtBR = new Text2('0', {
size: 70,
fill: 0xffffff
});
movesTxtBR.anchor.set(1, 1);
// Place to the left of the undo button, aligned at bottom right
movesTxtBR.x = undoBtn.x - 120;
movesTxtBR.y = undoBtn.y - 10;
LK.gui.bottomRight.addChild(movesTxtBR);
// Undo button handler (moved here after undoBtn is defined)
undoBtn.down = function (x, y, obj) {
if (undoStack.length === 0) return;
var prev = undoStack.pop();
// When redoing, do not decrease movesCount; keep current movesCount
var currentMoves = movesCount;
restoreGameState(prev);
// Restore everything except movesCount (keep current)
movesCount = currentMoves;
updateScore();
};
// Restart button (shown as a circular arrow)
var restartBtn = new Text2('⟳', {
size: 120,
fill: 0xffffff
});
restartBtn.anchor.set(0, 0);
// Place at the top left corner of the screen (using LK.gui.topLeft)
// Add extra space to the right of the pause button (assume pauseBtn is at x=40, width=80, so start at 40+80+24=144)
restartBtn.x = 144; // Increased offset for more space between pause and restart
restartBtn.y = 40; // Down from top edge, visually aligned
LK.gui.topLeft.addChild(restartBtn);
restartBtn.down = function (x, y, obj) {
resetGame();
};
// Removed dealBtn from top right
// Completed sets display
var completedSpiders = [];
// Initialize tableau piles
for (var i = 0; i < TABLEAU_COUNT; i++) {
var pile = new TableauPile();
// Align piles evenly across the screen, centered
pile.x = TABLEAU_LEFT + i * (CARD_WIDTH + TABLEAU_SPACING) + CARD_WIDTH / 2;
pile.y = TABLEAU_TOP;
game.addChild(pile);
tableauPiles.push(pile);
}
// Initialize stock pile
stockPile = new StockPile();
stockPile.x = STOCK_X;
stockPile.y = STOCK_Y;
game.addChild(stockPile);
// (Removed stock label)
// Shuffle and deal cards
function shuffleDeck() {
// 8 decks of spades (104 cards)
var deck = [];
for (var d = 0; d < 8; d++) {
for (var r = 1; r <= 13; r++) {
var card = new Card();
card.setCard(r, 0, false);
deck.push(card);
}
}
// Fisher-Yates shuffle
for (var i = deck.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var tmp = deck[i];
deck[i] = deck[j];
deck[j] = tmp;
}
return deck;
}
function dealInitial() {
var deck = shuffleDeck();
// 52 cards to tableau (first 4 piles get 7, rest get 6)
for (var i = 0; i < TABLEAU_COUNT; i++) {
var count = i < 4 ? 7 : 6;
for (var j = 0; j < count; j++) {
var card = deck.shift();
tableauPiles[i].addCard(card);
}
}
// Flip top card of each pile
for (var i = 0; i < TABLEAU_COUNT; i++) {
tableauPiles[i].topCard().flip(true);
}
// Remaining 50 cards to stock
while (deck.length > 0) {
var card = deck.shift();
stockPile.addCard(card);
}
completedSets = 0;
updateScore();
updateCompletedSpiders();
updateStockDecks();
canDeal = true;
}
function updateScore() {
scoreTxt.setText('Sets: ' + completedSets);
if (typeof movesTxt !== "undefined") {
movesTxt.setText('Moves: ' + movesCount);
}
if (typeof movesTxtBR !== "undefined") {
movesTxtBR.setText(movesCount + '');
}
}
function updateCompletedSpiders() {
// Remove old
for (var i = 0; i < completedSpiders.length; i++) {
if (completedSpiders[i].parent) completedSpiders[i].parent.removeChild(completedSpiders[i]);
}
completedSpiders = [];
// Add new
for (var i = 0; i < completedSets; i++) {
var spider = LK.getAsset('spider', {
anchorX: 0.5,
anchorY: 0.5
});
spider.x = COMPLETED_X + i * (CARD_WIDTH * 0.7);
spider.y = COMPLETED_Y;
game.addChild(spider);
completedSpiders.push(spider);
}
}
// Handle card selection and drag
game.onCardDown = function (card, x, y, obj) {
// Find which pile this card is in
var pile = null;
var idx = -1;
for (var i = 0; i < tableauPiles.length; i++) {
var p = tableauPiles[i];
var j = p.indexOf(card);
if (j !== -1) {
pile = p;
idx = j;
break;
}
}
if (!pile) return;
// Can move sequence?
if (!pile.canMoveSequence(idx)) return;
// Prepare drag
pushUndoState();
draggingCards = [];
for (var k = idx; k < pile.cards.length; k++) {
var c = pile.cards[k];
c.selected = true;
c.updateSelected();
draggingCards.push(c);
}
draggingFromPile = pile;
// Try to auto-move to a valid pile
var autoMoved = false;
for (var t = 0; t < tableauPiles.length; t++) {
var targetPile = tableauPiles[t];
if (targetPile === pile) continue;
if (targetPile.canAccept(draggingCards)) {
// Remove from old pile
pile.removeCardsFrom(idx);
// Add to new pile
for (var m = 0; m < draggingCards.length; m++) {
targetPile.addCard(draggingCards[m]);
}
// Play card move sound
LK.getSound('card_move').play();
// Reveal top card in old pile
pile.revealTop();
// Check for completed set
var completedAny = false;
while (targetPile.removeCompleteSet()) {
// Play card set sound
LK.getSound('card_set').play();
completedSets += 1;
updateScore();
updateCompletedSpiders();
// Animate spider
var spider = LK.getAsset('spider', {
anchorX: 0.5,
anchorY: 0.5
});
spider.x = targetPile.x + CARD_WIDTH / 2;
spider.y = targetPile.y + targetPile.cards.length * 40 + CARD_HEIGHT / 2;
game.addChild(spider);
(function (spider, setIndex) {
tween(spider, {
y: COMPLETED_Y,
x: COMPLETED_X + setIndex * (CARD_WIDTH * 0.7)
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
if (spider.parent) spider.parent.removeChild(spider);
updateCompletedSpiders();
}
});
})(spider, completedSets - 1);
completedAny = true;
}
autoCollectSets();
// Win condition: 8 sets
if (completedSets >= 8) {
LK.showYouWin();
return;
}
// Deselect
for (var m = 0; m < draggingCards.length; m++) {
draggingCards[m].selected = false;
draggingCards[m].updateSelected();
}
movesCount += 1; // Increment moves on auto-move
updateScore();
draggingCards = null;
draggingFromPile = null;
dragValidTarget = null;
dragTargetPile = null;
autoMoved = true;
break;
}
}
if (autoMoved) return;
// Bring to front for manual drag
for (var k = 0; k < draggingCards.length; k++) {
game.addChild(draggingCards[k]);
}
// Offset
dragOffsetX = card.x;
dragOffsetY = card.y;
dragStartX = card.parent.x + card.x;
dragStartY = card.parent.y + card.y;
};
// Handle drag move
game.move = function (x, y, obj) {
if (!draggingCards) return;
// Move cards
for (var i = 0; i < draggingCards.length; i++) {
draggingCards[i].x = x - dragOffsetX;
draggingCards[i].y = y - dragOffsetY + i * 60;
}
// Check for valid drop target
dragValidTarget = null;
dragTargetPile = null;
for (var i = 0; i < tableauPiles.length; i++) {
var pile = tableauPiles[i];
// Don't allow drop on self
if (pile === draggingFromPile) continue;
// Get pile bounds
var px = pile.x;
var py = pile.y + pile.cards.length * 40;
var pw = CARD_WIDTH;
var ph = CARD_HEIGHT;
// If pile is empty, allow drop anywhere in the pile area
if (pile.cards.length === 0) {
px = pile.x;
py = pile.y;
pw = CARD_WIDTH;
ph = CARD_HEIGHT;
if (x > px && x < px + pw && y > py && y < py + ph) {
if (pile.canAccept(draggingCards)) {
dragValidTarget = pile;
dragTargetPile = pile;
break;
}
}
} else {
if (x > px && x < px + pw && y > py - 40 && y < py + ph) {
if (pile.canAccept(draggingCards)) {
dragValidTarget = pile;
dragTargetPile = pile;
break;
}
}
}
}
};
// Handle drag end
game.up = function (x, y, obj) {
if (!draggingCards) return;
// Drop
if (dragValidTarget && dragTargetPile) {
pushUndoState();
// Remove from old pile
var idx = draggingFromPile.indexOf(draggingCards[0]);
draggingFromPile.removeCardsFrom(idx);
// Add to new pile
for (var i = 0; i < draggingCards.length; i++) {
dragTargetPile.addCard(draggingCards[i]);
}
// Play card move sound
LK.getSound('card_move').play();
movesCount += 1; // Increment moves on manual drag
updateScore();
// Reveal top card in old pile
draggingFromPile.revealTop();
// Check for completed set
var completedAny = false;
while (dragTargetPile.removeCompleteSet()) {
// Play card set sound
LK.getSound('card_set').play();
completedSets += 1;
updateScore();
updateCompletedSpiders();
// Animate spider
var spider = LK.getAsset('spider', {
anchorX: 0.5,
anchorY: 0.5
});
spider.x = dragTargetPile.x + CARD_WIDTH / 2;
spider.y = dragTargetPile.y + dragTargetPile.cards.length * 40 + CARD_HEIGHT / 2;
game.addChild(spider);
(function (spider, setIndex) {
tween(spider, {
y: COMPLETED_Y,
x: COMPLETED_X + setIndex * (CARD_WIDTH * 0.7)
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
if (spider.parent) spider.parent.removeChild(spider);
updateCompletedSpiders();
}
});
})(spider, completedSets - 1);
completedAny = true;
}
autoCollectSets();
// Win condition: 8 sets
if (completedSets >= 8) {
LK.showYouWin();
return;
}
} else {
// Return to original pile
for (var i = 0; i < draggingCards.length; i++) {
draggingCards[i].x = dragStartX - draggingFromPile.x;
draggingCards[i].y = dragStartY - draggingFromPile.y + i * 60;
draggingFromPile.addChild(draggingCards[i]);
}
draggingFromPile.layout();
}
// Deselect
for (var i = 0; i < draggingCards.length; i++) {
draggingCards[i].selected = false;
draggingCards[i].updateSelected();
}
draggingCards = null;
draggingFromPile = null;
dragValidTarget = null;
dragTargetPile = null;
};
// Deal button (shown above the stock pile decks)
var dealBtn = new Text2('Deal', {
size: 90,
fill: 0xffffff
});
dealBtn.anchor.set(0.5, 1);
// Position: above the stock pile decks, centered horizontally
dealBtn.x = GAME_WIDTH / 2;
dealBtn.y = GAME_HEIGHT - CARD_HEIGHT / 2 - 40 - 30;
game.addChild(dealBtn);
// Deal new row
dealBtn.down = function (x, y, obj) {
if (!canDeal) return;
// Only allow if all tableau piles have at least one card
for (var i = 0; i < tableauPiles.length; i++) {
if (tableauPiles[i].cards.length === 0) return;
}
if (stockPile.isEmpty()) return;
canDeal = false;
pushUndoState();
// Animate deal
var dealt = stockPile.dealToTableau(tableauPiles);
if (dealt) {
// Play shuffle sound when dealing from stock
LK.getSound('shuffle').play();
// Flip top card of each pile
for (var i = 0; i < tableauPiles.length; i++) {
tableauPiles[i].topCard().flip(true);
}
updateStockDecks();
autoCollectSets();
}
// Allow next deal after short delay
LK.setTimeout(function () {
canDeal = true;
}, 400);
};
// Touch on stock pile (deal)
stockPile.down = function (x, y, obj) {
dealBtn.down(x, y, obj);
};
// Reset game
function resetGame() {
// Remove all cards
for (var i = 0; i < tableauPiles.length; i++) {
var pile = tableauPiles[i];
while (pile.cards.length > 0) {
var c = pile.cards.pop();
if (c.parent) c.parent.removeChild(c);
}
}
while (stockPile.cards.length > 0) {
var c = stockPile.cards.pop();
if (c.parent) c.parent.removeChild(c);
}
completedSets = 0;
movesCount = 0; // Reset moves counter
updateScore();
updateCompletedSpiders();
updateStockDecks();
canDeal = true;
draggingCards = null;
draggingFromPile = null;
dragValidTarget = null;
dragTargetPile = null;
dealInitial();
}
// Game over: no more moves
function checkGameOver() {
// If no moves and stock is empty
var canMove = false;
for (var i = 0; i < tableauPiles.length; i++) {
var pile = tableauPiles[i];
for (var j = 0; j < pile.cards.length; j++) {
if (pile.canMoveSequence(j)) {
// Try to move to another pile
var seq = [];
for (var k = j; k < pile.cards.length; k++) seq.push(pile.cards[k]);
for (var t = 0; t < tableauPiles.length; t++) {
if (t === i) continue;
if (tableauPiles[t].canAccept(seq)) {
canMove = true;
break;
}
}
}
if (canMove) break;
}
if (canMove) break;
}
if (!canMove && stockPile.isEmpty()) {
LK.showGameOver();
}
}
// Game update
game.update = function () {
// (auto-collect logic moved to after every move, deal, and flip)
// Check for game over
checkGameOver();
};
// Start game
dealInitial();
LK.playMusic('relaxing_bg'); ===================================================================
--- original.js
+++ change.js
@@ -251,13 +251,14 @@
// Remove them
var removed = [];
for (var i = 0; i < 13; i++) {
var c = self.cards.pop();
- c.parent.removeChild(c);
+ if (c.parent) c.parent.removeChild(c);
removed.unshift(c);
}
self.layout();
- return true;
+ // Add to collected area (handled by animation in game logic)
+ return removed; // Return the collected set for animation/cleanup
};
return self;
});
@@ -270,10 +271,10 @@
/****
* Game Code
****/
-// Show number of stacks left as card decks on stock pile
// (Removed stock label)
+// Show number of stacks left as card decks on stock pile
var stockDecks = [];
function updateStockDecks() {
// Remove old
for (var i = 0; i < stockDecks.length; i++) {
@@ -363,9 +364,10 @@
function autoCollectSets() {
var collected = false;
for (var auto_i = 0; auto_i < tableauPiles.length; auto_i++) {
var pile = tableauPiles[auto_i];
- while (pile.removeCompleteSet()) {
+ var removed;
+ while (removed = pile.removeCompleteSet()) {
LK.getSound('card_set').play();
completedSets += 1;
updateScore();
updateCompletedSpiders();
@@ -541,13 +543,9 @@
restartBtn.x = 144; // Increased offset for more space between pause and restart
restartBtn.y = 40; // Down from top edge, visually aligned
LK.gui.topLeft.addChild(restartBtn);
restartBtn.down = function (x, y, obj) {
- LK.pauseGame();
- LK.setTimeout(function () {
- resetGame();
- LK.resumeGame && LK.resumeGame();
- }, 300);
+ resetGame();
};
// Removed dealBtn from top right
// Completed sets display
var completedSpiders = [];