web-dev-qa-db-fra.com

L'algorithme le plus simple pour l'évaluation des mains au poker

Je pense à l'évaluation de la main de poker (5 cartes) dans Java. Maintenant, je recherche la simplicité et la clarté plutôt que la performance et l'efficacité. Je peux probablement écrire un algorithme "naïf" mais cela nécessite beaucoup de code. 

J'ai aussi vu quelques bibliothèques d'évaluation de poker, qui utilisent des opérations de hachage et au niveau des bits, mais elles ont l'air plutôt complexes.

Quel est l'algorithme "le plus propre et le plus simple" pour l'évaluation des mains au poker? 

21
Michael

Voici une fonction de notation de poker à 5 cartes basée sur un histogramme très courte mais complète en Python (2.x). Il sera considérablement plus long s'il est converti en 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'
30
dansalmo

Les tables de consultation constituent la solution la plus simple et la plus simple au problème, ainsi que la plus rapide. L'astuce consiste à gérer la taille de la table et à garder le mode d'utilisation suffisamment simple pour traiter très rapidement ( compromis espace-temps ). Evidemment, en théorie, vous pouvez simplement encoder chaque main qui peut être tenue et avoir un tableau d’évaluations, puis --poof--, une table de consultation et vous avez terminé. Malheureusement, une telle table serait énorme et ingérable pour la plupart des machines, et vous inviterait invariablement à écraser des disques de toute façon à mesure que la mémoire sera échangée.

La solution dite deux plus deux utilise une grande table de 10M, mais implique littéralement une consultation de table pour chaque carte dans la main. Il est peu probable que vous trouviez un algorithme plus rapide et plus simple à comprendre.

D'autres solutions impliquent davantage de tables compressées avec une indexation plus complexe, mais elles sont facilement compréhensibles et assez rapides (bien que beaucoup plus lentes que 2 + 2). C'est ici que vous voyez le langage concernant le hachage, etc. - astuces pour réduire la taille d'une table à des tailles plus gérables.

Dans tous les cas, les solutions de recherche sont des ordres de grandeur plus rapides que les solutions de type histogramme-danse-sur-votre-tête-comparaison-spéciale-casse-et-le-par-la-voie dont dignes d'un second regard.

11
wizardwerdna

En réalité, vous n’avez pas besoin de fonctions avancées, cela peut être fait au niveau du bit: (source: http://www.codeproject.com/Articles/569271/A-Poker-hand-analyzer-in-JavaScript-using- bit-math )

(Celui-ci est en fait écrit en JavaScript, mais vous pouvez évaluer JavaScript à partir de Java si nécessaire. Cela ne devrait donc pas poser de problème. En outre, c'est aussi court que possible, donc même pour illustrer l'approche. ..):

D'abord, vous divisez vos cartes en deux tableaux: les rangs (cs) et les costumes (ss). Pour représenter les costumes, vous utiliserez soit 1,2,4, soit 8 (c'est-à-dire 0b0001, 0b0010, ...):

var J=11, Q=12, K=13, A=14, C=1, D=2, H=4, S=8;

Maintenant, voici la magie:

function evaluateHand(cs, ss) {
    var pokerHands = ["4 of a Kind", "Straight Flush","Straight","Flush","High Card","1 Pair","2 Pair","Royal Flush", "3 of a Kind","Full House"];

    var v,i,o,s = 1 << cs[0] | 1 << cs[1] | 1 << cs[2] | 1 << cs[3] | 1 << cs[4];
    for (i = -1, v = o = 0; i < 5; i++, o = Math.pow(2, cs[i] * 4)) {v += o * ((v / o & 15) + 1);}
    v = v % 15 - ((s / (s & -s) == 31) || (s == 0x403c) ? 3 : 1);
    v -= (ss[0] == (ss[1] | ss[2] | ss[3] | ss[4])) * ((s == 0x7c00) ? -5 : 1);
    return pokerHands[v];
}

Usage: 

evaluateHand([A,10,J,K,Q],[C,C,C,C,C]); // Royal Flush

