User prompt
at the bot choose screen bot boxes are wrong object can you made them one colour
User prompt
Can you add a menu to chose how many bot we want? 1-2-3 bot for example
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'shape')' in or related to this line: 'var boardBg = LK.getAsset(LK.init.shape('deskBg', {' Line Number: 109
User prompt
Background is wrong add a brown desk background
User prompt
Can you add a board backgroun
Code edit (1 edits merged)
Please save this source code
User prompt
Love Letter Duel: Me vs Bot
Initial prompt
Made me a me vs bot love letter game.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Card class: represents a card in hand or on table var Card = Container.expand(function () { var self = Container.call(this); // Card data: {id, name, value, desc} self.cardData = null; self.isFaceUp = true; self.owner = null; // 'player' or 'bot' self.index = 0; // hand index // Card graphics self.cardAsset = null; self.textName = null; self.textValue = null; // Set card data and visuals self.setCard = function (cardData, faceUp) { self.cardData = cardData; self.isFaceUp = faceUp; self.removeChildren(); var assetId = faceUp ? 'card' + cardData.value : 'cardBack'; self.cardAsset = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); if (faceUp) { self.textName = new Text2(cardData.name, { size: 48, fill: 0x333333 }); self.textName.anchor.set(0.5, 0); self.textName.x = 0; self.textName.y = -180; self.addChild(self.textName); self.textValue = new Text2(cardData.value + '', { size: 64, fill: 0x000000 }); self.textValue.anchor.set(0.5, 0.5); self.textValue.x = 0; self.textValue.y = 0; self.addChild(self.textValue); } }; // Flip card face up/down self.flip = function (faceUp) { if (self.cardData) { self.setCard(self.cardData, faceUp); } }; return self; }); // Simple popup for messages var Popup = Container.expand(function () { var self = Container.call(this); self.bg = self.attachAsset('cardBack', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1 }); self.bg.tint = 0x222222; self.bg.alpha = 0.95; self.text = new Text2('', { size: 72, fill: "#fff" }); self.text.anchor.set(0.5, 0.5); self.text.x = 0; self.text.y = 0; self.addChild(self.text); self.setText = function (str) { self.text.setText(str); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2d2d44 }); /**** * Game Code ****/ // Simple heart for score // Back of card // Princess // Countess // King // Prince // Handmaid // Baron // Priest // Guard // Card assets (simple colored boxes for each card type) // Card definitions (Love Letter 8-card deck) var CARD_DECK = [ // id, name, value, desc, count { id: 'guard', name: 'Guard', value: 1, desc: 'Guess a card', count: 5 }, { id: 'priest', name: 'Priest', value: 2, desc: 'See hand', count: 2 }, { id: 'baron', name: 'Baron', value: 3, desc: 'Compare hands', count: 2 }, { id: 'handmaid', name: 'Handmaid', value: 4, desc: 'Immunity', count: 2 }, { id: 'prince', name: 'Prince', value: 5, desc: 'Discard hand', count: 2 }, { id: 'king', name: 'King', value: 6, desc: 'Trade hands', count: 1 }, { id: 'countess', name: 'Countess', value: 7, desc: 'Must play if with King/Prince', count: 1 }, { id: 'princess', name: 'Princess', value: 8, desc: 'Lose if discarded', count: 1 }]; // Build deck array function buildDeck() { var deck = []; for (var i = 0; i < CARD_DECK.length; ++i) { for (var j = 0; j < CARD_DECK[i].count; ++j) { deck.push({ id: CARD_DECK[i].id, name: CARD_DECK[i].name, value: CARD_DECK[i].value, desc: CARD_DECK[i].desc }); } } return deck; } // Shuffle deck function shuffle(deck) { for (var i = deck.length - 1; i > 0; --i) { var j = Math.floor(Math.random() * (i + 1)); var t = deck[i]; deck[i] = deck[j]; deck[j] = t; } } // Game state var playerHand = []; var botHand = []; var deck = []; var discardPile = []; var playerProtected = false; var botProtected = false; var playerOut = false; var botOut = false; var playerScore = 0; var botScore = 0; var roundOver = false; var currentTurn = 'player'; // 'player' or 'bot' var popup = null; var playerCardNodes = []; var botCardNodes = []; var discardNodes = []; var heartsPlayer = []; var heartsBot = []; var guiText = null; var roundTarget = 3; // First to 3 wins // Card positions var playerHandY = 2732 - 350; var botHandY = 350; var handX = 2048 / 2; var handSpacing = 400; // GUI setup function setupGUI() { // Remove old if (guiText) LK.gui.top.removeChild(guiText); guiText = new Text2('', { size: 64, fill: "#fff" }); guiText.anchor.set(0.5, 0); LK.gui.top.addChild(guiText); // Hearts for score for (var i = 0; i < heartsPlayer.length; ++i) LK.gui.top.removeChild(heartsPlayer[i]); for (var i = 0; i < heartsBot.length; ++i) LK.gui.top.removeChild(heartsBot[i]); heartsPlayer = []; heartsBot = []; for (var i = 0; i < roundTarget; ++i) { var h1 = LK.getAsset('heart', { anchorX: 0.5, anchorY: 0.5 }); h1.x = 2048 / 2 - 200 + i * 60; h1.y = 120; LK.gui.top.addChild(h1); heartsPlayer.push(h1); var h2 = LK.getAsset('heart', { anchorX: 0.5, anchorY: 0.5 }); h2.x = 2048 / 2 - 200 + i * 60; h2.y = 60; LK.gui.top.addChild(h2); heartsBot.push(h2); } } // Update GUI function updateGUI() { guiText.setText("You: " + playerScore + " Bot: " + botScore + " First to " + roundTarget); for (var i = 0; i < roundTarget; ++i) { heartsPlayer[i].alpha = i < playerScore ? 1 : 0.3; heartsBot[i].alpha = i < botScore ? 1 : 0.3; } } // Start a new round function startRound() { // Reset state deck = buildDeck(); shuffle(deck); discardPile = []; playerHand = []; botHand = []; playerProtected = false; botProtected = false; playerOut = false; botOut = false; roundOver = false; currentTurn = 'player'; // Remove old cards for (var i = 0; i < playerCardNodes.length; ++i) game.removeChild(playerCardNodes[i]); for (var i = 0; i < botCardNodes.length; ++i) game.removeChild(botCardNodes[i]); for (var i = 0; i < discardNodes.length; ++i) game.removeChild(discardNodes[i]); playerCardNodes = []; botCardNodes = []; discardNodes = []; // Remove popup if (popup) { game.removeChild(popup); popup = null; } // Remove one card face down (not used in 2p, but for deduction) deck.pop(); // Deal 1 card to each playerHand.push(deck.pop()); botHand.push(deck.pop()); // Draw 1 more for player to start playerDraw(); // Show hands renderHands(); // Update GUI updateGUI(); // Show message showPopup("Your turn!", 1200, function () { // Player's turn currentTurn = 'player'; enablePlayerPlay(); }); } // Draw a card for player function playerDraw() { if (deck.length > 0) { playerHand.push(deck.pop()); } } // Draw a card for bot function botDraw() { if (deck.length > 0) { botHand.push(deck.pop()); } } // Render hands function renderHands() { // Remove old for (var i = 0; i < playerCardNodes.length; ++i) game.removeChild(playerCardNodes[i]); for (var i = 0; i < botCardNodes.length; ++i) game.removeChild(botCardNodes[i]); playerCardNodes = []; botCardNodes = []; // Player hand (always face up) for (var i = 0; i < playerHand.length; ++i) { var c = new Card(); c.setCard(playerHand[i], true); c.owner = 'player'; c.index = i; c.x = handX + (i - 0.5) * handSpacing; c.y = playerHandY; c.scaleX = c.scaleY = 1.1; game.addChild(c); playerCardNodes.push(c); } // Bot hand (always 1 card, face down) for (var i = 0; i < botHand.length; ++i) { var c = new Card(); c.setCard(botHand[i], false); c.owner = 'bot'; c.index = i; c.x = handX + (i - 0.5) * handSpacing; c.y = botHandY; c.scaleX = c.scaleY = 1.1; game.addChild(c); botCardNodes.push(c); } // Discard pile for (var i = 0; i < discardNodes.length; ++i) game.removeChild(discardNodes[i]); discardNodes = []; for (var i = 0; i < discardPile.length; ++i) { var c = new Card(); c.setCard(discardPile[i], true); c.x = 2048 - 200; c.y = 2732 / 2 + (i - 2) * 40; c.scaleX = c.scaleY = 0.7; game.addChild(c); discardNodes.push(c); } } // Show popup message function showPopup(msg, duration, cb) { if (popup) { game.removeChild(popup); popup = null; } popup = new Popup(); popup.setText(msg); popup.x = 2048 / 2; popup.y = 2732 / 2; game.addChild(popup); if (duration) { LK.setTimeout(function () { if (popup) { game.removeChild(popup); popup = null; } if (cb) cb(); }, duration); } } // Enable player to play a card function enablePlayerPlay() { // Only allow if not out if (playerOut || roundOver) return; // Add .down event to player's cards for (var i = 0; i < playerCardNodes.length; ++i) { (function (idx) { playerCardNodes[idx].down = function (x, y, obj) { // Play this card playerPlayCard(idx); }; })(i); } } // Disable player input function disablePlayerPlay() { for (var i = 0; i < playerCardNodes.length; ++i) { playerCardNodes[i].down = undefined; } } // Player plays a card function playerPlayCard(idx) { if (playerOut || roundOver) return; disablePlayerPlay(); // Play card at idx var card = playerHand[idx]; var otherIdx = idx === 0 ? 1 : 0; var keepCard = playerHand[otherIdx]; // Countess rule: must play Countess if holding King or Prince if (card.id !== 'countess' && keepCard && keepCard.id === 'countess' && (card.id === 'king' || card.id === 'prince')) { showPopup("You must play Countess!", 1200, function () { enablePlayerPlay(); }); return; } // Remove card from hand var played = playerHand.splice(idx, 1)[0]; discardPile.push(played); // Animate card to discard renderHands(); // Card effect resolveCardEffect('player', played, function () { // If not out, keep card in hand if (!playerOut && keepCard) { playerHand = [keepCard]; } renderHands(); // Next: bot's turn if (!roundOver) { LK.setTimeout(function () { botTurn(); }, 900); } }); } // Bot's turn function botTurn() { if (botOut || roundOver) return; // Draw botDraw(); renderHands(); // Choose card to play var idx = botChooseCard(); var card = botHand[idx]; var otherIdx = idx === 0 ? 1 : 0; var keepCard = botHand[otherIdx]; // Countess rule if (card.id !== 'countess' && keepCard && keepCard.id === 'countess' && (card.id === 'king' || card.id === 'prince')) { idx = otherIdx; card = botHand[idx]; otherIdx = idx === 0 ? 1 : 0; keepCard = botHand[otherIdx]; } // Remove card from hand var played = botHand.splice(idx, 1)[0]; discardPile.push(played); // Animate card to discard renderHands(); // Card effect resolveCardEffect('bot', played, function () { // If not out, keep card in hand if (!botOut && keepCard) { botHand = [keepCard]; } renderHands(); // Next: player's turn if (!roundOver) { LK.setTimeout(function () { playerDraw(); renderHands(); enablePlayerPlay(); }, 900); } }); } // Bot chooses which card to play (simple AI) function botChooseCard() { // If must play Countess if (botHand.length == 2) { var c0 = botHand[0], c1 = botHand[1]; if (c0.id === 'countess' && (c1.id === 'king' || c1.id === 'prince')) return 0; if (c1.id === 'countess' && (c0.id === 'king' || c0.id === 'prince')) return 1; } // Prefer not to play Princess for (var i = 0; i < botHand.length; ++i) { if (botHand[i].id !== 'princess') return i; } // Otherwise, play first return 0; } // Card effect resolution function resolveCardEffect(who, card, cb) { // who: 'player' or 'bot' // card: card object // cb: callback after effect // Helper: get opponent function getOpponent() { return who === 'player' ? 'bot' : 'player'; } function getHand(who) { return who === 'player' ? playerHand : botHand; } function setProtected(who, val) { if (who === 'player') playerProtected = val;else botProtected = val; } function isProtected(who) { return who === 'player' ? playerProtected : botProtected; } function setOut(who) { if (who === 'player') playerOut = true;else botOut = true; } // Card effects if (card.id === 'guard') { // Guess a card (not Guard) if (who === 'player') { // Show options to guess showGuardGuess(function (guessId) { if (botProtected) { showPopup("Bot is protected!", 1000, cb); } else if (botHand[0].id === guessId) { showPopup("Correct! Bot had " + botHand[0].name + ".", 1200, function () { setOut('bot'); endRound(); cb(); }); return; } else { showPopup("Wrong guess.", 1000, cb); } }); return; } else { // Bot guesses randomly (not Guard) if (playerProtected) { showPopup("You are protected!", 1000, cb); } else { // Guess random card (not Guard) var guessable = ['priest', 'baron', 'handmaid', 'prince', 'king', 'countess', 'princess']; var guessId = guessable[Math.floor(Math.random() * guessable.length)]; if (playerHand[0].id === guessId) { showPopup("Bot guessed " + CARD_DECK.filter(function (c) { return c.id === guessId; })[0].name + " and was right!", 1200, function () { setOut('player'); endRound(); cb(); }); return; } else { showPopup("Bot guessed " + CARD_DECK.filter(function (c) { return c.id === guessId; })[0].name + " and was wrong.", 1000, cb); } } } } else if (card.id === 'priest') { // See opponent's hand if (who === 'player') { if (botProtected) { showPopup("Bot is protected!", 1000, cb); } else { showPopup("Bot has " + botHand[0].name + ".", 1500, cb); } } else { if (playerProtected) { showPopup("You are protected!", 1000, cb); } else { showPopup("Bot looks at your hand.", 1000, cb); } } } else if (card.id === 'baron') { // Compare hands, lower is out if (who === 'player') { if (botProtected) { showPopup("Bot is protected!", 1000, cb); } else { var p = playerHand[0].value, b = botHand[0].value; if (p > b) { showPopup("You win! (" + playerHand[0].name + " > " + botHand[0].name + ")", 1200, function () { setOut('bot'); endRound(); cb(); }); return; } else if (b > p) { showPopup("You lose! (" + playerHand[0].name + " < " + botHand[0].name + ")", 1200, function () { setOut('player'); endRound(); cb(); }); return; } else { showPopup("Tie! (" + playerHand[0].name + " = " + botHand[0].name + ")", 1000, cb); } } } else { if (playerProtected) { showPopup("You are protected!", 1000, cb); } else { var p = playerHand[0].value, b = botHand[0].value; if (b > p) { showPopup("Bot wins! (" + botHand[0].name + " > " + playerHand[0].name + ")", 1200, function () { setOut('player'); endRound(); cb(); }); return; } else if (p > b) { showPopup("Bot loses! (" + botHand[0].name + " < " + playerHand[0].name + ")", 1200, function () { setOut('bot'); endRound(); cb(); }); return; } else { showPopup("Tie! (" + playerHand[0].name + " = " + botHand[0].name + ")", 1000, cb); } } } } else if (card.id === 'handmaid') { // Immunity until next turn setProtected(who, true); showPopup((who === 'player' ? "You" : "Bot") + " are protected until next turn.", 1000, cb); } else if (card.id === 'prince') { // Choose a player to discard hand if (who === 'player') { // If bot is protected, must target self if (botProtected && !playerProtected) { showPopup("Bot is protected. You discard your hand.", 1000, function () { princeDiscard('player', cb); }); } else if (playerProtected && !botProtected) { showPopup("You are protected. Bot discards hand.", 1000, function () { princeDiscard('bot', cb); }); } else if (playerProtected && botProtected) { showPopup("Both protected. Nothing happens.", 1000, cb); } else { // Choose target showPrinceTarget(function (target) { princeDiscard(target, cb); }); return; } } else { // Bot: prefer to target player if not protected if (!playerProtected) { showPopup("Bot makes you discard your hand.", 1000, function () { princeDiscard('player', cb); }); } else if (!botProtected) { showPopup("Bot discards its own hand.", 1000, function () { princeDiscard('bot', cb); }); } else { showPopup("Both protected. Nothing happens.", 1000, cb); } } } else if (card.id === 'king') { // Trade hands if (who === 'player') { if (botProtected) { showPopup("Bot is protected!", 1000, cb); } else { var tmp = playerHand[0]; playerHand[0] = botHand[0]; botHand[0] = tmp; showPopup("You swapped hands!", 1000, cb); } } else { if (playerProtected) { showPopup("You are protected!", 1000, cb); } else { var tmp = playerHand[0]; playerHand[0] = botHand[0]; botHand[0] = tmp; showPopup("Bot swapped hands!", 1000, cb); } } } else if (card.id === 'countess') { // No effect showPopup((who === 'player' ? "You" : "Bot") + " played Countess.", 1000, cb); } else if (card.id === 'princess') { // If discarded, out showPopup((who === 'player' ? "You" : "Bot") + " discarded the Princess and is out!", 1200, function () { setOut(who); endRound(); cb(); }); return; } else { showPopup("No effect.", 1000, cb); } } // Guard guess UI function showGuardGuess(cb) { // Show options for player to guess (not Guard) var opts = ['priest', 'baron', 'handmaid', 'prince', 'king', 'countess', 'princess']; var buttons = []; var y0 = 2732 / 2 - 120; for (var i = 0; i < opts.length; ++i) { (function (idx) { var c = new Card(); var cardData = CARD_DECK.filter(function (cd) { return cd.id === opts[idx]; })[0]; c.setCard(cardData, true); c.x = 2048 / 2 - (opts.length / 2 - idx) * 180; c.y = y0; c.scaleX = c.scaleY = 0.7; c.down = function (x, y, obj) { // Remove all for (var j = 0; j < buttons.length; ++j) game.removeChild(buttons[j]); cb(opts[idx]); }; game.addChild(c); buttons.push(c); })(i); } } // Prince target UI function showPrinceTarget(cb) { var buttons = []; var y0 = 2732 / 2 - 120; // Player var c1 = new Card(); c1.setCard(playerHand[0], true); c1.x = 2048 / 2 - 120; c1.y = y0; c1.scaleX = c1.scaleY = 0.8; c1.down = function (x, y, obj) { for (var j = 0; j < buttons.length; ++j) game.removeChild(buttons[j]); cb('player'); }; game.addChild(c1); buttons.push(c1); // Bot (face down) var c2 = new Card(); c2.setCard(botHand[0], false); c2.x = 2048 / 2 + 120; c2.y = y0; c2.scaleX = c2.scaleY = 0.8; c2.down = function (x, y, obj) { for (var j = 0; j < buttons.length; ++j) game.removeChild(buttons[j]); cb('bot'); }; game.addChild(c2); buttons.push(c2); } // Prince discard effect function princeDiscard(who, cb) { if (who === 'player') { var card = playerHand[0]; discardPile.push(card); if (card.id === 'princess') { showPopup("You discarded the Princess and are out!", 1200, function () { playerOut = true; endRound(); cb(); }); return; } else { // Draw new card if (deck.length > 0) { playerHand[0] = deck.pop(); showPopup("You drew a new card.", 1000, cb); } else { playerHand = []; showPopup("No cards left to draw.", 1000, cb); } } } else { var card = botHand[0]; discardPile.push(card); if (card.id === 'princess') { showPopup("Bot discarded the Princess and is out!", 1200, function () { botOut = true; endRound(); cb(); }); return; } else { // Draw new card if (deck.length > 0) { botHand[0] = deck.pop(); showPopup("Bot drew a new card.", 1000, cb); } else { botHand = []; showPopup("No cards left to draw.", 1000, cb); } } } } // End round: check for winner function endRound() { roundOver = true; disablePlayerPlay(); // Reveal bot's hand for (var i = 0; i < botCardNodes.length; ++i) { botCardNodes[i].flip(true); } // Who wins? var winner = null; if (playerOut && botOut) { winner = null; } else if (playerOut) { winner = 'bot'; } else if (botOut) { winner = 'player'; } else if (deck.length === 0) { // Compare hands if (playerHand[0].value > botHand[0].value) winner = 'player';else if (botHand[0].value > playerHand[0].value) winner = 'bot';else winner = null; } // Update score if (winner === 'player') { playerScore += 1; updateGUI(); showPopup("You win the round!", 1800, function () { checkGameEnd(); }); } else if (winner === 'bot') { botScore += 1; updateGUI(); showPopup("Bot wins the round!", 1800, function () { checkGameEnd(); }); } else { showPopup("Round is a tie!", 1500, function () { checkGameEnd(); }); } } // Check for game end function checkGameEnd() { if (playerScore >= roundTarget) { LK.setScore(playerScore); LK.showYouWin(); } else if (botScore >= roundTarget) { LK.setScore(playerScore); LK.showGameOver(); } else { // Start next round startRound(); } } // On every update, clear protection if needed game.update = function () { // Remove protection at start of player's/bot's turn if (currentTurn === 'player' && playerProtected) playerProtected = false; if (currentTurn === 'bot' && botProtected) botProtected = false; }; // Setup GUI and start game setupGUI(); updateGUI(); startRound();
===================================================================
--- original.js
+++ change.js
@@ -1,6 +1,846 @@
-/****
+/****
+* Plugins
+****/
+var tween = LK.import("@upit/tween.v1");
+
+/****
+* Classes
+****/
+// Card class: represents a card in hand or on table
+var Card = Container.expand(function () {
+ var self = Container.call(this);
+ // Card data: {id, name, value, desc}
+ self.cardData = null;
+ self.isFaceUp = true;
+ self.owner = null; // 'player' or 'bot'
+ self.index = 0; // hand index
+ // Card graphics
+ self.cardAsset = null;
+ self.textName = null;
+ self.textValue = null;
+ // Set card data and visuals
+ self.setCard = function (cardData, faceUp) {
+ self.cardData = cardData;
+ self.isFaceUp = faceUp;
+ self.removeChildren();
+ var assetId = faceUp ? 'card' + cardData.value : 'cardBack';
+ self.cardAsset = self.attachAsset(assetId, {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ if (faceUp) {
+ self.textName = new Text2(cardData.name, {
+ size: 48,
+ fill: 0x333333
+ });
+ self.textName.anchor.set(0.5, 0);
+ self.textName.x = 0;
+ self.textName.y = -180;
+ self.addChild(self.textName);
+ self.textValue = new Text2(cardData.value + '', {
+ size: 64,
+ fill: 0x000000
+ });
+ self.textValue.anchor.set(0.5, 0.5);
+ self.textValue.x = 0;
+ self.textValue.y = 0;
+ self.addChild(self.textValue);
+ }
+ };
+ // Flip card face up/down
+ self.flip = function (faceUp) {
+ if (self.cardData) {
+ self.setCard(self.cardData, faceUp);
+ }
+ };
+ return self;
+});
+// Simple popup for messages
+var Popup = Container.expand(function () {
+ var self = Container.call(this);
+ self.bg = self.attachAsset('cardBack', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ scaleX: 1.5,
+ scaleY: 1
+ });
+ self.bg.tint = 0x222222;
+ self.bg.alpha = 0.95;
+ self.text = new Text2('', {
+ size: 72,
+ fill: "#fff"
+ });
+ self.text.anchor.set(0.5, 0.5);
+ self.text.x = 0;
+ self.text.y = 0;
+ self.addChild(self.text);
+ self.setText = function (str) {
+ self.text.setText(str);
+ };
+ return self;
+});
+
+/****
* Initialize Game
-****/
+****/
var game = new LK.Game({
- backgroundColor: 0x000000
-});
\ No newline at end of file
+ backgroundColor: 0x2d2d44
+});
+
+/****
+* Game Code
+****/
+// Simple heart for score
+// Back of card
+// Princess
+// Countess
+// King
+// Prince
+// Handmaid
+// Baron
+// Priest
+// Guard
+// Card assets (simple colored boxes for each card type)
+// Card definitions (Love Letter 8-card deck)
+var CARD_DECK = [
+// id, name, value, desc, count
+{
+ id: 'guard',
+ name: 'Guard',
+ value: 1,
+ desc: 'Guess a card',
+ count: 5
+}, {
+ id: 'priest',
+ name: 'Priest',
+ value: 2,
+ desc: 'See hand',
+ count: 2
+}, {
+ id: 'baron',
+ name: 'Baron',
+ value: 3,
+ desc: 'Compare hands',
+ count: 2
+}, {
+ id: 'handmaid',
+ name: 'Handmaid',
+ value: 4,
+ desc: 'Immunity',
+ count: 2
+}, {
+ id: 'prince',
+ name: 'Prince',
+ value: 5,
+ desc: 'Discard hand',
+ count: 2
+}, {
+ id: 'king',
+ name: 'King',
+ value: 6,
+ desc: 'Trade hands',
+ count: 1
+}, {
+ id: 'countess',
+ name: 'Countess',
+ value: 7,
+ desc: 'Must play if with King/Prince',
+ count: 1
+}, {
+ id: 'princess',
+ name: 'Princess',
+ value: 8,
+ desc: 'Lose if discarded',
+ count: 1
+}];
+// Build deck array
+function buildDeck() {
+ var deck = [];
+ for (var i = 0; i < CARD_DECK.length; ++i) {
+ for (var j = 0; j < CARD_DECK[i].count; ++j) {
+ deck.push({
+ id: CARD_DECK[i].id,
+ name: CARD_DECK[i].name,
+ value: CARD_DECK[i].value,
+ desc: CARD_DECK[i].desc
+ });
+ }
+ }
+ return deck;
+}
+// Shuffle deck
+function shuffle(deck) {
+ for (var i = deck.length - 1; i > 0; --i) {
+ var j = Math.floor(Math.random() * (i + 1));
+ var t = deck[i];
+ deck[i] = deck[j];
+ deck[j] = t;
+ }
+}
+// Game state
+var playerHand = [];
+var botHand = [];
+var deck = [];
+var discardPile = [];
+var playerProtected = false;
+var botProtected = false;
+var playerOut = false;
+var botOut = false;
+var playerScore = 0;
+var botScore = 0;
+var roundOver = false;
+var currentTurn = 'player'; // 'player' or 'bot'
+var popup = null;
+var playerCardNodes = [];
+var botCardNodes = [];
+var discardNodes = [];
+var heartsPlayer = [];
+var heartsBot = [];
+var guiText = null;
+var roundTarget = 3; // First to 3 wins
+// Card positions
+var playerHandY = 2732 - 350;
+var botHandY = 350;
+var handX = 2048 / 2;
+var handSpacing = 400;
+// GUI setup
+function setupGUI() {
+ // Remove old
+ if (guiText) LK.gui.top.removeChild(guiText);
+ guiText = new Text2('', {
+ size: 64,
+ fill: "#fff"
+ });
+ guiText.anchor.set(0.5, 0);
+ LK.gui.top.addChild(guiText);
+ // Hearts for score
+ for (var i = 0; i < heartsPlayer.length; ++i) LK.gui.top.removeChild(heartsPlayer[i]);
+ for (var i = 0; i < heartsBot.length; ++i) LK.gui.top.removeChild(heartsBot[i]);
+ heartsPlayer = [];
+ heartsBot = [];
+ for (var i = 0; i < roundTarget; ++i) {
+ var h1 = LK.getAsset('heart', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ h1.x = 2048 / 2 - 200 + i * 60;
+ h1.y = 120;
+ LK.gui.top.addChild(h1);
+ heartsPlayer.push(h1);
+ var h2 = LK.getAsset('heart', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ h2.x = 2048 / 2 - 200 + i * 60;
+ h2.y = 60;
+ LK.gui.top.addChild(h2);
+ heartsBot.push(h2);
+ }
+}
+// Update GUI
+function updateGUI() {
+ guiText.setText("You: " + playerScore + " Bot: " + botScore + " First to " + roundTarget);
+ for (var i = 0; i < roundTarget; ++i) {
+ heartsPlayer[i].alpha = i < playerScore ? 1 : 0.3;
+ heartsBot[i].alpha = i < botScore ? 1 : 0.3;
+ }
+}
+// Start a new round
+function startRound() {
+ // Reset state
+ deck = buildDeck();
+ shuffle(deck);
+ discardPile = [];
+ playerHand = [];
+ botHand = [];
+ playerProtected = false;
+ botProtected = false;
+ playerOut = false;
+ botOut = false;
+ roundOver = false;
+ currentTurn = 'player';
+ // Remove old cards
+ for (var i = 0; i < playerCardNodes.length; ++i) game.removeChild(playerCardNodes[i]);
+ for (var i = 0; i < botCardNodes.length; ++i) game.removeChild(botCardNodes[i]);
+ for (var i = 0; i < discardNodes.length; ++i) game.removeChild(discardNodes[i]);
+ playerCardNodes = [];
+ botCardNodes = [];
+ discardNodes = [];
+ // Remove popup
+ if (popup) {
+ game.removeChild(popup);
+ popup = null;
+ }
+ // Remove one card face down (not used in 2p, but for deduction)
+ deck.pop();
+ // Deal 1 card to each
+ playerHand.push(deck.pop());
+ botHand.push(deck.pop());
+ // Draw 1 more for player to start
+ playerDraw();
+ // Show hands
+ renderHands();
+ // Update GUI
+ updateGUI();
+ // Show message
+ showPopup("Your turn!", 1200, function () {
+ // Player's turn
+ currentTurn = 'player';
+ enablePlayerPlay();
+ });
+}
+// Draw a card for player
+function playerDraw() {
+ if (deck.length > 0) {
+ playerHand.push(deck.pop());
+ }
+}
+// Draw a card for bot
+function botDraw() {
+ if (deck.length > 0) {
+ botHand.push(deck.pop());
+ }
+}
+// Render hands
+function renderHands() {
+ // Remove old
+ for (var i = 0; i < playerCardNodes.length; ++i) game.removeChild(playerCardNodes[i]);
+ for (var i = 0; i < botCardNodes.length; ++i) game.removeChild(botCardNodes[i]);
+ playerCardNodes = [];
+ botCardNodes = [];
+ // Player hand (always face up)
+ for (var i = 0; i < playerHand.length; ++i) {
+ var c = new Card();
+ c.setCard(playerHand[i], true);
+ c.owner = 'player';
+ c.index = i;
+ c.x = handX + (i - 0.5) * handSpacing;
+ c.y = playerHandY;
+ c.scaleX = c.scaleY = 1.1;
+ game.addChild(c);
+ playerCardNodes.push(c);
+ }
+ // Bot hand (always 1 card, face down)
+ for (var i = 0; i < botHand.length; ++i) {
+ var c = new Card();
+ c.setCard(botHand[i], false);
+ c.owner = 'bot';
+ c.index = i;
+ c.x = handX + (i - 0.5) * handSpacing;
+ c.y = botHandY;
+ c.scaleX = c.scaleY = 1.1;
+ game.addChild(c);
+ botCardNodes.push(c);
+ }
+ // Discard pile
+ for (var i = 0; i < discardNodes.length; ++i) game.removeChild(discardNodes[i]);
+ discardNodes = [];
+ for (var i = 0; i < discardPile.length; ++i) {
+ var c = new Card();
+ c.setCard(discardPile[i], true);
+ c.x = 2048 - 200;
+ c.y = 2732 / 2 + (i - 2) * 40;
+ c.scaleX = c.scaleY = 0.7;
+ game.addChild(c);
+ discardNodes.push(c);
+ }
+}
+// Show popup message
+function showPopup(msg, duration, cb) {
+ if (popup) {
+ game.removeChild(popup);
+ popup = null;
+ }
+ popup = new Popup();
+ popup.setText(msg);
+ popup.x = 2048 / 2;
+ popup.y = 2732 / 2;
+ game.addChild(popup);
+ if (duration) {
+ LK.setTimeout(function () {
+ if (popup) {
+ game.removeChild(popup);
+ popup = null;
+ }
+ if (cb) cb();
+ }, duration);
+ }
+}
+// Enable player to play a card
+function enablePlayerPlay() {
+ // Only allow if not out
+ if (playerOut || roundOver) return;
+ // Add .down event to player's cards
+ for (var i = 0; i < playerCardNodes.length; ++i) {
+ (function (idx) {
+ playerCardNodes[idx].down = function (x, y, obj) {
+ // Play this card
+ playerPlayCard(idx);
+ };
+ })(i);
+ }
+}
+// Disable player input
+function disablePlayerPlay() {
+ for (var i = 0; i < playerCardNodes.length; ++i) {
+ playerCardNodes[i].down = undefined;
+ }
+}
+// Player plays a card
+function playerPlayCard(idx) {
+ if (playerOut || roundOver) return;
+ disablePlayerPlay();
+ // Play card at idx
+ var card = playerHand[idx];
+ var otherIdx = idx === 0 ? 1 : 0;
+ var keepCard = playerHand[otherIdx];
+ // Countess rule: must play Countess if holding King or Prince
+ if (card.id !== 'countess' && keepCard && keepCard.id === 'countess' && (card.id === 'king' || card.id === 'prince')) {
+ showPopup("You must play Countess!", 1200, function () {
+ enablePlayerPlay();
+ });
+ return;
+ }
+ // Remove card from hand
+ var played = playerHand.splice(idx, 1)[0];
+ discardPile.push(played);
+ // Animate card to discard
+ renderHands();
+ // Card effect
+ resolveCardEffect('player', played, function () {
+ // If not out, keep card in hand
+ if (!playerOut && keepCard) {
+ playerHand = [keepCard];
+ }
+ renderHands();
+ // Next: bot's turn
+ if (!roundOver) {
+ LK.setTimeout(function () {
+ botTurn();
+ }, 900);
+ }
+ });
+}
+// Bot's turn
+function botTurn() {
+ if (botOut || roundOver) return;
+ // Draw
+ botDraw();
+ renderHands();
+ // Choose card to play
+ var idx = botChooseCard();
+ var card = botHand[idx];
+ var otherIdx = idx === 0 ? 1 : 0;
+ var keepCard = botHand[otherIdx];
+ // Countess rule
+ if (card.id !== 'countess' && keepCard && keepCard.id === 'countess' && (card.id === 'king' || card.id === 'prince')) {
+ idx = otherIdx;
+ card = botHand[idx];
+ otherIdx = idx === 0 ? 1 : 0;
+ keepCard = botHand[otherIdx];
+ }
+ // Remove card from hand
+ var played = botHand.splice(idx, 1)[0];
+ discardPile.push(played);
+ // Animate card to discard
+ renderHands();
+ // Card effect
+ resolveCardEffect('bot', played, function () {
+ // If not out, keep card in hand
+ if (!botOut && keepCard) {
+ botHand = [keepCard];
+ }
+ renderHands();
+ // Next: player's turn
+ if (!roundOver) {
+ LK.setTimeout(function () {
+ playerDraw();
+ renderHands();
+ enablePlayerPlay();
+ }, 900);
+ }
+ });
+}
+// Bot chooses which card to play (simple AI)
+function botChooseCard() {
+ // If must play Countess
+ if (botHand.length == 2) {
+ var c0 = botHand[0],
+ c1 = botHand[1];
+ if (c0.id === 'countess' && (c1.id === 'king' || c1.id === 'prince')) return 0;
+ if (c1.id === 'countess' && (c0.id === 'king' || c0.id === 'prince')) return 1;
+ }
+ // Prefer not to play Princess
+ for (var i = 0; i < botHand.length; ++i) {
+ if (botHand[i].id !== 'princess') return i;
+ }
+ // Otherwise, play first
+ return 0;
+}
+// Card effect resolution
+function resolveCardEffect(who, card, cb) {
+ // who: 'player' or 'bot'
+ // card: card object
+ // cb: callback after effect
+ // Helper: get opponent
+ function getOpponent() {
+ return who === 'player' ? 'bot' : 'player';
+ }
+ function getHand(who) {
+ return who === 'player' ? playerHand : botHand;
+ }
+ function setProtected(who, val) {
+ if (who === 'player') playerProtected = val;else botProtected = val;
+ }
+ function isProtected(who) {
+ return who === 'player' ? playerProtected : botProtected;
+ }
+ function setOut(who) {
+ if (who === 'player') playerOut = true;else botOut = true;
+ }
+ // Card effects
+ if (card.id === 'guard') {
+ // Guess a card (not Guard)
+ if (who === 'player') {
+ // Show options to guess
+ showGuardGuess(function (guessId) {
+ if (botProtected) {
+ showPopup("Bot is protected!", 1000, cb);
+ } else if (botHand[0].id === guessId) {
+ showPopup("Correct! Bot had " + botHand[0].name + ".", 1200, function () {
+ setOut('bot');
+ endRound();
+ cb();
+ });
+ return;
+ } else {
+ showPopup("Wrong guess.", 1000, cb);
+ }
+ });
+ return;
+ } else {
+ // Bot guesses randomly (not Guard)
+ if (playerProtected) {
+ showPopup("You are protected!", 1000, cb);
+ } else {
+ // Guess random card (not Guard)
+ var guessable = ['priest', 'baron', 'handmaid', 'prince', 'king', 'countess', 'princess'];
+ var guessId = guessable[Math.floor(Math.random() * guessable.length)];
+ if (playerHand[0].id === guessId) {
+ showPopup("Bot guessed " + CARD_DECK.filter(function (c) {
+ return c.id === guessId;
+ })[0].name + " and was right!", 1200, function () {
+ setOut('player');
+ endRound();
+ cb();
+ });
+ return;
+ } else {
+ showPopup("Bot guessed " + CARD_DECK.filter(function (c) {
+ return c.id === guessId;
+ })[0].name + " and was wrong.", 1000, cb);
+ }
+ }
+ }
+ } else if (card.id === 'priest') {
+ // See opponent's hand
+ if (who === 'player') {
+ if (botProtected) {
+ showPopup("Bot is protected!", 1000, cb);
+ } else {
+ showPopup("Bot has " + botHand[0].name + ".", 1500, cb);
+ }
+ } else {
+ if (playerProtected) {
+ showPopup("You are protected!", 1000, cb);
+ } else {
+ showPopup("Bot looks at your hand.", 1000, cb);
+ }
+ }
+ } else if (card.id === 'baron') {
+ // Compare hands, lower is out
+ if (who === 'player') {
+ if (botProtected) {
+ showPopup("Bot is protected!", 1000, cb);
+ } else {
+ var p = playerHand[0].value,
+ b = botHand[0].value;
+ if (p > b) {
+ showPopup("You win! (" + playerHand[0].name + " > " + botHand[0].name + ")", 1200, function () {
+ setOut('bot');
+ endRound();
+ cb();
+ });
+ return;
+ } else if (b > p) {
+ showPopup("You lose! (" + playerHand[0].name + " < " + botHand[0].name + ")", 1200, function () {
+ setOut('player');
+ endRound();
+ cb();
+ });
+ return;
+ } else {
+ showPopup("Tie! (" + playerHand[0].name + " = " + botHand[0].name + ")", 1000, cb);
+ }
+ }
+ } else {
+ if (playerProtected) {
+ showPopup("You are protected!", 1000, cb);
+ } else {
+ var p = playerHand[0].value,
+ b = botHand[0].value;
+ if (b > p) {
+ showPopup("Bot wins! (" + botHand[0].name + " > " + playerHand[0].name + ")", 1200, function () {
+ setOut('player');
+ endRound();
+ cb();
+ });
+ return;
+ } else if (p > b) {
+ showPopup("Bot loses! (" + botHand[0].name + " < " + playerHand[0].name + ")", 1200, function () {
+ setOut('bot');
+ endRound();
+ cb();
+ });
+ return;
+ } else {
+ showPopup("Tie! (" + playerHand[0].name + " = " + botHand[0].name + ")", 1000, cb);
+ }
+ }
+ }
+ } else if (card.id === 'handmaid') {
+ // Immunity until next turn
+ setProtected(who, true);
+ showPopup((who === 'player' ? "You" : "Bot") + " are protected until next turn.", 1000, cb);
+ } else if (card.id === 'prince') {
+ // Choose a player to discard hand
+ if (who === 'player') {
+ // If bot is protected, must target self
+ if (botProtected && !playerProtected) {
+ showPopup("Bot is protected. You discard your hand.", 1000, function () {
+ princeDiscard('player', cb);
+ });
+ } else if (playerProtected && !botProtected) {
+ showPopup("You are protected. Bot discards hand.", 1000, function () {
+ princeDiscard('bot', cb);
+ });
+ } else if (playerProtected && botProtected) {
+ showPopup("Both protected. Nothing happens.", 1000, cb);
+ } else {
+ // Choose target
+ showPrinceTarget(function (target) {
+ princeDiscard(target, cb);
+ });
+ return;
+ }
+ } else {
+ // Bot: prefer to target player if not protected
+ if (!playerProtected) {
+ showPopup("Bot makes you discard your hand.", 1000, function () {
+ princeDiscard('player', cb);
+ });
+ } else if (!botProtected) {
+ showPopup("Bot discards its own hand.", 1000, function () {
+ princeDiscard('bot', cb);
+ });
+ } else {
+ showPopup("Both protected. Nothing happens.", 1000, cb);
+ }
+ }
+ } else if (card.id === 'king') {
+ // Trade hands
+ if (who === 'player') {
+ if (botProtected) {
+ showPopup("Bot is protected!", 1000, cb);
+ } else {
+ var tmp = playerHand[0];
+ playerHand[0] = botHand[0];
+ botHand[0] = tmp;
+ showPopup("You swapped hands!", 1000, cb);
+ }
+ } else {
+ if (playerProtected) {
+ showPopup("You are protected!", 1000, cb);
+ } else {
+ var tmp = playerHand[0];
+ playerHand[0] = botHand[0];
+ botHand[0] = tmp;
+ showPopup("Bot swapped hands!", 1000, cb);
+ }
+ }
+ } else if (card.id === 'countess') {
+ // No effect
+ showPopup((who === 'player' ? "You" : "Bot") + " played Countess.", 1000, cb);
+ } else if (card.id === 'princess') {
+ // If discarded, out
+ showPopup((who === 'player' ? "You" : "Bot") + " discarded the Princess and is out!", 1200, function () {
+ setOut(who);
+ endRound();
+ cb();
+ });
+ return;
+ } else {
+ showPopup("No effect.", 1000, cb);
+ }
+}
+// Guard guess UI
+function showGuardGuess(cb) {
+ // Show options for player to guess (not Guard)
+ var opts = ['priest', 'baron', 'handmaid', 'prince', 'king', 'countess', 'princess'];
+ var buttons = [];
+ var y0 = 2732 / 2 - 120;
+ for (var i = 0; i < opts.length; ++i) {
+ (function (idx) {
+ var c = new Card();
+ var cardData = CARD_DECK.filter(function (cd) {
+ return cd.id === opts[idx];
+ })[0];
+ c.setCard(cardData, true);
+ c.x = 2048 / 2 - (opts.length / 2 - idx) * 180;
+ c.y = y0;
+ c.scaleX = c.scaleY = 0.7;
+ c.down = function (x, y, obj) {
+ // Remove all
+ for (var j = 0; j < buttons.length; ++j) game.removeChild(buttons[j]);
+ cb(opts[idx]);
+ };
+ game.addChild(c);
+ buttons.push(c);
+ })(i);
+ }
+}
+// Prince target UI
+function showPrinceTarget(cb) {
+ var buttons = [];
+ var y0 = 2732 / 2 - 120;
+ // Player
+ var c1 = new Card();
+ c1.setCard(playerHand[0], true);
+ c1.x = 2048 / 2 - 120;
+ c1.y = y0;
+ c1.scaleX = c1.scaleY = 0.8;
+ c1.down = function (x, y, obj) {
+ for (var j = 0; j < buttons.length; ++j) game.removeChild(buttons[j]);
+ cb('player');
+ };
+ game.addChild(c1);
+ buttons.push(c1);
+ // Bot (face down)
+ var c2 = new Card();
+ c2.setCard(botHand[0], false);
+ c2.x = 2048 / 2 + 120;
+ c2.y = y0;
+ c2.scaleX = c2.scaleY = 0.8;
+ c2.down = function (x, y, obj) {
+ for (var j = 0; j < buttons.length; ++j) game.removeChild(buttons[j]);
+ cb('bot');
+ };
+ game.addChild(c2);
+ buttons.push(c2);
+}
+// Prince discard effect
+function princeDiscard(who, cb) {
+ if (who === 'player') {
+ var card = playerHand[0];
+ discardPile.push(card);
+ if (card.id === 'princess') {
+ showPopup("You discarded the Princess and are out!", 1200, function () {
+ playerOut = true;
+ endRound();
+ cb();
+ });
+ return;
+ } else {
+ // Draw new card
+ if (deck.length > 0) {
+ playerHand[0] = deck.pop();
+ showPopup("You drew a new card.", 1000, cb);
+ } else {
+ playerHand = [];
+ showPopup("No cards left to draw.", 1000, cb);
+ }
+ }
+ } else {
+ var card = botHand[0];
+ discardPile.push(card);
+ if (card.id === 'princess') {
+ showPopup("Bot discarded the Princess and is out!", 1200, function () {
+ botOut = true;
+ endRound();
+ cb();
+ });
+ return;
+ } else {
+ // Draw new card
+ if (deck.length > 0) {
+ botHand[0] = deck.pop();
+ showPopup("Bot drew a new card.", 1000, cb);
+ } else {
+ botHand = [];
+ showPopup("No cards left to draw.", 1000, cb);
+ }
+ }
+ }
+}
+// End round: check for winner
+function endRound() {
+ roundOver = true;
+ disablePlayerPlay();
+ // Reveal bot's hand
+ for (var i = 0; i < botCardNodes.length; ++i) {
+ botCardNodes[i].flip(true);
+ }
+ // Who wins?
+ var winner = null;
+ if (playerOut && botOut) {
+ winner = null;
+ } else if (playerOut) {
+ winner = 'bot';
+ } else if (botOut) {
+ winner = 'player';
+ } else if (deck.length === 0) {
+ // Compare hands
+ if (playerHand[0].value > botHand[0].value) winner = 'player';else if (botHand[0].value > playerHand[0].value) winner = 'bot';else winner = null;
+ }
+ // Update score
+ if (winner === 'player') {
+ playerScore += 1;
+ updateGUI();
+ showPopup("You win the round!", 1800, function () {
+ checkGameEnd();
+ });
+ } else if (winner === 'bot') {
+ botScore += 1;
+ updateGUI();
+ showPopup("Bot wins the round!", 1800, function () {
+ checkGameEnd();
+ });
+ } else {
+ showPopup("Round is a tie!", 1500, function () {
+ checkGameEnd();
+ });
+ }
+}
+// Check for game end
+function checkGameEnd() {
+ if (playerScore >= roundTarget) {
+ LK.setScore(playerScore);
+ LK.showYouWin();
+ } else if (botScore >= roundTarget) {
+ LK.setScore(playerScore);
+ LK.showGameOver();
+ } else {
+ // Start next round
+ startRound();
+ }
+}
+// On every update, clear protection if needed
+game.update = function () {
+ // Remove protection at start of player's/bot's turn
+ if (currentTurn === 'player' && playerProtected) playerProtected = false;
+ if (currentTurn === 'bot' && botProtected) botProtected = false;
+};
+// Setup GUI and start game
+setupGUI();
+updateGUI();
+startRound();
\ No newline at end of file