The simplest algorithm for poker hand evaluation

后端 未结 12 994
独厮守ぢ
独厮守ぢ 2020-12-04 12:39

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

相关标签:
12条回答
  • 2020-12-04 13:03

    I have written a poker hand evaluator in both C++ and Javascript. Basically the program would convert a randomly picked set of cards to a 3d array of 1s and 0s. By converting the cards into this format I was then able to write functions that would test for each type of hand starting from the highest.

    So in recap, my program would generate random cards, convert them into a 3D array of hearts, diamonds, spades and clubs, where 1 represented one of the cards I had. I would then test the 3D array to see if I had a Royal Flush, Then Straight Flush, Then 4 of a Kind until a match was detected. Once a match was detected say after testing for a flush, then my program wouldn't have to test for straight, 3 of a kind, etc as a flush beats a straight.

    Below is outputted data from my program:

    My random cards:

    Table Cards
    { Value: '9', Suit: 'H' }
    { Value: 'A', Suit: 'H' }
    { Value: '9', Suit: 'D' }
    { Value: '7', Suit: 'S' }
    { Value: '6', Suit: 'S' }
    

    3D array representing my cards:

     A  2  3  4  5  6  7  8  9  10 J  Q  K  A
    
    Spades
    [ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0 ]
    Diamonds
    [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 ]
    Clubs
    [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
    Hearts
    [ 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]
    

    Using the values above I can tell that I have a pair 9s with an A, 7, 6 kicker.

    You can see the array includes Aces twice. This is because you want to test for a straight flush starting from A. So (A,2,3,4,5).

    If you wanted to test for 7 cards instead of 5 you could also use this system. You can include the users 2 cards with the 5 on the table and run it through my system. You can also do the same for other players at the table and compare the results.

    I hope this helps a little.

    0 讨论(0)
  • 2020-12-04 13:07

    Here's a modified version of dansalmo's program which works for holdem hands:

    def holdem(board, hands):
        scores = [(evaluate((board + ' ' + hand).split()), i) for i, hand in enumerate(hands)]
        best = max(scores)[0]
        return [x[1] for x in filter(lambda(x): x[0] == best, scores)]
    
    def evaluate(hand):
        ranks = '23456789TJQKA'
        if len(hand) > 5: return max([evaluate(hand[:i] + hand[i+1:]) for i in range(len(hand))])
        score, ranks = zip(*sorted((cnt, rank) for rank, cnt in {ranks.find(r): ''.join(hand).count(r) for r, _ in hand}.items())[::-1])
        if len(score) == 5: # if there are 5 different ranks it could be a straight or a flush (or both)
            if ranks[0:2] == (12, 3): ranks = (3, 2, 1, 0, -1) # adjust if 5 high straight
            score = ([1,(3,1,2)],[(3,1,3),(5,)])[len({suit for _, suit in hand}) == 1][ranks[0] - ranks[4] == 4] # high card, straight, flush, straight flush
        return score, ranks
    
    def test():
        print holdem('9H TC JC QS KC', [
            'JS JD', # 0
            'AD 9C', # 1 A-straight
            'JD 2C', # 2
            'AC 8D', # 3 A-straight
            'QH KH', # 4
            'TS 9C', # 5
            'AH 3H', # 6 A-straight
            '3D 2C', # 7
          # '8C 2C', # 8 flush
        ])
    
    test()
    

    holdem() returns a list of indices of the winning hand(s). In the test() example that's [1, 3, 6], since the three hands with aces split the pot, or [8] if the flush hand is uncommented.

    0 讨论(0)
  • 2020-12-04 13:07

    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.

    0 讨论(0)
  • 2020-12-04 13:08

    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.

    0 讨论(0)
  • 2020-12-04 13:09

    Here is a very short but complete histogram based 5 card poker scoring function in Python (2.x). It will get considerably longer if converted to Java.

    def poker(hands):
        scores = [(i, score(hand.split())) for i, hand in enumerate(hands)]
        winner = sorted(scores , key=lambda x:x[1])[-1][0]
        return hands[winner]
    
    def score(hand):
        ranks = '23456789TJQKA'
        rcounts = {ranks.find(r): ''.join(hand).count(r) for r, _ in hand}.items()
        score, ranks = zip(*sorted((cnt, rank) for rank, cnt in rcounts)[::-1])
        if len(score) == 5:
            if ranks[0:2] == (12, 3): #adjust if 5 high straight
                ranks = (3, 2, 1, 0, -1)
            straight = ranks[0] - ranks[4] == 4
            flush = len({suit for _, suit in hand}) == 1
            '''no pair, straight, flush, or straight flush'''
            score = ([1, (3,1,1,1)], [(3,1,1,2), (5,)])[flush][straight]
        return score, ranks
    
     >>> poker(['8C TS KC 9H 4S', '7D 2S 5D 3S AC', '8C AD 8D AC 9C', '7C 5H 8D TD KS'])
     '8C AD 8D AC 9C'
    
    0 讨论(0)
  • 2020-12-04 13:14

    Here's a simple rule-based implementation in Kotlin:

    class PokerHand constructor(hand: String) : Comparable<PokerHand> {
    
    companion object {
        const val WIN = 1
        const val TIE = 0
        const val LOSS = -1
    }
    
    val cards: List<Card>
    
    val isStraightFlush: Boolean
        get() = isStraight && isFlush
    
    val isFourOfAKind: Boolean
        get() = cards.groupBy { it.weight }.map { it.value }.any { it.size == 4 }
    
    val isFullHouse: Boolean
        get() = cards.groupBy { it.weight }.map { it.value }.size == 2
    
    val isFlush: Boolean
        get() = cards.groupBy { it.suit }.map { it.value }.size == 1
    
    val isStraight: Boolean
        get() = cards.map { it.weight.ordinal } == (cards[0].weight.ordinal..cards[0].weight.ordinal + 4).toList()
    
    val isThreeOfAKind: Boolean
        get() = cards.groupBy { it.weight }.map { it.value }.any { it.size == 3 }
    
    val isTwoPair: Boolean
        get() = cards.groupBy { it.weight }.map { it.value }.filter { it.size == 2 }.count() == 2
    
    val isPair: Boolean
        get() = cards.groupBy { it.weight }.map { it.value }.any { it.size == 2 }
    
    init {
        val cards = ArrayList<Card>()
        hand.split(" ").forEach {
            when (it.length != 2) {
                true -> throw RuntimeException("A card code must be two characters")
                else -> cards += Card(Weight.forCode(it[0]), Suit.forCode(it[1]))
            }
        }
        if (cards.size != 5) {
            throw RuntimeException("There must be five cards in a hand")
        }
        this.cards = cards.sortedBy { it.weight.ordinal }
    }
    
    override fun compareTo(other: PokerHand): Int = when {
        (this.isStraightFlush || other.isStraightFlush) ->
            if (this.isStraightFlush) if (other.isStraightFlush) compareByHighCard(other) else WIN else LOSS
        (this.isFourOfAKind || other.isFourOfAKind) ->
            if (this.isFourOfAKind) if (other.isFourOfAKind) compareByHighCard(other) else WIN else LOSS
        (this.isFullHouse || other.isFullHouse) ->
            if (this.isFullHouse) if (other.isFullHouse) compareByHighCard(other) else WIN else LOSS
        (this.isFlush || other.isFlush) ->
            if (this.isFlush) if (other.isFlush) compareByHighCard(other) else WIN else LOSS
        (this.isStraight || other.isStraight) ->
            if (this.isStraight) if (other.isStraight) compareByHighCard(other) else WIN else LOSS
        (this.isThreeOfAKind || other.isThreeOfAKind) ->
            if (this.isThreeOfAKind) if (other.isThreeOfAKind) compareByHighCard(other) else WIN else LOSS
        (this.isTwoPair || other.isTwoPair) ->
            if (this.isTwoPair) if (other.isTwoPair) compareByHighCard(other) else WIN else LOSS
        (this.isPair || other.isPair) ->
            if (this.isPair) if (other.isPair) compareByHighCard(other) else WIN else LOSS
        else -> compareByHighCard(other)
    }
    
    private fun compareByHighCard(other: PokerHand, index: Int = 4): Int = when {
        (index < 0) -> TIE
        cards[index].weight === other.cards[index].weight -> compareByHighCard(other, index - 1)
        cards[index].weight.ordinal > other.cards[index].weight.ordinal -> WIN
        else -> LOSS
    }
    

    }

    Implementation details:

    • Instantiate with a coded hand, eg 2H 3H 4H 5H 6H
    • Methods to evaluate whether the hand is a 'Straight Flush', 'Four of a Kind', 'Full House' etc. These are easy to express in Kotlin.
    • Implements Comparable<PokerHand> to evaluate against another hand using a simple rules approach, eg a straight flush beats four of a kind, which beats a full house, and so forth.

    The sources are here.

    0 讨论(0)
提交回复
热议问题