Avatar of artemkorsakov

artemkorsakov's solution

to Poker in the Java Track

Published at Feb 12 2019 · 0 comments
Instructions
Test suite
Solution

Pick the best hand(s) from a list of poker hands.

See wikipedia for an overview of poker hands.

Running the tests

You can run all the tests for an exercise by entering

$ gradle test

in your terminal.

Source

Inspired by the training course from Udacity. https://www.udacity.com/course/viewer#!/c-cs212/

Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

PokerTest.java

import org.junit.Ignore;
import org.junit.Test;

import java.util.Arrays;
import java.util.Collections;

import static org.junit.Assert.assertEquals;

public class PokerTest {
    @Test
    public void oneHand() {
        String hand = "4S 5S 7H 8D JC";
        assertEquals(Collections.singletonList(hand), new Poker(Collections.singletonList(hand)).getBestHands());
    }

    @Ignore("Remove to run test")
    @Test
    public void highestCardWins() {
        String highest8 = "4D 5S 6S 8D 3C";
        String highest10 = "2S 4C 7S 9H 10H";
        String highestJ = "3S 4S 5D 6H JH";
        assertEquals(Collections.singletonList(highestJ),
                     new Poker(Arrays.asList(highest8, highest10, highestJ)).getBestHands());
    }

    @Ignore("Remove to run test")
    @Test
    public void tieHasMultipleWinners() {
        String highest8 = "4D 5S 6S 8D 3C";
        String highest10 = "2S 4C 7S 9H 10H";
        String highestJh = "3S 4S 5D 6H JH";
        String highestJd = "3H 4H 5C 6C JD";
        assertEquals(Arrays.asList(highestJh, highestJd),
                     new Poker(Arrays.asList(highest8, highest10, highestJh, highestJd)).getBestHands());
    }

    @Ignore("Remove to run test")
    @Test
    public void sameHighCards() {
        String nextHighest3 = "3S 5H 6S 8D 7H";
        String nextHighest2 = "2S 5D 6D 8C 7S";
        assertEquals(Collections.singletonList(nextHighest3),
                     new Poker(Arrays.asList(nextHighest3, nextHighest2)).getBestHands());
    }

    @Ignore("Remove to run test")
    @Test
    public void nothingVsOnePair() {
        String nothing = "4S 5H 6C 8D KH";
        String pairOf4 = "2S 4H 6S 4D JH";
        assertEquals(Collections.singletonList(pairOf4),
                     new Poker(Arrays.asList(nothing, pairOf4)).getBestHands());
    }

    @Ignore("Remove to run test")
    @Test
    public void twoPairs() {
        String pairOf2 = "4S 2H 6S 2D JH";
        String pairOf4 = "2S 4H 6C 4D JD";
        assertEquals(Collections.singletonList(pairOf4),
                     new Poker(Arrays.asList(pairOf2, pairOf4)).getBestHands());
    }

    @Ignore("Remove to run test")
    @Test
    public void onePairVsDoublePair() {
        String pairOf8 = "2S 8H 6S 8D JH";
        String doublePair = "4S 5H 4C 8C 5C";
        assertEquals(Collections.singletonList(doublePair),
                     new Poker(Arrays.asList(pairOf8, doublePair)).getBestHands());
    }

    @Ignore("Remove to run test")
    @Test
    public void twoDoublePairs() {
        String doublePair2And8 = "2S 8H 2D 8D 3H";
        String doublePair4And5 = "4S 5H 4C 8S 5D";
        assertEquals(Collections.singletonList(doublePair2And8),
                     new Poker(Arrays.asList(doublePair2And8, doublePair4And5)).getBestHands());
    }

    @Ignore("Remove to run test")
    @Test
    public void sameHighestPair() {
        String doublePair2AndQ = "2S QS 2C QD JH";
        String doublePairJAndQ = "JD QH JS 8D QC";
        assertEquals(Collections.singletonList(doublePairJAndQ),
                     new Poker(Arrays.asList(doublePairJAndQ, doublePair2AndQ)).getBestHands());
    }

