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 === '♦') {
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 {
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
@@ -129,10 +129,14 @@
if (typeof rankText.setFill === "function") {
rankText.setFill("#d22");
suitText.setFill("#d22");
} else {
- rankText.style.fill = "#d22";
- suitText.style.fill = "#d22";
+ if (rankText.style) {
+ rankText.style.fill = "#d22";
+ }
+ if (suitText.style) {
+ suitText.style.fill = "#d22";
+ }
}
}
} else {
if (rankText.style && typeof rankText.style.setFill === "function") {