Source code for src.cards

"""
UNO Card and Deck classes for the card game.

This module provides the core data structures for representing
UNO cards and the deck they come from.
"""

import random
from typing import List, Union, Optional


# Type alias for card values
CardValue = Union[int, str]  # 0-9 or "SKI", "REV", "PL2", "COL", "PL4"
CardColor = str  # "RED", "GRE", "BLU", "YEL", "WILD"


[docs] class Card: """ Represents a single UNO card with color and value. Attributes: color: Card color - "RED", "GRE", "BLU", "YEL", or "WILD" value: Card value - 0-9 for number cards, or special values: "SKI" (Skip), "REV" (Reverse), "PL2" (+2), "COL" (Wild Color), "PL4" (Wild +4) Example: >>> card = Card("RED", 5) >>> card.evaluate_card("RED", 3) # Same color True >>> card.evaluate_card("BLU", 5) # Same value True """ def __init__(self, color: CardColor, value: CardValue) -> None: """ Initialize a card with color and value. Args: color: The card's color ("RED", "GRE", "BLU", "YEL", "WILD") value: The card's value (0-9 or special string) """ self.color = color self.value = value def evaluate_card(self, open_color: CardColor, open_value: CardValue) -> bool: """ Check if this card can be played on the given open card. A card is playable if: - It matches the open card's color, OR - It matches the open card's value, OR - It's a wild card (COL or PL4) Args: open_color: The color of the card currently in play open_value: The value of the card currently in play Returns: True if this card can be played, False otherwise """ if ( (self.color == open_color) or (self.value == open_value) or (self.value in ["COL", "PL4"]) ): return True return False def show_card(self) -> None: """Print the card's color and value to console.""" print(self.color, self.value) def print_card(self) -> str: """ Get a string representation of the card. Returns: String in format "COLOR VALUE" (e.g., "RED 5") """ return f"{self.color} {self.value}" def __repr__(self) -> str: """Return a developer-friendly string representation.""" return f"Card({self.color!r}, {self.value!r})" def __eq__(self, other: object) -> bool: """Check equality with another card.""" if not isinstance(other, Card): return NotImplemented return self.color == other.color and self.value == other.value
[docs] class Deck: """ Represents a standard UNO deck of 108 cards. A standard UNO deck contains: - 76 Number cards (0-9 in each of 4 colors, with one 0 and two of 1-9) - 24 Action cards (2 each of Skip, Reverse, +2 in each of 4 colors) - 8 Wild cards (4 Wild and 4 Wild +4) Attributes: cards: List of cards currently in the draw pile cards_disc: List of discarded cards Example: >>> deck = Deck() >>> len(deck.cards) 108 >>> card = deck.draw_from_deck() >>> deck.discard(card) """ # UNO card colors COLORS: List[str] = ["RED", "GRE", "BLU", "YEL"] # Special card values ACTION_VALUES: List[str] = ["SKI", "REV", "PL2"] WILD_VALUES: List[str] = ["COL", "PL4"] def __init__(self) -> None: """Initialize a new shuffled deck of 108 UNO cards.""" self.cards: List[Card] = [] self.cards_disc: List[Card] = [] self.build() self.shuffle() def build(self) -> None: """ Build the standard 108-card UNO deck. Creates: - One 0 card per color (4 total) - Two of each 1-9 per color (72 total) - Two of each action card per color (24 total) - Four of each wild card (8 total) """ # Zero cards - one per color cards_zero = [Card(c, 0) for c in self.COLORS] # Number cards 1-9 - two per color cards_normal = [Card(c, v) for c in self.COLORS for v in range(1, 10)] * 2 # Action cards - two per color cards_action = [Card(c, v) for c in self.COLORS for v in self.ACTION_VALUES] * 2 # Wild cards - four of each cards_wild = [Card("WILD", v) for v in self.WILD_VALUES] * 4 # Combine all cards cards_all = cards_normal + cards_action + cards_zero + cards_wild self.cards.extend(cards_all) def discard(self, card: Card) -> None: """ Add a card to the discard pile. Args: card: The card to discard """ self.cards_disc.append(card)
[docs] def shuffle(self) -> None: """Randomly shuffle the cards in the draw pile.""" random.shuffle(self.cards)
def draw_from_deck(self) -> Card: """ Draw the top card from the deck. If the draw pile is empty, the discard pile is shuffled and becomes the new draw pile. Returns: The drawn card Raises: IndexError: If both draw and discard piles are empty """ if len(self.cards) == 0: # Reshuffle discard pile into draw pile self.cards = self.cards_disc self.cards_disc = [] self.shuffle() return self.cards.pop() def show_deck(self) -> None: """Print all cards in the draw pile.""" for card in self.cards: card.show_card() def show_discarded(self) -> None: """Print all cards in the discard pile.""" for card in self.cards_disc: card.show_card() def __len__(self) -> int: """Return the number of cards in the draw pile.""" return len(self.cards)