    @Ignore("Remove to run test")
    @Test
    public void identicallyRankedPairs() {
        String kicker8 = "JD QH JS 8D QC";
        String kicker2 = "JS QS JC 2D QD";
        assertEquals(Collections.singletonList(kicker8), new Poker(Arrays.asList(kicker8, kicker2)).getBestHands());
    }

    @Ignore("Remove to run test")
    @Test
    public void doublePairVsThree() {
        String doublePair2And8 = "2S 8H 2H 8D JH";
        String threeOf4 = "4S 5H 4C 8S 4H";
        assertEquals(Collections.singletonList(threeOf4),
                     new Poker(Arrays.asList(doublePair2And8, threeOf4)).getBestHands());
    }

    @Ignore("Remove to run test")
    @Test
    public void twoThrees() {
        String threeOf2 = "2S 2H 2C 8D JH";
        String threeOf1 = "4S AH AS 8C AD";
        assertEquals(Collections.singletonList(threeOf1), new Poker(Arrays.asList(threeOf2, threeOf1)).getBestHands());
    }

    @Ignore("Remove to run test")
    @Test
    public void sameThreesMultipleDecks() {
        String remainingCard7 = "4S AH AS 7C AD";
        String remainingCard8 = "4S AH AS 8C AD";
        assertEquals(Collections.singletonList(remainingCard8),
                new Poker(Arrays.asList(remainingCard7, remainingCard8)).getBestHands());
    }

    @Ignore("Remove to run test")
    @Test
    public void threeVsStraight() {
        String threeOf4 = "4S 5H 4C 8D 4H";
        String straight = "3S 4D 2S 6D 5C";
        assertEquals(Collections.singletonList(straight), new Poker(Arrays.asList(threeOf4, straight)).getBestHands());
    }

    @Ignore("Remove to run test")
    @Test
    public void acesCanEndAStraight() {
        String hand = "4S 5H 4C 8D 4H";
        String straightEndsA = "10D JH QS KD AC";
        assertEquals(Collections.singletonList(straightEndsA),
                     new Poker(Arrays.asList(hand, straightEndsA)).getBestHands());
    }

    @Ignore("Remove to run test")
    @Test
    public void acesCanStartAStraight() {
        String hand = "4S 5H 4C 8D 4H";
        String straightStartA = "4D AH 3S 2D 5C";
        assertEquals(Collections.singletonList(straightStartA),
                     new Poker(Arrays.asList(hand, straightStartA)).getBestHands());
    }

    @Ignore("Remove to run test")
    @Test
    public void twoStraights() {
        String straightTo8 = "4S 6C 7S 8D 5H";
        String straightTo9 = "5S 7H 8S 9D 6H";
        assertEquals(Collections.singletonList(straightTo9),
                     new Poker(Arrays.asList(straightTo8, straightTo9)).getBestHands());
    }

    @Ignore("Remove to run test")
    @Test
    public void theLowestStraightStartsWithAce() {
        String straight = "2H 3C 4D 5D 6H";
        String straightStartA = "4S AH 3S 2D 5H";
        assertEquals(Collections.singletonList(straight),
                     new Poker(Arrays.asList(straight, straightStartA)).getBestHands());
    }

    @Ignore("Remove to run test")
    @Test
    public void straightVsFlush() {
        String straightTo8 = "4C 6H 7D 8D 5H";
        String flushTo7 = "2S 4S 5S 6S 7S";
        assertEquals(Collections.singletonList(flushTo7),
                     new Poker(Arrays.asList(straightTo8, flushTo7)).getBestHands());
    }

    @Ignore("Remove to run test")
    @Test
    public void twoFlushes() {
        String flushTo8 = "4H 7H 8H 9H 6H";
        String flushTo7 = "2S 4S 5S 6S 7S";
        assertEquals(Collections.singletonList(flushTo8), new Poker(Arrays.asList(flushTo8, flushTo7)).getBestHands());
    }

    @Ignore("Remove to run test")
    @Test
    public void flushVsFull() {
        String flushTo8 = "3H 6H 7H 8H 5H";
        String full = "4S 5H 4C 5D 4H";
        assertEquals(Collections.singletonList(full), new Poker(Arrays.asList(full, flushTo8)).getBestHands());
    }

