/**** * 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();