This week's review challenge is a poker hand evaluator. I started by enumerating the possible hands:
public enum PokerHands
{
Pair,
TwoPair,
ThreeOfKind,
Straight,
Flush,
FullHouse,
FourOfKind,
StraightFlush,
RoyalFlush
}
Then I thought I was going to need cards... and cards have a suit...
public enum CardSuit
{
Hearts,
Diamonds,
Clubs,
Spades
}
...and a nominal value:
public enum PlayingCardNominalValue
{
Two,
Three,
Four,
Five,
Six,
Seven,
Eight,
Nine,
Ten,
Jack,
Queen,
King,
Ace
}
So I had enough of a concept to formally define a PlayingCard
:
public class PlayingCard
{
public CardSuit Suit { get; private set; }
public PlayingCardNominalValue NominalValue { get; private set; }
public PlayingCard(CardSuit suit, PlayingCardNominalValue nominalValue)
{
Suit = suit;
NominalValue = nominalValue;
}
}
At this point I had everything I need to write my actual Poker hand evaluator - because the last time I implemented this (like, 10 years ago) it was in VB6 and that I'm now spoiled with .net, I decided to leverage LINQ here:
public class PokerGame
{
private readonly IDictionary<PokerHands, Func<IEnumerable<PlayingCard>, bool>> _rules;
public IDictionary<PokerHands, Func<IEnumerable<PlayingCard>, bool>> Rules { get { return _rules; } }
public PokerGame()
{
// overly verbose for readability
Func<IEnumerable<PlayingCard>, bool> hasPair =
cards => cards.GroupBy(card => card.NominalValue)
.Count(group => group.Count() == 2) == 1;
Func<IEnumerable<PlayingCard>, bool> isPair =
cards => cards.GroupBy(card => card.NominalValue)
.Count(group => group.Count() == 3) == 0
&& hasPair(cards);
Func<IEnumerable<PlayingCard>, bool> isTwoPair =
cards => cards.GroupBy(card => card.NominalValue)
.Count(group => group.Count() >= 2) == 2;
Func<IEnumerable<PlayingCard>, bool> isStraight =
cards => cards.GroupBy(card => card.NominalValue)
.Count() == cards.Count()
&& cards.Max(card => (int) card.NominalValue)
- cards.Min(card => (int) card.NominalValue) == 4;
Func<IEnumerable<PlayingCard>, bool> hasThreeOfKind =
cards => cards.GroupBy(card => card.NominalValue)
.Any(group => group.Count() == 3);
Func<IEnumerable<PlayingCard>, bool> isThreeOfKind =
cards => hasThreeOfKind(cards) && !hasPair(cards);
Func<IEnumerable<PlayingCard>, bool> isFlush =
cards => cards.GroupBy(card => card.Suit).Count() == 1;
Func<IEnumerable<PlayingCard>, bool> isFourOfKind =
cards => cards.GroupBy(card => card.NominalValue)
.Any(group => group.Count() == 4);
Func<IEnumerable<PlayingCard>, bool> isFullHouse =
cards => hasPair(cards) && hasThreeOfKind(cards);
Func<IEnumerable<PlayingCard>, bool> hasStraightFlush =
cards =>isFlush(cards) && isStraight(cards);
Func<IEnumerable<PlayingCard>, bool> isRoyalFlush =
cards => cards.Min(card => (int)card.NominalValue) == (int)PlayingCardNominalValue.Ten
&& hasStraightFlush(cards);
Func<IEnumerable<PlayingCard>, bool> isStraightFlush =
cards => hasStraightFlush(cards) && !isRoyalFlush(cards);
_rules = new Dictionary<PokerHands, Func<IEnumerable<PlayingCard>, bool>>
{
{ PokerHands.Pair, isPair },
{ PokerHands.TwoPair, isTwoPair },
{ PokerHands.ThreeOfKind, isThreeOfKind },
{ PokerHands.Straight, isStraight },
{ PokerHands.Flush, isFlush },
{ PokerHands.FullHouse, isFullHouse },
{ PokerHands.FourOfKind, isFourOfKind },
{ PokerHands.StraightFlush, isStraightFlush },
{ PokerHands.RoyalFlush, isRoyalFlush }
};
}
}
evaluate
method. \$\endgroup\$new PokerGame().Rules[PokerHands.Straight](new PlayingCard[]{ new PlayingCard(CardSuit.Hearts, PlayingCardNominalValue.Ace), new PlayingCard(CardSuit.Clubs, PlayingCardNominalValue.Two), new PlayingCard(CardSuit.Clubs, PlayingCardNominalValue.Three), new PlayingCard(CardSuit.Clubs, PlayingCardNominalValue.Four), new PlayingCard(CardSuit.Clubs, PlayingCardNominalValue.Five), }) == false
\$\endgroup\$