Reputation: 744
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
Upvotes: 3
Views: 2533
Reputation: 14205
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.
Upvotes: 9