    @Ignore("Remove to run test")
    @Test
    public void twoFulls() {
        String fullOf4By9 = "4H 4S 4D 9S 9D";
        String fullOf5By8 = "5H 5S 5D 8S 8D";
        assertEquals(Collections.singletonList(fullOf5By8),
                     new Poker(Arrays.asList(fullOf4By9, fullOf5By8)).getBestHands());
    }

    @Ignore("Remove to run test")
    @Test
    public void twoFullssameThripletMultipleDecks() {
        String fullOf5By9 = "5H 5S 5D 9S 9D";
        String fullOf5By8 = "5H 5S 5D 8S 8D";
        assertEquals(Collections.singletonList(fullOf5By9),
                     new Poker(Arrays.asList(fullOf5By9, fullOf5By8)).getBestHands());
    }

    @Ignore("Remove to run test")
    @Test
    public void fullVsSquare() {
        String full = "4S 5H 4D 5D 4H";
        String squareOf3 = "3S 3H 2S 3D 3C";
        assertEquals(Collections.singletonList(squareOf3), new Poker(Arrays.asList(full, squareOf3)).getBestHands());
    }

    @Ignore("Remove to run test")
    @Test
    public void twoSquares() {
        String squareOf2 = "2S 2H 2C 8D 2D";
        String squareOf5 = "4S 5H 5S 5D 5C";
        assertEquals(Collections.singletonList(squareOf5),
                     new Poker(Arrays.asList(squareOf2, squareOf5)).getBestHands());
    }

    @Ignore("Remove to run test")
    @Test
    public void sameSquaresMultipleDecks() {
        String kicker2 = "3S 3H 2S 3D 3C";
        String kicker4 = "3S 3H 4S 3D 3C";
        assertEquals(Collections.singletonList(kicker4), new Poker(Arrays.asList(kicker2, kicker4)).getBestHands());
    }

    @Ignore("Remove to run test")
    @Test
    public void squareVsStraightFlush() {
        String squareOf5 = "4S 5H 5S 5D 5C";
        String straightFlushTo9 = "7S 8S 9S 6S 10S";
        assertEquals(Collections.singletonList(straightFlushTo9),
                new Poker(Arrays.asList(squareOf5, straightFlushTo9)).getBestHands());
    }

    @Ignore("Remove to run test")
    @Test
    public void twoStraightFlushes() {
        String straightFlushTo8 = "4H 6H 7H 8H 5H";
        String straightFlushTo9 = "5S 7S 8S 9S 6S";
        assertEquals(Collections.singletonList(straightFlushTo9),
                new Poker(Arrays.asList(straightFlushTo8, straightFlushTo9)).getBestHands());
    }
}

src/main/java/Poker.java

import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

class Poker {
    private List<Hand> hands;

    Poker(List<String> hands) {
        this.hands = hands.stream().map(Hand::new).sorted(Comparator.comparing(o -> ((Hand) o))).collect(Collectors.toList());
    }

    List<String> getBestHands() {
        return hands.stream().filter(h -> h.compareTo(hands.get(0)) == 0).map(Hand::getHand).collect(Collectors.toList());
    }
}

src/main/java/Hand.java

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

class Hand implements Comparable<Hand>, Comparator<Hand> {
    private String hand;
    private List<Card> cards;
    private PokerHands pokerHands = PokerHands.HIGH_CARD;
    private List<PokerRank> ranks = Arrays.asList(PokerRank.TWO, PokerRank.TWO, PokerRank.TWO, PokerRank.TWO, PokerRank.TWO);

    Hand(String hand) {
        this.hand = hand;
        String[] split = hand.split(" ");
        if (split.length != 5) {
            throw new IllegalArgumentException("Invalid hand");
        }
        cards = Arrays.stream(split).map(Card::new).collect(Collectors.toList());
        calculate();
    }

