it-swarm.com.de

Der einfachste Algorithmus für die Auswertung der Pokerhand

Ich denke an die Pokerhand-Bewertung (5 Karten) in Java. Jetzt suche ich mehr nach Einfachheit und Klarheit als nach Leistung und Effizienz. Ich kann wahrscheinlich einen "naiven" Algorithmus schreiben, aber er erfordert viel Code. 

Ich habe auch ein paar Poker-Evaluierungsbibliotheken gesehen, die Hashing und bitweise Operationen verwenden, aber sie sehen ziemlich komplex aus.

Was ist der "sauberste und einfachste" Algorithmus für die Pokerhandbewertung? 

21
Michael

Hier ist eine sehr kurze aber vollständige Histogramm-basierte 5-Karten-Poker-Funktion in Python (2.x). Bei der Konvertierung nach Java wird es erheblich länger.

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

Nachschlagetabellen sind die einfachste und einfachste Lösung für das Problem und auch die schnellste. Der Trick besteht darin, die Größe der Tabelle zu verwalten und die Verwendungsart so einfach zu halten, dass sie sehr schnell verarbeitet werden kann ( Space-Time Tradeoff ). Offensichtlich könnten Sie theoretisch einfach jede Hand, die gehalten werden könnte, kodieren und eine Reihe von Auswertungen haben. Dann können Sie eine Tabelle nachschlagen und sind fertig. Unglücklicherweise wäre eine solche Tabelle für die meisten Maschinen riesig und unüberschaubar und würde Sie sowieso dazu bringen, Festplatten zu verprügeln, da der Speicher viel ausgelagert wird.

Die so genannte Zwei-plus-Zwei-Lösung enthält einen großen 10-MB-Tisch, wobei jedoch für jede Karte in der Hand buchstäblich ein Tisch gesucht wird. Sie werden wahrscheinlich keinen schnelleren und einfacheren Algorithmus finden.

Andere Lösungen beinhalten mehr komprimierte Tabellen mit komplexerer Indexierung, sind aber leicht verständlich und ziemlich schnell (wenn auch viel langsamer als 2 + 2). Hier sehen Sie die Sprache in Bezug auf Hashing und so weiter - Tricks, um die Größe einer Tabelle auf handlichere Größen zu reduzieren.

In jedem Fall sind Lookup-Lösungen um Größenordnungen schneller als die Histogramm-Sortier-Tanz-auf-Ihrem-Kopf-Vergleich-Sonderfälle-und-auf-dem-Weg-was-es-It-a-Flush-Lösungen, fast keine von denen sind einen zweiten Blick wert.

11
wizardwerdna

Sie benötigen eigentlich keine erweiterten Funktionen, es kann alles bitweise gemacht werden: (Quelle: http://www.codeproject.com/Articles/569271/A-Poker-hand-analyzer-in-JavaScript- using- Bit-Mathematik )

(Dieses ist eigentlich in JavaScript geschrieben, aber Sie können JavaScript bei Bedarf aus Java auswerten. Es sollte also kein Problem sein. Auch dies ist so kurz wie es wird, wenn auch nur zur Veranschaulichung des Ansatzes.) ..):

Zuerst teilen Sie Ihre Karten in zwei Arrays auf: Ränge (cs) und Anzüge (ss). Um Anzüge darzustellen, verwenden Sie entweder 1,2,4 oder 8 (dh 0b0001, 0b0010, ...):

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

Hier ist die 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];
}

Verwendungszweck: 

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

Was es (sehr kurz) tut, ist, dass es 1 in das dritte Bit von s setzt, wenn es eine 2 gibt, in die vierte, wenn es 3 usw. gibt, so dass s so aussieht:

0b111110000000000

für [A, 2,3,4,5] würde es so aussehen

0b100 0000 0011 1100

usw.

v verwendet vier Bits, um mehrere Vorkommen derselben Karte aufzuzeichnen. Es ist also 52 Bits lang. Wenn Sie drei Asse und zwei Könige haben, sehen die 8 MSB-Bits folgendermaßen aus:

