Machine Coding Problem

Deck of Cards / Poker

maco60macoAllgamesrule-enginehand-evaluationsortingkickers-tie-breaking
Commonly Asked By:SonyMicrosoftZynga

Requirements & System Scope

Functional Scope (In-Scope)

  • Complete Playing structures: Encapsulate Rank, Suit, and immutable Card representations cleanly.
  • Fisher-Yates Shuffling: Distribute and randomize card array indexes uniformly.
  • Poker Hand Classification: Evaluate 5-card combinations into ranked bands (Royal Flush, Straight Flush, Four of a Kind, etc.).
  • Tie-Breaking Kickers logic: Resolve matching hands by evaluating outstanding high card kickers.

Explicit Boundaries (Out-of-Scope)

  • No Pot betting loops: Excludes tracking chips balances or dealer bets calculations.
  • No Multi-player Lobby: Bypasses live networks connections or server lobbies.

Class Diagram & Entity Relationships

Class organization showing Deck, Card, and Hand evaluation:

Loading...

Design Patterns & SOLID Principles

  • Strategy/Rule Engine Pattern: Delegates complex combination rules (Flush, Straight, Frequencies) to isolated checking loops, preventing colossal conditional chains.
  • Value Object Pattern: Defining Card as immutable prevents reference manipulation and safeguards structural rules of the card deck.

Core Execution Workflows

Deck & Hand Evaluation Sequence

  1. Initialize 52-card deck and invoke Fisher-Yates: deck.shuffle().
  2. Deal cards to players: deck.deal(5).
  3. Classify combinations by evaluating:
    1. Flush predicates (all suits uniform).
    2. Straight predicates (cards sorted sequentially, allowing Ace-low straight A,2,3,4,5).
    3. Group occurrences of card ranks to detect pairs, double-pairs, triplets, full houses, and quads.
  4. Apply high kickers to ties to identify ultimate winners.

Concurrency & Thread Safety Strategy

When multiple matches process hand updates concurrently:

  • Immutable Hand Evaluation State: Keeps evaluation structures strictly read-only after construction, enabling concurrent thread comparisons.
  • Stateless Deck Shuffling Actions: Protects deck pools with standard lock controls at deal stages.

Complete Clean Code Blueprint

Clean reference designs demonstrating modular shufflers in Java and Python:

// โ”€โ”€โ”€ JAVA BLUEPRINT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import java.util.*;
import java.util.stream.Collectors;

enum Suit { SPADES, HEARTS, DIAMONDS, CLUBS }

enum Rank {
    TWO(2), THREE(3), FOUR(4), FIVE(5), SIX(6), SEVEN(7), EIGHT(8), NINE(9), TEN(10),
    JACK(11), QUEEN(12), KING(13), ACE(14);

    private final int value;
    Rank(int val) { this.value = val; }
    public int getValue() { return value; }
}

enum HandRank {
    HIGH_CARD(1), ONE_PAIR(2), TWO_PAIR(3), THREE_OF_A_KIND(4), STRAIGHT(5),
    FLUSH(6), FULL_HOUSE(7), FOUR_OF_A_KIND(8), STRAIGHT_FLUSH(9), ROYAL_FLUSH(10);

    private final int score;
    HandRank(int score) { this.score = score; }
    public int getScore() { return score; }
}

class Card implements Comparable<Card> {
    private final Rank rank;
    private final Suit suit;

    public Card(Rank rank, Suit suit) {
        this.rank = rank;
        this.suit = suit;
    }

    public Rank getRank() { return rank; }
    public Suit getSuit() { return suit; }

    @Override
    public int compareTo(Card other) {
        return Integer.compare(this.rank.getValue(), other.rank.getValue());
    }

    @Override
    public String toString() { return rank + " of " + suit; }
}

class Deck {
    private final List<Card> cards = new ArrayList<>();

    public Deck() { reset(); }

    public synchronized void reset() {
        cards.clear();
        for (Suit s : Suit.values()) {
            for (Rank r : Rank.values()) {
                cards.add(new Card(r, s));
            }
        }
    }

    public synchronized void shuffle() {
        Random rand = new Random();
        for (int i = cards.size() - 1; i > 0; i--) {
            int j = rand.nextInt(i + 1);
            Card temp = cards.get(i);
            cards.set(i, cards.get(j));
            cards.set(j, temp);
        }
    }

    public synchronized List<Card> deal(int count) {
        List<Card> hand = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            if (!cards.isEmpty()) {
                hand.add(cards.remove(cards.size() - 1));
            }
        }
        return hand;
    }
}

class PokerHand implements Comparable<PokerHand> {
    private final List<Card> cards;
    private HandRank handRank;
    private List<Integer> tieBreakers; // Sorted representation for kicker checks

    public PokerHand(List<Card> cards) {
        if (cards.size() != 5) throw new IllegalArgumentException("Must have exactly 5 cards.");
        this.cards = new ArrayList<>(cards);
        Collections.sort(this.cards);
        evaluate();
    }

    public HandRank getHandRank() { return handRank; }
    public List<Card> getCards() { return cards; }