    private void calculate() {
        if (isFiveOfAKind()) {
            calculateFiveOfAKind();
        } else if (isStraightFlush()) {
            calculateStraightFlush();
        } else if (isFourOfAKind()) {
            calculateFourOfAKind();
        } else if (isFullHouse()) {
            calculateFullHouse();
        } else if (isFlush()) {
            calculateFlush();
        } else if (isStraight()) {
            calculateStraight();
        } else if (isThreeOfAKind()) {
            calculateThreeOfAKind();
        } else if (isTwoPair()) {
            calculateTwoPair();
        } else if (isOnePair()) {
            calculateOnePair();
        } else {
            calculateHighCard();
        }
    }

    String getHand() {
        return hand;
    }

    @Override
    public int compare(Hand o1, Hand o2) {
        return o1.compareTo(o2);
    }

    @Override
    public int compareTo(Hand o) {
        int diff = pokerHands.compareTo(o.pokerHands);
        return diff != 0 ? diff : IntStream.range(0, ranks.size()).map(i -> ranks.get(i).compareTo(o.ranks.get(i)))
                .filter(r -> r != 0).findFirst().orElse(0);
    }

    private List<PokerRank> getSortedPokerRanks() {
        if (sortedPokerRanks == null) {
            sortedPokerRanks = cards.stream().map(Card::getPokerRank).sorted((o1, o2) -> o2.getRank() - o1.getRank()).collect(Collectors.toList());
        }
        return sortedPokerRanks;
    }

    private List<PokerRank> getSortedDistinctPokerRanks() {
        if (sortedDistinctPokerRanks == null) {
            sortedDistinctPokerRanks = getSortedPokerRanks().stream().distinct().collect(Collectors.toList());
        }
        return sortedDistinctPokerRanks;
    }

    private List<PokerRank> sortedDistinctPokerRanks = null;
    private List<PokerRank> sortedPokerRanks = null;

    private boolean isFiveOfAKind() {
        return getSortedDistinctPokerRanks().size() == 1;
    }

    private void calculateFiveOfAKind() {
        pokerHands = PokerHands.FIVE_OF_A_KIND;
        ranks.set(0, cards.get(0).getPokerRank());
    }

    private boolean isStraightFlush() {
        return isStraight() && isFlush();
    }

    private void calculateStraightFlush() {
        pokerHands = PokerHands.STRAIGHT_FLUSH;
        List<PokerRank> sortedRanks = getSortedPokerRanks();
        PokerRank pokerRank = (sortedRanks.get(0) == PokerRank.A && sortedRanks.get(1) == PokerRank.FIVE) ? PokerRank.FIVE : sortedRanks.get(0);
        ranks.set(0, pokerRank);
    }

    private boolean isStraight() {
        List<PokerRank> ranks = getSortedPokerRanks();
        if (ranks.containsAll(Arrays.asList(PokerRank.FIVE, PokerRank.FOUR, PokerRank.THREE, PokerRank.TWO, PokerRank.A))) {
            return true;
        }
        for (int i = 0; i < ranks.size() - 1; i++) {
            if (ranks.get(i).getRank() - ranks.get(i + 1).getRank() != 1) {
                return false;
            }
        }
        return true;
    }

    private void calculateStraight() {
        pokerHands = PokerHands.STRAIGHT;
        List<PokerRank> sortedRanks = getSortedPokerRanks();
        PokerRank pokerRank = (sortedRanks.get(0) == PokerRank.A && sortedRanks.get(1) == PokerRank.FIVE) ? PokerRank.FIVE : sortedRanks.get(0);
        ranks.set(0, pokerRank);
    }

    private boolean isFlush() {
        return cards.stream().map(Card::getPokerSuit).distinct().count() == 1;
    }

    private void calculateFlush() {
        ranks = getSortedPokerRanks();
        pokerHands = PokerHands.FLUSH;
    }

    private boolean isFourOfAKind() {
        long countOfRank = getSortedDistinctPokerRanks().size();
        long countOfFirstCard = cards.stream().filter(c -> c.getPokerRank() == cards.get(0).getPokerRank()).count();
        return countOfRank == 2 && (countOfFirstCard == 1 || countOfFirstCard == 4);
    }

