User prompt
cards slide to position ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
move piles to the right
User prompt
center the piles
User prompt
reduce card piles to 9
User prompt
fit cards to screen
User prompt
rotate screen horizontally
User prompt
rotate game screen horizontally
User prompt
put clicked card under related card automatically
User prompt
write card numbers bigger
User prompt
remove the word stock
User prompt
cardback brighter
User prompt
move deck near word sets
User prompt
move deck little left
User prompt
move word stock near the deck
User prompt
put word stock near deck
User prompt
put word stock below adjescent to deck
User prompt
put word stock below deck
User prompt
put open cards separate from top deck
User prompt
put spider icon on background
User prompt
set background to grey
User prompt
set background to white
User prompt
numbers on two corners
Code edit (1 edits merged)
Please save this source code
User prompt
Spider Solitaire: Web of Cards
Initial prompt
solitaire game with spider theme
/****
* 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
var cardFace = self.attachAsset('cardFace', {
anchorX: 0.5,
anchorY: 0.5
});
var cardBack = self.attachAsset('cardBack', {
anchorX: 0.5,
anchorY: 0.5
});
cardBack.visible = true;
cardFace.visible = false;
// Card label (rank/suit)
var label = new Text2('', {
size: 60,
fill: 0x222244
});
label.anchor.set(0.5, 0.5);
label.x = 0;
label.y = 0;
self.addChild(label);
// 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;
if (self.faceUp) {
// Show rank and suit
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;
// Only spades for MVP
label.setText(rankStr + ' ♠');
label.visible = true;
} else {
label.visible = false;
}
self.updateSelected();
};
// Visual feedback for selection
self.updateSelected = function () {
if (self.selected) {
cardFace.tint = 0x99ccff;
cardBack.tint = 0x99ccff;
} else {
cardFace.tint = 0xffffff;
cardBack.tint = 0x222244;
}
};
// 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 () {
for (var i = 0; i < self.cards.length; i++) {
var c = self.cards[i];
c.x = 0;
c.y = i * 60; // Overlap
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();
c.parent.removeChild(c);
removed.unshift(c);
}
self.layout();
return true;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181824
});
/****
* Game Code
****/
// Spider icon for completed set
// Card face (white for now, will overlay suit/rank)
// Card back (spider web themed)
// Game constants
var CARD_WIDTH = 180;
var CARD_HEIGHT = 260;
var TABLEAU_COUNT = 10;
var TABLEAU_SPACING = 32;
var TABLEAU_TOP = 320;
var TABLEAU_LEFT = (2048 - (TABLEAU_COUNT * CARD_WIDTH + (TABLEAU_COUNT - 1) * TABLEAU_SPACING)) / 2;
var STOCK_X = 2048 - 200;
var STOCK_Y = 120;
var COMPLETED_X = 200;
var COMPLETED_Y = 120;
// Game state
var tableauPiles = [];
var stockPile = null;
var completedSets = 0;
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;
// GUI
var scoreTxt = new Text2('0', {
size: 90,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var dealBtn = new Text2('Deal', {
size: 70,
fill: 0xFFFFFF
});
dealBtn.anchor.set(0.5, 0.5);
dealBtn.x = STOCK_X + CARD_WIDTH / 2;
dealBtn.y = STOCK_Y + CARD_HEIGHT + 60;
LK.gui.top.addChild(dealBtn);
// Completed sets display
var completedSpiders = [];
// Initialize tableau piles
for (var i = 0; i < TABLEAU_COUNT; i++) {
var pile = new TableauPile();
pile.x = TABLEAU_LEFT + i * (CARD_WIDTH + TABLEAU_SPACING);
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);
// 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();
// 54 cards to tableau (first 4 piles get 6, rest get 5)
for (var i = 0; i < TABLEAU_COUNT; i++) {
var count = i < 4 ? 6 : 5;
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();
canDeal = true;
}
function updateScore() {
scoreTxt.setText('Sets: ' + completedSets);
}
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 * 110;
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
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;
// Bring to front
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 * 60;
var pw = CARD_WIDTH;
var ph = CARD_HEIGHT;
if (x > px && x < px + pw && y > py - 60 && y < py + ph) {
// Can accept?
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) {
// 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]);
}
// Reveal top card in old pile
draggingFromPile.revealTop();
// Check for completed set
if (dragTargetPile.removeCompleteSet()) {
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 * 60 + CARD_HEIGHT / 2;
game.addChild(spider);
tween(spider, {
y: COMPLETED_Y,
x: COMPLETED_X + (completedSets - 1) * 110
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
if (spider.parent) spider.parent.removeChild(spider);
updateCompletedSpiders();
}
});
}
// 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 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;
// Animate deal
var dealt = stockPile.dealToTableau(tableauPiles);
if (dealt) {
// Flip top card of each pile
for (var i = 0; i < tableauPiles.length; i++) {
tableauPiles[i].topCard().flip(true);
}
}
// 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;
updateScore();
updateCompletedSpiders();
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 () {
// Check for game over
checkGameOver();
};
// Start game
dealInitial(); ===================================================================
--- original.js
+++ change.js
@@ -1,6 +1,547 @@
-/****
+/****
+* 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
+ var cardFace = self.attachAsset('cardFace', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ var cardBack = self.attachAsset('cardBack', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ cardBack.visible = true;
+ cardFace.visible = false;
+ // Card label (rank/suit)
+ var label = new Text2('', {
+ size: 60,
+ fill: 0x222244
+ });
+ label.anchor.set(0.5, 0.5);
+ label.x = 0;
+ label.y = 0;
+ self.addChild(label);
+ // 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;
+ if (self.faceUp) {
+ // Show rank and suit
+ 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;
+ // Only spades for MVP
+ label.setText(rankStr + ' ♠');
+ label.visible = true;
+ } else {
+ label.visible = false;
+ }
+ self.updateSelected();
+ };
+ // Visual feedback for selection
+ self.updateSelected = function () {
+ if (self.selected) {
+ cardFace.tint = 0x99ccff;
+ cardBack.tint = 0x99ccff;
+ } else {
+ cardFace.tint = 0xffffff;
+ cardBack.tint = 0x222244;
+ }
+ };
+ // 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 () {
+ for (var i = 0; i < self.cards.length; i++) {
+ var c = self.cards[i];
+ c.x = 0;
+ c.y = i * 60; // Overlap
+ 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();
+ c.parent.removeChild(c);
+ removed.unshift(c);
+ }
+ self.layout();
+ return true;
+ };
+ return self;
+});
+
+/****
* Initialize Game
-****/
+****/
var game = new LK.Game({
- backgroundColor: 0x000000
-});
\ No newline at end of file
+ backgroundColor: 0x181824
+});
+
+/****
+* Game Code
+****/
+// Spider icon for completed set
+// Card face (white for now, will overlay suit/rank)
+// Card back (spider web themed)
+// Game constants
+var CARD_WIDTH = 180;
+var CARD_HEIGHT = 260;
+var TABLEAU_COUNT = 10;
+var TABLEAU_SPACING = 32;
+var TABLEAU_TOP = 320;
+var TABLEAU_LEFT = (2048 - (TABLEAU_COUNT * CARD_WIDTH + (TABLEAU_COUNT - 1) * TABLEAU_SPACING)) / 2;
+var STOCK_X = 2048 - 200;
+var STOCK_Y = 120;
+var COMPLETED_X = 200;
+var COMPLETED_Y = 120;
+// Game state
+var tableauPiles = [];
+var stockPile = null;
+var completedSets = 0;
+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;
+// GUI
+var scoreTxt = new Text2('0', {
+ size: 90,
+ fill: 0xFFFFFF
+});
+scoreTxt.anchor.set(0.5, 0);
+LK.gui.top.addChild(scoreTxt);
+var dealBtn = new Text2('Deal', {
+ size: 70,
+ fill: 0xFFFFFF
+});
+dealBtn.anchor.set(0.5, 0.5);
+dealBtn.x = STOCK_X + CARD_WIDTH / 2;
+dealBtn.y = STOCK_Y + CARD_HEIGHT + 60;
+LK.gui.top.addChild(dealBtn);
+// Completed sets display
+var completedSpiders = [];
+// Initialize tableau piles
+for (var i = 0; i < TABLEAU_COUNT; i++) {
+ var pile = new TableauPile();
+ pile.x = TABLEAU_LEFT + i * (CARD_WIDTH + TABLEAU_SPACING);
+ 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);
+// 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();
+ // 54 cards to tableau (first 4 piles get 6, rest get 5)
+ for (var i = 0; i < TABLEAU_COUNT; i++) {
+ var count = i < 4 ? 6 : 5;
+ 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();
+ canDeal = true;
+}
+function updateScore() {
+ scoreTxt.setText('Sets: ' + completedSets);
+}
+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 * 110;
+ 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
+ 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;
+ // Bring to front
+ 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 * 60;
+ var pw = CARD_WIDTH;
+ var ph = CARD_HEIGHT;
+ if (x > px && x < px + pw && y > py - 60 && y < py + ph) {
+ // Can accept?
+ 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) {
+ // 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]);
+ }
+ // Reveal top card in old pile
+ draggingFromPile.revealTop();
+ // Check for completed set
+ if (dragTargetPile.removeCompleteSet()) {
+ 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 * 60 + CARD_HEIGHT / 2;
+ game.addChild(spider);
+ tween(spider, {
+ y: COMPLETED_Y,
+ x: COMPLETED_X + (completedSets - 1) * 110
+ }, {
+ duration: 800,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ if (spider.parent) spider.parent.removeChild(spider);
+ updateCompletedSpiders();
+ }
+ });
+ }
+ // 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 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;
+ // Animate deal
+ var dealt = stockPile.dealToTableau(tableauPiles);
+ if (dealt) {
+ // Flip top card of each pile
+ for (var i = 0; i < tableauPiles.length; i++) {
+ tableauPiles[i].topCard().flip(true);
+ }
+ }
+ // 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;
+ updateScore();
+ updateCompletedSpiders();
+ 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 () {
+ // Check for game over
+ checkGameOver();
+};
+// Start game
+dealInitial();
\ No newline at end of file