3

I've written an equilator for Hold'em Poker as a hobbyproject. It works correctly, but there is still one thing I am not happy with: In the whole process of simulating hands, the process of evaluating the hands takes about 35% of the time. That seems to be pretty much to me compared with what else has to be done like iterating through and cloning large arrays and stuff.

Any idea of how to get this more performant would be great.

This is the code:

    private static int getHandvalue(List<Card> sCards)
    {

        // --- Auf Straight / Straight Flush prüfen ---
        if ((sCards[0].Value - 1 == sCards[1].Value) && (sCards[1].Value - 1 == sCards[2].Value) && (sCards[2].Value - 1 == sCards[3].Value) && (sCards[3].Value - 1 == sCards[4].Value))
        {

            if ((sCards[0].Color == sCards[1].Color) && (sCards[1].Color == sCards[2].Color) && (sCards[2].Color == sCards[3].Color) && (sCards[3].Color == sCards[4].Color))
                return (8 << 20) + (byte)sCards[0].Value; // Höchste Karte Kicker

            else
                return (4 << 20) + (byte)sCards[0].Value; // Höchste Karte Kicker (Straight)

        }

        // --- Auf Wheel / Wheel Flush prüfen ---
        if ((sCards[4].Value == Card.CardValue.Two) && (sCards[3].Value == Card.CardValue.Three) && (sCards[2].Value == Card.CardValue.Four) && (sCards[1].Value == Card.CardValue.Five) && (sCards[0].Value == Card.CardValue.Ace))
        {

            if ((sCards[0].Color == sCards[1].Color) && (sCards[1].Color == sCards[2].Color) && (sCards[2].Color == sCards[3].Color) && (sCards[3].Color == sCards[4].Color))
                return(8 << 20) + (byte)sCards[1].Value; // Zweithöchste Karte Kicker

            else
                return(4 << 20) + (byte)sCards[1].Value; // Zweithöchste Karte Kicker (Straight)

        }


        // --- Auf Flush prüfen ---
        if ((sCards[0].Color == sCards[1].Color) && (sCards[1].Color == sCards[2].Color) && (sCards[2].Color == sCards[3].Color) && (sCards[3].Color == sCards[4].Color))
            return (5 << 20) + ((byte)sCards[0].Value << 16) + ((byte)sCards[1].Value << 12) + ((byte)sCards[2].Value << 8) + ((byte)sCards[3].Value << 4) + (byte)sCards[4].Value;


        // --- Auf Vierling prüfen ---
        if (((sCards[0].Value == sCards[1].Value) && (sCards[1].Value == sCards[2].Value) && (sCards[2].Value == sCards[3].Value)) || ((sCards[1].Value == sCards[2].Value) && (sCards[2].Value == sCards[3].Value) && (sCards[3].Value == sCards[4].Value)))
            return (7 << 20) + (byte)sCards[1].Value; // Wert des Vierlings (keine Kicker, da nicht mehrere Spieler den selben Vierling haben können)


        // --- Auf Full-House / Drilling prüfen ---
        // Drilling vorne
        if ((sCards[0].Value == sCards[1].Value) && (sCards[1].Value == sCards[2].Value))
        {

            // Full House
            if (sCards[3].Value == sCards[4].Value)
                return (6 << 20) + ((byte)sCards[0].Value << 4) + (byte)sCards[3].Value; // Drilling (höher bewerten)

            // Drilling
            return (3 << 20) + ((byte)sCards[0].Value << 8) + ((byte)sCards[3].Value << 4) + (byte)sCards[4].Value; // Drilling + Kicker 1 + Kicker 2

        }

        // Drilling hinten
        if ((sCards[2].Value == sCards[3].Value) && (sCards[3].Value == sCards[4].Value))
        {

            // Full House
            if (sCards[0].Value == sCards[1].Value)
                return (6 << 20) + ((byte)sCards[2].Value << 4) + (byte)sCards[0].Value; // Drilling (höher bewerten)

            // Drilling
            return (3 << 20) + ((byte)sCards[2].Value << 8) + ((byte)sCards[0].Value << 4) + (byte)sCards[1].Value; // Drilling + Kicker 1 + Kicker 2

        }

        // Drilling mitte
        if ((sCards[1].Value == sCards[2].Value) && (sCards[2].Value == sCards[3].Value))
            return (3 << 20) + ((byte)sCards[1].Value << 8) + ((byte)sCards[0].Value << 4) + (byte)sCards[4].Value; // Drilling + Kicker 1 + Kicker 2                            


        // --- Auf Zwei Paare prüfen ---
        // Erstes Paar vorne, zweites Paar mitte
        if ((sCards[0].Value == sCards[1].Value) && (sCards[2].Value == sCards[3].Value))
            return (2 << 20) + ((byte)sCards[0].Value << 8) + ((byte)sCards[2].Value << 4) + (byte)sCards[4].Value; // Erstes Paar + Zweites Paar + Kicker

        // Erstes Paar vorne, zweites Paar hinten
        if ((sCards[0].Value == sCards[1].Value) && (sCards[3].Value == sCards[4].Value))
            return (2 << 20) + ((byte)sCards[0].Value << 8) + ((byte)sCards[3].Value << 4) + (byte)sCards[2].Value; // Erstes Paar + Zweites Paar + Kicker

        // Erstes Paar mitte, zweites Paar hinten
        if ((sCards[1].Value == sCards[2].Value) && (sCards[3].Value == sCards[4].Value))
            return (2 << 20) + ((byte)sCards[1].Value << 8) + ((byte)sCards[3].Value << 4) + (byte)sCards[0].Value; // Erstes Paar + Zweites Paar + Kicker


        // --- Auf Paar prüfen ---
        // Paar vorne
        if (sCards[0].Value == sCards[1].Value)
            return (1 << 20) + ((byte)sCards[0].Value << 12) + ((byte)sCards[2].Value << 8) + ((byte)sCards[3].Value << 4) + (byte)sCards[4].Value; // Paar + Kicker 1 + Kicker 2 + Kicker 3

        // Paar mitte-vorne
        if (sCards[1].Value == sCards[2].Value)
            return (1 << 20) + ((byte)sCards[1].Value << 12) + ((byte)sCards[0].Value << 8) + ((byte)sCards[3].Value << 4) + (byte)sCards[4].Value; // Paar + Kicker 1 + Kicker 2 + Kicker 3

        // Paar mitte-hinten
        if (sCards[2].Value == sCards[3].Value)
            return (1 << 20) + ((byte)sCards[2].Value << 12) + ((byte)sCards[0].Value << 8) + ((byte)sCards[1].Value << 4) + (byte)sCards[4].Value; // Paar + Kicker 1 + Kicker 2 + Kicker 3

        // Paar hinten
        if (sCards[3].Value == sCards[4].Value)
            return (1 << 20) + ((byte)sCards[3].Value << 12) + ((byte)sCards[0].Value << 8) + ((byte)sCards[1].Value << 4) + (byte)sCards[2].Value; // Paar + Kicker 1 + Kicker 2 + Kicker 3


        // --- High Card bleibt übrig ---
        return ((byte)sCards[0].Value << 16) + ((byte)sCards[1].Value << 12) + ((byte)sCards[2].Value << 8) + ((byte)sCards[3].Value << 4) + (byte)sCards[4].Value; // High Card + Kicker 1 + Kicker 2 + Kicker 3 + Kicker 4

    }