    private void calculateFourOfAKind() {
        List<PokerRank> sortedRanks = getSortedPokerRanks();
        pokerHands = PokerHands.FOUR_OF_A_KIND;
        boolean isFirstEqualSecond = sortedRanks.get(0).getRank() == sortedRanks.get(1).getRank();
        ranks.set(0, isFirstEqualSecond ? sortedRanks.get(0) : sortedRanks.get(1));
        ranks.set(1, isFirstEqualSecond ? sortedRanks.get(4) : sortedRanks.get(0));
    }

    private boolean isFullHouse() {
        long countOfRank = getSortedDistinctPokerRanks().size();
        long countOfFirstCard = cards.stream().filter(c -> c.getPokerRank() == cards.get(0).getPokerRank()).count();
        return countOfRank == 2 && (countOfFirstCard == 2 || countOfFirstCard == 3);
    }

    private void calculateFullHouse() {
        List<PokerRank> sortedRanks = getSortedPokerRanks();
        pokerHands = PokerHands.FULL_HOUSE;
        ranks.set(0, sortedRanks.get(2));
        boolean isSecondEqualThird = sortedRanks.get(1).getRank() == sortedRanks.get(2).getRank();
        ranks.set(1, isSecondEqualThird ? sortedRanks.get(3) : sortedRanks.get(1));
    }

    private boolean isThreeOfAKind() {
        long countOfRank = getSortedDistinctPokerRanks().size();
        if (countOfRank != 3) {
            return false;
        }
        List<PokerRank> ranks = getSortedPokerRanks();
        long countOfFirstCard = cards.stream().filter(c -> c.getPokerRank() == ranks.get(0)).count();
        long countOfLastCard = cards.stream().filter(c -> c.getPokerRank() == ranks.get(ranks.size() - 1)).count();
        return (countOfFirstCard == 1 && countOfLastCard == 1) ||
                (countOfFirstCard == 3 && countOfLastCard == 1) ||
                (countOfFirstCard == 1 && countOfLastCard == 3);
    }

    private void calculateThreeOfAKind() {
        List<PokerRank> sortedRanks = getSortedPokerRanks();
        pokerHands = PokerHands.THREE_OF_A_KIND;
        int index = IntStream.range(0, sortedRanks.size() - 2)
                .filter(i -> sortedRanks.get(i).getRank() == sortedRanks.get(i + 1).getRank()
                        && sortedRanks.get(i + 1).getRank() == sortedRanks.get(i + 2).getRank())
                .findFirst().orElse(0);
        ranks.set(0, sortedRanks.get(2));
        ranks.set(1, sortedRanks.get(index == 0 ? 3 : 0));
        ranks.set(2, sortedRanks.get(index == 2 ? 1 : 4));
    }

    private boolean isTwoPair() {
        long countOfRank = getSortedDistinctPokerRanks().size();
        if (countOfRank != 3) {
            return false;
        }
        List<PokerRank> ranks = getSortedPokerRanks();
        long countOfFirstCard = cards.stream().filter(c -> c.getPokerRank() == ranks.get(0)).count();
        long countOfLastCard = cards.stream().filter(c -> c.getPokerRank() == ranks.get(ranks.size() - 1)).count();
        return (countOfFirstCard == 2 && countOfLastCard == 2) ||
                (countOfFirstCard == 2 && countOfLastCard == 1) ||
                (countOfFirstCard == 1 && countOfLastCard == 2);
    }

    private void calculateTwoPair() {
        List<PokerRank> sortedRanks = getSortedPokerRanks();
        pokerHands = PokerHands.TWO_PAIR;
        boolean isFirstEqualSecond = sortedRanks.get(0).getRank() == sortedRanks.get(1).getRank();
        boolean isThirdEqualFourth = sortedRanks.get(2).getRank() == sortedRanks.get(3).getRank();
        ranks.set(0, sortedRanks.get(1));
        ranks.set(1, sortedRanks.get(3));
        int thirdIndex = isFirstEqualSecond && isThirdEqualFourth ? 4 : isFirstEqualSecond ? 2 : 0;
        ranks.set(2, sortedRanks.get(thirdIndex));
    }

    private boolean isOnePair() {
        return getSortedDistinctPokerRanks().size() == 4;
    }

