/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Card = Container.expand(function (color, value, type) { var self = Container.call(this); self.color = color; // 'red', 'blue', 'green', 'yellow', 'wild' self.value = value; // 0-9, 'skip', 'reverse', 'draw2', 'wild', 'wild4' self.type = type || 'number'; // 'number', 'action', 'wild' self.playable = false; self.isAnimating = false; // Create card background var colorMap = { 'red': 'cardRed', 'blue': 'cardBlue', 'green': 'cardGreen', 'yellow': 'cardYellow', 'wild': 'cardWild' }; var cardBg = self.attachAsset(colorMap[self.color] || 'cardBack', { anchorX: 0.5, anchorY: 0.5 }); // Add card text var displayText = self.value; if (self.value === 'skip') { displayText = 'S'; } else if (self.value === 'reverse') { displayText = 'R'; } else if (self.value === 'draw2') { displayText = '+2'; } else if (self.value === 'wild') { displayText = 'W'; } else if (self.value === 'wild4') { displayText = '+4'; } var cardText = new Text2(displayText.toString(), { size: 40, fill: self.color === 'yellow' ? "#000000" : "#ffffff" }); cardText.anchor.set(0.5, 0.5); self.addChild(cardText); self.setPlayable = function (playable) { self.playable = playable; cardBg.alpha = playable ? 1.0 : 0.7; // Add subtle glow effect for playable cards if (playable) { tween(cardBg, { tint: 0xffffff }, { duration: 500, easing: tween.easeInOut }); } else { tween.stop(cardBg, { tint: true }); cardBg.tint = 0xffffff; } }; self.canPlayOn = function (otherCard) { if (self.type === 'wild') { return true; } if (self.color === otherCard.color) { return true; } if (self.value === otherCard.value && self.type === 'number' && otherCard.type === 'number') { return true; } return false; }; return self; }); var Player = Container.expand(function (isAI) { var self = Container.call(this); self.cards = []; self.isAI = isAI || false; self.hasCalledUno = false; self.addCard = function (card) { self.cards.push(card); self.addChild(card); // Hide AI cards by showing card backs if (self.isAI) { card.visible = false; // Create card back for AI var cardBack = LK.getAsset('cardBack', { anchorX: 0.5, anchorY: 0.5 }); card.cardBack = cardBack; self.addChild(cardBack); } self.arrangeCards(); }; self.removeCard = function (card) { var index = self.cards.indexOf(card); if (index > -1) { self.cards.splice(index, 1); self.removeChild(card); // Remove card back for AI if (self.isAI && card.cardBack) { self.removeChild(card.cardBack); card.cardBack = null; } self.arrangeCards(); } }; self.arrangeCards = function () { var startX = -(self.cards.length - 1) * 60; for (var i = 0; i < self.cards.length; i++) { var card = self.cards[i]; if (!card.isAnimating) { var targetX = startX + i * 120; var targetY = 0; // Animate card position tween(card, { x: targetX, y: targetY }, { duration: 300, easing: tween.easeOut }); // Animate card back position for AI if (self.isAI && card.cardBack) { tween(card.cardBack, { x: targetX, y: targetY }, { duration: 300, easing: tween.easeOut }); } } } }; self.getPlayableCards = function (topCard) { var playable = []; for (var i = 0; i < self.cards.length; i++) { if (self.cards[i].canPlayOn(topCard)) { playable.push(self.cards[i]); } } return playable; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x0f4c3a }); /**** * Game Code ****/ // Game state variables var deck = []; var discardPile = []; var player; var aiPlayer; var currentPlayer = 0; // 0 = player, 1 = AI var gameDirection = 1; // 1 = forward, -1 = reverse var drawCount = 0; // For draw 2/4 cards var selectedWildColor = null; var gamePhase = 'playing'; // 'playing', 'colorSelect', 'gameOver' var draggedCard = null; var dragOffset = { x: 0, y: 0 }; var waitingForUno = false; // UI elements var deckPileSprite; var discardPileSprite; var topCard; var unoButton; var colorSelectors = []; var gameStateText; var playerCardCountText; var aiCardCountText; // Initialize deck function createDeck() { deck = []; var colors = ['red', 'blue', 'green', 'yellow']; // Number cards (0-9) for (var c = 0; c < colors.length; c++) { var color = colors[c]; // One 0 card per color deck.push(new Card(color, 0, 'number')); // Two of each number 1-9 per color for (var num = 1; num <= 9; num++) { deck.push(new Card(color, num, 'number')); deck.push(new Card(color, num, 'number')); } // Action cards (2 of each per color) deck.push(new Card(color, 'skip', 'action')); deck.push(new Card(color, 'skip', 'action')); deck.push(new Card(color, 'reverse', 'action')); deck.push(new Card(color, 'reverse', 'action')); deck.push(new Card(color, 'draw2', 'action')); deck.push(new Card(color, 'draw2', 'action')); } // Wild cards for (var w = 0; w < 4; w++) { deck.push(new Card('wild', 'wild', 'wild')); deck.push(new Card('wild', 'wild4', 'wild')); } // Shuffle deck for (var i = deck.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = deck[i]; deck[i] = deck[j]; deck[j] = temp; } } function dealCards() { // Deal 7 cards to each player for (var i = 0; i < 7; i++) { player.addCard(deck.pop()); aiPlayer.addCard(deck.pop()); } // Place first card on discard pile do { topCard = deck.pop(); } while (topCard.type === 'wild'); // Ensure first card isn't wild discardPile.push(topCard); game.addChild(topCard); topCard.x = 1024 + 200; topCard.y = 1366; } function updatePlayableCards() { var currentTopCard = discardPile[discardPile.length - 1]; // Update player cards for (var i = 0; i < player.cards.length; i++) { var card = player.cards[i]; var canPlay = card.canPlayOn(currentTopCard); if (selectedWildColor && currentTopCard.type === 'wild') { canPlay = card.color === selectedWildColor || card.type === 'wild'; } card.setPlayable(canPlay && currentPlayer === 0 && gamePhase === 'playing'); } } function drawCard(targetPlayer) { if (deck.length === 0) { // Reshuffle discard pile into deck var currentTop = discardPile.pop(); deck = discardPile.slice(); discardPile = [currentTop]; // Shuffle new deck for (var i = deck.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = deck[i]; deck[i] = deck[j]; deck[j] = temp; } } if (deck.length > 0) { var card = deck.pop(); // Start card at deck position card.x = 1024 - 200; card.y = 1366; // Add animation when drawing card.isAnimating = true; tween(card, { scaleX: 1.2, scaleY: 1.2 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(card, { scaleX: 1.0, scaleY: 1.0 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { card.isAnimating = false; targetPlayer.addCard(card); } }); } }); LK.getSound('cardDraw').play(); return card; } return null; } function playCard(card, playerObj) { // Remove card from player playerObj.removeCard(card); // Add to discard pile discardPile.push(card); // Make AI card visible when played and ensure proper setup if (playerObj.isAI) { card.visible = true; // Reset any transformations that might affect visibility card.alpha = 1.0; card.scaleX = 1.0; card.scaleY = 1.0; } // Animate card to discard pile card.isAnimating = true; tween(card, { x: 1024 + 200, y: 1366, scaleX: 1.0, scaleY: 1.0 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { card.isAnimating = false; // Remove old top card but keep it in discard pile if (topCard && topCard !== card && topCard.parent) { topCard.parent.removeChild(topCard); } topCard = card; // Ensure the new top card is visible and properly positioned topCard.visible = true; topCard.x = 1024 + 200; topCard.y = 1366; if (!topCard.parent) { game.addChild(topCard); } // Handle special cards handleCardEffect(card, playerObj); // Check for Uno if (playerObj.cards.length === 1 && !playerObj.hasCalledUno) { waitingForUno = true; if (playerObj === player) { showUnoButton(); } else { // AI automatically calls Uno LK.setTimeout(function () { playerObj.hasCalledUno = true; waitingForUno = false; LK.getSound('unoCall').play(); nextTurn(); }, 500); } } else { nextTurn(); } // Check win condition if (playerObj.cards.length === 0) { endGame(playerObj === player); } } }); LK.getSound('cardPlay').play(); selectedWildColor = null; playerObj.hasCalledUno = false; } function handleCardEffect(card, playerObj) { if (card.value === 'skip') { // Skip next player currentPlayer = (currentPlayer + gameDirection + 2) % 2; } else if (card.value === 'reverse') { gameDirection *= -1; } else if (card.value === 'draw2') { drawCount += 2; } else if (card.value === 'wild4') { drawCount += 4; } if (card.type === 'wild') { gamePhase = 'colorSelect'; if (playerObj === player) { showColorSelector(); } else { // AI chooses color var aiColors = ['red', 'blue', 'green', 'yellow']; selectedWildColor = aiColors[Math.floor(Math.random() * aiColors.length)]; LK.getSound('colorSelect').play(); // Show AI color choice visually var colorIndicator = LK.getAsset('colorSelector', { anchorX: 0.5, anchorY: 0.5 }); var colorMap = { 'red': 0xe74c3c, 'blue': 0x3498db, 'green': 0x27ae60, 'yellow': 0xf1c40f }; colorIndicator.tint = colorMap[selectedWildColor]; colorIndicator.x = 1024; colorIndicator.y = 1366 - 300; game.addChild(colorIndicator); // Add text to show AI choice var choiceText = new Text2('AI chose: ' + selectedWildColor.toUpperCase(), { size: 30, fill: 0xFFFFFF }); choiceText.anchor.set(0.5, 0.5); choiceText.x = 1024; choiceText.y = 1366 - 400; game.addChild(choiceText); // Remove indicator after delay LK.setTimeout(function () { if (colorIndicator.parent) { colorIndicator.destroy(); } if (choiceText.parent) { choiceText.destroy(); } }, 2000); gamePhase = 'playing'; } } } function nextTurn() { if (gamePhase !== 'playing') { return; } // Handle draw effects if (drawCount > 0) { var targetPlayer = currentPlayer === 0 ? aiPlayer : player; // Add stagger to card drawing animation for (var i = 0; i < drawCount; i++) { (function (index) { LK.setTimeout(function () { drawCard(targetPlayer); }, index * 200); })(i); } drawCount = 0; } // Switch turns currentPlayer = (currentPlayer + gameDirection + 2) % 2; // Reset auto-draw flag when switching to player turn if (currentPlayer === 0) { player.autoDrawnThisTurn = false; } updatePlayableCards(); updateUI(); // AI turn if (currentPlayer === 1) { LK.setTimeout(function () { aiTurn(); }, 1200); } } function aiTurn() { if (currentPlayer !== 1 || gamePhase !== 'playing') { return; } var currentTopCard = discardPile[discardPile.length - 1]; var playableCards = aiPlayer.getPlayableCards(currentTopCard); if (selectedWildColor && currentTopCard.type === 'wild') { playableCards = aiPlayer.cards.filter(function (card) { return card.color === selectedWildColor || card.type === 'wild'; }); } if (playableCards.length > 0) { // AI strategy: prefer action cards, then matching color, then wild cards var selectedCard = playableCards[0]; for (var i = 0; i < playableCards.length; i++) { var card = playableCards[i]; if (card.type === 'action' && selectedCard.type !== 'action') { selectedCard = card; } else if (card.type === 'wild' && selectedCard.type === 'number') { selectedCard = card; } } playCard(selectedCard, aiPlayer); } else { // Must draw card and end turn var drawnCard = drawCard(aiPlayer); // Wait for draw animation then check if can play LK.setTimeout(function () { if (drawnCard) { var canPlayDrawn = drawnCard.canPlayOn(currentTopCard); if (selectedWildColor && currentTopCard.type === 'wild') { canPlayDrawn = drawnCard.color === selectedWildColor || drawnCard.type === 'wild'; } if (canPlayDrawn) { // Play the drawn card immediately LK.setTimeout(function () { playCard(drawnCard, aiPlayer); }, 300); } else { // Check if any other cards became playable after drawing var allPlayableCards = aiPlayer.getPlayableCards(currentTopCard); if (selectedWildColor && currentTopCard.type === 'wild') { allPlayableCards = aiPlayer.cards.filter(function (card) { return card.color === selectedWildColor || card.type === 'wild'; }); } if (allPlayableCards.length > 0) { // AI can play a card from hand var selectedCard = allPlayableCards[0]; for (var j = 0; j < allPlayableCards.length; j++) { var handCard = allPlayableCards[j]; if (handCard.type === 'action' && selectedCard.type !== 'action') { selectedCard = handCard; } else if (handCard.type === 'wild' && selectedCard.type === 'number') { selectedCard = handCard; } } LK.setTimeout(function () { playCard(selectedCard, aiPlayer); }, 300); } else { // Cannot play, end turn nextTurn(); } } } else { nextTurn(); } }, 600); } } function showColorSelector() { var colors = ['red', 'blue', 'green', 'yellow']; var colorValues = [0xe74c3c, 0x3498db, 0x27ae60, 0xf1c40f]; for (var i = 0; i < colors.length; i++) { var selector = LK.getAsset('colorSelector', { anchorX: 0.5, anchorY: 0.5 }); selector.tint = colorValues[i]; selector.x = 1024 + (i - 1.5) * 120; selector.y = 1366 - 150; selector.colorName = colors[i]; selector.interactive = true; game.addChild(selector); colorSelectors.push(selector); } } function hideColorSelector() { for (var i = 0; i < colorSelectors.length; i++) { colorSelectors[i].destroy(); } colorSelectors = []; } function showUnoButton() { if (!unoButton) { unoButton = LK.getAsset('unoButton', { anchorX: 0.5, anchorY: 0.5 }); var unoText = new Text2('UNO!', { size: 30, fill: 0xFFFFFF }); unoText.anchor.set(0.5, 0.5); unoButton.addChild(unoText); unoButton.x = 1024 - 300; unoButton.y = 1366 - 150; game.addChild(unoButton); } } function hideUnoButton() { if (unoButton) { unoButton.destroy(); unoButton = null; } } function updateUI() { gameStateText.setText('Turn: ' + (currentPlayer === 0 ? 'Player' : 'AI')); playerCardCountText.setText('Your cards: ' + player.cards.length); aiCardCountText.setText('AI cards: ' + aiPlayer.cards.length); } function endGame(playerWon) { gamePhase = 'gameOver'; if (playerWon) { LK.setScore(LK.getScore() + 100); LK.showYouWin(); } else { LK.showGameOver(); } } function checkWinCondition() { if (player.cards.length === 0) { endGame(true); return true; } if (aiPlayer.cards.length === 0) { endGame(false); return true; } return false; } // Initialize game createDeck(); // Create players player = game.addChild(new Player(false)); player.x = 1024; player.y = 2200; aiPlayer = game.addChild(new Player(true)); aiPlayer.x = 1024; aiPlayer.y = 500; // Create deck and discard pile sprites deckPileSprite = LK.getAsset('deckPile', { anchorX: 0.5, anchorY: 0.5 }); deckPileSprite.x = 1024 - 200; deckPileSprite.y = 1366; game.addChild(deckPileSprite); discardPileSprite = LK.getAsset('discardPile', { anchorX: 0.5, anchorY: 0.5 }); discardPileSprite.x = 1024 + 200; discardPileSprite.y = 1366; game.addChild(discardPileSprite); // Create UI text gameStateText = new Text2('Turn: Player', { size: 40, fill: 0xFFFFFF }); gameStateText.anchor.set(0.5, 0); LK.gui.top.addChild(gameStateText); playerCardCountText = new Text2('Your cards: 7', { size: 30, fill: 0xFFFFFF }); playerCardCountText.anchor.set(0, 1); LK.gui.bottomLeft.addChild(playerCardCountText); aiCardCountText = new Text2('AI cards: 7', { size: 30, fill: 0xFFFFFF }); aiCardCountText.anchor.set(0, 0); LK.gui.topLeft.addChild(aiCardCountText); aiCardCountText.x = 120; // Deal initial cards dealCards(); updatePlayableCards(); updateUI(); // Event handlers game.down = function (x, y, obj) { if (gamePhase === 'colorSelect') { // Check color selector clicks for (var i = 0; i < colorSelectors.length; i++) { var selector = colorSelectors[i]; // Use direct position checking instead of getBounds var selectorCenterX = selector.x; var selectorCenterY = selector.y; var selectorRadius = 40; // Half of selector width/height var distance = Math.sqrt((x - selectorCenterX) * (x - selectorCenterX) + (y - selectorCenterY) * (y - selectorCenterY)); if (distance <= selectorRadius) { selectedWildColor = selector.colorName; LK.getSound('colorSelect').play(); hideColorSelector(); gamePhase = 'playing'; // Show player color choice var choiceText = new Text2('You chose: ' + selectedWildColor.toUpperCase(), { size: 30, fill: 0xFFFFFF }); choiceText.anchor.set(0.5, 0.5); choiceText.x = 1024; choiceText.y = 1366 - 400; game.addChild(choiceText); // Remove text after delay LK.setTimeout(function () { if (choiceText.parent) { choiceText.destroy(); } }, 2000); nextTurn(); return; } } } if (currentPlayer === 0 && gamePhase === 'playing') { // Check Uno button if (unoButton && waitingForUno) { // Use direct position checking like color selector var unoCenterX = unoButton.x; var unoCenterY = unoButton.y; var unoHalfWidth = 60; // Half of button width (120/2) var unoHalfHeight = 30; // Half of button height (60/2) if (x >= unoCenterX - unoHalfWidth && x <= unoCenterX + unoHalfWidth && y >= unoCenterY - unoHalfHeight && y <= unoCenterY + unoHalfHeight) { player.hasCalledUno = true; waitingForUno = false; hideUnoButton(); LK.getSound('unoCall').play(); nextTurn(); return; } } // Check deck click for drawing var deckBounds = deckPileSprite.getBounds(); if (x >= deckBounds.x && x <= deckBounds.x + deckBounds.width && y >= deckBounds.y && y <= deckBounds.y + deckBounds.height) { var currentTopCard = discardPile[discardPile.length - 1]; var playableCards = player.getPlayableCards(currentTopCard); if (selectedWildColor && currentTopCard.type === 'wild') { playableCards = player.cards.filter(function (card) { return card.color === selectedWildColor || card.type === 'wild'; }); } if (playableCards.length === 0) { var drawnCard = drawCard(player); // Check if drawn card is playable after animation completes LK.setTimeout(function () { var canPlayDrawn = false; if (drawnCard) { canPlayDrawn = drawnCard.canPlayOn(currentTopCard); if (selectedWildColor && currentTopCard.type === 'wild') { canPlayDrawn = drawnCard.color === selectedWildColor || drawnCard.type === 'wild'; } } if (!canPlayDrawn) { nextTurn(); } updatePlayableCards(); }, 500); } return; } // Check card selection - convert coordinates to player space var playerPos = game.toLocal(player.toGlobal({ x: 0, y: 0 })); var localX = x - playerPos.x; var localY = y - playerPos.y; // Reset all cards to allow new selection for (var j = 0; j < player.cards.length; j++) { var resetCard = player.cards[j]; if (resetCard.isAnimating) { tween.stop(resetCard, { x: true, y: true, scaleX: true, scaleY: true }); resetCard.isAnimating = false; // Return card to original position if it was lifted if (resetCard.originalX !== undefined && resetCard.originalY !== undefined) { resetCard.x = resetCard.originalX; resetCard.y = resetCard.originalY; resetCard.scaleX = 1.0; resetCard.scaleY = 1.0; } } } for (var i = 0; i < player.cards.length; i++) { var card = player.cards[i]; if (card.playable) { // Check if click is within card bounds using local coordinates var cardLeft = card.x - 70; // Half of card width (140/2) var cardRight = card.x + 70; var cardTop = card.y - 100; // Half of card height (200/2) var cardBottom = card.y + 100; if (localX >= cardLeft && localX <= cardRight && localY >= cardTop && localY <= cardBottom) { draggedCard = card; dragOffset.x = localX - card.x; dragOffset.y = localY - card.y; // Store original position for return animation card.originalX = card.x; card.originalY = card.y; // Add selection animation - lift card up and scale slightly card.isAnimating = true; tween.stop(card, { x: true, y: true, scaleX: true, scaleY: true }); tween(card, { y: card.y - 50, scaleX: 1.15, scaleY: 1.15 }, { duration: 250, easing: tween.easeOut, onFinish: function onFinish() { card.isAnimating = false; } }); break; } } } } }; game.move = function (x, y, obj) { if (draggedCard && currentPlayer === 0 && gamePhase === 'playing') { // Convert coordinates to player space var playerPos = game.toLocal(player.toGlobal({ x: 0, y: 0 })); var localX = x - playerPos.x; var localY = y - playerPos.y; draggedCard.x = localX - dragOffset.x; draggedCard.y = localY - dragOffset.y; } }; game.up = function (x, y, obj) { if (draggedCard && currentPlayer === 0 && gamePhase === 'playing') { // Check if card was dropped on discard pile area var discardCenterX = 1024 + 200; var discardCenterY = 1366; var discardRadius = 120; // Generous drop zone var distance = Math.sqrt((x - discardCenterX) * (x - discardCenterX) + (y - discardCenterY) * (y - discardCenterY)); if (distance <= discardRadius) { playCard(draggedCard, player); } else { // Return card to hand with animation draggedCard.isAnimating = true; tween.stop(draggedCard, { x: true, y: true, scaleX: true, scaleY: true }); tween(draggedCard, { x: draggedCard.originalX, y: draggedCard.originalY, scaleX: 1.0, scaleY: 1.0 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { if (draggedCard) { draggedCard.isAnimating = false; } player.arrangeCards(); } }); } if (draggedCard) { draggedCard = null; } } }; game.update = function () { // Check win condition if (checkWinCondition()) { return; } // Check if player has no playable cards and auto-draw if (currentPlayer === 0 && gamePhase === 'playing' && !waitingForUno) { var currentTopCard = discardPile[discardPile.length - 1]; var playableCards = player.getPlayableCards(currentTopCard); if (selectedWildColor && currentTopCard.type === 'wild') { playableCards = player.cards.filter(function (card) { return card.color === selectedWildColor || card.type === 'wild'; }); } // Auto-draw if no playable cards (but only once per turn) if (playableCards.length === 0 && !player.autoDrawnThisTurn) { player.autoDrawnThisTurn = true; var drawnCard = drawCard(player); // Check if drawn card is playable after animation LK.setTimeout(function () { var canPlayDrawn = false; if (drawnCard) { canPlayDrawn = drawnCard.canPlayOn(currentTopCard); if (selectedWildColor && currentTopCard.type === 'wild') { canPlayDrawn = drawnCard.color === selectedWildColor || drawnCard.type === 'wild'; } } if (!canPlayDrawn) { // Still can't play, end turn nextTurn(); } updatePlayableCards(); }, 500); } } else if (currentPlayer === 1) { // Reset auto-draw flag when it's AI turn player.autoDrawnThisTurn = false; } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Card = Container.expand(function (color, value, type) {
var self = Container.call(this);
self.color = color; // 'red', 'blue', 'green', 'yellow', 'wild'
self.value = value; // 0-9, 'skip', 'reverse', 'draw2', 'wild', 'wild4'
self.type = type || 'number'; // 'number', 'action', 'wild'
self.playable = false;
self.isAnimating = false;
// Create card background
var colorMap = {
'red': 'cardRed',
'blue': 'cardBlue',
'green': 'cardGreen',
'yellow': 'cardYellow',
'wild': 'cardWild'
};
var cardBg = self.attachAsset(colorMap[self.color] || 'cardBack', {
anchorX: 0.5,
anchorY: 0.5
});
// Add card text
var displayText = self.value;
if (self.value === 'skip') {
displayText = 'S';
} else if (self.value === 'reverse') {
displayText = 'R';
} else if (self.value === 'draw2') {
displayText = '+2';
} else if (self.value === 'wild') {
displayText = 'W';
} else if (self.value === 'wild4') {
displayText = '+4';
}
var cardText = new Text2(displayText.toString(), {
size: 40,
fill: self.color === 'yellow' ? "#000000" : "#ffffff"
});
cardText.anchor.set(0.5, 0.5);
self.addChild(cardText);
self.setPlayable = function (playable) {
self.playable = playable;
cardBg.alpha = playable ? 1.0 : 0.7;
// Add subtle glow effect for playable cards
if (playable) {
tween(cardBg, {
tint: 0xffffff
}, {
duration: 500,
easing: tween.easeInOut
});
} else {
tween.stop(cardBg, {
tint: true
});
cardBg.tint = 0xffffff;
}
};
self.canPlayOn = function (otherCard) {
if (self.type === 'wild') {
return true;
}
if (self.color === otherCard.color) {
return true;
}
if (self.value === otherCard.value && self.type === 'number' && otherCard.type === 'number') {
return true;
}
return false;
};
return self;
});
var Player = Container.expand(function (isAI) {
var self = Container.call(this);
self.cards = [];
self.isAI = isAI || false;
self.hasCalledUno = false;
self.addCard = function (card) {
self.cards.push(card);
self.addChild(card);
// Hide AI cards by showing card backs
if (self.isAI) {
card.visible = false;
// Create card back for AI
var cardBack = LK.getAsset('cardBack', {
anchorX: 0.5,
anchorY: 0.5
});
card.cardBack = cardBack;
self.addChild(cardBack);
}
self.arrangeCards();
};
self.removeCard = function (card) {
var index = self.cards.indexOf(card);
if (index > -1) {
self.cards.splice(index, 1);
self.removeChild(card);
// Remove card back for AI
if (self.isAI && card.cardBack) {
self.removeChild(card.cardBack);
card.cardBack = null;
}
self.arrangeCards();
}
};
self.arrangeCards = function () {
var startX = -(self.cards.length - 1) * 60;
for (var i = 0; i < self.cards.length; i++) {
var card = self.cards[i];
if (!card.isAnimating) {
var targetX = startX + i * 120;
var targetY = 0;
// Animate card position
tween(card, {
x: targetX,
y: targetY
}, {
duration: 300,
easing: tween.easeOut
});
// Animate card back position for AI
if (self.isAI && card.cardBack) {
tween(card.cardBack, {
x: targetX,
y: targetY
}, {
duration: 300,
easing: tween.easeOut
});
}
}
}
};
self.getPlayableCards = function (topCard) {
var playable = [];
for (var i = 0; i < self.cards.length; i++) {
if (self.cards[i].canPlayOn(topCard)) {
playable.push(self.cards[i]);
}
}
return playable;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0f4c3a
});
/****
* Game Code
****/
// Game state variables
var deck = [];
var discardPile = [];
var player;
var aiPlayer;
var currentPlayer = 0; // 0 = player, 1 = AI
var gameDirection = 1; // 1 = forward, -1 = reverse
var drawCount = 0; // For draw 2/4 cards
var selectedWildColor = null;
var gamePhase = 'playing'; // 'playing', 'colorSelect', 'gameOver'
var draggedCard = null;
var dragOffset = {
x: 0,
y: 0
};
var waitingForUno = false;
// UI elements
var deckPileSprite;
var discardPileSprite;
var topCard;
var unoButton;
var colorSelectors = [];
var gameStateText;
var playerCardCountText;
var aiCardCountText;
// Initialize deck
function createDeck() {
deck = [];
var colors = ['red', 'blue', 'green', 'yellow'];
// Number cards (0-9)
for (var c = 0; c < colors.length; c++) {
var color = colors[c];
// One 0 card per color
deck.push(new Card(color, 0, 'number'));
// Two of each number 1-9 per color
for (var num = 1; num <= 9; num++) {
deck.push(new Card(color, num, 'number'));
deck.push(new Card(color, num, 'number'));
}
// Action cards (2 of each per color)
deck.push(new Card(color, 'skip', 'action'));
deck.push(new Card(color, 'skip', 'action'));
deck.push(new Card(color, 'reverse', 'action'));
deck.push(new Card(color, 'reverse', 'action'));
deck.push(new Card(color, 'draw2', 'action'));
deck.push(new Card(color, 'draw2', 'action'));
}
// Wild cards
for (var w = 0; w < 4; w++) {
deck.push(new Card('wild', 'wild', 'wild'));
deck.push(new Card('wild', 'wild4', 'wild'));
}
// Shuffle deck
for (var i = deck.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = deck[i];
deck[i] = deck[j];
deck[j] = temp;
}
}
function dealCards() {
// Deal 7 cards to each player
for (var i = 0; i < 7; i++) {
player.addCard(deck.pop());
aiPlayer.addCard(deck.pop());
}
// Place first card on discard pile
do {
topCard = deck.pop();
} while (topCard.type === 'wild'); // Ensure first card isn't wild
discardPile.push(topCard);
game.addChild(topCard);
topCard.x = 1024 + 200;
topCard.y = 1366;
}
function updatePlayableCards() {
var currentTopCard = discardPile[discardPile.length - 1];
// Update player cards
for (var i = 0; i < player.cards.length; i++) {
var card = player.cards[i];
var canPlay = card.canPlayOn(currentTopCard);
if (selectedWildColor && currentTopCard.type === 'wild') {
canPlay = card.color === selectedWildColor || card.type === 'wild';
}
card.setPlayable(canPlay && currentPlayer === 0 && gamePhase === 'playing');
}
}
function drawCard(targetPlayer) {
if (deck.length === 0) {
// Reshuffle discard pile into deck
var currentTop = discardPile.pop();
deck = discardPile.slice();
discardPile = [currentTop];
// Shuffle new deck
for (var i = deck.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = deck[i];
deck[i] = deck[j];
deck[j] = temp;
}
}
if (deck.length > 0) {
var card = deck.pop();
// Start card at deck position
card.x = 1024 - 200;
card.y = 1366;
// Add animation when drawing
card.isAnimating = true;
tween(card, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(card, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
card.isAnimating = false;
targetPlayer.addCard(card);
}
});
}
});
LK.getSound('cardDraw').play();
return card;
}
return null;
}
function playCard(card, playerObj) {
// Remove card from player
playerObj.removeCard(card);
// Add to discard pile
discardPile.push(card);
// Make AI card visible when played and ensure proper setup
if (playerObj.isAI) {
card.visible = true;
// Reset any transformations that might affect visibility
card.alpha = 1.0;
card.scaleX = 1.0;
card.scaleY = 1.0;
}
// Animate card to discard pile
card.isAnimating = true;
tween(card, {
x: 1024 + 200,
y: 1366,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
card.isAnimating = false;
// Remove old top card but keep it in discard pile
if (topCard && topCard !== card && topCard.parent) {
topCard.parent.removeChild(topCard);
}
topCard = card;
// Ensure the new top card is visible and properly positioned
topCard.visible = true;
topCard.x = 1024 + 200;
topCard.y = 1366;
if (!topCard.parent) {
game.addChild(topCard);
}
// Handle special cards
handleCardEffect(card, playerObj);
// Check for Uno
if (playerObj.cards.length === 1 && !playerObj.hasCalledUno) {
waitingForUno = true;
if (playerObj === player) {
showUnoButton();
} else {
// AI automatically calls Uno
LK.setTimeout(function () {
playerObj.hasCalledUno = true;
waitingForUno = false;
LK.getSound('unoCall').play();
nextTurn();
}, 500);
}
} else {
nextTurn();
}
// Check win condition
if (playerObj.cards.length === 0) {
endGame(playerObj === player);
}
}
});
LK.getSound('cardPlay').play();
selectedWildColor = null;
playerObj.hasCalledUno = false;
}
function handleCardEffect(card, playerObj) {
if (card.value === 'skip') {
// Skip next player
currentPlayer = (currentPlayer + gameDirection + 2) % 2;
} else if (card.value === 'reverse') {
gameDirection *= -1;
} else if (card.value === 'draw2') {
drawCount += 2;
} else if (card.value === 'wild4') {
drawCount += 4;
}
if (card.type === 'wild') {
gamePhase = 'colorSelect';
if (playerObj === player) {
showColorSelector();
} else {
// AI chooses color
var aiColors = ['red', 'blue', 'green', 'yellow'];
selectedWildColor = aiColors[Math.floor(Math.random() * aiColors.length)];
LK.getSound('colorSelect').play();
// Show AI color choice visually
var colorIndicator = LK.getAsset('colorSelector', {
anchorX: 0.5,
anchorY: 0.5
});
var colorMap = {
'red': 0xe74c3c,
'blue': 0x3498db,
'green': 0x27ae60,
'yellow': 0xf1c40f
};
colorIndicator.tint = colorMap[selectedWildColor];
colorIndicator.x = 1024;
colorIndicator.y = 1366 - 300;
game.addChild(colorIndicator);
// Add text to show AI choice
var choiceText = new Text2('AI chose: ' + selectedWildColor.toUpperCase(), {
size: 30,
fill: 0xFFFFFF
});
choiceText.anchor.set(0.5, 0.5);
choiceText.x = 1024;
choiceText.y = 1366 - 400;
game.addChild(choiceText);
// Remove indicator after delay
LK.setTimeout(function () {
if (colorIndicator.parent) {
colorIndicator.destroy();
}
if (choiceText.parent) {
choiceText.destroy();
}
}, 2000);
gamePhase = 'playing';
}
}
}
function nextTurn() {
if (gamePhase !== 'playing') {
return;
}
// Handle draw effects
if (drawCount > 0) {
var targetPlayer = currentPlayer === 0 ? aiPlayer : player;
// Add stagger to card drawing animation
for (var i = 0; i < drawCount; i++) {
(function (index) {
LK.setTimeout(function () {
drawCard(targetPlayer);
}, index * 200);
})(i);
}
drawCount = 0;
}
// Switch turns
currentPlayer = (currentPlayer + gameDirection + 2) % 2;
// Reset auto-draw flag when switching to player turn
if (currentPlayer === 0) {
player.autoDrawnThisTurn = false;
}
updatePlayableCards();
updateUI();
// AI turn
if (currentPlayer === 1) {
LK.setTimeout(function () {
aiTurn();
}, 1200);
}
}
function aiTurn() {
if (currentPlayer !== 1 || gamePhase !== 'playing') {
return;
}
var currentTopCard = discardPile[discardPile.length - 1];
var playableCards = aiPlayer.getPlayableCards(currentTopCard);
if (selectedWildColor && currentTopCard.type === 'wild') {
playableCards = aiPlayer.cards.filter(function (card) {
return card.color === selectedWildColor || card.type === 'wild';
});
}
if (playableCards.length > 0) {
// AI strategy: prefer action cards, then matching color, then wild cards
var selectedCard = playableCards[0];
for (var i = 0; i < playableCards.length; i++) {
var card = playableCards[i];
if (card.type === 'action' && selectedCard.type !== 'action') {
selectedCard = card;
} else if (card.type === 'wild' && selectedCard.type === 'number') {
selectedCard = card;
}
}
playCard(selectedCard, aiPlayer);
} else {
// Must draw card and end turn
var drawnCard = drawCard(aiPlayer);
// Wait for draw animation then check if can play
LK.setTimeout(function () {
if (drawnCard) {
var canPlayDrawn = drawnCard.canPlayOn(currentTopCard);
if (selectedWildColor && currentTopCard.type === 'wild') {
canPlayDrawn = drawnCard.color === selectedWildColor || drawnCard.type === 'wild';
}
if (canPlayDrawn) {
// Play the drawn card immediately
LK.setTimeout(function () {
playCard(drawnCard, aiPlayer);
}, 300);
} else {
// Check if any other cards became playable after drawing
var allPlayableCards = aiPlayer.getPlayableCards(currentTopCard);
if (selectedWildColor && currentTopCard.type === 'wild') {
allPlayableCards = aiPlayer.cards.filter(function (card) {
return card.color === selectedWildColor || card.type === 'wild';
});
}
if (allPlayableCards.length > 0) {
// AI can play a card from hand
var selectedCard = allPlayableCards[0];
for (var j = 0; j < allPlayableCards.length; j++) {
var handCard = allPlayableCards[j];
if (handCard.type === 'action' && selectedCard.type !== 'action') {
selectedCard = handCard;
} else if (handCard.type === 'wild' && selectedCard.type === 'number') {
selectedCard = handCard;
}
}
LK.setTimeout(function () {
playCard(selectedCard, aiPlayer);
}, 300);
} else {
// Cannot play, end turn
nextTurn();
}
}
} else {
nextTurn();
}
}, 600);
}
}
function showColorSelector() {
var colors = ['red', 'blue', 'green', 'yellow'];
var colorValues = [0xe74c3c, 0x3498db, 0x27ae60, 0xf1c40f];
for (var i = 0; i < colors.length; i++) {
var selector = LK.getAsset('colorSelector', {
anchorX: 0.5,
anchorY: 0.5
});
selector.tint = colorValues[i];
selector.x = 1024 + (i - 1.5) * 120;
selector.y = 1366 - 150;
selector.colorName = colors[i];
selector.interactive = true;
game.addChild(selector);
colorSelectors.push(selector);
}
}
function hideColorSelector() {
for (var i = 0; i < colorSelectors.length; i++) {
colorSelectors[i].destroy();
}
colorSelectors = [];
}
function showUnoButton() {
if (!unoButton) {
unoButton = LK.getAsset('unoButton', {
anchorX: 0.5,
anchorY: 0.5
});
var unoText = new Text2('UNO!', {
size: 30,
fill: 0xFFFFFF
});
unoText.anchor.set(0.5, 0.5);
unoButton.addChild(unoText);
unoButton.x = 1024 - 300;
unoButton.y = 1366 - 150;
game.addChild(unoButton);
}
}
function hideUnoButton() {
if (unoButton) {
unoButton.destroy();
unoButton = null;
}
}
function updateUI() {
gameStateText.setText('Turn: ' + (currentPlayer === 0 ? 'Player' : 'AI'));
playerCardCountText.setText('Your cards: ' + player.cards.length);
aiCardCountText.setText('AI cards: ' + aiPlayer.cards.length);
}
function endGame(playerWon) {
gamePhase = 'gameOver';
if (playerWon) {
LK.setScore(LK.getScore() + 100);
LK.showYouWin();
} else {
LK.showGameOver();
}
}
function checkWinCondition() {
if (player.cards.length === 0) {
endGame(true);
return true;
}
if (aiPlayer.cards.length === 0) {
endGame(false);
return true;
}
return false;
}
// Initialize game
createDeck();
// Create players
player = game.addChild(new Player(false));
player.x = 1024;
player.y = 2200;
aiPlayer = game.addChild(new Player(true));
aiPlayer.x = 1024;
aiPlayer.y = 500;
// Create deck and discard pile sprites
deckPileSprite = LK.getAsset('deckPile', {
anchorX: 0.5,
anchorY: 0.5
});
deckPileSprite.x = 1024 - 200;
deckPileSprite.y = 1366;
game.addChild(deckPileSprite);
discardPileSprite = LK.getAsset('discardPile', {
anchorX: 0.5,
anchorY: 0.5
});
discardPileSprite.x = 1024 + 200;
discardPileSprite.y = 1366;
game.addChild(discardPileSprite);
// Create UI text
gameStateText = new Text2('Turn: Player', {
size: 40,
fill: 0xFFFFFF
});
gameStateText.anchor.set(0.5, 0);
LK.gui.top.addChild(gameStateText);
playerCardCountText = new Text2('Your cards: 7', {
size: 30,
fill: 0xFFFFFF
});
playerCardCountText.anchor.set(0, 1);
LK.gui.bottomLeft.addChild(playerCardCountText);
aiCardCountText = new Text2('AI cards: 7', {
size: 30,
fill: 0xFFFFFF
});
aiCardCountText.anchor.set(0, 0);
LK.gui.topLeft.addChild(aiCardCountText);
aiCardCountText.x = 120;
// Deal initial cards
dealCards();
updatePlayableCards();
updateUI();
// Event handlers
game.down = function (x, y, obj) {
if (gamePhase === 'colorSelect') {
// Check color selector clicks
for (var i = 0; i < colorSelectors.length; i++) {
var selector = colorSelectors[i];
// Use direct position checking instead of getBounds
var selectorCenterX = selector.x;
var selectorCenterY = selector.y;
var selectorRadius = 40; // Half of selector width/height
var distance = Math.sqrt((x - selectorCenterX) * (x - selectorCenterX) + (y - selectorCenterY) * (y - selectorCenterY));
if (distance <= selectorRadius) {
selectedWildColor = selector.colorName;
LK.getSound('colorSelect').play();
hideColorSelector();
gamePhase = 'playing';
// Show player color choice
var choiceText = new Text2('You chose: ' + selectedWildColor.toUpperCase(), {
size: 30,
fill: 0xFFFFFF
});
choiceText.anchor.set(0.5, 0.5);
choiceText.x = 1024;
choiceText.y = 1366 - 400;
game.addChild(choiceText);
// Remove text after delay
LK.setTimeout(function () {
if (choiceText.parent) {
choiceText.destroy();
}
}, 2000);
nextTurn();
return;
}
}
}
if (currentPlayer === 0 && gamePhase === 'playing') {
// Check Uno button
if (unoButton && waitingForUno) {
// Use direct position checking like color selector
var unoCenterX = unoButton.x;
var unoCenterY = unoButton.y;
var unoHalfWidth = 60; // Half of button width (120/2)
var unoHalfHeight = 30; // Half of button height (60/2)
if (x >= unoCenterX - unoHalfWidth && x <= unoCenterX + unoHalfWidth && y >= unoCenterY - unoHalfHeight && y <= unoCenterY + unoHalfHeight) {
player.hasCalledUno = true;
waitingForUno = false;
hideUnoButton();
LK.getSound('unoCall').play();
nextTurn();
return;
}
}
// Check deck click for drawing
var deckBounds = deckPileSprite.getBounds();
if (x >= deckBounds.x && x <= deckBounds.x + deckBounds.width && y >= deckBounds.y && y <= deckBounds.y + deckBounds.height) {
var currentTopCard = discardPile[discardPile.length - 1];
var playableCards = player.getPlayableCards(currentTopCard);
if (selectedWildColor && currentTopCard.type === 'wild') {
playableCards = player.cards.filter(function (card) {
return card.color === selectedWildColor || card.type === 'wild';
});
}
if (playableCards.length === 0) {
var drawnCard = drawCard(player);
// Check if drawn card is playable after animation completes
LK.setTimeout(function () {
var canPlayDrawn = false;
if (drawnCard) {
canPlayDrawn = drawnCard.canPlayOn(currentTopCard);
if (selectedWildColor && currentTopCard.type === 'wild') {
canPlayDrawn = drawnCard.color === selectedWildColor || drawnCard.type === 'wild';
}
}
if (!canPlayDrawn) {
nextTurn();
}
updatePlayableCards();
}, 500);
}
return;
}
// Check card selection - convert coordinates to player space
var playerPos = game.toLocal(player.toGlobal({
x: 0,
y: 0
}));
var localX = x - playerPos.x;
var localY = y - playerPos.y;
// Reset all cards to allow new selection
for (var j = 0; j < player.cards.length; j++) {
var resetCard = player.cards[j];
if (resetCard.isAnimating) {
tween.stop(resetCard, {
x: true,
y: true,
scaleX: true,
scaleY: true
});
resetCard.isAnimating = false;
// Return card to original position if it was lifted
if (resetCard.originalX !== undefined && resetCard.originalY !== undefined) {
resetCard.x = resetCard.originalX;
resetCard.y = resetCard.originalY;
resetCard.scaleX = 1.0;
resetCard.scaleY = 1.0;
}
}
}
for (var i = 0; i < player.cards.length; i++) {
var card = player.cards[i];
if (card.playable) {
// Check if click is within card bounds using local coordinates
var cardLeft = card.x - 70; // Half of card width (140/2)
var cardRight = card.x + 70;
var cardTop = card.y - 100; // Half of card height (200/2)
var cardBottom = card.y + 100;
if (localX >= cardLeft && localX <= cardRight && localY >= cardTop && localY <= cardBottom) {
draggedCard = card;
dragOffset.x = localX - card.x;
dragOffset.y = localY - card.y;
// Store original position for return animation
card.originalX = card.x;
card.originalY = card.y;
// Add selection animation - lift card up and scale slightly
card.isAnimating = true;
tween.stop(card, {
x: true,
y: true,
scaleX: true,
scaleY: true
});
tween(card, {
y: card.y - 50,
scaleX: 1.15,
scaleY: 1.15
}, {
duration: 250,
easing: tween.easeOut,
onFinish: function onFinish() {
card.isAnimating = false;
}
});
break;
}
}
}
}
};
game.move = function (x, y, obj) {
if (draggedCard && currentPlayer === 0 && gamePhase === 'playing') {
// Convert coordinates to player space
var playerPos = game.toLocal(player.toGlobal({
x: 0,
y: 0
}));
var localX = x - playerPos.x;
var localY = y - playerPos.y;
draggedCard.x = localX - dragOffset.x;
draggedCard.y = localY - dragOffset.y;
}
};
game.up = function (x, y, obj) {
if (draggedCard && currentPlayer === 0 && gamePhase === 'playing') {
// Check if card was dropped on discard pile area
var discardCenterX = 1024 + 200;
var discardCenterY = 1366;
var discardRadius = 120; // Generous drop zone
var distance = Math.sqrt((x - discardCenterX) * (x - discardCenterX) + (y - discardCenterY) * (y - discardCenterY));
if (distance <= discardRadius) {
playCard(draggedCard, player);
} else {
// Return card to hand with animation
draggedCard.isAnimating = true;
tween.stop(draggedCard, {
x: true,
y: true,
scaleX: true,
scaleY: true
});
tween(draggedCard, {
x: draggedCard.originalX,
y: draggedCard.originalY,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
if (draggedCard) {
draggedCard.isAnimating = false;
}
player.arrangeCards();
}
});
}
if (draggedCard) {
draggedCard = null;
}
}
};
game.update = function () {
// Check win condition
if (checkWinCondition()) {
return;
}
// Check if player has no playable cards and auto-draw
if (currentPlayer === 0 && gamePhase === 'playing' && !waitingForUno) {
var currentTopCard = discardPile[discardPile.length - 1];
var playableCards = player.getPlayableCards(currentTopCard);
if (selectedWildColor && currentTopCard.type === 'wild') {
playableCards = player.cards.filter(function (card) {
return card.color === selectedWildColor || card.type === 'wild';
});
}
// Auto-draw if no playable cards (but only once per turn)
if (playableCards.length === 0 && !player.autoDrawnThisTurn) {
player.autoDrawnThisTurn = true;
var drawnCard = drawCard(player);
// Check if drawn card is playable after animation
LK.setTimeout(function () {
var canPlayDrawn = false;
if (drawnCard) {
canPlayDrawn = drawnCard.canPlayOn(currentTopCard);
if (selectedWildColor && currentTopCard.type === 'wild') {
canPlayDrawn = drawnCard.color === selectedWildColor || drawnCard.type === 'wild';
}
}
if (!canPlayDrawn) {
// Still can't play, end turn
nextTurn();
}
updatePlayableCards();
}, 500);
}
} else if (currentPlayer === 1) {
// Reset auto-draw flag when it's AI turn
player.autoDrawnThisTurn = false;
}
};
black card. In-Game asset. 2d. High contrast. No shadows
gray card. In-Game asset. 2d. High contrast. No shadows
green card. In-Game asset. 2d. High contrast. No shadows
blue card. In-Game asset. 2d. High contrast. No shadows
red card. In-Game asset. 2d. High contrast. No shadows
yellow card. In-Game asset. 2d. High contrast. No shadows
? card. In-Game asset. 2d. High contrast. No shadows
pink button. In-Game asset. 2d. High contrast. No shadows
uno. In-Game asset. 2d. High contrast. No shadows