Hackbright Code Challenges

Poker

Poker

Challenge

Harder

Concepts

Logic, Data Structures

Download

poker.zip

Solution

Poker: Solution


In this challenge, you’ll need to write code to evaluate the strength of a poker hand.

Poker is played with a standard 52-card deck, made up of cards of thirteen “ranks” (2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, Queen, King, Ace) and four suits (Clubs, Diamonds, Hearts, Spades; these are written here as “c”, “d”, “h”, “s”). There are no wild cards or jokers in the version of Poker we’ll use here.

The card ranks tell the strength of a card: a Jack is stronger than a 10. Aces are typically the strongest rank, but we’ll see there’s one special case where an Ace can be the weakest card. The suits have nothing to do with the strength of a card: the Ks is the exact same strength as the Kh.

A poker hand is made up of exactly five cards. The order of the cards in the hand does not matter (players can rearrange the cards in their hands).

The strength of a hard is found is described here (strongest combinations to weakest):

Name

Description

Examples

Straight Flush

Cards are in order (straight) and are all same suit (flush).

When comparing two straight flushes, card rank break ties. See note below on straights.

  • As Ks Qs Js 10s

  • 6s 5s 4s 3s 2s

Four of a Kind

Four cards of the same rank.

When comparing two four-of-kinds, break ties using rank of the four-of-kind then using other cards.

  • 6c 6d 6s 6h 2c

  • 5c 5d 5s 5h Ah

Full House

Three cards of one rank and two cards of a second rank (both three-of-kind and one pair).

When comparing two full house hands, compare first using the three-of-kind rank, breaking tie with pair rank.

  • 3c 3d 3s 2h 2s

  • 2c 2d 2s Ah As

Flush

All cards have the same suit.

When comparing two flush hands, use rank.

  • 9s 8s 6s 4s 2s

  • 9s 7s 6s 4s 2s

Straight

Card ranks in order.

When comparing two straights, use ranks.

Aces can be high (as normal, coming after king) or low (coming before 2). It cannot “wrap around” (Q-K-A-2-3 isn’t a straight). An Ace played low is rank as 1 for breaking ties using card ranks.

  • Ah Kc Qd Jc 10h

  • 7s 5c 4c 3d 2h

  • 5c 4c 3d 2h Ah

Three of a Kind

Three cards of same rank.

When comparing two three-of-kind hands, use rank of three-of-kind, then ranks of other cards to break ties.

  • 7s 7c 7d 2h 3d

  • 6s 6c 6d Ah 2d

  • 6s 6c 6d Qh Jd

Two Pair

Two pairs of cards of same rank.

When comparing two two-pair hands, compare higher-ranking pairs first, then lower-ranking pairs, then remaining card.

  • 6s 6c 2h 2s 9d

  • 5s 5c 4h 4s 9d

Pair

One pair of cards of same rank.

When comparing two one-pair hands, compare pair ranks, then remaining cards.

  • 6s 6c 2h 3s 9d

  • 5s 5c 4h 6s 9d

High Card

No combinations listed above.

When comparing two hands like this, use ranks of cards.

  • As 7d 5c 4d 3h

  • As 7d 5c 4d 2h

(The examples for each combination in order of strongest-to-weakest).

Cards

We’ve given you a Card class to represent a playing card:

poker.py
class Card(object):
    """Playing card."""

    def __init__(self, name):
        """Create a card.

            rank: 2-10 or 11=J, 12=Q, 13=K, 14=A
            suit: h, d, c, s

        We create cards by name::

            >>> ks = Card("Ks")

            >>> ks.rank
            13
            >>> ks.suit
            's'
            >>> ks.name
            'Ks'

        Other examples::

            >>> ac = Card("Ac")
            >>> ac.rank
            14

            >>> td = Card("10d")
            >>> td.rank
            10
        """

        rank = name[0:-1]  # "10" is 2 chars
        suit = name[-1]

        assert rank in RANK_NAME_TO_RANK, "Bad rank: %s" % name
        assert suit in "hdcs", "Bad suit: %s" % name

        self.name = name
        self.rank = RANK_NAME_TO_RANK.get(rank)
        self.suit = suit

    def __str__(self):
        """Public print representation of a card."""

        return self.name

    def __repr__(self):
        """Debugging representation of a card."""

        return "<Card %s>" % self.name

Hands

We’ve also given you a Hand class with a lot of existing code:

