Python Forum
I need help understanding a program structure using classes
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
I need help understanding a program structure using classes
#21
I added a rules class that uses Hand to determine value. I should flatten the list before returning but I grow weary of this exercise.
import collections
import copy
import random

class Card():
    """A playing card that has a rank and suit"""
    # These are class variables.  They are associated with the class, not instances of the class
    suit_names = ["Clubs", "Diamonds", "Hearts", "Spades"]
    short_suit_names = ["C", "D", "H", "S"]
    rank_names = ["", "Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King", "Ace"]
    short_rank_names = ["", "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"]
    rank_range = range(2, 15)

    def __init__(self, rank, suit=None):
        # These are instance variables.  Each instance has their own rank an suit
        if suit is None:
            # Assume rank is a card repr.  Extract rank and suit
            suit = Card.suit_names[Card.short_suit_names.index(rank[-1])]
            rank = Card.short_rank_names[2:].index(rank[:-1])+2
        self.rank = rank
        self.suit = suit

    def __lt__(self, other):
        """For sorting and comparing by rank"""
        return self.rank < other.rank  # self.rank is my rank.  other.rank is the rank of another card

    def __gt__(self, other):
        """For sorting and comparing by rank"""
        return self.rank > other.rank

    def __eq__(self, other):
        """For sorting and comparing by rank"""
        return self.rank == other.rank

    def name(self):
        """Get long name for card"""
        return f"{self.rank_names[self.rank]} {self.suit}"

    def __repr__(self):
        """Get short name for card"""
        return f"{self.short_rank_names[self.rank]}{self.suit[0]}"

class Hand():
    """A list of cards"""
    def __init__(self, cards=None):
        self.cards = [] if cards is None else cards

    def ranks(self):
        """Group cards into ranks.  Sort ranks by number of cards in a rank"""
        def sort_key(item):
            """Sorting primarily on number of matching cards and secondarily on rank value"""
            return (len(item), item[0].rank)

        ranks = {}
        for card in self.cards:
            if card.rank in ranks:
                ranks[card.rank].append(card)
            else:
                ranks[card.rank] = [card]
        ranks = list(ranks.values())
        ranks.sort(key=sort_key, reverse=True)
        return ranks

    def suits(self):
        """Group cards into suits. Sort suits by number of cards in a suit"""
        suits = {}
        for card in self.cards:
            if card.suit in suits:
                suits[card.suit].append(card)
            else:
                suits[card.suit] = [card]
        suits = list(suits.values())
        for suit in suits:
            suit.sort(reverse=True)
        suits.sort(key=len, reverse=True)
        return suits

    def __add__(self, other):
        """Create a new hand by combining two existing hands"""
        return Hand(self.cards + other.cards)

    def __repr__(self):
        """Print cards in hand"""
        return ", ".join([str(card) for card in self.cards])


class Deck():
    """Deck of cards"""
    def __init__(self, shuffle=False):
        self.cards = [Card(rank, suit) for rank in Card.rank_range for suit in Card.suit_names]
        if shuffle:
            random.shuffle(self.cards)

    def __len__(self):
        """Return number of cards in deck"""
        return len(self.cards)

    def deal(self, count):
        """Deal count cards from top of deck"""
        cards = self.cards[:count]
        self.cards = self.cards[count:]
        return cards