0111 0011 ... 

Die letzte Zeile sucht dann nach Flush, Straight Flush oder Royal Flush (0x7c00).

5
Matěj Balga

Hier ist eine modifizierte Version des Programms von dansalmo, die für Holdem-Hände funktioniert:

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 () gibt eine Liste der Indizes der gewinnenden Hand (n) zurück. Im Test () - Beispiel ist dies [1, 3, 6], da die drei Hände mit Assen den Pot teilen oder [8], wenn die Flush-Hand unkommentiert ist.

4
Chris Moore

Hier ist ein naiver Ansatz für den Vergleich mit fünf Karten, mit dem ich anfänglich eine Nachschlagetabelle fülle:

Anstatt so knapp wie möglich zu sein, habe ich die Sicherheit der Typen und den klaren, selbstdokumentierenden Code priorisiert. Wenn Sie nicht mit den verwendeten Guava-Typen vertraut sind, können Sie deren Dokumentation durchsuchen.

Und ich füge den Code hier hinzu (minus statische Importe für die Enumerationskonstanten unten), obwohl es wirklich zu lang ist, um eine Antwort bequem anzeigen zu können.

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

Wenn Sie nur wissen wollen, wie es hier funktioniert, ist ein einfacher Algorithmus:

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

Es ist von "ALGORITHMEN UND BEWERTUNG IN COMPUTER POKER" von Darse Billings.

3
Adam

Wenn Sie eine Hand als ein Array von Card-Objekten darstellen, hätte ich Methoden, um dieses Array zu durchlaufen und festzustellen, ob es ein 2-of-a-kind, ein Flush usw. hat Typ ist es; Sie könnten also die Methode 3ofaKind() zurückgeben, wenn eine Hand drei 5s hatte. Dann würde ich eine Hierarchie von Möglichkeiten aufstellen (z. B. 3 einer Art ist höher als 2 einer Art) und von dort aus arbeiten. Die Methoden selbst sollten ziemlich einfach zu schreiben sein.

2
arshajii

Hier ist eine einfache regelbasierte Implementierung 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
}

}

Implementierungsdetails: 

  • Instanziieren mit einer codierten Hand, zB 2H 3H 4H 5H 6H
  • Methoden zur Beurteilung, ob die Hand ein Straight Flush, ein Vierling, ein Full House usw. ist. Diese sind in Kotlin leicht auszudrücken. 
  • Implementiert Comparable<PokerHand>, um anhand eines einfachen Regelansatzes gegen eine andere Hand auszuwerten, z. 

Die Quellen sind hier

1
Jasper Blues

Hier ist der Algorithmus übersetzt nachR, getestet mit einem 6-Karten-Deck, entsprechend 42.504 Kombinationen, die durch das Ergebnis gegeben werden:

C65

kombinationen von Pokerhänden. Wurde aufgrund von Verarbeitungsbeschränkungen nicht mit 13 Kartendecks getestet (dies entsprach 2.598.960 Kombinationen).

Der Algorithmus repräsentiert den Wert einer Hand durch einen String , der aus 2 Teilen besteht:

  • 5 Zeichen mit bestellter Kartenzählung (Bsp. "31100" bedeutet "Drilling")
  • Die Kartennummern werden durch Buchstaben von 'B' (Deuce) bis 'N' (Ass) bewertet (Beispiel: 'NILH' bedeutet Ass, Dame, Neunund Acht). Es beginnt mit dem Buchstaben "B" wegen der A2345-Pokerhand, bei der das Ass vor der "2" steht, die (das Ass) den Wert "A" hat.

Zum Beispiel wird "32000NB" ein Full House aus drei Assen und zwei Deuce sein.

Die Pokerhand-Wertzeichenfolge ist praktisch für Vergleichs- und Bestellzwecke .

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

}

Testen Sie die Funktion mit den gleichen Händen des obigen Beispiels:

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

Was das gleiche Ergebnis liefert:

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

Ich hoffe es hilft.

1
TSRTSR