poker.py
class Hand(object):
    """Hand of poker cards."""

    def __init__(self, cards):
        """Add cards to hand.

            >>> h1 = Hand([Card("As"),
            ...            Card("Ks"),
            ...            Card("Qs"),
            ...            Card("Js"),
            ...            Card("10s")])

            >>> h1.cards
            [<Card As>, <Card Ks>, <Card Qs>, <Card Js>, <Card 10s>]

        As a convenience, you can list the card names instead and
        this will turn them into Card objects before adding them::

            >>> h2 = Hand("As Ks Qs Js 10s")

            >>> h2.cards
            [<Card As>, <Card Ks>, <Card Qs>, <Card Js>, <Card 10s>]
        """

        if type(cards) is str:
            cards = cards.split()

        self.cards = [c if type(c) is Card else Card(c) for c in cards]
            
        assert len(self.cards) == 5, "Hands must have 5 cards."

    def __repr__(self):
        """Display hand.

        To make testing easier, we'll sort hand before displaying it.
        There's no formal order for suits, so we'll just use
        alphabetical order for display purposes:

            >>> h2 = Hand("8d 7s 7h 7c 7d")
            >>> h2
            <Hand 8d 7c 7d 7h 7s>
        """

        hand = sorted(self.cards,
                      key=lambda c: (14 - c.rank, c.suit))

        return "<Hand %s>" % (" ".join(str(c) for c in hand))

    def eval(self):
        """Evaluate the value of a hand."""

    def __eq__(self, other):
        """Are these two hands equal?

            >>> h1 = Hand("2d 3d 4d 5d 6d")
            >>> h2 = Hand("6d 5d 4d 3d 2d")

            >>> h1 == h2
            True
        """

        return self.eval() == other.eval()

    def __ne__(self, other):
        """Are these two hands not equal?

            >>> h1 = Hand("2d 3d 4d 5d 6d")
            >>> h2 = Hand("6d 5d 4d 3d 2d")

            >>> h1 != h2
            False
        """

        return self.eval() != other.eval()

    def __lt__(self, other):
        """Is this hand lower-ranked than the other hand?

            >>> h1 = Hand("2d 3d 4d 5d 6d")
            >>> h2 = Hand("3d 4d 5d 6d 7d")

            >>> h1 < h2
            True
        """

        return self.eval() < other.eval()

    def __le__(self, other):
        """Is this hand lower-ranked than the other hand?

            >>> h1 = Hand("2d 3d 4d 5d 6d")
            >>> h2 = Hand("3d 4d 5d 6d 7d")

            >>> h1 <= h2
            True
        """

        return self.eval() <= other.eval()

A hand contains 5 cards, and this has an __init__ method to take those cards at the time of instantiation (note that, to be helpful, you can either supply the cards as a list of Card objects or as simple string of card names, like “Qs Kd 9c 8c 7c”).

It contains an eval method, which is unimplemented. This method should output the “strength” of a hand. (How this is done is up to you: it could be a number, a string, a tuple, etc).

It then contains several special methods (__eq__, __ne__, __lt__, __le__) which handle comparisons of hands. These methods are used when you use the ==, !=, <, and <= operators to compare hands). Having these defines allows us to do things like:

>>> king_high_straight == another_king_high_straight
True

>>> king_high_straight != another_king_high_straight
False

>>> queen_high_straight < king_high_straight
True

Of course, since these special methods rely on the eval method returning something useful, they don’t yet really work.

Your Challenge

Implement the eval method. This is a tricky challenge.

This method could return a number of the strength of a hand (so, an ace-high straight flush might return 1,000,000, whereas a king-high straight flush might return 999,999). You’d have to figure out how to map the combinations to numbers.

It might be easier to return a tuple, though: imagine if you we only wanted to compare flushes and straights. A flush is higher than a straight, so any flush beats a straight. We could return a tuple like:

(is_a_flush, rank_of_straight)

And these hands would become:

Js 9s 6s 4s  2s  →  (1, 0)    # is flush
Ac Kd Qh Js 10d  →  (0, 14)   # ace-high straight
Kd Qh Js 10d 9d  →  (0, 13)   # king-high straight

When Python compares or sorts tuples (or lists), it first compares the first item and, if they’re equal, compares the second item, and so on. You could use this feature to make a data structure with enough information about the combinations that comparing this structure could compare the strengths of the hands.

Don’t forget about the tie-breaking rules (listed in the table above). So, for two hands that both contain the same two pair, make sure your eval method emits something higher for the first of these:

>>> h1 = Hand("6d 6s 2d 2s Ac")
>>> h2 = Hand("6h 6c 2h 2c Kc")

That way, we can compare them correctly:

>>> h1 == h2
False

>>> h1 > h2
True

Take your time and think carefully. Good luck!