class Rules():
    ROYAL_FLUSH = 9
    STRAIGHT_FLUSH = 8
    FOUR_OF_A_KIND = 7
    FULL_HOUSE = 6
    FLUSH = 5
    STRAIGHT = 4
    THREE_OF_A_KIND = 3
    TWO_PAIR = 2
    ONE_PAIR = 1
    HIGH_CARD = 0

    HAND_NAMES = [
        "High Card",
        "One Pair",
        "Two Pair",
        "Three of a Kind",
        "Straight",
        "Flush",
        "Full House",
        "Four of a Kind",
        "Straight Flush",
        "Royal Flush"
    ]

    @staticmethod
    def ofAKind(ranks, count):
        """Return cards if we have four card with the same rank"""
        for rank in ranks:
            if len(rank) == count:
                return rank
        return None

    @staticmethod
    def flush(suits):
        """Return cards if there are 5 of the same suit"""
        return suits[0] if len(suits[0]) > 4 else None

    @staticmethod
    def straight(ranks):
        """Return cards if there are 5 cards in a row"""
        def sort_key(item):
            """Sort ranks by rank value"""
            return item[0].rank

        straight = None
        # Sort the ranks by decreasing value.
        ranks = copy.deepcopy(ranks)
        ranks.sort(key=sort_key, reverse=True)
        if ranks[0][0].rank == 14:
            # If we have aces, treat them as both rank 14 and rank 1
            ranks.append([Card(1, card.suit) for card in ranks[0]])

        # look for 5 consecutive ranks
        for rank in ranks:
            if straight is None or straight[-1][0].rank - rank[0].rank > 1:
                straight = [rank]
            else:
                straight.append(rank)
                if len(straight) >= 5:
                    return straight
        return None

    @staticmethod
    def straightFlush(suits):
        """Return cards if there are 5 cards in a row with the same rank"""
        if (cards := Rules.flush(suits)) is not None:
            return Rules.straight(Hand(cards).ranks())
        return None

    @staticmethod
    def royalFlush(suits):
        """Return cards if there is an ace high straight flush"""
        if (cards := Rules.straightFlush(suits)) is not None and cards[0][0].rank == 14:
            return cards
        return None

    @staticmethod
    def fullHouse(ranks):
        """Return cards if there is a full house"""
        if len(ranks) > 1 and len(ranks[0]) >= 3 and len(ranks[1]) >= 2:
            return ranks[0] + ranks[1]
        return None

    @staticmethod
    def twoPair(ranks):
        """Return cards if there are two pairs"""
        if len(ranks) > 1 and len(ranks[0]) >= 2 and len(ranks[1]) >= 2:
            return ranks[0] + ranks[1]
        return None

    @staticmethod
    def highCard(ranks):
        """Return the high card"""
        high_rank = 0
        for rank in ranks:
            high = max(rank[0].rank, high_rank)
        return high_rank

    @classmethod
    def evaluate(cls, hand):
        ranks = hand.ranks()
        suits = hand.suits()
        high_card = cls.highCard(ranks)

        if (cards := cls.royalFlush(suits)):
            return cls.ROYAL_FLUSH, cards, high_card
        elif (cards := cls.straightFlush(suits)):
            return cls.STRAIGHT_FLUSH, cards, high_card
        elif (cards := cls.ofAKind(ranks, 4)):
            return cls.FOUR_OF_A_KIND, cards, high_card
        elif (cards := cls.fullHouse(ranks)):
            return cls.FULL_HOUSE, cards, high_card
        elif (cards := cls.flush(suits)):
            return cls.FLUSH, cards, high_card
        elif (cards := cls.straight(ranks)):
            return cls.STRAIGHT, cards, high_card
        elif (cards := cls.ofAKind(ranks, 3)):
            return cls.THREE_OF_A_KIND, cards, high_card
        elif (cards := cls.twoPair(ranks)):
            return cls.TWO_PAIR, cards, high_card
        elif (cards := cls.ofAKind(ranks, 2)):
            return cls.ONE_PAIR, cards, high_card
        else:
            return cls.HIGH_CARD, high_card, high_card

# Test different hands
flop = Hand([Card(card) for card in ["KS", "QS", "JS"]])

def test(*cards):
    hand = Hand([Card(card) for card in cards]) + flop
    result, cards, high_card = Rules.evaluate(hand)
    print(f"{cards}, {Rules.HAND_NAMES[result]}")

test("AC", "AD", "AH", "AS", "10S")
test("KC", "KD", "JH", "10S", "9S")
test("AC", "AD", "AH", "AS", "10H")
test("KC", "KD", "QH", "5S", "4H")
test("2S", "4S", "2C", "4C", "5S")
test("AD", "2D", "3H", "4C", "5C")
test("AC", "AD", "AH", "5H", "3D")
test("AC", "AD", "2S", "2D", "3D")
test("AS", "AD", "3H", "4C", "5C")
Output:
[[AS], [KS], [QS], [JS], [10S]], Royal Flush [[KS], [QS], [JS], [10S], [9S]], Straight Flush [AC, AD, AH, AS], Four of a Kind [KC, KD, KS, QH, QS], Full House [KS, QS, JS, 5S, 4S, 2S], Flush [[5C], [4C], [3H], [2D], [AD]], Straight [AC, AD, AH], Three of a Kind [AC, AD, 2S, 2D], Two Pair [AS, AD], One Pair
Reply


Messages In This Thread
RE: I need help understanding a program structure using classes - by deanhystad - Dec-29-2021, 06:49 PM

Possibly Related Threads…
Thread Author Replies Views Last Post
  Understanding Python classes PythonNewbee 3 1,241 Nov-10-2022, 11:07 PM
Last Post: deanhystad
  Understanding Python super() for classes OmegaRed94 1 1,876 Jun-09-2021, 09:02 AM
Last Post: buran
  Understanding program blocks newbieAuggie2019 2 2,031 Oct-02-2019, 06:22 PM
Last Post: newbieAuggie2019
  help with understanding a program prompt drasil 5 3,019 Feb-14-2019, 05:54 PM
Last Post: ichabod801
  Help, not understanding how classes work... Peter_EU 1 2,390 Jan-20-2018, 06:07 PM
Last Post: wavic
  Using classes? Can I just use classes to structure code? muteboy 5 5,140 Nov-01-2017, 04:20 PM
Last Post: metulburr
  I need help understanding how to use and run this program! Thanks in advance! tc1chosen 6 4,858 Sep-01-2017, 01:56 PM
Last Post: tc1chosen

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020