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