This method returns an exact value for every sorted 5-Card-Combination in poker. It gets called by another method:

    private static int getHandvalueList(List<Card> sCards)
    {

        int count = sCards.Count;
        if (count == 5) return getHandvalue(sCards);

        int HighestValue = 0;
        Card missingOne;
        int tempValue;

        for (int i = 0; i < count - 1; i++)
        {

            missingOne = sCards[i];
            sCards.RemoveAt(i);

            tempValue = getHandvalueList(sCards);
            if (tempValue > HighestValue) HighestValue = tempValue;

            sCards.Insert(i, missingOne);

        }

        missingOne = sCards[count - 1];
        sCards.RemoveAt(count - 1);

        tempValue = getHandvalueList(sCards);
        if (tempValue > HighestValue) HighestValue = tempValue;

        sCards.Add(missingOne);
        return HighestValue;

    }

This recursive method returns the highest value of all possible 5-card-combinations. And this one gets called by the final public method:

    public static int GetHandvalue(List<Card> sCards)
    {

        if (sCards.Count < 5) return 0;
        sCards.Sort(new ICardComparer());
        return getHandvalueList(sCards);

    }

It gets delivered a maximum of 7 cards.

Update

So far: Each time, the public function gets called with 7 cards (which is the case most of the time), it has to call the hand-evaluation method 21 times (one time for every possible 5-card-combo).

