I am thinking about poker hand (5 cards) evaluation in Java
. Now I am looking for simplicity and clarity rather than performance and efficiency. I probably can
Here's a naive approach to five-card hand comparison that I'm using to help initially populate a lookup table:
Instead of being as terse as possible, I prioritized type safety and clear, self-documenting code. If you're not familiar with the Guava types I'm using, you can browse their documentation.
And I'll include the code here (minus static imports for the enum constants at the bottom), although it's really too long to comfortably view in an answer.
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Ordering.from;
import static com.google.common.collect.Ordering.natural;
import static java.util.Comparator.comparing;
import static java.util.Comparator.comparingInt;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.Set;
import java.util.function.Function;
import com.google.common.collect.EnumMultiset;
import com.google.common.collect.Multiset;
import com.google.common.collect.Multiset.Entry;
import com.google.common.collect.Ordering;
public class Hand implements Comparable {
public final Category category;
private final LinkedList distinctRanks = new LinkedList<>();
public Hand(Set cards) {
checkArgument(cards.size() == 5);
Set suits = EnumSet.noneOf(Suit.class);
Multiset ranks = EnumMultiset.create(Rank.class);
for (Card card : cards) {
suits.add(card.suit);
ranks.add(card.rank);
}
Set> entries = ranks.entrySet();
for (Entry entry : byCountThenRank.immutableSortedCopy(entries)) {
distinctRanks.addFirst(entry.getElement());
}
Rank first = distinctRanks.getFirst();
int distinctCount = distinctRanks.size();
if (distinctCount == 5) {
boolean flush = suits.size() == 1;
if (first.ordinal() - distinctRanks.getLast().ordinal() == 4) {
category = flush ? STRAIGHT_FLUSH : STRAIGHT;
}
else if (first == ACE && distinctRanks.get(1) == FIVE) {
category = flush ? STRAIGHT_FLUSH : STRAIGHT;
// ace plays low, move to end
distinctRanks.addLast(distinctRanks.removeFirst());
}
else {
category = flush ? FLUSH : HIGH_CARD;
}
}
else if (distinctCount == 4) {
category = ONE_PAIR;
}
else if (distinctCount == 3) {
category = ranks.count(first) == 2 ? TWO_PAIR : THREE_OF_A_KIND;
}
else {
category = ranks.count(first) == 3 ? FULL_HOUSE : FOUR_OF_A_KIND;
}
}
@Override
public final int compareTo(Hand that) {
return byCategoryThenRanks.compare(this, that);
}
private static final Ordering> byCountThenRank;
private static final Comparator byCategoryThenRanks;
static {
Comparator> byCount = comparingInt(Entry::getCount);
Comparator> byRank = comparing(Entry::getElement);
byCountThenRank = from(byCount.thenComparing(byRank));
Comparator byCategory = comparing((Hand hand) -> hand.category);
Function> getRanks =
(Hand hand) -> hand.distinctRanks;
Comparator byRanks =
comparing(getRanks, natural().lexicographical());
byCategoryThenRanks = byCategory.thenComparing(byRanks);
}
public enum Category {
HIGH_CARD,
ONE_PAIR,
TWO_PAIR,
THREE_OF_A_KIND,
STRAIGHT,
FLUSH,
FULL_HOUSE,
FOUR_OF_A_KIND,
STRAIGHT_FLUSH;
}
public enum Rank {
TWO,
THREE,
FOUR,
FIVE,
SIX,
SEVEN,
EIGHT,
NINE,
TEN,
JACK,
QUEEN,
KING,
ACE;
}
public enum Suit {
DIAMONDS,
CLUBS,
HEARTS,
SPADES;
}
public enum Card {
TWO_DIAMONDS(TWO, DIAMONDS),
THREE_DIAMONDS(THREE, DIAMONDS),
FOUR_DIAMONDS(FOUR, DIAMONDS),
FIVE_DIAMONDS(FIVE, DIAMONDS),
SIX_DIAMONDS(SIX, DIAMONDS),
SEVEN_DIAMONDS(SEVEN, DIAMONDS),
EIGHT_DIAMONDS(EIGHT, DIAMONDS),
NINE_DIAMONDS(NINE, DIAMONDS),
TEN_DIAMONDS(TEN, DIAMONDS),
JACK_DIAMONDS(JACK, DIAMONDS),
QUEEN_DIAMONDS(QUEEN, DIAMONDS),
KING_DIAMONDS(KING, DIAMONDS),
ACE_DIAMONDS(ACE, DIAMONDS),
TWO_CLUBS(TWO, CLUBS),
THREE_CLUBS(THREE, CLUBS),
FOUR_CLUBS(FOUR, CLUBS),
FIVE_CLUBS(FIVE, CLUBS),
SIX_CLUBS(SIX, CLUBS),
SEVEN_CLUBS(SEVEN, CLUBS),
EIGHT_CLUBS(EIGHT, CLUBS),
NINE_CLUBS(NINE, CLUBS),
TEN_CLUBS(TEN, CLUBS),
JACK_CLUBS(JACK, CLUBS),
QUEEN_CLUBS(QUEEN, CLUBS),
KING_CLUBS(KING, CLUBS),
ACE_CLUBS(ACE, CLUBS),
TWO_HEARTS(TWO, HEARTS),
THREE_HEARTS(THREE, HEARTS),
FOUR_HEARTS(FOUR, HEARTS),
FIVE_HEARTS(FIVE, HEARTS),
SIX_HEARTS(SIX, HEARTS),
SEVEN_HEARTS(SEVEN, HEARTS),
EIGHT_HEARTS(EIGHT, HEARTS),
NINE_HEARTS(NINE, HEARTS),
TEN_HEARTS(TEN, HEARTS),
JACK_HEARTS(JACK, HEARTS),
QUEEN_HEARTS(QUEEN, HEARTS),
KING_HEARTS(KING, HEARTS),
ACE_HEARTS(ACE, HEARTS),
TWO_SPADES(TWO, SPADES),
THREE_SPADES(THREE, SPADES),
FOUR_SPADES(FOUR, SPADES),
FIVE_SPADES(FIVE, SPADES),
SIX_SPADES(SIX, SPADES),
SEVEN_SPADES(SEVEN, SPADES),
EIGHT_SPADES(EIGHT, SPADES),
NINE_SPADES(NINE, SPADES),
TEN_SPADES(TEN, SPADES),
JACK_SPADES(JACK, SPADES),
QUEEN_SPADES(QUEEN, SPADES),
KING_SPADES(KING, SPADES),
ACE_SPADES(ACE, SPADES);
public final Rank rank;
public final Suit suit;
Card(Rank rank, Suit suit) {
this.rank = rank;
this.suit = suit;
}
}
}