Maintenant ce qu’il fait (très brièvement) c’est qu’il met 1 dans le 3ème bit de s quand il y a un 2, dans le 4ème quand il y a 3, etc., donc pour l’exemple ci-dessus, s ressemble à ceci:

0b111110000000000

pour [A, 2,3,4,5] cela ressemblerait à ceci

0b100 0000 0011 1100

etc.

v utilise quatre bits pour enregistrer plusieurs occurrences de la même carte. Sa longueur est donc de 52 bits. Si vous avez trois as et deux rois, ses 8 bits de poids fort ressemblent à:

0111 0011 ... 

La dernière ligne recherche ensuite une couleur, une couleur ou une couleur royale (0x7c00).

5
Matěj Balga

Voici une version modifiée du programme de dansalmo qui fonctionne pour les mains de Holdem:

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 () renvoie une liste d'indices de la ou des mains gagnantes. Dans l’exemple test (), c’est [1, 3, 6], puisque les trois mains avec les as divisent le pot, ou [8] si la main de couleur n’est pas commentée.

4
Chris Moore

Voici une approche naïve de la comparaison des mains à cinq cartes que j'utilise pour aider à remplir initialement une table de recherche:

Au lieu d'être aussi concis que possible, j'ai privilégié la sécurité et un code clair et auto-documenté. Si vous ne connaissez pas les types de goyave que j'utilise, vous pouvez parcourir leur documentation .

Et je vais inclure le code ici (moins les importations statiques pour les constantes d’énumération au bas de la page), bien que ce soit vraiment trop long à afficher confortablement dans une réponse.

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;
        }
    }
}
4
gdejohn

Si vous voulez juste comprendre comment cela fonctionne, voici un algorithme simple:

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)
}

Il est tiré de "ALGORITHMES ET ÉVALUATION IN POKER INFORMATIQUE" de Darse Billings.

3
Adam

Si vous représentez une main sous la forme d'un tableau d'objets, par exemple, Card, vous disposez de méthodes permettant de parcourir ce tableau en boucle et de déterminer s'il possède un type de type 2, un flush, etc. tapez c'est; vous pourriez donc avoir la méthode 3ofaKind() retourner 5 si une main avait trois 5. Ensuite, j’établirais une hiérarchie des possibilités (par exemple, 3 d’un type est supérieur à 2 d’un genre) et travaille à partir de là. Les méthodes elles-mêmes devraient être assez simples à écrire.

2
arshajii

Voici une implémentation simple basée sur des règles dans 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
}

}

Détails d'implémentation: 

  • Instancier avec une main codée, par exemple 2H 3H 4H 5H 6H
  • Méthodes permettant d’évaluer si la main est un «Straight Flush», un «Four of a Kind», un «Full House», etc. Celles-ci sont faciles à exprimer en kotlin. 
  • Implémente Comparable<PokerHand> pour évaluer par rapport à une autre main en utilisant une approche de règles simples, par exemple, une quinte flush bat quatre points du même genre, ce qui bat un full house, etc. 

Les sources sont ici

1
Jasper Blues

Voici l'algorithme traduit enR, testé avec un jeu de 6 cartes, correspondant à 42.504 combinaisons données par le résultat de:

C65

combinaisons de mains de poker. N'a pas été testé avec un jeu de 13 cartes en raison de limitations de traitement (cela correspondrait à 2.598.960 combinaisons).

L'algorithme représente la valeur d'une main par une chaîne , composée de 2 parties:

  • 5 caractères avec un nombre de cartes ordonné (ex. "31100" signifie un brelan)
  • Les numéros de carte sont évalués par des lettres allant de "B" (Deuce) à "N" (Ace) (par exemple, "NILH" signifie As, Reine, Neuf et Huit). Cela commence par la lettre "B" en raison de la main de poker A2345 où l'as vient avant le "2" qui (l'as) aura la valeur "A".

Ainsi, par exemple, "32000NB" sera un full house de trois As et de deux Deuce.

La chaîne de valeur de la main de poker est pratique à des fins de comparaison et de commande .

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

}

Tester la fonction avec les mêmes mains que dans l'exemple ci-dessus:

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 = " ")

Ce qui retourne le même résultat:

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

J'espère que ça aide.

1
TSRTSR