/****
* 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