User prompt
ai can discard cards that newly draw ı want to discard new drawed cards
User prompt
remove text in the paranthesis from buttons
User prompt
make buttons curved and large a little
User prompt
ı want to see which cards ai discrads
User prompt
ı want to see which cards ai discrads and also draw clearly alsa give more space to card so ı can read symbol and letter ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
incoming card for ai from deck is moving so fast that ı couldnt see so repair it like ı can see clearly which card ai is taking ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
I want to see ai hand and create animation for drawing card from table for both of player and ai ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'Cannot read properties of null (reading 'setText')' in or related to this line: 'infoTxt.setText('Choose difficulty');' Line Number: 606
User prompt
remove white texts from top of the game and I want see draws of ai
User prompt
ı want to create seperate buttons for game mode section
User prompt
draw all cards seperatly create all 52 asset and use as cards in game
User prompt
I want to see ai take card from middle slowly with animation ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
ı want to see the cards that ai playing also cards need some space between them and draw real play cards
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot set properties of undefined (setting 'fill')' in or related to this line: 'txt.style.fill = "#222";' Line Number: 64
Code edit (1 edits merged)
Please save this source code
User prompt
Suit Collector: Card Duel
Initial prompt
A player and computer play matching card game using one deck of cards. Two levels must be established. The easy level requires to obtain 3 cards of same suit at a player hand in order to win the game. The hardest level requires 6 cards of same suit at a player hand. 3 cards or 6 cards are initially given to the players randomly. Later in order they open a new card and selects a card to throw out. Please obviously define your strategies and intellegince for computer, so that it can win the game mostly. If no one finishes game at the end of the cards, the points (Jack, Queen and King evaluated as 10 points) at hand evaluated and the player who has minimum total wins the game.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // CardSprite: visual representation of a card var CardSprite = Container.expand(function () { var self = Container.call(this); // Card data self.card = null; // Card background var bg = self.attachAsset('cardBg', { width: 260, height: 380, color: 0xffffff, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); // Card suit and rank text var txt = new Text2('', { size: 90, fill: 0x222222 }); txt.anchor.set(0.5, 0.5); txt.x = 0; txt.y = 0; self.addChild(txt); // Set card data and update display self.setCard = function (card) { self.card = card; if (!card) { txt.setText(''); bg.tint = 0xcccccc; return; } var rankStr = ''; if (card.rank === 1) rankStr = 'A';else if (card.rank === 11) rankStr = 'J';else if (card.rank === 12) rankStr = 'Q';else if (card.rank === 13) rankStr = 'K';else rankStr = '' + card.rank; var suitStr = card.suit; txt.setText(rankStr + suitStr); // Color by suit if (card.suit === '♥' || card.suit === '♦') { txt.setStyle({ fill: "#d22" }); } else { txt.setStyle({ fill: "#222" }); } bg.tint = 0xffffff; }; self.setCard(null); return self; }); // Discard pile visual var DiscardSprite = Container.expand(function () { var self = Container.call(this); var bg = self.attachAsset('discardBg', { width: 260, height: 380, color: 0xeeeeee, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); var txt = new Text2('Discard', { size: 60, fill: 0x888888 }); txt.anchor.set(0.5, 0.5); txt.x = 0; txt.y = 0; self.addChild(txt); self.setCard = function (card) { if (!card) { txt.setText('Discard'); bg.tint = 0xeeeeee; return; } var rankStr = ''; if (card.rank === 1) rankStr = 'A';else if (card.rank === 11) rankStr = 'J';else if (card.rank === 12) rankStr = 'Q';else if (card.rank === 13) rankStr = 'K';else rankStr = '' + card.rank; var suitStr = card.suit; txt.setText(rankStr + suitStr); if (card.suit === '♥' || card.suit === '♦') { txt.setStyle({ fill: "#d22" }); } else { txt.setStyle({ fill: "#222" }); } bg.tint = 0xffffff; }; self.setCard(null); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x0a2a3a }); /**** * Game Code ****/ // Card class: represents a single card (not a display object, just data) // --- Game constants --- function Card(suit, rank) { this.suit = suit; // '♠', '♥', '♦', '♣' this.rank = rank; // 1-13 (1=Ace, 11=J, 12=Q, 13=K) } var SUITS = ['♠', '♥', '♦', '♣']; var RANKS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; var HAND_SIZE_EASY = 3; var HAND_SIZE_HARD = 6; // --- Game state --- var handSize = HAND_SIZE_EASY; // Default to easy var playerHand = []; var aiHand = []; var deck = []; var discardPile = []; var currentDraw = null; // Card drawn this turn var isPlayerTurn = true; var gameOver = false; var playerSuitGoal = null; // suit player is collecting var aiSuitGoal = null; // suit AI is collecting var modeTxt = null; var infoTxt = null; var playerCardSprites = []; var aiCardSprites = []; var drawSprite = null; var discardSprite = null; var easyBtn = null; var hardBtn = null; var turnAnim = null; // --- Asset initialization (shapes) --- // --- GUI elements --- // Removed modeTxt and infoTxt from top of the game // Create infoTxt for showing instructions/messages infoTxt = new Text2('', { size: 80, fill: "#fff" }); infoTxt.anchor.set(0.5, 0.5); infoTxt.x = 2048 / 2; infoTxt.y = 2732 / 2 - 350; game.addChild(infoTxt); // --- Difficulty buttons as separate button containers --- easyBtn = new Container(); var easyBtnBg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, width: 540, height: 260 }); easyBtnBg.tint = 0x44E044; easyBtn.addChild(easyBtnBg); var easyBtnTxt = new Text2('Easy', { size: 90, fill: "#fff" }); easyBtnTxt.anchor.set(0.5, 0.5); easyBtn.addChild(easyBtnTxt); easyBtn.x = 2048 / 2 - 350; easyBtn.y = 2732 / 2 - 100; game.addChild(easyBtn); hardBtn = new Container(); var hardBtnBg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, width: 540, height: 260 }); hardBtnBg.tint = 0xE04444; hardBtn.addChild(hardBtnBg); var hardBtnTxt = new Text2('Hard', { size: 90, fill: "#fff" }); hardBtnTxt.anchor.set(0.5, 0.5); hardBtn.addChild(hardBtnTxt); hardBtn.x = 2048 / 2 + 350; hardBtn.y = 2732 / 2 - 100; game.addChild(hardBtn); // --- Helper functions --- function shuffleDeck(deckArr) { for (var i = deckArr.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var t = deckArr[i]; deckArr[i] = deckArr[j]; deckArr[j] = t; } } function makeDeck() { var d = []; for (var s = 0; s < SUITS.length; s++) { for (var r = 0; r < RANKS.length; r++) { d.push(new Card(SUITS[s], RANKS[r])); } } shuffleDeck(d); return d; } function handHasSuit(hand, suit) { for (var i = 0; i < hand.length; i++) { if (hand[i].suit !== suit) return false; } return true; } function handSuitCounts(hand) { var counts = {}; for (var i = 0; i < hand.length; i++) { var s = hand[i].suit; counts[s] = (counts[s] || 0) + 1; } return counts; } function handValue(hand) { var v = 0; for (var i = 0; i < hand.length; i++) { var r = hand[i].rank; if (r > 10) v += 10;else v += r; } return v; } function checkWin() { // Player win by suit var playerCounts = handSuitCounts(playerHand); for (var s in playerCounts) { if (playerCounts[s] === handSize) { LK.effects.flashScreen(0x44e044, 800); LK.showYouWin(); gameOver = true; return true; } } // AI win by suit var aiCounts = handSuitCounts(aiHand); for (var s in aiCounts) { if (aiCounts[s] === handSize) { LK.effects.flashScreen(0xe04444, 800); LK.showGameOver(); gameOver = true; return true; } } // Deck out: compare hand values if (deck.length === 0 && !currentDraw) { var pv = handValue(playerHand); var av = handValue(aiHand); if (pv < av) { LK.effects.flashScreen(0x44e044, 800); LK.showYouWin(); } else if (av < pv) { LK.effects.flashScreen(0xe04444, 800); LK.showGameOver(); } else { LK.effects.flashScreen(0x888888, 800); LK.showGameOver(); } gameOver = true; return true; } return false; } function updateInfoText() { if (gameOver) return; if (deck.length === 0 && !currentDraw) { infoTxt.setText("Deck empty! Lowest hand value wins."); return; } if (isPlayerTurn) { infoTxt.setText("Your turn: Tap a card to discard."); } else { infoTxt.setText("AI is thinking..."); } } function updateHandsDisplay() { // Player hand for (var i = 0; i < playerCardSprites.length; i++) { var cs = playerCardSprites[i]; if (playerHand[i]) { cs.setCard(playerHand[i]); cs.visible = true; } else { cs.setCard(null); cs.visible = false; } } // AI hand (show as face up) for (var i = 0; i < aiCardSprites.length; i++) { var cs = aiCardSprites[i]; if (aiHand[i]) { cs.setCard(aiHand[i]); cs.visible = true; } else { cs.setCard(null); cs.visible = false; } } } function updateDrawDisplay() { if (currentDraw) { drawSprite.setCard(currentDraw); drawSprite.visible = true; } else { drawSprite.setCard(null); drawSprite.visible = false; } } function updateDiscardDisplay() { if (discardPile.length > 0) { discardSprite.setCard(discardPile[discardPile.length - 1]); } else { discardSprite.setCard(null); } } function startGame(selectedHandSize) { handSize = selectedHandSize; gameOver = false; playerHand = []; aiHand = []; deck = makeDeck(); discardPile = []; currentDraw = null; isPlayerTurn = true; playerSuitGoal = null; aiSuitGoal = null; // Remove menu buttons easyBtn.visible = false; hardBtn.visible = false; // Deal hands for (var i = 0; i < handSize; i++) { playerHand.push(deck.pop()); aiHand.push(deck.pop()); } // Draw first card currentDraw = deck.pop(); updateHandsDisplay(); updateDrawDisplay(); updateDiscardDisplay(); updateInfoText(); } // --- Layout setup --- // Player hand sprites playerCardSprites = []; var handY = 2732 - 500; var handX0 = 2048 / 2 - handSize * 150; for (var i = 0; i < 6; i++) { var cs = new CardSprite(); cs.x = 2048 / 2 - 390 + i * 160; cs.y = handY; cs.visible = false; cs.cardIndex = i; playerCardSprites.push(cs); game.addChild(cs); } // AI hand sprites aiCardSprites = []; var aiY = 500; for (var i = 0; i < 6; i++) { var cs = new CardSprite(); cs.x = 2048 / 2 - 390 + i * 160; cs.y = aiY; cs.visible = false; aiCardSprites.push(cs); game.addChild(cs); } // Draw pile sprite drawSprite = new CardSprite(); drawSprite.x = 2048 / 2 - 200; drawSprite.y = 2732 / 2; drawSprite.visible = false; game.addChild(drawSprite); // Discard pile sprite discardSprite = new DiscardSprite(); discardSprite.x = 2048 / 2 + 200; discardSprite.y = 2732 / 2; game.addChild(discardSprite); // --- Event handlers --- // Difficulty selection easyBtn.down = function (x, y, obj) { startGame(HAND_SIZE_EASY); }; hardBtn.down = function (x, y, obj) { startGame(HAND_SIZE_HARD); }; // Player discards by tapping a card for (var i = 0; i < playerCardSprites.length; i++) { (function (idx) { playerCardSprites[idx].down = function (x, y, obj) { if (!isPlayerTurn || gameOver) return; if (!currentDraw) return; if (idx >= playerHand.length) return; // If player taps on an empty slot (shouldn't happen), ignore if (!playerHand[idx]) return; // Animate draw: move drawSprite to the selected card position var targetSprite = playerCardSprites[idx]; var startX = drawSprite.x, startY = drawSprite.y; var endX = targetSprite.x, endY = targetSprite.y; drawSprite.visible = true; tween(drawSprite, { x: endX, y: endY }, { duration: 350, easing: tween.cubicOut, onFinish: function onFinish() { // Discard selected card, add drawn card to hand var discarded = playerHand[idx]; playerHand[idx] = currentDraw; discardPile.push(discarded); currentDraw = null; // Reset drawSprite position drawSprite.x = startX; drawSprite.y = startY; updateHandsDisplay(); updateDrawDisplay(); updateDiscardDisplay(); isPlayerTurn = false; updateInfoText(); checkWin(); if (!gameOver) { LK.setTimeout(aiTurn, 700); } } }); }; // Allow player to discard the newly drawn card directly by tapping the draw pile })(i); } // Allow player to discard the newly drawn card directly by tapping the draw pile drawSprite.down = function (x, y, obj) { if (!isPlayerTurn || gameOver) return; if (!currentDraw) return; // Animate drawSprite to discard pile var startX = drawSprite.x, startY = drawSprite.y; var endX = discardSprite.x, endY = discardSprite.y; drawSprite.visible = true; tween(drawSprite, { x: endX, y: endY }, { duration: 350, easing: tween.cubicOut, onFinish: function onFinish() { discardPile.push(currentDraw); currentDraw = null; // Reset drawSprite position drawSprite.x = startX; drawSprite.y = startY; updateHandsDisplay(); updateDrawDisplay(); updateDiscardDisplay(); isPlayerTurn = false; updateInfoText(); checkWin(); if (!gameOver) { LK.setTimeout(aiTurn, 700); } } }); }; // --- AI logic --- function aiTurn() { if (gameOver) return; if (!currentDraw) { // Draw a card if needed if (deck.length > 0) { currentDraw = deck.pop(); updateDrawDisplay(); isPlayerTurn = false; updateInfoText && updateInfoText(); // Animate drawSprite to AI hand var aiTargetIdx = -1; for (var i = 0; i < aiHand.length; i++) { if (!aiHand[i]) { aiTargetIdx = i; break; } } if (aiTargetIdx === -1) aiTargetIdx = 0; var aiTargetSprite = aiCardSprites[aiTargetIdx]; var startX = drawSprite.x, startY = drawSprite.y; var endX = aiTargetSprite.x, endY = aiTargetSprite.y; drawSprite.visible = true; tween(drawSprite, { x: endX, y: endY }, { duration: 900, easing: tween.cubicOut, onFinish: function onFinish() { // Reset drawSprite position drawSprite.x = startX; drawSprite.y = startY; // Wait 550ms before AI discards, so player can see the draw LK.setTimeout(function () { aiTurn(); // Call aiTurn again to continue after showing the draw }, 550); } }); return; // Stop here, will continue after animation and timeout } else { // Deck empty, skip draw currentDraw = null; } } // AI chooses which card to discard var suitCounts = handSuitCounts(aiHand.concat([currentDraw])); var bestSuit = null; var maxCount = 0; for (var s in suitCounts) { if (suitCounts[s] > maxCount) { maxCount = suitCounts[s]; bestSuit = s; } } aiSuitGoal = bestSuit; // Find a card NOT of bestSuit, or if all are, discard highest value var discardIdx = -1; for (var i = 0; i < aiHand.length; i++) { if (aiHand[i].suit !== bestSuit) { discardIdx = i; break; } } if (discardIdx === -1) { // All cards are of bestSuit, discard highest value var maxVal = -1; for (var i = 0; i < aiHand.length; i++) { var v = aiHand[i].rank > 10 ? 10 : aiHand[i].rank; if (v > maxVal) { maxVal = v; discardIdx = i; } } } // Compare with drawn card: if drawn card is not bestSuit, maybe discard it instead if (currentDraw && currentDraw.suit !== bestSuit) { // Discard drawn card // Animate drawSprite to discard pile var startX = drawSprite.x, startY = drawSprite.y; var endX = discardSprite.x, endY = discardSprite.y; drawSprite.visible = true; tween(drawSprite, { x: endX, y: endY }, { duration: 600, easing: tween.cubicOut, onFinish: function onFinish() { discardPile.push(currentDraw); currentDraw = null; // Reset drawSprite position drawSprite.x = startX; drawSprite.y = startY; updateHandsDisplay(); updateDrawDisplay(); updateDiscardDisplay(); isPlayerTurn = true; updateInfoText(); checkWin(); // Draw for player if deck not empty if (!gameOver && deck.length > 0) { currentDraw = deck.pop(); updateDrawDisplay(); } } }); return; // Wait for animation to finish } else { // Discard from hand, add drawn card to hand if (currentDraw) { var discarded = aiHand[discardIdx]; // Animate AI hand card to discard pile var aiCardSprite = aiCardSprites[discardIdx]; var tempCardSprite = new CardSprite(); tempCardSprite.setCard(discarded); tempCardSprite.x = aiCardSprite.x; tempCardSprite.y = aiCardSprite.y; tempCardSprite.visible = true; game.addChild(tempCardSprite); tween(tempCardSprite, { x: discardSprite.x, y: discardSprite.y }, { duration: 600, easing: tween.cubicOut, onFinish: function onFinish() { discardPile.push(discarded); aiHand[discardIdx] = currentDraw; currentDraw = null; tempCardSprite.destroy(); updateHandsDisplay(); updateDrawDisplay(); updateDiscardDisplay(); isPlayerTurn = true; updateInfoText(); checkWin(); // Draw for player if deck not empty if (!gameOver && deck.length > 0) { currentDraw = deck.pop(); updateDrawDisplay(); } } }); return; // Wait for animation to finish } else { // No draw, just discard highest value var maxVal = -1, idx = 0; for (var i = 0; i < aiHand.length; i++) { var v = aiHand[i].rank > 10 ? 10 : aiHand[i].rank; if (v > maxVal) { maxVal = v; idx = i; } } // Animate AI hand card to discard pile var aiCardSprite2 = aiCardSprites[idx]; var tempCardSprite2 = new CardSprite(); tempCardSprite2.setCard(aiHand[idx]); tempCardSprite2.x = aiCardSprite2.x; tempCardSprite2.y = aiCardSprite2.y; tempCardSprite2.visible = true; game.addChild(tempCardSprite2); tween(tempCardSprite2, { x: discardSprite.x, y: discardSprite.y }, { duration: 600, easing: tween.cubicOut, onFinish: function onFinish() { discardPile.push(aiHand[idx]); aiHand.splice(idx, 1); tempCardSprite2.destroy(); updateHandsDisplay(); updateDrawDisplay(); updateDiscardDisplay(); isPlayerTurn = true; updateInfoText(); checkWin(); // Draw for player if deck not empty if (!gameOver && deck.length > 0) { currentDraw = deck.pop(); updateDrawDisplay(); } } }); return; // Wait for animation to finish } } } // --- Main update loop --- game.update = function () { // No per-frame logic needed }; // --- On game start, show menu --- function showMenu() { easyBtn.visible = true; hardBtn.visible = true; infoTxt.setText('Choose difficulty'); for (var i = 0; i < playerCardSprites.length; i++) { playerCardSprites[i].visible = false; aiCardSprites[i].visible = false; } drawSprite.visible = false; discardSprite.visible = false; } showMenu();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// CardSprite: visual representation of a card
var CardSprite = Container.expand(function () {
var self = Container.call(this);
// Card data
self.card = null;
// Card background
var bg = self.attachAsset('cardBg', {
width: 260,
height: 380,
color: 0xffffff,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
// Card suit and rank text
var txt = new Text2('', {
size: 90,
fill: 0x222222
});
txt.anchor.set(0.5, 0.5);
txt.x = 0;
txt.y = 0;
self.addChild(txt);
// Set card data and update display
self.setCard = function (card) {
self.card = card;
if (!card) {
txt.setText('');
bg.tint = 0xcccccc;
return;
}
var rankStr = '';
if (card.rank === 1) rankStr = 'A';else if (card.rank === 11) rankStr = 'J';else if (card.rank === 12) rankStr = 'Q';else if (card.rank === 13) rankStr = 'K';else rankStr = '' + card.rank;
var suitStr = card.suit;
txt.setText(rankStr + suitStr);
// Color by suit
if (card.suit === '♥' || card.suit === '♦') {
txt.setStyle({
fill: "#d22"
});
} else {
txt.setStyle({
fill: "#222"
});
}
bg.tint = 0xffffff;
};
self.setCard(null);
return self;
});
// Discard pile visual
var DiscardSprite = Container.expand(function () {
var self = Container.call(this);
var bg = self.attachAsset('discardBg', {
width: 260,
height: 380,
color: 0xeeeeee,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
var txt = new Text2('Discard', {
size: 60,
fill: 0x888888
});
txt.anchor.set(0.5, 0.5);
txt.x = 0;
txt.y = 0;
self.addChild(txt);
self.setCard = function (card) {
if (!card) {
txt.setText('Discard');
bg.tint = 0xeeeeee;
return;
}
var rankStr = '';
if (card.rank === 1) rankStr = 'A';else if (card.rank === 11) rankStr = 'J';else if (card.rank === 12) rankStr = 'Q';else if (card.rank === 13) rankStr = 'K';else rankStr = '' + card.rank;
var suitStr = card.suit;
txt.setText(rankStr + suitStr);
if (card.suit === '♥' || card.suit === '♦') {
txt.setStyle({
fill: "#d22"
});
} else {
txt.setStyle({
fill: "#222"
});
}
bg.tint = 0xffffff;
};
self.setCard(null);
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0a2a3a
});
/****
* Game Code
****/
// Card class: represents a single card (not a display object, just data)
// --- Game constants ---
function Card(suit, rank) {
this.suit = suit; // '♠', '♥', '♦', '♣'
this.rank = rank; // 1-13 (1=Ace, 11=J, 12=Q, 13=K)
}
var SUITS = ['♠', '♥', '♦', '♣'];
var RANKS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
var HAND_SIZE_EASY = 3;
var HAND_SIZE_HARD = 6;
// --- Game state ---
var handSize = HAND_SIZE_EASY; // Default to easy
var playerHand = [];
var aiHand = [];
var deck = [];
var discardPile = [];
var currentDraw = null; // Card drawn this turn
var isPlayerTurn = true;
var gameOver = false;
var playerSuitGoal = null; // suit player is collecting
var aiSuitGoal = null; // suit AI is collecting
var modeTxt = null;
var infoTxt = null;
var playerCardSprites = [];
var aiCardSprites = [];
var drawSprite = null;
var discardSprite = null;
var easyBtn = null;
var hardBtn = null;
var turnAnim = null;
// --- Asset initialization (shapes) ---
// --- GUI elements ---
// Removed modeTxt and infoTxt from top of the game
// Create infoTxt for showing instructions/messages
infoTxt = new Text2('', {
size: 80,
fill: "#fff"
});
infoTxt.anchor.set(0.5, 0.5);
infoTxt.x = 2048 / 2;
infoTxt.y = 2732 / 2 - 350;
game.addChild(infoTxt);
// --- Difficulty buttons as separate button containers ---
easyBtn = new Container();
var easyBtnBg = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
width: 540,
height: 260
});
easyBtnBg.tint = 0x44E044;
easyBtn.addChild(easyBtnBg);
var easyBtnTxt = new Text2('Easy', {
size: 90,
fill: "#fff"
});
easyBtnTxt.anchor.set(0.5, 0.5);
easyBtn.addChild(easyBtnTxt);
easyBtn.x = 2048 / 2 - 350;
easyBtn.y = 2732 / 2 - 100;
game.addChild(easyBtn);
hardBtn = new Container();
var hardBtnBg = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
width: 540,
height: 260
});
hardBtnBg.tint = 0xE04444;
hardBtn.addChild(hardBtnBg);
var hardBtnTxt = new Text2('Hard', {
size: 90,
fill: "#fff"
});
hardBtnTxt.anchor.set(0.5, 0.5);
hardBtn.addChild(hardBtnTxt);
hardBtn.x = 2048 / 2 + 350;
hardBtn.y = 2732 / 2 - 100;
game.addChild(hardBtn);
// --- Helper functions ---
function shuffleDeck(deckArr) {
for (var i = deckArr.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var t = deckArr[i];
deckArr[i] = deckArr[j];
deckArr[j] = t;
}
}
function makeDeck() {
var d = [];
for (var s = 0; s < SUITS.length; s++) {
for (var r = 0; r < RANKS.length; r++) {
d.push(new Card(SUITS[s], RANKS[r]));
}
}
shuffleDeck(d);
return d;
}
function handHasSuit(hand, suit) {
for (var i = 0; i < hand.length; i++) {
if (hand[i].suit !== suit) return false;
}
return true;
}
function handSuitCounts(hand) {
var counts = {};
for (var i = 0; i < hand.length; i++) {
var s = hand[i].suit;
counts[s] = (counts[s] || 0) + 1;
}
return counts;
}
function handValue(hand) {
var v = 0;
for (var i = 0; i < hand.length; i++) {
var r = hand[i].rank;
if (r > 10) v += 10;else v += r;
}
return v;
}
function checkWin() {
// Player win by suit
var playerCounts = handSuitCounts(playerHand);
for (var s in playerCounts) {
if (playerCounts[s] === handSize) {
LK.effects.flashScreen(0x44e044, 800);
LK.showYouWin();
gameOver = true;
return true;
}
}
// AI win by suit
var aiCounts = handSuitCounts(aiHand);
for (var s in aiCounts) {
if (aiCounts[s] === handSize) {
LK.effects.flashScreen(0xe04444, 800);
LK.showGameOver();
gameOver = true;
return true;
}
}
// Deck out: compare hand values
if (deck.length === 0 && !currentDraw) {
var pv = handValue(playerHand);
var av = handValue(aiHand);
if (pv < av) {
LK.effects.flashScreen(0x44e044, 800);
LK.showYouWin();
} else if (av < pv) {
LK.effects.flashScreen(0xe04444, 800);
LK.showGameOver();
} else {
LK.effects.flashScreen(0x888888, 800);
LK.showGameOver();
}
gameOver = true;
return true;
}
return false;
}
function updateInfoText() {
if (gameOver) return;
if (deck.length === 0 && !currentDraw) {
infoTxt.setText("Deck empty! Lowest hand value wins.");
return;
}
if (isPlayerTurn) {
infoTxt.setText("Your turn: Tap a card to discard.");
} else {
infoTxt.setText("AI is thinking...");
}
}
function updateHandsDisplay() {
// Player hand
for (var i = 0; i < playerCardSprites.length; i++) {
var cs = playerCardSprites[i];
if (playerHand[i]) {
cs.setCard(playerHand[i]);
cs.visible = true;
} else {
cs.setCard(null);
cs.visible = false;
}
}
// AI hand (show as face up)
for (var i = 0; i < aiCardSprites.length; i++) {
var cs = aiCardSprites[i];
if (aiHand[i]) {
cs.setCard(aiHand[i]);
cs.visible = true;
} else {
cs.setCard(null);
cs.visible = false;
}
}
}
function updateDrawDisplay() {
if (currentDraw) {
drawSprite.setCard(currentDraw);
drawSprite.visible = true;
} else {
drawSprite.setCard(null);
drawSprite.visible = false;
}
}
function updateDiscardDisplay() {
if (discardPile.length > 0) {
discardSprite.setCard(discardPile[discardPile.length - 1]);
} else {
discardSprite.setCard(null);
}
}
function startGame(selectedHandSize) {
handSize = selectedHandSize;
gameOver = false;
playerHand = [];
aiHand = [];
deck = makeDeck();
discardPile = [];
currentDraw = null;
isPlayerTurn = true;
playerSuitGoal = null;
aiSuitGoal = null;
// Remove menu buttons
easyBtn.visible = false;
hardBtn.visible = false;
// Deal hands
for (var i = 0; i < handSize; i++) {
playerHand.push(deck.pop());
aiHand.push(deck.pop());
}
// Draw first card
currentDraw = deck.pop();
updateHandsDisplay();
updateDrawDisplay();
updateDiscardDisplay();
updateInfoText();
}
// --- Layout setup ---
// Player hand sprites
playerCardSprites = [];
var handY = 2732 - 500;
var handX0 = 2048 / 2 - handSize * 150;
for (var i = 0; i < 6; i++) {
var cs = new CardSprite();
cs.x = 2048 / 2 - 390 + i * 160;
cs.y = handY;
cs.visible = false;
cs.cardIndex = i;
playerCardSprites.push(cs);
game.addChild(cs);
}
// AI hand sprites
aiCardSprites = [];
var aiY = 500;
for (var i = 0; i < 6; i++) {
var cs = new CardSprite();
cs.x = 2048 / 2 - 390 + i * 160;
cs.y = aiY;
cs.visible = false;
aiCardSprites.push(cs);
game.addChild(cs);
}
// Draw pile sprite
drawSprite = new CardSprite();
drawSprite.x = 2048 / 2 - 200;
drawSprite.y = 2732 / 2;
drawSprite.visible = false;
game.addChild(drawSprite);
// Discard pile sprite
discardSprite = new DiscardSprite();
discardSprite.x = 2048 / 2 + 200;
discardSprite.y = 2732 / 2;
game.addChild(discardSprite);
// --- Event handlers ---
// Difficulty selection
easyBtn.down = function (x, y, obj) {
startGame(HAND_SIZE_EASY);
};
hardBtn.down = function (x, y, obj) {
startGame(HAND_SIZE_HARD);
};
// Player discards by tapping a card
for (var i = 0; i < playerCardSprites.length; i++) {
(function (idx) {
playerCardSprites[idx].down = function (x, y, obj) {
if (!isPlayerTurn || gameOver) return;
if (!currentDraw) return;
if (idx >= playerHand.length) return;
// If player taps on an empty slot (shouldn't happen), ignore
if (!playerHand[idx]) return;
// Animate draw: move drawSprite to the selected card position
var targetSprite = playerCardSprites[idx];
var startX = drawSprite.x,
startY = drawSprite.y;
var endX = targetSprite.x,
endY = targetSprite.y;
drawSprite.visible = true;
tween(drawSprite, {
x: endX,
y: endY
}, {
duration: 350,
easing: tween.cubicOut,
onFinish: function onFinish() {
// Discard selected card, add drawn card to hand
var discarded = playerHand[idx];
playerHand[idx] = currentDraw;
discardPile.push(discarded);
currentDraw = null;
// Reset drawSprite position
drawSprite.x = startX;
drawSprite.y = startY;
updateHandsDisplay();
updateDrawDisplay();
updateDiscardDisplay();
isPlayerTurn = false;
updateInfoText();
checkWin();
if (!gameOver) {
LK.setTimeout(aiTurn, 700);
}
}
});
};
// Allow player to discard the newly drawn card directly by tapping the draw pile
})(i);
}
// Allow player to discard the newly drawn card directly by tapping the draw pile
drawSprite.down = function (x, y, obj) {
if (!isPlayerTurn || gameOver) return;
if (!currentDraw) return;
// Animate drawSprite to discard pile
var startX = drawSprite.x,
startY = drawSprite.y;
var endX = discardSprite.x,
endY = discardSprite.y;
drawSprite.visible = true;
tween(drawSprite, {
x: endX,
y: endY
}, {
duration: 350,
easing: tween.cubicOut,
onFinish: function onFinish() {
discardPile.push(currentDraw);
currentDraw = null;
// Reset drawSprite position
drawSprite.x = startX;
drawSprite.y = startY;
updateHandsDisplay();
updateDrawDisplay();
updateDiscardDisplay();
isPlayerTurn = false;
updateInfoText();
checkWin();
if (!gameOver) {
LK.setTimeout(aiTurn, 700);
}
}
});
};
// --- AI logic ---
function aiTurn() {
if (gameOver) return;
if (!currentDraw) {
// Draw a card if needed
if (deck.length > 0) {
currentDraw = deck.pop();
updateDrawDisplay();
isPlayerTurn = false;
updateInfoText && updateInfoText();
// Animate drawSprite to AI hand
var aiTargetIdx = -1;
for (var i = 0; i < aiHand.length; i++) {
if (!aiHand[i]) {
aiTargetIdx = i;
break;
}
}
if (aiTargetIdx === -1) aiTargetIdx = 0;
var aiTargetSprite = aiCardSprites[aiTargetIdx];
var startX = drawSprite.x,
startY = drawSprite.y;
var endX = aiTargetSprite.x,
endY = aiTargetSprite.y;
drawSprite.visible = true;
tween(drawSprite, {
x: endX,
y: endY
}, {
duration: 900,
easing: tween.cubicOut,
onFinish: function onFinish() {
// Reset drawSprite position
drawSprite.x = startX;
drawSprite.y = startY;
// Wait 550ms before AI discards, so player can see the draw
LK.setTimeout(function () {
aiTurn(); // Call aiTurn again to continue after showing the draw
}, 550);
}
});
return; // Stop here, will continue after animation and timeout
} else {
// Deck empty, skip draw
currentDraw = null;
}
}
// AI chooses which card to discard
var suitCounts = handSuitCounts(aiHand.concat([currentDraw]));
var bestSuit = null;
var maxCount = 0;
for (var s in suitCounts) {
if (suitCounts[s] > maxCount) {
maxCount = suitCounts[s];
bestSuit = s;
}
}
aiSuitGoal = bestSuit;
// Find a card NOT of bestSuit, or if all are, discard highest value
var discardIdx = -1;
for (var i = 0; i < aiHand.length; i++) {
if (aiHand[i].suit !== bestSuit) {
discardIdx = i;
break;
}
}
if (discardIdx === -1) {
// All cards are of bestSuit, discard highest value
var maxVal = -1;
for (var i = 0; i < aiHand.length; i++) {
var v = aiHand[i].rank > 10 ? 10 : aiHand[i].rank;
if (v > maxVal) {
maxVal = v;
discardIdx = i;
}
}
}
// Compare with drawn card: if drawn card is not bestSuit, maybe discard it instead
if (currentDraw && currentDraw.suit !== bestSuit) {
// Discard drawn card
// Animate drawSprite to discard pile
var startX = drawSprite.x,
startY = drawSprite.y;
var endX = discardSprite.x,
endY = discardSprite.y;
drawSprite.visible = true;
tween(drawSprite, {
x: endX,
y: endY
}, {
duration: 600,
easing: tween.cubicOut,
onFinish: function onFinish() {
discardPile.push(currentDraw);
currentDraw = null;
// Reset drawSprite position
drawSprite.x = startX;
drawSprite.y = startY;
updateHandsDisplay();
updateDrawDisplay();
updateDiscardDisplay();
isPlayerTurn = true;
updateInfoText();
checkWin();
// Draw for player if deck not empty
if (!gameOver && deck.length > 0) {
currentDraw = deck.pop();
updateDrawDisplay();
}
}
});
return; // Wait for animation to finish
} else {
// Discard from hand, add drawn card to hand
if (currentDraw) {
var discarded = aiHand[discardIdx];
// Animate AI hand card to discard pile
var aiCardSprite = aiCardSprites[discardIdx];
var tempCardSprite = new CardSprite();
tempCardSprite.setCard(discarded);
tempCardSprite.x = aiCardSprite.x;
tempCardSprite.y = aiCardSprite.y;
tempCardSprite.visible = true;
game.addChild(tempCardSprite);
tween(tempCardSprite, {
x: discardSprite.x,
y: discardSprite.y
}, {
duration: 600,
easing: tween.cubicOut,
onFinish: function onFinish() {
discardPile.push(discarded);
aiHand[discardIdx] = currentDraw;
currentDraw = null;
tempCardSprite.destroy();
updateHandsDisplay();
updateDrawDisplay();
updateDiscardDisplay();
isPlayerTurn = true;
updateInfoText();
checkWin();
// Draw for player if deck not empty
if (!gameOver && deck.length > 0) {
currentDraw = deck.pop();
updateDrawDisplay();
}
}
});
return; // Wait for animation to finish
} else {
// No draw, just discard highest value
var maxVal = -1,
idx = 0;
for (var i = 0; i < aiHand.length; i++) {
var v = aiHand[i].rank > 10 ? 10 : aiHand[i].rank;
if (v > maxVal) {
maxVal = v;
idx = i;
}
}
// Animate AI hand card to discard pile
var aiCardSprite2 = aiCardSprites[idx];
var tempCardSprite2 = new CardSprite();
tempCardSprite2.setCard(aiHand[idx]);
tempCardSprite2.x = aiCardSprite2.x;
tempCardSprite2.y = aiCardSprite2.y;
tempCardSprite2.visible = true;
game.addChild(tempCardSprite2);
tween(tempCardSprite2, {
x: discardSprite.x,
y: discardSprite.y
}, {
duration: 600,
easing: tween.cubicOut,
onFinish: function onFinish() {
discardPile.push(aiHand[idx]);
aiHand.splice(idx, 1);
tempCardSprite2.destroy();
updateHandsDisplay();
updateDrawDisplay();
updateDiscardDisplay();
isPlayerTurn = true;
updateInfoText();
checkWin();
// Draw for player if deck not empty
if (!gameOver && deck.length > 0) {
currentDraw = deck.pop();
updateDrawDisplay();
}
}
});
return; // Wait for animation to finish
}
}
}
// --- Main update loop ---
game.update = function () {
// No per-frame logic needed
};
// --- On game start, show menu ---
function showMenu() {
easyBtn.visible = true;
hardBtn.visible = true;
infoTxt.setText('Choose difficulty');
for (var i = 0; i < playerCardSprites.length; i++) {
playerCardSprites[i].visible = false;
aiCardSprites[i].visible = false;
}
drawSprite.visible = false;
discardSprite.visible = false;
}
showMenu();