I thought about caching the value for each possible set of 5 to 7 cards in a hashtable und just look it up. But if I am not wrong, it would have to store more than 133.784.560 32-bit-integer values, which is about 500MB.

What could be good hashfunction to assign every possible combination to exactly one arrayindex?

Created a new question on that: Hashfunction to map combinations of 5 to 7 cards

Update: For further improvement regarding the accepted answer, have alook at: Efficient way to randomly select set bit

Community
  • 1
  • 1
Martin Tausch
  • 714
  • 5
  • 20
  • If you have a high level of Visual Studio, use the Performance Wizard to see where the time is spent. Otherwise you can do some of that yourself by gather timing information. Without knowing the exact parts that are slow, you can't really optimize. – crashmstr Mar 14 '14 at 18:16
  • Okay, I will do so and update the question then – Martin Tausch Mar 14 '14 at 18:19
  • The fastest way i've found when I coded that, it's to pre-calculate all hands (5 cards) possibles and store it in a database (on installation for example). A simple request on execution give you a value, and/or the max of your list of hands. – Xaruth Mar 18 '14 at 10:41
  • Came to the same idea ^^ Have a look at this question: http://stackoverflow.com/questions/22477192/hashfunction-to-map-combinations-of-5-to-7-cards – Martin Tausch Mar 18 '14 at 10:55

1 Answers1

9

I've written a rather fast poker hand evaluator in the past. I used a rather different representation from yours, and I think that was the key to performance.

I represented a hand of at most 7 cards by a 64-bit integer; bit 4 was for the two of hearts, bit 5 for the two of diamonds, 6 for the two of spades, 7 for the two of clubs, 8 for the three of hearts, and so forth.

You first check for straight flushes; form hh = h & (h>>4) & (h>>8) & (h>>12) & (h>>16). If hh is nonzero, then you have a straight flush beginning at its high bit.

Then you check for four-of-a-kinds; form hh = h & (h>>1) & (h>>2) & (h>>3) & 0x1111...1. If hh is nonzero, you've got yourself a four-of-a-kind.

At this point, you want to find out which ranks have three-of-a-kind and which ranks have pairs. Similar to the four-of-a-kind case, form bitmasks telling you which ranks have at least three cards, which ranks have at least two cards, and which ranks have at least one card. (Think about sorting each nibble of the hand.) If popcount(threes) + popcount(twos) >= 2, you have a full house to find.

Flush and straight conditions are straightforward to check. And, at this point, so are three-of-a-kind, two pair, and pair conditions.

A nice feature of this approach is that it can straightforwardly return an integer representing the rank of the hand, reducing hand comparison to a bunch of bit-fiddling to preprocess the hands and then a single integer comparison. (Exactly as you're doing, now that I look at your post again.) It also directly operates on 7-card hands if written properly, eliminating the need to iterate over all subsets of 5 cards in the hand.

tmyklebu
  • 13,915
  • 3
  • 28
  • 57
  • +1: This seems to be a very good approach. I will have a try on that! – Martin Tausch Mar 16 '14 at 08:50
  • it seems like your code for checking four of a kinds would also output true for a hand like 0x0000 0011 1100 00...00 where there are four bits set in a row, but the bits should represent two duces and two treys. – Bruno Zell Nov 01 '16 at 21:37
  • @BrunoZell: That's what the `& 0x1111...1` is for. You only care when there are four set bits in a row and the low of your group of four is the low bit of a rank. – tmyklebu Nov 02 '16 at 02:49
  • Oh, I've over seen that it's hexadecimal. Thought in banary. Thanks – Bruno Zell Nov 02 '16 at 06:12