    private void evaluate() {
        boolean isFlush = cards.stream().map(Card::getSuit).distinct().count() == 1;
        boolean isStraight = checkStraight();

        Map<Integer, Long> freq = cards.stream()
                .collect(Collectors.groupingBy(c -> c.getRank().getValue(), Collectors.counting()));

        List<Map.Entry<Integer, Long>> sortedFreq = freq.entrySet().stream()
                .sorted((e1, e2) -> {
                    int c = Long.compare(e2.getValue(), e1.getValue());
                    if (c != 0) return c;
                    return Integer.compare(e2.getKey(), e1.getKey());
                }).collect(Collectors.toList());

        tieBreakers = sortedFreq.stream().map(Map.Entry::getKey).collect(Collectors.toList());

        // Straight Adjustment for Ace-low (A, 2, 3, 4, 5)
        if (isStraight && cards.get(4).getRank() == Rank.ACE && cards.get(0).getRank() == Rank.TWO) {
            tieBreakers = Arrays.asList(5, 4, 3, 2, 1); // Rank the 5 high straight
        }

        if (isFlush && isStraight) {
            if (cards.get(0).getRank() == Rank.TEN) {
                handRank = HandRank.ROYAL_FLUSH;
            } else {
                handRank = HandRank.STRAIGHT_FLUSH;
            }
        } else if (sortedFreq.get(0).getValue() == 4) {
            handRank = HandRank.FOUR_OF_A_KIND;
        } else if (sortedFreq.get(0).getValue() == 3 && sortedFreq.get(1).getValue() == 2) {
            handRank = HandRank.FULL_HOUSE;
        } else if (isFlush) {
            handRank = HandRank.FLUSH;
            // Overwrite tieBreakers to sort card ranks descending
            tieBreakers = cards.stream().map(c -> c.getRank().getValue())
                    .sorted(Comparator.reverseOrder()).collect(Collectors.toList());
        } else if (isStraight) {
            handRank = HandRank.STRAIGHT;
        } else if (sortedFreq.get(0).getValue() == 3) {
            handRank = HandRank.THREE_OF_A_KIND;
        } else if (sortedFreq.get(0).getValue() == 2 && sortedFreq.get(1).getValue() == 2) {
            handRank = HandRank.TWO_PAIR;
        } else if (sortedFreq.get(0).getValue() == 2) {
            handRank = HandRank.ONE_PAIR;
        } else {
            handRank = HandRank.HIGH_CARD;
            tieBreakers = cards.stream().map(c -> c.getRank().getValue())
                    .sorted(Comparator.reverseOrder()).collect(Collectors.toList());
        }
    }

    private boolean checkStraight() {
        // Ace-low straight edge case
        if (cards.get(0).getRank() == Rank.TWO && cards.get(1).getRank() == Rank.THREE &&
            cards.get(2).getRank() == Rank.FOUR && cards.get(3).getRank() == Rank.FIVE &&
            cards.get(4).getRank() == Rank.ACE) {
            return true;
        }
        for (int i = 0; i < 4; i++) {
            if (cards.get(i).getRank().getValue() + 1 != cards.get(i + 1).getRank().getValue()) {
                return false;
            }
        }
        return true;
    }

    @Override
    public int compareTo(PokerHand other) {
        int rankCompare = Integer.compare(this.handRank.getScore(), other.handRank.getScore());
        if (rankCompare != 0) return rankCompare;

        // Kickers check
        for (int i = 0; i < this.tieBreakers.size(); i++) {
            int comp = Integer.compare(this.tieBreakers.get(i), other.tieBreakers.get(i));
            if (comp != 0) return comp;
        }
        return 0;
    }

    @Override
    public String toString() {
        return handRank + " [" + cards.stream().map(Card::toString).collect(Collectors.joining(", ")) + "]";
    }
}

public class Main {
    public static void main(String[] args) {
        Deck deck = new Deck();
        deck.shuffle();

        List<Card> player1Cards = Arrays.asList(
                new Card(Rank.ACE, Suit.HEARTS), new Card(Rank.KING, Suit.HEARTS),
                new Card(Rank.QUEEN, Suit.HEARTS), new Card(Rank.JACK, Suit.HEARTS),
                new Card(Rank.TEN, Suit.HEARTS)
        );

        List<Card> player2Cards = Arrays.asList(
                new Card(Rank.ACE, Suit.SPADES), new Card(Rank.ACE, Suit.CLUBS),
                new Card(Rank.KING, Suit.DIAMONDS), new Card(Rank.KING, Suit.SPADES),
                new Card(Rank.TEN, Suit.CLUBS)
        );

        PokerHand hand1 = new PokerHand(player1Cards);
        PokerHand hand2 = new PokerHand(player2Cards);

        System.out.println("Player 1 Hand: " + hand1);
        System.out.println("Player 2 Hand: " + hand2);

        int comparison = hand1.compareTo(hand2);
        if (comparison > 0) {
            System.out.println("Player 1 Wins!");
        } else if (comparison < 0) {
            System.out.println("Player 2 Wins!");
        } else {
            System.out.println("Split Pot (Tie)!");
        }
    }
}

๐Ÿ’ฌReview

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...