User prompt
Please fix the bug: 'Timeout.tick error: Cannot read properties of undefined (reading 'length')' in or related to this line: 'if (game.deck.length === 0) {' Line Number: 70
User prompt
Please fix the bug: 'Timeout.tick error: Cannot read properties of undefined (reading 'length')' in or related to this line: 'var topDiscard = game.discardPile.length > 0 ? game.discardPile[game.discardPile.length - 1] : null;' Line Number: 39
User prompt
Please fix the bug: 'Cannot set properties of undefined (setting 'fill')' in or related to this line: 'infoText.style.fill = color || "#fff";' Line Number: 387
User prompt
Please fix the bug: 'Cannot set properties of undefined (setting 'fill')' in or related to this line: 'rankText.style.fill = "#222";' Line Number: 165
User prompt
Please fix the bug: 'Cannot set properties of undefined (setting 'fill')' in or related to this line: 'if (typeof rankText.setFill === "function") {' Line Number: 157
User prompt
Please fix the bug: 'Cannot set properties of undefined (setting 'fill')' in or related to this line: 'rankText.style.fill = "#d22";' Line Number: 139
User prompt
Please fix the bug: 'Cannot set properties of undefined (setting 'fill')' in or related to this line: 'rankText.style.fill = "#222";' Line Number: 152
User prompt
Please fix the bug: 'Cannot set properties of undefined (setting 'fill')' in or related to this line: 'rankText.style.fill = "#222";' Line Number: 147
User prompt
Please fix the bug: 'Cannot set properties of undefined (setting 'fill')' in or related to this line: 'rankText.style.fill = "#d22";' Line Number: 135
Code edit (1 edits merged)
Please save this source code
User prompt
Thirty-One Blitz
Initial prompt
31
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Simple AI class (draws/discards at intervals)
var AIPlayer = Container.expand(function () {
var self = Container.call(this);
self.hand = [];
self.name = "AI";
self.knocked = false;
// AI makes a move (draw/discard/knock)
self.makeMove = function () {
if (game.gameOver) return;
// If hand value >= 28, knock with 50% chance, always knock at 31
var best = bestHandValue(self.hand);
if (best.value === 31) {
game.aiKnock();
return;
}
if (best.value >= 28 && Math.random() < 0.5) {
game.aiKnock();
return;
}
// Draw from discard if it helps, else draw from deck
var topDiscard = game.discardPile.length > 0 ? game.discardPile[game.discardPile.length - 1] : null;
var wantDiscard = false;
if (topDiscard) {
// Would taking this card improve hand?
var replaced = -1,
bestWithDiscard = best;
for (var i = 0; i < self.hand.length; ++i) {
var testHand = self.hand.slice();
testHand[i] = topDiscard;
var val = bestHandValue(testHand);
if (val.value > bestWithDiscard.value) {
bestWithDiscard = val;
replaced = i;
}
}
if (bestWithDiscard.value > best.value) {
wantDiscard = true;
}
}
if (wantDiscard) {
// Take from discard
var card = game.discardPile.pop();
self.hand.push(card);
// Discard lowest value card
var idx = lowestValueCardIndex(self.hand);
var discard = self.hand.splice(idx, 1)[0];
game.discardPile.push(discard);
game.animateAIDraw(card, 'discard');
game.animateAIDiscard(discard);
} else {
// Draw from deck
if (game.deck.length === 0) {
// Can't draw, must knock
game.aiKnock();
return;
}
var card = game.deck.pop();
self.hand.push(card);
// Discard lowest value card
var idx = lowestValueCardIndex(self.hand);
var discard = self.hand.splice(idx, 1)[0];
game.discardPile.push(discard);
game.animateAIDraw(card, 'deck');
game.animateAIDiscard(discard);
}
};
return self;
});
// Card class: represents a single card (visual and data)
var Card = Container.expand(function () {
var self = Container.call(this);
// Card data
self.suit = null; // '♠', '♥', '♦', '♣'
self.rank = null; // 1-13 (Ace-King)
self.faceUp = true;
self.cardIndex = -1; // index in deck, for uniqueness
// Visuals
var cardFront = self.attachAsset('cardFront', {
anchorX: 0.5,
anchorY: 0.5
});
var cardBack = self.attachAsset('cardBack', {
anchorX: 0.5,
anchorY: 0.5
});
cardBack.visible = false;
// Text overlays
var rankText = new Text2('', {
size: 70,
fill: "#222"
});
rankText.anchor.set(0.5, 0.2);
self.addChild(rankText);
var suitText = new Text2('', {
size: 90,
fill: "#222"
});
suitText.anchor.set(0.5, 0.6);
self.addChild(suitText);
// Set card data and visuals
self.setCard = function (suit, rank, faceUp, cardIndex) {
self.suit = suit;
self.rank = rank;
self.faceUp = faceUp !== false;
self.cardIndex = cardIndex;
self.updateVisual();
};
// Update visuals based on faceUp
self.updateVisual = function () {
cardFront.visible = self.faceUp;
cardBack.visible = !self.faceUp;
if (self.faceUp) {
rankText.setText(cardRankToString(self.rank));
suitText.setText(self.suit);
// Color for suit
if (self.suit === '♥' || self.suit === '♦') {
rankText.style.fill = "#d22";
suitText.style.fill = "#d22";
} else {
rankText.style.fill = "#222";
suitText.style.fill = "#222";
}
} else {
rankText.setText('');
suitText.setText('');
}
};
// Flip card
self.flip = function (toFaceUp) {
self.faceUp = toFaceUp;
self.updateVisual();
};
// For drag highlight
self.highlight = function (color, duration) {
tween(self, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: duration || 100,
easing: tween.easeIn
});
}
});
};
// For simple card tap feedback
self.flash = function () {
LK.effects.flashObject(self, 0xffff99, 200);
};
// Card tap event (handled in game)
self.down = function (x, y, obj) {};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0a3a1a
});
/****
* Game Code
****/
// Draw pile highlight
// Discard pile highlight
// Card front (white, will overlay suit/rank)
// Card back
// Card data
var SUITS = ['♠', '♥', '♦', '♣'];
var RANKS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; // Ace-King
// Layout
var HAND_Y = 2200;
var HAND_X_START = 400;
var HAND_SPACING = 260;
var AI_HAND_Y = 400;
var AI_HAND_X_START = 400;
var PILE_X = 1024;
var DRAW_PILE_Y = 1200;
var DISCARD_PILE_Y = 1500;
// Game state
var deck = [];
var discardPile = [];
var playerHand = [];
var aiHand = [];
var ai = null;
var selectedCardIndex = -1;
var draggingCard = null;
var dragOffsetX = 0;
var dragOffsetY = 0;
var canDraw = true;
var canDiscard = false;
var gameOver = false;
var aiMoveTimeout = null;
var playerKnocked = false;
var aiKnocked = false;
var turn = 'player'; // 'player' or 'ai'
var timerText = null;
var timer = null;
var timeLeft = 60; // seconds
// GUI
var infoText = null;
var knockButton = null;
var discardHighlight = null;
var drawHighlight = null;
// Helper: Card rank to string
function cardRankToString(rank) {
if (rank === 1) return 'A';
if (rank === 11) return 'J';
if (rank === 12) return 'Q';
if (rank === 13) return 'K';
return '' + rank;
}
// Helper: Card value for 31
function cardValue(card) {
if (!card) return 0;
if (card.rank > 10) return 10;
if (card.rank === 1) return 11;
return card.rank;
}
// Helper: Best hand value (max sum of one suit)
function bestHandValue(hand) {
var suitTotals = {
'♠': 0,
'♥': 0,
'♦': 0,
'♣': 0
};
for (var i = 0; i < hand.length; ++i) {
var c = hand[i];
suitTotals[c.suit] += cardValue(c);
}
var bestSuit = '♠',
bestValue = suitTotals['♠'];
for (var s = 0; s < SUITS.length; ++s) {
if (suitTotals[SUITS[s]] > bestValue) {
bestSuit = SUITS[s];
bestValue = suitTotals[SUITS[s]];
}
}
return {
suit: bestSuit,
value: bestValue
};
}
// Helper: Find lowest value card index in hand
function lowestValueCardIndex(hand) {
var best = bestHandValue(hand);
var minIdx = -1,
minVal = 100;
for (var i = 0; i < hand.length; ++i) {
var c = hand[i];
// If not in best suit, prefer to discard
if (c.suit !== best.suit) return i;
// Otherwise, lowest value in best suit
var v = cardValue(c);
if (v < minVal) {
minVal = v;
minIdx = i;
}
}
return minIdx >= 0 ? minIdx : 0;
}
// Helper: Shuffle array
function shuffle(arr) {
for (var i = arr.length - 1; i > 0; --i) {
var j = Math.floor(Math.random() * (i + 1));
var t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
// Helper: Create a new deck (array of Card objects)
function createDeck() {
var cards = [];
var idx = 0;
for (var s = 0; s < SUITS.length; ++s) {
for (var r = 0; r < RANKS.length; ++r) {
var c = new Card();
c.setCard(SUITS[s], RANKS[r], false, idx++);
cards.push(c);
}
}
shuffle(cards);
return cards;
}
// Helper: Remove all children from a container
function removeAll(container) {
while (container.children.length > 0) {
var c = container.children[0];
if (c.destroy) c.destroy();else container.removeChild(c);
}
}
// Helper: Animate card to position
function animateCardTo(card, x, y, onFinish) {
tween(card, {
x: x,
y: y
}, {
duration: 250,
easing: tween.cubicOut,
onFinish: onFinish
});
}
// Helper: Show info text
function showInfo(msg, color) {
infoText.setText(msg);
infoText.style.fill = color || "#fff";
infoText.visible = true;
tween(infoText, {
alpha: 1
}, {
duration: 100
});
LK.setTimeout(function () {
tween(infoText, {
alpha: 0
}, {
duration: 600,
onFinish: function onFinish() {
infoText.visible = false;
}
});
}, 1200);
}
// Helper: End game
function endGame(msg, color) {
gameOver = true;
showInfo(msg, color);
LK.setTimeout(function () {
LK.showGameOver();
}, 1200);
}
// Helper: Win game
function winGame(msg, color) {
gameOver = true;
showInfo(msg, color);
LK.setTimeout(function () {
LK.showYouWin();
}, 1200);
}
// Helper: Update timer
function updateTimer() {
timerText.setText(timeLeft + "s");
if (timeLeft <= 0) {
endGame("Time's up!", "#f44");
}
}
// Helper: Lay out player's hand
function layoutPlayerHand() {
for (var i = 0; i < playerHand.length; ++i) {
var c = playerHand[i];
c.x = HAND_X_START + i * HAND_SPACING;
c.y = HAND_Y;
c.zIndex = 10 + i;
c.scaleX = c.scaleY = 1;
c.faceUp = true;
c.updateVisual();
}
}
// Helper: Lay out AI's hand (face down)
function layoutAIHand() {
for (var i = 0; i < aiHand.length; ++i) {
var c = aiHand[i];
c.x = AI_HAND_X_START + i * HAND_SPACING;
c.y = AI_HAND_Y;
c.zIndex = 10 + i;
c.faceUp = false;
c.updateVisual();
}
}
// Helper: Lay out piles
function layoutPiles() {
// Draw pile
if (deck.length > 0) {
var top = deck[deck.length - 1];
top.x = PILE_X - 180;
top.y = DRAW_PILE_Y;
top.zIndex = 100;
top.faceUp = false;
top.updateVisual();
top.visible = true;
}
// Discard pile
if (discardPile.length > 0) {
var top = discardPile[discardPile.length - 1];
top.x = PILE_X + 180;
top.y = DISCARD_PILE_Y;
top.zIndex = 100;
top.faceUp = true;
top.updateVisual();
top.visible = true;
}
}
// Helper: Remove all cards from stage
function removeAllCards() {
for (var i = 0; i < deck.length; ++i) deck[i].visible = false;
for (var i = 0; i < discardPile.length; ++i) discardPile[i].visible = false;
for (var i = 0; i < playerHand.length; ++i) playerHand[i].visible = false;
for (var i = 0; i < aiHand.length; ++i) aiHand[i].visible = false;
}
// Helper: Animate AI draw
game.animateAIDraw = function (card, from) {
card.visible = true;
if (from === 'deck') {
card.x = PILE_X - 180;
card.y = DRAW_PILE_Y;
} else {
card.x = PILE_X + 180;
card.y = DISCARD_PILE_Y;
}
animateCardTo(card, AI_HAND_X_START + (aiHand.length - 1) * HAND_SPACING, AI_HAND_Y, function () {
layoutAIHand();
});
};
// Helper: Animate AI discard
game.animateAIDiscard = function (card) {
animateCardTo(card, PILE_X + 180, DISCARD_PILE_Y, function () {
layoutPiles();
});
};
// Helper: AI knock
game.aiKnock = function () {
aiKnocked = true;
showInfo("AI knocks!", "#ff0");
// Reveal AI hand
for (var i = 0; i < aiHand.length; ++i) {
aiHand[i].faceUp = true;
aiHand[i].updateVisual();
}
// Compare hands after short delay
LK.setTimeout(function () {
var playerVal = bestHandValue(playerHand).value;
var aiVal = bestHandValue(aiHand).value;
if (aiVal > playerVal) {
endGame("AI wins! (" + aiVal + " vs " + playerVal + ")", "#f44");
} else if (playerVal > aiVal) {
winGame("You win! (" + playerVal + " vs " + aiVal + ")", "#4f4");
} else {
endGame("Draw! (" + playerVal + ")", "#fff");
}
}, 1200);
};
// Helper: Player knock
function playerKnock() {
if (gameOver || playerKnocked) return;
playerKnocked = true;
showInfo("You knock!", "#ff0");
// Reveal AI hand
for (var i = 0; i < aiHand.length; ++i) {
aiHand[i].faceUp = true;
aiHand[i].updateVisual();
}
// Compare hands after short delay
LK.setTimeout(function () {
var playerVal = bestHandValue(playerHand).value;
var aiVal = bestHandValue(aiHand).value;
if (playerVal > aiVal) {
winGame("You win! (" + playerVal + " vs " + aiVal + ")", "#4f4");
} else if (aiVal > playerVal) {
endGame("AI wins! (" + aiVal + " vs " + playerVal + ")", "#f44");
} else {
endGame("Draw! (" + playerVal + ")", "#fff");
}
}, 1200);
}
// Helper: Start AI move timer
function startAIMove() {
if (aiMoveTimeout) LK.clearTimeout(aiMoveTimeout);
aiMoveTimeout = LK.setTimeout(function () {
if (!gameOver && ai && !aiKnocked) {
ai.makeMove();
// If AI didn't knock, return turn to player
if (!aiKnocked) {
turn = 'player';
canDraw = true;
canDiscard = false;
showInfo("Your turn!", "#fff");
}
}
}, 900);
}
// Helper: Start timer
function startTimer() {
if (timer) LK.clearInterval(timer);
timer = LK.setInterval(function () {
if (gameOver) return;
timeLeft -= 1;
updateTimer();
}, 1000);
}
// Helper: Reset game state
function resetGame() {
// Remove all cards
removeAllCards();
deck = createDeck();
discardPile = [];
playerHand = [];
aiHand = [];
ai = new AIPlayer();
selectedCardIndex = -1;
draggingCard = null;
canDraw = true;
canDiscard = false;
gameOver = false;
aiMoveTimeout = null;
playerKnocked = false;
aiKnocked = false;
turn = 'player';
timeLeft = 60;
// Deal 3 cards to each
for (var i = 0; i < 3; ++i) {
var c = deck.pop();
c.faceUp = true;
c.visible = true;
playerHand.push(c);
game.addChild(c);
}
for (var i = 0; i < 3; ++i) {
var c = deck.pop();
c.faceUp = false;
c.visible = true;
aiHand.push(c);
game.addChild(c);
}
// Discard one card to start
var c = deck.pop();
c.faceUp = true;
c.visible = true;
discardPile.push(c);
game.addChild(c);
// Add remaining deck cards to stage (face down)
for (var i = 0; i < deck.length; ++i) {
deck[i].faceUp = false;
deck[i].visible = true;
game.addChild(deck[i]);
}
// Add AI hand to stage
for (var i = 0; i < aiHand.length; ++i) {
aiHand[i].faceUp = false;
aiHand[i].visible = true;
game.addChild(aiHand[i]);
}
// Layout
layoutPlayerHand();
layoutAIHand();
layoutPiles();
// Update GUI
updateHandValueText();
updateTimer();
// Start timer
startTimer();
// Show info
showInfo("Your turn!", "#fff");
}
// Helper: Update hand value text
function updateHandValueText() {
var val = bestHandValue(playerHand);
handValueText.setText("Best: " + val.value + " " + val.suit);
if (val.value === 31) {
winGame("31! You win!", "#4f4");
}
}
// GUI: Info text
infoText = new Text2('', {
size: 90,
fill: "#fff"
});
infoText.anchor.set(0.5, 0.5);
infoText.x = 1024;
infoText.y = 900;
infoText.alpha = 0;
infoText.visible = false;
LK.gui.top.addChild(infoText);
// GUI: Timer
timerText = new Text2('60s', {
size: 90,
fill: "#fff"
});
timerText.anchor.set(0.5, 0);
timerText.x = 1024;
timerText.y = 110;
LK.gui.top.addChild(timerText);
// GUI: Hand value
var handValueText = new Text2('', {
size: 80,
fill: "#fff"
});
handValueText.anchor.set(0.5, 0);
handValueText.x = 1024;
handValueText.y = HAND_Y + 200;
LK.gui.top.addChild(handValueText);
// GUI: Knock button
knockButton = new Text2('Knock', {
size: 90,
fill: "#fff",
font: "Impact"
});
knockButton.anchor.set(0.5, 0.5);
knockButton.x = 1800;
knockButton.y = HAND_Y + 100;
knockButton.interactive = true;
knockButton.buttonMode = true;
knockButton.alpha = 0.95;
knockButton.down = function (x, y, obj) {
if (gameOver || !canDraw) return;
playerKnock();
};
LK.gui.bottom.addChild(knockButton);
// GUI: Discard highlight
discardHighlight = LK.getAsset('discardHighlight', {
anchorX: 0.5,
anchorY: 0.5,
x: PILE_X + 180,
y: DISCARD_PILE_Y
});
discardHighlight.alpha = 0;
game.addChild(discardHighlight);
// GUI: Draw highlight
drawHighlight = LK.getAsset('drawHighlight', {
anchorX: 0.5,
anchorY: 0.5,
x: PILE_X - 180,
y: DRAW_PILE_Y
});
drawHighlight.alpha = 0;
game.addChild(drawHighlight);
// --- Input Handling ---
// Touch/click down
game.down = function (x, y, obj) {
if (gameOver) return;
// If it's player's turn
if (turn !== 'player') return;
// Check if drawing from deck
if (canDraw && deck.length > 0) {
var top = deck[deck.length - 1];
var dx = x - (PILE_X - 180),
dy = y - DRAW_PILE_Y;
if (Math.abs(dx) < 110 && Math.abs(dy) < 160) {
// Draw from deck
canDraw = false;
canDiscard = true;
var card = deck.pop();
card.faceUp = true;
card.visible = true;
playerHand.push(card);
animateCardTo(card, HAND_X_START + (playerHand.length - 1) * HAND_SPACING, HAND_Y, function () {
layoutPlayerHand();
updateHandValueText();
});
drawHighlight.alpha = 0.2;
LK.setTimeout(function () {
drawHighlight.alpha = 0;
}, 200);
return;
}
}
// Check if drawing from discard
if (canDraw && discardPile.length > 0) {
var top = discardPile[discardPile.length - 1];
var dx = x - (PILE_X + 180),
dy = y - DISCARD_PILE_Y;
if (Math.abs(dx) < 110 && Math.abs(dy) < 160) {
// Draw from discard
canDraw = false;
canDiscard = true;
var card = discardPile.pop();
card.faceUp = true;
card.visible = true;
playerHand.push(card);
animateCardTo(card, HAND_X_START + (playerHand.length - 1) * HAND_SPACING, HAND_Y, function () {
layoutPlayerHand();
updateHandValueText();
});
discardHighlight.alpha = 0.2;
LK.setTimeout(function () {
discardHighlight.alpha = 0;
}, 200);
return;
}
}
// If can discard, check if a card in hand is tapped
if (canDiscard) {
for (var i = 0; i < playerHand.length; ++i) {
var c = playerHand[i];
var dx = x - c.x,
dy = y - c.y;
if (Math.abs(dx) < 110 && Math.abs(dy) < 160) {
// Discard this card
canDiscard = false;
var discard = playerHand.splice(i, 1)[0];
discard.faceUp = true;
discard.visible = true;
discardPile.push(discard);
animateCardTo(discard, PILE_X + 180, DISCARD_PILE_Y, function () {
layoutPiles();
});
discardHighlight.alpha = 0.2;
LK.setTimeout(function () {
discardHighlight.alpha = 0;
}, 200);
layoutPlayerHand();
updateHandValueText();
// End turn, AI moves
turn = 'ai';
startAIMove();
return;
}
}
}
};
// Touch/click up
game.up = function (x, y, obj) {
// No drag in this version
};
// Touch/click move
game.move = function (x, y, obj) {
// No drag in this version
};
// Main update loop
game.update = function () {
// No per-frame logic needed
};
// Start game
resetGame(); ===================================================================
--- original.js
+++ change.js
@@ -1,6 +1,739 @@
-/****
+/****
+* Plugins
+****/
+var tween = LK.import("@upit/tween.v1");
+
+/****
+* Classes
+****/
+// Simple AI class (draws/discards at intervals)
+var AIPlayer = Container.expand(function () {
+ var self = Container.call(this);
+ self.hand = [];
+ self.name = "AI";
+ self.knocked = false;
+ // AI makes a move (draw/discard/knock)
+ self.makeMove = function () {
+ if (game.gameOver) return;
+ // If hand value >= 28, knock with 50% chance, always knock at 31
+ var best = bestHandValue(self.hand);
+ if (best.value === 31) {
+ game.aiKnock();
+ return;
+ }
+ if (best.value >= 28 && Math.random() < 0.5) {
+ game.aiKnock();
+ return;
+ }
+ // Draw from discard if it helps, else draw from deck
+ var topDiscard = game.discardPile.length > 0 ? game.discardPile[game.discardPile.length - 1] : null;
+ var wantDiscard = false;
+ if (topDiscard) {
+ // Would taking this card improve hand?
+ var replaced = -1,
+ bestWithDiscard = best;
+ for (var i = 0; i < self.hand.length; ++i) {
+ var testHand = self.hand.slice();
+ testHand[i] = topDiscard;
+ var val = bestHandValue(testHand);
+ if (val.value > bestWithDiscard.value) {
+ bestWithDiscard = val;
+ replaced = i;
+ }
+ }
+ if (bestWithDiscard.value > best.value) {
+ wantDiscard = true;
+ }
+ }
+ if (wantDiscard) {
+ // Take from discard
+ var card = game.discardPile.pop();
+ self.hand.push(card);
+ // Discard lowest value card
+ var idx = lowestValueCardIndex(self.hand);
+ var discard = self.hand.splice(idx, 1)[0];
+ game.discardPile.push(discard);
+ game.animateAIDraw(card, 'discard');
+ game.animateAIDiscard(discard);
+ } else {
+ // Draw from deck
+ if (game.deck.length === 0) {
+ // Can't draw, must knock
+ game.aiKnock();
+ return;
+ }
+ var card = game.deck.pop();
+ self.hand.push(card);
+ // Discard lowest value card
+ var idx = lowestValueCardIndex(self.hand);
+ var discard = self.hand.splice(idx, 1)[0];
+ game.discardPile.push(discard);
+ game.animateAIDraw(card, 'deck');
+ game.animateAIDiscard(discard);
+ }
+ };
+ return self;
+});
+// Card class: represents a single card (visual and data)
+var Card = Container.expand(function () {
+ var self = Container.call(this);
+ // Card data
+ self.suit = null; // '♠', '♥', '♦', '♣'
+ self.rank = null; // 1-13 (Ace-King)
+ self.faceUp = true;
+ self.cardIndex = -1; // index in deck, for uniqueness
+ // Visuals
+ var cardFront = self.attachAsset('cardFront', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ var cardBack = self.attachAsset('cardBack', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ cardBack.visible = false;
+ // Text overlays
+ var rankText = new Text2('', {
+ size: 70,
+ fill: "#222"
+ });
+ rankText.anchor.set(0.5, 0.2);
+ self.addChild(rankText);
+ var suitText = new Text2('', {
+ size: 90,
+ fill: "#222"
+ });
+ suitText.anchor.set(0.5, 0.6);
+ self.addChild(suitText);
+ // Set card data and visuals
+ self.setCard = function (suit, rank, faceUp, cardIndex) {
+ self.suit = suit;
+ self.rank = rank;
+ self.faceUp = faceUp !== false;
+ self.cardIndex = cardIndex;
+ self.updateVisual();
+ };
+ // Update visuals based on faceUp
+ self.updateVisual = function () {
+ cardFront.visible = self.faceUp;
+ cardBack.visible = !self.faceUp;
+ if (self.faceUp) {
+ rankText.setText(cardRankToString(self.rank));
+ suitText.setText(self.suit);
+ // Color for suit
+ if (self.suit === '♥' || self.suit === '♦') {
+ rankText.style.fill = "#d22";
+ suitText.style.fill = "#d22";
+ } else {
+ rankText.style.fill = "#222";
+ suitText.style.fill = "#222";
+ }
+ } else {
+ rankText.setText('');
+ suitText.setText('');
+ }
+ };
+ // Flip card
+ self.flip = function (toFaceUp) {
+ self.faceUp = toFaceUp;
+ self.updateVisual();
+ };
+ // For drag highlight
+ self.highlight = function (color, duration) {
+ tween(self, {
+ scaleX: 1.1,
+ scaleY: 1.1
+ }, {
+ duration: 100,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ tween(self, {
+ scaleX: 1,
+ scaleY: 1
+ }, {
+ duration: duration || 100,
+ easing: tween.easeIn
+ });
+ }
+ });
+ };
+ // For simple card tap feedback
+ self.flash = function () {
+ LK.effects.flashObject(self, 0xffff99, 200);
+ };
+ // Card tap event (handled in game)
+ self.down = function (x, y, obj) {};
+ return self;
+});
+
+/****
* Initialize Game
-****/
+****/
var game = new LK.Game({
- backgroundColor: 0x000000
-});
\ No newline at end of file
+ backgroundColor: 0x0a3a1a
+});
+
+/****
+* Game Code
+****/
+// Draw pile highlight
+// Discard pile highlight
+// Card front (white, will overlay suit/rank)
+// Card back
+// Card data
+var SUITS = ['♠', '♥', '♦', '♣'];
+var RANKS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; // Ace-King
+// Layout
+var HAND_Y = 2200;
+var HAND_X_START = 400;
+var HAND_SPACING = 260;
+var AI_HAND_Y = 400;
+var AI_HAND_X_START = 400;
+var PILE_X = 1024;
+var DRAW_PILE_Y = 1200;
+var DISCARD_PILE_Y = 1500;
+// Game state
+var deck = [];
+var discardPile = [];
+var playerHand = [];
+var aiHand = [];
+var ai = null;
+var selectedCardIndex = -1;
+var draggingCard = null;
+var dragOffsetX = 0;
+var dragOffsetY = 0;
+var canDraw = true;
+var canDiscard = false;
+var gameOver = false;
+var aiMoveTimeout = null;
+var playerKnocked = false;
+var aiKnocked = false;
+var turn = 'player'; // 'player' or 'ai'
+var timerText = null;
+var timer = null;
+var timeLeft = 60; // seconds
+// GUI
+var infoText = null;
+var knockButton = null;
+var discardHighlight = null;
+var drawHighlight = null;
+// Helper: Card rank to string
+function cardRankToString(rank) {
+ if (rank === 1) return 'A';
+ if (rank === 11) return 'J';
+ if (rank === 12) return 'Q';
+ if (rank === 13) return 'K';
+ return '' + rank;
+}
+// Helper: Card value for 31
+function cardValue(card) {
+ if (!card) return 0;
+ if (card.rank > 10) return 10;
+ if (card.rank === 1) return 11;
+ return card.rank;
+}
+// Helper: Best hand value (max sum of one suit)
+function bestHandValue(hand) {
+ var suitTotals = {
+ '♠': 0,
+ '♥': 0,
+ '♦': 0,
+ '♣': 0
+ };
+ for (var i = 0; i < hand.length; ++i) {
+ var c = hand[i];
+ suitTotals[c.suit] += cardValue(c);
+ }
+ var bestSuit = '♠',
+ bestValue = suitTotals['♠'];
+ for (var s = 0; s < SUITS.length; ++s) {
+ if (suitTotals[SUITS[s]] > bestValue) {
+ bestSuit = SUITS[s];
+ bestValue = suitTotals[SUITS[s]];
+ }
+ }
+ return {
+ suit: bestSuit,
+ value: bestValue
+ };
+}
+// Helper: Find lowest value card index in hand
+function lowestValueCardIndex(hand) {
+ var best = bestHandValue(hand);
+ var minIdx = -1,
+ minVal = 100;
+ for (var i = 0; i < hand.length; ++i) {
+ var c = hand[i];
+ // If not in best suit, prefer to discard
+ if (c.suit !== best.suit) return i;
+ // Otherwise, lowest value in best suit
+ var v = cardValue(c);
+ if (v < minVal) {
+ minVal = v;
+ minIdx = i;
+ }
+ }
+ return minIdx >= 0 ? minIdx : 0;
+}
+// Helper: Shuffle array
+function shuffle(arr) {
+ for (var i = arr.length - 1; i > 0; --i) {
+ var j = Math.floor(Math.random() * (i + 1));
+ var t = arr[i];
+ arr[i] = arr[j];
+ arr[j] = t;
+ }
+}
+// Helper: Create a new deck (array of Card objects)
+function createDeck() {
+ var cards = [];
+ var idx = 0;
+ for (var s = 0; s < SUITS.length; ++s) {
+ for (var r = 0; r < RANKS.length; ++r) {
+ var c = new Card();
+ c.setCard(SUITS[s], RANKS[r], false, idx++);
+ cards.push(c);
+ }
+ }
+ shuffle(cards);
+ return cards;
+}
+// Helper: Remove all children from a container
+function removeAll(container) {
+ while (container.children.length > 0) {
+ var c = container.children[0];
+ if (c.destroy) c.destroy();else container.removeChild(c);
+ }
+}
+// Helper: Animate card to position
+function animateCardTo(card, x, y, onFinish) {
+ tween(card, {
+ x: x,
+ y: y
+ }, {
+ duration: 250,
+ easing: tween.cubicOut,
+ onFinish: onFinish
+ });
+}
+// Helper: Show info text
+function showInfo(msg, color) {
+ infoText.setText(msg);
+ infoText.style.fill = color || "#fff";
+ infoText.visible = true;
+ tween(infoText, {
+ alpha: 1
+ }, {
+ duration: 100
+ });
+ LK.setTimeout(function () {
+ tween(infoText, {
+ alpha: 0
+ }, {
+ duration: 600,
+ onFinish: function onFinish() {
+ infoText.visible = false;
+ }
+ });
+ }, 1200);
+}
+// Helper: End game
+function endGame(msg, color) {
+ gameOver = true;
+ showInfo(msg, color);
+ LK.setTimeout(function () {
+ LK.showGameOver();
+ }, 1200);
+}
+// Helper: Win game
+function winGame(msg, color) {
+ gameOver = true;
+ showInfo(msg, color);
+ LK.setTimeout(function () {
+ LK.showYouWin();
+ }, 1200);
+}
+// Helper: Update timer
+function updateTimer() {
+ timerText.setText(timeLeft + "s");
+ if (timeLeft <= 0) {
+ endGame("Time's up!", "#f44");
+ }
+}
+// Helper: Lay out player's hand
+function layoutPlayerHand() {
+ for (var i = 0; i < playerHand.length; ++i) {
+ var c = playerHand[i];
+ c.x = HAND_X_START + i * HAND_SPACING;
+ c.y = HAND_Y;
+ c.zIndex = 10 + i;
+ c.scaleX = c.scaleY = 1;
+ c.faceUp = true;
+ c.updateVisual();
+ }
+}
+// Helper: Lay out AI's hand (face down)
+function layoutAIHand() {
+ for (var i = 0; i < aiHand.length; ++i) {
+ var c = aiHand[i];
+ c.x = AI_HAND_X_START + i * HAND_SPACING;
+ c.y = AI_HAND_Y;
+ c.zIndex = 10 + i;
+ c.faceUp = false;
+ c.updateVisual();
+ }
+}
+// Helper: Lay out piles
+function layoutPiles() {
+ // Draw pile
+ if (deck.length > 0) {
+ var top = deck[deck.length - 1];
+ top.x = PILE_X - 180;
+ top.y = DRAW_PILE_Y;
+ top.zIndex = 100;
+ top.faceUp = false;
+ top.updateVisual();
+ top.visible = true;
+ }
+ // Discard pile
+ if (discardPile.length > 0) {
+ var top = discardPile[discardPile.length - 1];
+ top.x = PILE_X + 180;
+ top.y = DISCARD_PILE_Y;
+ top.zIndex = 100;
+ top.faceUp = true;
+ top.updateVisual();
+ top.visible = true;
+ }
+}
+// Helper: Remove all cards from stage
+function removeAllCards() {
+ for (var i = 0; i < deck.length; ++i) deck[i].visible = false;
+ for (var i = 0; i < discardPile.length; ++i) discardPile[i].visible = false;
+ for (var i = 0; i < playerHand.length; ++i) playerHand[i].visible = false;
+ for (var i = 0; i < aiHand.length; ++i) aiHand[i].visible = false;
+}
+// Helper: Animate AI draw
+game.animateAIDraw = function (card, from) {
+ card.visible = true;
+ if (from === 'deck') {
+ card.x = PILE_X - 180;
+ card.y = DRAW_PILE_Y;
+ } else {
+ card.x = PILE_X + 180;
+ card.y = DISCARD_PILE_Y;
+ }
+ animateCardTo(card, AI_HAND_X_START + (aiHand.length - 1) * HAND_SPACING, AI_HAND_Y, function () {
+ layoutAIHand();
+ });
+};
+// Helper: Animate AI discard
+game.animateAIDiscard = function (card) {
+ animateCardTo(card, PILE_X + 180, DISCARD_PILE_Y, function () {
+ layoutPiles();
+ });
+};
+// Helper: AI knock
+game.aiKnock = function () {
+ aiKnocked = true;
+ showInfo("AI knocks!", "#ff0");
+ // Reveal AI hand
+ for (var i = 0; i < aiHand.length; ++i) {
+ aiHand[i].faceUp = true;
+ aiHand[i].updateVisual();
+ }
+ // Compare hands after short delay
+ LK.setTimeout(function () {
+ var playerVal = bestHandValue(playerHand).value;
+ var aiVal = bestHandValue(aiHand).value;
+ if (aiVal > playerVal) {
+ endGame("AI wins! (" + aiVal + " vs " + playerVal + ")", "#f44");
+ } else if (playerVal > aiVal) {
+ winGame("You win! (" + playerVal + " vs " + aiVal + ")", "#4f4");
+ } else {
+ endGame("Draw! (" + playerVal + ")", "#fff");
+ }
+ }, 1200);
+};
+// Helper: Player knock
+function playerKnock() {
+ if (gameOver || playerKnocked) return;
+ playerKnocked = true;
+ showInfo("You knock!", "#ff0");
+ // Reveal AI hand
+ for (var i = 0; i < aiHand.length; ++i) {
+ aiHand[i].faceUp = true;
+ aiHand[i].updateVisual();
+ }
+ // Compare hands after short delay
+ LK.setTimeout(function () {
+ var playerVal = bestHandValue(playerHand).value;
+ var aiVal = bestHandValue(aiHand).value;
+ if (playerVal > aiVal) {
+ winGame("You win! (" + playerVal + " vs " + aiVal + ")", "#4f4");
+ } else if (aiVal > playerVal) {
+ endGame("AI wins! (" + aiVal + " vs " + playerVal + ")", "#f44");
+ } else {
+ endGame("Draw! (" + playerVal + ")", "#fff");
+ }
+ }, 1200);
+}
+// Helper: Start AI move timer
+function startAIMove() {
+ if (aiMoveTimeout) LK.clearTimeout(aiMoveTimeout);
+ aiMoveTimeout = LK.setTimeout(function () {
+ if (!gameOver && ai && !aiKnocked) {
+ ai.makeMove();
+ // If AI didn't knock, return turn to player
+ if (!aiKnocked) {
+ turn = 'player';
+ canDraw = true;
+ canDiscard = false;
+ showInfo("Your turn!", "#fff");
+ }
+ }
+ }, 900);
+}
+// Helper: Start timer
+function startTimer() {
+ if (timer) LK.clearInterval(timer);
+ timer = LK.setInterval(function () {
+ if (gameOver) return;
+ timeLeft -= 1;
+ updateTimer();
+ }, 1000);
+}
+// Helper: Reset game state
+function resetGame() {
+ // Remove all cards
+ removeAllCards();
+ deck = createDeck();
+ discardPile = [];
+ playerHand = [];
+ aiHand = [];
+ ai = new AIPlayer();
+ selectedCardIndex = -1;
+ draggingCard = null;
+ canDraw = true;
+ canDiscard = false;
+ gameOver = false;
+ aiMoveTimeout = null;
+ playerKnocked = false;
+ aiKnocked = false;
+ turn = 'player';
+ timeLeft = 60;
+ // Deal 3 cards to each
+ for (var i = 0; i < 3; ++i) {
+ var c = deck.pop();
+ c.faceUp = true;
+ c.visible = true;
+ playerHand.push(c);
+ game.addChild(c);
+ }
+ for (var i = 0; i < 3; ++i) {
+ var c = deck.pop();
+ c.faceUp = false;
+ c.visible = true;
+ aiHand.push(c);
+ game.addChild(c);
+ }
+ // Discard one card to start
+ var c = deck.pop();
+ c.faceUp = true;
+ c.visible = true;
+ discardPile.push(c);
+ game.addChild(c);
+ // Add remaining deck cards to stage (face down)
+ for (var i = 0; i < deck.length; ++i) {
+ deck[i].faceUp = false;
+ deck[i].visible = true;
+ game.addChild(deck[i]);
+ }
+ // Add AI hand to stage
+ for (var i = 0; i < aiHand.length; ++i) {
+ aiHand[i].faceUp = false;
+ aiHand[i].visible = true;
+ game.addChild(aiHand[i]);
+ }
+ // Layout
+ layoutPlayerHand();
+ layoutAIHand();
+ layoutPiles();
+ // Update GUI
+ updateHandValueText();
+ updateTimer();
+ // Start timer
+ startTimer();
+ // Show info
+ showInfo("Your turn!", "#fff");
+}
+// Helper: Update hand value text
+function updateHandValueText() {
+ var val = bestHandValue(playerHand);
+ handValueText.setText("Best: " + val.value + " " + val.suit);
+ if (val.value === 31) {
+ winGame("31! You win!", "#4f4");
+ }
+}
+// GUI: Info text
+infoText = new Text2('', {
+ size: 90,
+ fill: "#fff"
+});
+infoText.anchor.set(0.5, 0.5);
+infoText.x = 1024;
+infoText.y = 900;
+infoText.alpha = 0;
+infoText.visible = false;
+LK.gui.top.addChild(infoText);
+// GUI: Timer
+timerText = new Text2('60s', {
+ size: 90,
+ fill: "#fff"
+});
+timerText.anchor.set(0.5, 0);
+timerText.x = 1024;
+timerText.y = 110;
+LK.gui.top.addChild(timerText);
+// GUI: Hand value
+var handValueText = new Text2('', {
+ size: 80,
+ fill: "#fff"
+});
+handValueText.anchor.set(0.5, 0);
+handValueText.x = 1024;
+handValueText.y = HAND_Y + 200;
+LK.gui.top.addChild(handValueText);
+// GUI: Knock button
+knockButton = new Text2('Knock', {
+ size: 90,
+ fill: "#fff",
+ font: "Impact"
+});
+knockButton.anchor.set(0.5, 0.5);
+knockButton.x = 1800;
+knockButton.y = HAND_Y + 100;
+knockButton.interactive = true;
+knockButton.buttonMode = true;
+knockButton.alpha = 0.95;
+knockButton.down = function (x, y, obj) {
+ if (gameOver || !canDraw) return;
+ playerKnock();
+};
+LK.gui.bottom.addChild(knockButton);
+// GUI: Discard highlight
+discardHighlight = LK.getAsset('discardHighlight', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ x: PILE_X + 180,
+ y: DISCARD_PILE_Y
+});
+discardHighlight.alpha = 0;
+game.addChild(discardHighlight);
+// GUI: Draw highlight
+drawHighlight = LK.getAsset('drawHighlight', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ x: PILE_X - 180,
+ y: DRAW_PILE_Y
+});
+drawHighlight.alpha = 0;
+game.addChild(drawHighlight);
+// --- Input Handling ---
+// Touch/click down
+game.down = function (x, y, obj) {
+ if (gameOver) return;
+ // If it's player's turn
+ if (turn !== 'player') return;
+ // Check if drawing from deck
+ if (canDraw && deck.length > 0) {
+ var top = deck[deck.length - 1];
+ var dx = x - (PILE_X - 180),
+ dy = y - DRAW_PILE_Y;
+ if (Math.abs(dx) < 110 && Math.abs(dy) < 160) {
+ // Draw from deck
+ canDraw = false;
+ canDiscard = true;
+ var card = deck.pop();
+ card.faceUp = true;
+ card.visible = true;
+ playerHand.push(card);
+ animateCardTo(card, HAND_X_START + (playerHand.length - 1) * HAND_SPACING, HAND_Y, function () {
+ layoutPlayerHand();
+ updateHandValueText();
+ });
+ drawHighlight.alpha = 0.2;
+ LK.setTimeout(function () {
+ drawHighlight.alpha = 0;
+ }, 200);
+ return;
+ }
+ }
+ // Check if drawing from discard
+ if (canDraw && discardPile.length > 0) {
+ var top = discardPile[discardPile.length - 1];
+ var dx = x - (PILE_X + 180),
+ dy = y - DISCARD_PILE_Y;
+ if (Math.abs(dx) < 110 && Math.abs(dy) < 160) {
+ // Draw from discard
+ canDraw = false;
+ canDiscard = true;
+ var card = discardPile.pop();
+ card.faceUp = true;
+ card.visible = true;
+ playerHand.push(card);
+ animateCardTo(card, HAND_X_START + (playerHand.length - 1) * HAND_SPACING, HAND_Y, function () {
+ layoutPlayerHand();
+ updateHandValueText();
+ });
+ discardHighlight.alpha = 0.2;
+ LK.setTimeout(function () {
+ discardHighlight.alpha = 0;
+ }, 200);
+ return;
+ }
+ }
+ // If can discard, check if a card in hand is tapped
+ if (canDiscard) {
+ for (var i = 0; i < playerHand.length; ++i) {
+ var c = playerHand[i];
+ var dx = x - c.x,
+ dy = y - c.y;
+ if (Math.abs(dx) < 110 && Math.abs(dy) < 160) {
+ // Discard this card
+ canDiscard = false;
+ var discard = playerHand.splice(i, 1)[0];
+ discard.faceUp = true;
+ discard.visible = true;
+ discardPile.push(discard);
+ animateCardTo(discard, PILE_X + 180, DISCARD_PILE_Y, function () {
+ layoutPiles();
+ });
+ discardHighlight.alpha = 0.2;
+ LK.setTimeout(function () {
+ discardHighlight.alpha = 0;
+ }, 200);
+ layoutPlayerHand();
+ updateHandValueText();
+ // End turn, AI moves
+ turn = 'ai';
+ startAIMove();
+ return;
+ }
+ }
+ }
+};
+// Touch/click up
+game.up = function (x, y, obj) {
+ // No drag in this version
+};
+// Touch/click move
+game.move = function (x, y, obj) {
+ // No drag in this version
+};
+// Main update loop
+game.update = function () {
+ // No per-frame logic needed
+};
+// Start game
+resetGame();
\ No newline at end of file