So… I don’t know why. I read this thread and it made me interested in seeing if I could do this in an O(N) way with as little to no garbage as possible.
And I think I got it (mind you I didn’t test this for ALL possible hands… I just threw random things at it and saw if it spat back what I expected).
//low 4-bits are face
public enum CardFace : byte
{
None = 0,
Ace = 1,
Two = 2,
Three = 3,
Four = 4,
Five = 5,
Six = 6,
Seven = 7,
Eight = 8,
Nine = 9,
Ten = 10,
Jack = 11,
Queen = 12,
King = 13
}
//bits 5-8 are the suit
public enum CardSuit : byte
{
Diamonds = 16,
Clubs = 32,
Hearts = 64,
Spades = 128
}
public enum Card : byte
{
None = 0,
AceDiamonds = CardFace.Ace | CardSuit.Diamonds,
TwoDiamonds = CardFace.Two | CardSuit.Diamonds,
ThreeDiamonds = CardFace.Three | CardSuit.Diamonds,
FourDiamonds = CardFace.Four | CardSuit.Diamonds,
FiveDiamonds = CardFace.Five | CardSuit.Diamonds,
SixDiamonds = CardFace.Six | CardSuit.Diamonds,
SevenDiamonds = CardFace.Seven | CardSuit.Diamonds,
EightDiamonds = CardFace.Eight | CardSuit.Diamonds,
NineDiamonds = CardFace.Nine | CardSuit.Diamonds,
TenDiamonds = CardFace.Ten | CardSuit.Diamonds,
JackDiamonds = CardFace.Jack | CardSuit.Diamonds,
QueenDiamonds = CardFace.Queen | CardSuit.Diamonds,
KingDiamonds = CardFace.King | CardSuit.Diamonds,
AceClubs = CardFace.Ace | CardSuit.Clubs,
TwoClubs = CardFace.Two | CardSuit.Clubs,
ThreeClubs = CardFace.Three | CardSuit.Clubs,
FourClubs = CardFace.Four | CardSuit.Clubs,
FiveClubs = CardFace.Five | CardSuit.Clubs,
SixClubs = CardFace.Six | CardSuit.Clubs,
SevenClubs = CardFace.Seven | CardSuit.Clubs,
EightClubs = CardFace.Eight | CardSuit.Clubs,
NineClubs = CardFace.Nine | CardSuit.Clubs,
TenClubs = CardFace.Ten | CardSuit.Clubs,
JackClubs = CardFace.Jack | CardSuit.Clubs,
QueenClubs = CardFace.Queen | CardSuit.Clubs,
KingClubs = CardFace.King | CardSuit.Clubs,
AceHearts = CardFace.Ace | CardSuit.Hearts,
TwoHearts = CardFace.Two | CardSuit.Hearts,
ThreeHearts = CardFace.Three | CardSuit.Hearts,
FourHearts = CardFace.Four | CardSuit.Hearts,
FiveHearts = CardFace.Five | CardSuit.Hearts,
SixHearts = CardFace.Six | CardSuit.Hearts,
SevenHearts = CardFace.Seven | CardSuit.Hearts,
EightHearts = CardFace.Eight | CardSuit.Hearts,
NineHearts = CardFace.Nine | CardSuit.Hearts,
TenHearts = CardFace.Ten | CardSuit.Hearts,
JackHearts = CardFace.Jack | CardSuit.Hearts,
QueenHearts = CardFace.Queen | CardSuit.Hearts,
KingHearts = CardFace.King | CardSuit.Hearts,
AceSpades = CardFace.Ace | CardSuit.Spades,
TwoSpades = CardFace.Two | CardSuit.Spades,
ThreeSpades = CardFace.Three | CardSuit.Spades,
FourSpades = CardFace.Four | CardSuit.Spades,
FiveSpades = CardFace.Five | CardSuit.Spades,
SixSpades = CardFace.Six | CardSuit.Spades,
SevenSpades = CardFace.Seven | CardSuit.Spades,
EightSpades = CardFace.Eight | CardSuit.Spades,
NineSpades = CardFace.Nine | CardSuit.Spades,
TenSpades = CardFace.Ten | CardSuit.Spades,
JackSpades = CardFace.Jack | CardSuit.Spades,
QueenSpades = CardFace.Queen | CardSuit.Spades,
KingSpades = CardFace.King | CardSuit.Spades
}
public enum PokerHandType
{
HighCard = 0,
OnePair = 1,
TwoPair = 2,
Triple = 3,
Straight = 4,
Flush = 5,
FullHouse = 6,
Quad = 7,
StraightFlush = 8,
RoyalFlush = 9
}
public struct PokerHand
{
public PokerHandType HandType;
public CardFace HighCard; //highest card of the play
public CardFace Kicker; //alternate 'kicker', if a 4-less card play was done, this is the highest leftover card if it's > the highest played card. Otherwise it's = HighCard
public int Count;
public static readonly PokerHand None = new PokerHand();
}
public static class CardExtensions
{
public static CardSuit GetSuit(this Card card)
{
return (CardSuit)((byte)card & (byte)240);
}
public static CardFace GetFace(this Card card)
{
return (CardFace)((byte)card & 15);
}
public static IList<Card> Shuffle(this IList<Card> deck, Random rng = null)
{
if (deck == null) throw new System.ArgumentNullException("deck");
if (rng == null) rng = new Random();
int j;
Card temp;
int cnt = deck.Count;
for (int i = 0; i < cnt - 1; i++)
{
j = rng.Next(i, cnt);
temp = deck[j];
deck[j] = deck[i];
deck[i] = temp;
}
return deck;
}
public static IEnumerable<Card> Deal(this IList<Card> deck, int count)
{
while(count > 0 && deck.Count > 0)
{
count--;
var card = deck[deck.Count - 1];
deck.RemoveAt(deck.Count - 1);
yield return card;
}
}
}
public class SimplePokerRules
{
public PokerHand GetBestHand(IEnumerable<Card> cards)
{
if (cards == null) return PokerHand.None;
//gather card info
int count = 0;
long matchtable = 0;
CardFace low = (CardFace)byte.MaxValue;
CardFace high = CardFace.None;
CardSuit flush = (CardSuit)byte.MaxValue;
foreach(var card in cards)
{
count++;
CardFace face = card.GetFace();
if (face < low) low = face;
if (face > high) high = face;
flush &= card.GetSuit();
//we are going to fill a bit table up with the count of this facevalue... every 4-bits is a face.
//This means we can't count higher than 15, but if you passed in a hand that large, that's stupid.
int offset = ((int)face - 1) * 4;
long mask = 15L << offset;
long matchcount = (matchtable & mask) >> offset;
if (matchcount < 15L) matchcount++;
matchtable = (matchtable & ~mask) | (matchcount << offset);
}
if (count == 0) return PokerHand.None;
//group the cards
CardFace lowpair = CardFace.None;
CardFace highpair = CardFace.None;
CardFace triple = CardFace.None;
CardFace quad = CardFace.None;
CardFace straight_high = CardFace.None;
int straight_count = 0;
int straight_len = Math.Min(5, count);
int c = 0;
for(int i = 0; i < 14; i++)
{
CardFace face = (CardFace)((i % 14) + 1);
int offset = ((int)face - 1) * 4;
long mask = 15L << offset;
int matchcount = (int)((matchtable & mask) >> offset);
if(matchcount > 0)
{
c++;
if(c >= straight_len)
{
straight_count = c;
straight_high = face;
}
switch (matchcount)
{
case 1:
//do nothing
break;
case 2:
lowpair = highpair;
highpair = face;
break;
case 3:
if (triple > highpair)
{
lowpair = highpair;
highpair = triple;
}
triple = face;
break;
default:
//4 and higher all count as quad
if (quad > triple)
{
if (triple > highpair)
{
lowpair = highpair;
highpair = triple;
}
triple = quad;
}
else if (quad > highpair)
{
lowpair = highpair;
highpair = quad;
}
quad = face;
break;
}
}
else
{
c = 0;
}
}
//find our best hand from above information
if (straight_count >= straight_len && (int)flush != 0 && straight_high == CardFace.Ace)
{
return new PokerHand()
{
HandType = PokerHandType.RoyalFlush,
HighCard = straight_high,
Kicker = high,
Count = count
};
}
else if (straight_count >= straight_len && (int)flush != 0)
{
return new PokerHand()
{
HandType = PokerHandType.StraightFlush,
HighCard = straight_high,
Kicker = high,
Count = count
};
}
else if (quad != CardFace.None)
{
return new PokerHand()
{
HandType = PokerHandType.Quad,
HighCard = quad,
Kicker = high,
Count = count
};
}
else if(triple != CardFace.None && highpair != CardFace.None)
{
return new PokerHand()
{
HandType = PokerHandType.FullHouse,
HighCard = (CardFace)System.Math.Max((int)triple, (int)highpair),
Kicker = high,
Count = count
};
}
else if((int)flush != 0)
{
return new PokerHand()
{
HandType = PokerHandType.Flush,
HighCard = high,
Kicker = high,
Count = count
};
}
else if(straight_count >= straight_len)
{
return new PokerHand()
{
HandType = PokerHandType.Straight,
HighCard = high,
Kicker = high,
Count = count
};
}
else if(triple != CardFace.None)
{
return new PokerHand()
{
HandType = PokerHandType.Triple,
HighCard = triple,
Kicker = high,
Count = count
};
}
else if(highpair != CardFace.None && lowpair != CardFace.None)
{
return new PokerHand()
{
HandType = PokerHandType.TwoPair,
HighCard = highpair,
Kicker = high,
Count = count
};
}
else if(highpair != CardFace.None)
{
return new PokerHand()
{
HandType = PokerHandType.OnePair,
HighCard = highpair,
Kicker = high,
Count = count
};
}
else
{
return new PokerHand()
{
HandType = PokerHandType.HighCard,
HighCard = high,
Kicker = high,
Count = count
};
}
}
}
Technically the algo is card count agnostic… but the “flush” rules fall apart on anything over 5 card since I resolve flushes on ALL cards.
Also I wanted to set it up so that all cards could be represented in an enum. This way I could do something like this:
var deck = System.Enum.GetValues(typeof(Card)).Cast<Card>().Where(c => c != Card.None).ToList().Shuffle().Shuffle();
var rules = new SimplePokerRules();
var hand1 = rules.GetBestHand(deck.Deal(5));
var hand2 = rules.GetBestHand(deck.Deal(5));
if(hand1.HandType > hand2.HandType || (hand1.HandType == hand2.HandType && hand1.HighCard > hand2.HighCard))
{
Console.WriteLine("Player 1 Wins With: " + hand1.HandType);
}
else if(hand2.HandType > hand1.HandType || (hand1.HandType == hand2.HandType && hand2.HighCard > hand1.HighCard))
{
Console.WriteLine("Player 2 Wins With: " + hand2.HandType);
}
else
{
Console.WriteLine("DRAW");
}
Anyways… that was fun.