    private void calculateOnePair() {
        pokerHands = PokerHands.ONE_PAIR;
        List<PokerRank> sortedRanks = getSortedPokerRanks();
        int index = IntStream.range(0, sortedRanks.size() - 1)
                .filter(i -> sortedRanks.get(i).getRank() == sortedRanks.get(i + 1).getRank())
                .findFirst().orElse(0);
        ranks.set(0, sortedRanks.get(index));
        ranks.set(1, sortedRanks.get(index == 0 ? 2 : 0));
        ranks.set(2, sortedRanks.get(index < 2 ? 3 : 1));
        ranks.set(3, sortedRanks.get(index != 3 ? 4 : 2));
    }

    private void calculateHighCard() {
        ranks = getSortedPokerRanks();
        pokerHands = PokerHands.HIGH_CARD;
    }
}

src/main/java/PokerHands.java

enum PokerHands {
    FIVE_OF_A_KIND(9),    // All cards are equal
    STRAIGHT_FLUSH(8),    // A straight flush is a hand that contains five cards of sequential rank, all of the same suit
    FOUR_OF_A_KIND(7),    // Four of a kind is a hand that contains four cards of one rank and one card of another rank
    FULL_HOUSE(6),        // A full house is a hand that contains three cards of one rank and two cards of another rank
    FLUSH(5),             // A flush is a hand that contains five cards all of the same suit, not all of sequential rank
    STRAIGHT(4),          // A straight is a hand that contains five cards of sequential rank, not all of the same suit
    THREE_OF_A_KIND(3),   // Three of a kind is a hand that contains three cards of one rank and two cards of two other ranks (the kickers)
    TWO_PAIR(2),          // Two pair is a hand that contains two cards of one rank, two cards of another rank and one card of a third rank (the kicker)
    ONE_PAIR(1),          // One pair is a hand that contains two cards of one rank and three cards of three other ranks (the kickers)
    HIGH_CARD(0);          // High card is a hand that does not fall into any other category

    private final int rank;

    PokerHands(int rank) {
        this.rank = rank;
    }

    int getRank() {
        return rank;
    }
}

src/main/java/Card.java

class Card {
    private PokerRank pokerRank;
    private PokerSuit pokerSuit;

    Card(String card) {
        if (card.length() != 2 && card.length() != 3) {
            throw new IllegalArgumentException("Invalid card");
        }
        pokerRank = PokerRank.getPokerRank(card.substring(0, card.length() - 1));
        pokerSuit = PokerSuit.valueOf(card.substring(card.length() - 1));
    }

    PokerRank getPokerRank() {
        return pokerRank;
    }

    PokerSuit getPokerSuit() {
        return pokerSuit;
    }
}

src/main/java/PokerRank.java

enum PokerRank {
    A(14), K(13), Q(12), J(11), TEN(10), NINE(9), EIGHT(8), SEVEN(7), SIX(6), FIVE(5), FOUR(4), THREE(3), TWO(2);

    private final int rank;

    PokerRank(int rank) {
        this.rank = rank;
    }

    static PokerRank getPokerRank(String s) {
        switch (s) {
            case "A":
                return A;
            case "K":
                return K;
            case "Q":
                return Q;
            case "J":
                return J;
            case "10":
                return TEN;
            case "9":
                return NINE;
            case "8":
                return EIGHT;
            case "7":
                return SEVEN;
            case "6":
                return SIX;
            case "5":
                return FIVE;
            case "4":
                return FOUR;
            case "3":
                return THREE;
            case "2":
                return TWO;
        }
        return null;
    }

    int getRank() {
        return rank;
    }
}

src/main/java/PokerSuit.java

enum PokerSuit {
    D, // Diamonds
    S, // Spades
    C, // Clubs
    H  // Hearts
}

Community comments

Find this solution interesting? Ask the author a question to learn more.

What can you learn from this solution?

A huge amount can be learned from reading other people’s code. This is why we wanted to give exercism users the option of making their solutions public.

Here are some questions to help you reflect on this solution and learn the most from it.

  • What compromises have been made?
  • Are there new concepts here that you could read more about to improve your understanding?