The simplest algorithm for poker hand evaluation

2019-01-30 15:55发布

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 write a "naive" algorithm but it requires a lot of code.

I saw also a few poker evaluation libraries, which use hashing and bitwise operations, but they look rather complex.

What is the "cleanest and simplest" algorithm for poker hand evaluation ?

9条回答
Deceive 欺骗
2楼-- · 2019-01-30 16:45

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<Hand> {
    public final Category category;

    private final LinkedList<Rank> distinctRanks = new LinkedList<>();

    public Hand(Set<Card> cards) {
        checkArgument(cards.size() == 5);
        Set<Suit> suits = EnumSet.noneOf(Suit.class);
        Multiset<Rank> ranks = EnumMultiset.create(Rank.class);
        for (Card card : cards) {
            suits.add(card.suit);
            ranks.add(card.rank);
        }
        Set<Entry<Rank>> entries = ranks.entrySet();
        for (Entry<Rank> 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<Entry<Rank>> byCountThenRank;

    private static final Comparator<Hand> byCategoryThenRanks;

    static {
        Comparator<Entry<Rank>> byCount = comparingInt(Entry::getCount);
        Comparator<Entry<Rank>> byRank = comparing(Entry::getElement);
        byCountThenRank = from(byCount.thenComparing(byRank));
        Comparator<Hand> byCategory = comparing((Hand hand) -> hand.category);
        Function<Hand, Iterable<Rank>> getRanks =
                (Hand hand) -> hand.distinctRanks;
        Comparator<Hand> 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;
        }
    }
}
查看更多
家丑人穷心不美
3楼-- · 2019-01-30 16:45

Here is the algorithm translated to R, tested with a 6 card deck, corresponding to 42.504 combinations given by the result of:

C65

combinations of poker hands. Did not tested with 13 card deck due to processing limitations (it would correspond to 2.598.960 combinations).

The algorithm represents the value of a hand by a string, composed by 2 parts:

  • 5 character with ordered card count (ex. "31100" means three of a kind)
  • The card numbers are valued by letters from 'B' (Deuce) to 'N' (Ace) (ex. 'NILH' means Ace, Queen, Nine and Eight). It starts in letter 'B' because of the A2345 poker hand where the Ace comes before the '2' which (the Ace) will have the value 'A'.

So, for example, "32000NB" will be a Full House of three Aces and two Deuce.

The poker hand value string is convenient for comparative and ordering purposes.

library(tidyverse)
library(gtools)

hand_value <- function(playerhand) {

  numbers <- str_split("23456789TJQKA", "")[[1]]
  suits <- str_split("DCHS", "")[[1]]

  playerhand <- data.frame(card = playerhand) %>% separate(card, c("number", "suit"), sep = 1)

  number_values <- data.frame(number = numbers, value = LETTERS[2:14], stringsAsFactors = FALSE)

  playerhand_number <- playerhand %>% 
    group_by(number) %>% 
    count(number) %>%
    inner_join(number_values, by = "number") %>%
    arrange(desc(n), desc(value))

  playerhand_suit <- playerhand %>% 
    group_by(suit) %>% 
    count(suit) %>%
    arrange(desc(n))

  if (nrow(playerhand_number) == 5)
    {
      if (playerhand_number[1,1] == 'A' & playerhand_number[2,1] == '5')
        playerhand_number <- data.frame(playerhand_number[,1:2], value = str_split("EDCBA", "")[[1]], stringsAsFactors = FALSE)
      straight <- asc(playerhand_number[1,3]) - asc(playerhand_number[5,3]) == 4
    } else
      straight = FALSE

  flush <- nrow(playerhand_suit) == 1

  if (flush)
    {
    if (straight)
      playerhand_number <- data.frame(playerhand_number[,c(1,3)], n = c(5, 0, 0, 0, 0), stringsAsFactors = FALSE) else
      playerhand_number <- data.frame(playerhand_number[,c(1,3)], n = c(3, 1, 1, 2, 0), stringsAsFactors = FALSE)
    } else
    {
    if (straight)
      playerhand_number <- data.frame(playerhand_number[,c(1,3)], n = c(3, 1, 1, 1, 0), stringsAsFactors = FALSE)
    }  

  playerhand_value <- append(append(c(playerhand_number$n), rep("0", 5 - nrow(playerhand_number))), c(playerhand_number$value))
  playerhand_value <- paste(playerhand_value, collapse = '')

  playerhand_value

}

Testing the function with the same hands of above example:

l <- c("8C TS KC 9H 4S", "7D 2S 5D 3S AC", "8C AD 8D AC 9C", '7C 5H 8D TD KS')
t <- as_tibble(l)
t <- t %>% mutate(hand = str_split(value, " ")) %>% select(hand)
t <- t %>% mutate(value = sapply(t[,1]$hand, hand_value)) %>% arrange(desc(value))
paste(t[[1]][[1]], collapse = " ")

Which returns the same result:

[1] "8C AD 8D AC 9C"

Hope it helps.

查看更多
叼着烟拽天下
4楼-- · 2019-01-30 16:49

If you just want to understand how it works here is simple algorithm:

HandStrength(ourcards,boardcards)
{
    ahead = tied = behind = 0
    ourrank = Rank(ourcards,boardcards)
    /* Consider all two-card combinations
    of the remaining cards. */
    for each case(oppcards)
    {
        opprank = Rank(oppcards,boardcards)
        if(ourrank>opprank)
            ahead += 1
        else if(ourrank==opprank)
            tied += 1
        else /* < */
            behind += 1
    }
    handstrength = (ahead+tied/2) / (ahead+tied+behind)
    return(handstrength)
}

It is from "ALGORITHMS AND ASSESSMENT IN COMPUTER POKER" by Darse Billings.

查看更多
登录 后发表回答