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 && 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 || 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 === '♦') { if (rankText.style && typeof rankText.style.setFill === "function") { rankText.style.setFill("#d22"); suitText.style.setFill("#d22"); } else { if (typeof rankText.setFill === "function") { rankText.setFill("#d22"); suitText.setFill("#d22"); } else { if (rankText.style) { rankText.style.fill = "#d22"; } if (suitText.style) { suitText.style.fill = "#d22"; } } } } else { if (rankText.style && typeof rankText.style.setFill === "function") { rankText.style.setFill("#222"); suitText.style.setFill("#222"); } else { if (rankText.style && typeof rankText.style.setFill === "function") { rankText.style.setFill("#222"); suitText.style.setFill("#222"); } else { // fallback for older Text2 versions if (typeof rankText.setFill === "function") { rankText.setFill("#222"); suitText.setFill("#222"); } else { if (typeof rankText.setFill === "function") { rankText.setFill("#222"); suitText.setFill("#222"); } else if (rankText.style) { 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); if (typeof infoText.setFill === "function") { infoText.setFill(color || "#fff"); } else if (infoText.style) { 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
@@ -56,9 +56,9 @@
game.animateAIDraw(card, 'discard');
game.animateAIDiscard(discard);
} else {
// Draw from deck
- if (game.deck.length === 0) {
+ if (!game.deck || game.deck.length === 0) {
// Can't draw, must knock
game.aiKnock();
return;
}