Spaces:
Sleeping
Sleeping
| """ | |
| Set-finding algorithm. | |
| A valid Set consists of 3 cards where, for each attribute, | |
| the values are either ALL THE SAME or ALL DIFFERENT. | |
| """ | |
| from dataclasses import dataclass | |
| from enum import IntEnum | |
| from itertools import combinations | |
| from typing import List, Tuple | |
| class Shape(IntEnum): | |
| DIAMOND = 0 | |
| OVAL = 1 | |
| SQUIGGLE = 2 | |
| class Color(IntEnum): | |
| RED = 0 | |
| GREEN = 1 | |
| PURPLE = 2 | |
| class Number(IntEnum): | |
| ONE = 0 | |
| TWO = 1 | |
| THREE = 2 | |
| class Fill(IntEnum): | |
| SOLID = 0 | |
| STRIPED = 1 | |
| EMPTY = 2 | |
| class Card: | |
| """A Set card with 4 attributes.""" | |
| shape: Shape | |
| color: Color | |
| number: Number | |
| fill: Fill | |
| # Optional: position in image (for visualization) | |
| bbox: Tuple[float, float, float, float] = None # x, y, w, h | |
| def __hash__(self): | |
| return hash((self.shape, self.color, self.number, self.fill)) | |
| def __eq__(self, other): | |
| if not isinstance(other, Card): | |
| return False | |
| return (self.shape == other.shape and | |
| self.color == other.color and | |
| self.number == other.number and | |
| self.fill == other.fill) | |
| def to_tuple(self) -> Tuple[int, int, int, int]: | |
| """Return attributes as tuple of ints.""" | |
| return (self.shape, self.color, self.number, self.fill) | |
| def from_tuple(cls, attrs: Tuple[int, int, int, int], bbox=None) -> "Card": | |
| """Create card from tuple of attribute indices.""" | |
| return cls( | |
| shape=Shape(attrs[0]), | |
| color=Color(attrs[1]), | |
| number=Number(attrs[2]), | |
| fill=Fill(attrs[3]), | |
| bbox=bbox | |
| ) | |
| def __repr__(self): | |
| n = ["one", "two", "three"][self.number] | |
| return f"{n} {self.fill.name.lower()} {self.color.name.lower()} {self.shape.name.lower()}(s)" | |
| def is_valid_set(card1: Card, card2: Card, card3: Card) -> bool: | |
| """ | |
| Check if three cards form a valid Set. | |
| For each attribute, the three values must be either: | |
| - All the same (e.g., all red) | |
| - All different (e.g., red, green, purple) | |
| """ | |
| for attr in ['shape', 'color', 'number', 'fill']: | |
| values = [getattr(card1, attr), getattr(card2, attr), getattr(card3, attr)] | |
| unique = len(set(values)) | |
| # Valid: all same (1 unique) or all different (3 unique) | |
| # Invalid: exactly 2 unique | |
| if unique == 2: | |
| return False | |
| return True | |
| def find_all_sets(cards: List[Card]) -> List[Tuple[Card, Card, Card]]: | |
| """ | |
| Find all valid Sets among the given cards. | |
| Uses brute force: check all C(n,3) combinations. | |
| For 12 cards: C(12,3) = 220 combinations - very fast. | |
| For 21 cards (max in real game): C(21,3) = 1330 combinations - still fast. | |
| """ | |
| valid_sets = [] | |
| for combo in combinations(cards, 3): | |
| if is_valid_set(*combo): | |
| valid_sets.append(combo) | |
| return valid_sets | |
| def find_first_set(cards: List[Card]) -> Tuple[Card, Card, Card] | None: | |
| """Find the first valid Set, or None if no Set exists.""" | |
| for combo in combinations(cards, 3): | |
| if is_valid_set(*combo): | |
| return combo | |
| return None | |
| # --- Utilities --- | |
| def generate_all_cards() -> List[Card]: | |
| """Generate all 81 unique Set cards.""" | |
| cards = [] | |
| for s in Shape: | |
| for c in Color: | |
| for n in Number: | |
| for f in Fill: | |
| cards.append(Card(shape=s, color=c, number=n, fill=f)) | |
| return cards | |
| def card_to_index(card: Card) -> int: | |
| """Convert card to unique index (0-80).""" | |
| return (card.shape * 27 + card.color * 9 + card.number * 3 + card.fill) | |
| def index_to_card(idx: int) -> Card: | |
| """Convert index (0-80) to card.""" | |
| fill = idx % 3 | |
| idx //= 3 | |
| number = idx % 3 | |
| idx //= 3 | |
| color = idx % 3 | |
| idx //= 3 | |
| shape = idx | |
| return Card(Shape(shape), Color(color), Number(number), Fill(fill)) | |
| # --- Demo --- | |
| if __name__ == "__main__": | |
| # Example: find sets in a random deal | |
| import random | |
| all_cards = generate_all_cards() | |
| print(f"Total cards in deck: {len(all_cards)}") | |
| # Deal 12 cards | |
| deal = random.sample(all_cards, 12) | |
| print(f"\nDealt {len(deal)} cards:") | |
| for i, card in enumerate(deal): | |
| print(f" {i+1}. {card}") | |
| # Find all sets | |
| sets = find_all_sets(deal) | |
| print(f"\nFound {len(sets)} valid Set(s):") | |
| for i, (c1, c2, c3) in enumerate(sets): | |
| print(f"\n Set {i+1}:") | |
| print(f" - {c1}") | |
| print(f" - {c2}") | |
| print(f" - {c3}") | |