Another programming challenge that I decided to do was a poker challenge The goal of this challenge is to parse 1000 random poker hands of 2 players and determine how many times the first player wins. This was actually quite a fun challenge and I suggest you try to do it on your own even though I'm posting my code.
I decided to do this in a more TDD way than I normally do. So just as I started, I'll start by posting all of my unit tests. Granted, I didn't have them all at the time of development, but I don't think it's necessary to post what I did every step of the way.
PokerTests.csusing NUnit.Framework; using Poker_Kata; namespace Poker_Tests { [TestFixture] public class PokerTests { private Player P1; private Player P2; [Test] public void P13OfAKind() { P1.SetHand("3D", "3H", "3C", "4D", "7D"); P2.SetHand("2S", "2D", "2C", "AS", "KS"); var winners = Poker.DetermineWinner(P1, P2); Assert.That(winners.Length, Is.EqualTo(1)); Assert.That(winners[0].Player, Is.EqualTo(P1)); Assert.That(winners[0].Hand, Is.EqualTo(Hand.ThreeKind)); } [Test] public void P1FourKind() { P1.SetHand("2D", "2S", "2C", "2H", "TD"); P2.SetHand("2S", "AS", "4S", "5S", "6S"); var winners = Poker.DetermineWinner(P1, P2); Assert.That(winners.Length, Is.EqualTo(1)); Assert.That(winners[0].Player, Is.EqualTo(P1)); Assert.That(winners[0].Hand, Is.EqualTo(Hand.FourKind)); } [Test] public void P1HighCard() { P1.SetHand("2D", "3D", "4D", "5D", "8C"); P2.SetHand("2C", "3S", "4S", "5S", "7S"); var winners = Poker.DetermineWinner(P1, P2); Assert.That(winners.Length, Is.EqualTo(1)); Assert.That(winners[0].Player, Is.EqualTo(P1)); Assert.That(winners[0].Hand, Is.EqualTo(Hand.HighCard)); } [Test] public void P1HighPair() { P1.SetHand("3D", "3H", "4D", "5D", "6D"); P2.SetHand("2S", "2C", "QS", "KD", "AS"); var winners = Poker.DetermineWinner(P1, P2); Assert.That(winners.Length, Is.EqualTo(1)); Assert.That(winners[0].Player, Is.EqualTo(P1)); Assert.That(winners[0].Hand, Is.EqualTo(Hand.Pair)); } [Test] public void P1Pair() { P1.SetHand("2D", "2H", "4D", "5D", "7D"); P2.SetHand("2C", "3S", "4S", "5S", "7S"); var winners = Poker.DetermineWinner(P1, P2); Assert.That(winners.Length, Is.EqualTo(1)); Assert.That(winners[0].Player, Is.EqualTo(P1)); Assert.That(winners[0].Hand, Is.EqualTo(Hand.Pair)); } [Test] public void P1RoyalFlush() { P1.SetHand("AD", "TD", "KD", "QD", "JD"); P2.SetHand("AS", "2S", "3S", "4S", "5S"); var winners = Poker.DetermineWinner(P1, P2); Assert.That(winners.Length, Is.EqualTo(1)); Assert.That(winners[0].Player, Is.EqualTo(P1)); Assert.That(winners[0].Hand, Is.EqualTo(Hand.StraightFlush)); } [Test] public void P1Straight() { P1.SetHand("2D", "3H", "4C", "5D", "6D"); P2.SetHand("2S", "2D", "2C", "AS", "AS"); var winners = Poker.DetermineWinner(P1, P2); Assert.That(winners.Length, Is.EqualTo(1)); Assert.That(winners[0].Player, Is.EqualTo(P1)); Assert.That(winners[0].Hand, Is.EqualTo(Hand.Straight)); } [Test] public void P1TwoPair() { P1.SetHand("2D", "2H", "3D", "3D", "7D"); P2.SetHand("2S", "3S", "4S", "AS", "AS"); var winners = Poker.DetermineWinner(P1, P2); Assert.That(winners.Length, Is.EqualTo(1)); Assert.That(winners[0].Player, Is.EqualTo(P1)); Assert.That(winners[0].Hand, Is.EqualTo(Hand.TwoPair)); } [Test] public void P1TwoPairHighCard() { P1.SetHand("4C", "4S", "2D", "AD", "2H"); P2.SetHand("4S", "4D", "2S", "KS", "2C"); var winners = Poker.DetermineWinner(P1, P2); Assert.That(winners.Length, Is.EqualTo(1)); Assert.That(winners[0].Player, Is.EqualTo(P1)); Assert.That(winners[0].Hand, Is.EqualTo(Hand.TwoPair)); } [Test] public void P2AceHighCard() { P1.SetHand("2D", "3D", "4S", "5D", "7D"); P2.SetHand("2S", "3S", "6D", "5S", "AS"); var winners = Poker.DetermineWinner(P1, P2); Assert.That(winners.Length, Is.EqualTo(1)); Assert.That(winners[0].Player, Is.EqualTo(P2)); Assert.That(winners[0].Hand, Is.EqualTo(Hand.HighCard)); } [Test] public void P2Flush() { P1.SetHand("AD", "KH", "QC", "JD", "TD"); P2.SetHand("2S", "TS", "4S", "5S", "6S"); var winners = Poker.DetermineWinner(P1, P2); Assert.That(winners.Length, Is.EqualTo(1)); Assert.That(winners[0].Player, Is.EqualTo(P2)); Assert.That(winners[0].Hand, Is.EqualTo(Hand.Flush)); } [Test] public void P2FullHouse() { P1.SetHand("AD", "AH", "AC", "3D", "7D"); P2.SetHand("2S", "2D", "2C", "3S", "3D"); var winners = Poker.DetermineWinner(P1, P2); Assert.That(winners.Length, Is.EqualTo(1)); Assert.That(winners[0].Player, Is.EqualTo(P2)); Assert.That(winners[0].Hand, Is.EqualTo(Hand.FullHouse)); } [Test] public void P2FullHouse3CardHigher() { P1.SetHand("3D", "3H", "3C", "AD", "AD"); P2.SetHand("4S", "4D", "4C", "2S", "2D"); var winners = Poker.DetermineWinner(P1, P2); Assert.That(winners.Length, Is.EqualTo(1)); Assert.That(winners[0].Player, Is.EqualTo(P2)); Assert.That(winners[0].Hand, Is.EqualTo(Hand.FullHouse)); } [Test] public void P2HighCard2() { P1.SetHand("8C", "TS", "KC", "9H", "4S"); P2.SetHand("7D", "2S", "5D", "3S", "AC"); var winners = Poker.DetermineWinner(P1, P2); Assert.That(winners.Length, Is.EqualTo(1)); Assert.That(winners[0].Player, Is.EqualTo(P2)); Assert.That(winners[0].Hand, Is.EqualTo(Hand.HighCard)); } [Test] public void P2HighCard3() { P1.SetHand("4D", "5H", "TH", "QS", "KS"); P2.SetHand("2H", "3D", "4S", "6C", "AD"); var winners = Poker.DetermineWinner(P1, P2); Assert.That(winners.Length, Is.EqualTo(1)); Assert.That(winners[0].Player, Is.EqualTo(P2)); Assert.That(winners[0].Hand, Is.EqualTo(Hand.HighCard)); } [Test] public void P2HighFlush() { P1.SetHand("4D", "KD", "QD", "JD", "TD"); P2.SetHand("2S", "AS", "4S", "5S", "6S"); var winners = Poker.DetermineWinner(P1, P2); Assert.That(winners.Length, Is.EqualTo(1)); Assert.That(winners[0].Player, Is.EqualTo(P2)); Assert.That(winners[0].Hand, Is.EqualTo(Hand.Flush)); } [Test] public void P2LowStraight() { P1.SetHand("AD", "2H", "3C", "4D", "5C"); P2.SetHand("2D", "3H", "4C", "5D", "6D"); var winners = Poker.DetermineWinner(P1, P2); Assert.That(winners.Length, Is.EqualTo(1)); Assert.That(winners[0].Player, Is.EqualTo(P2)); Assert.That(winners[0].Hand, Is.EqualTo(Hand.Straight)); } [Test] public void P2StraightFlush() { P1.SetHand("AD", "2H", "3C", "4D", "5C"); P2.SetHand("AS", "2S", "3S", "4S", "5S"); var winners = Poker.DetermineWinner(P1, P2); Assert.That(winners.Length, Is.EqualTo(1)); Assert.That(winners[0].Player, Is.EqualTo(P2)); Assert.That(winners[0].Hand, Is.EqualTo(Hand.StraightFlush)); } [Test] public void P2TwoPair() { P1.SetHand("2D", "2H", "3D", "3D", "7D"); P2.SetHand("4S", "4D", "2S", "AS", "2C"); var winners = Poker.DetermineWinner(P1, P2); Assert.That(winners.Length, Is.EqualTo(1)); Assert.That(winners[0].Player, Is.EqualTo(P2)); Assert.That(winners[0].Hand, Is.EqualTo(Hand.TwoPair)); } [Test] public void PairTie() { P1.SetHand("2D", "2H", "4D", "5D", "6D"); P2.SetHand("2S", "2C", "4S", "5S", "6S"); var winners = Poker.DetermineWinner(P1, P2); Assert.That(winners.Length, Is.EqualTo(2)); Assert.That(winners[0].Hand, Is.EqualTo(Hand.Pair)); } [OneTimeSetUp] public void TestsSetup() { P1 = new Player { Name = "P1" }; P2 = new Player { Name = "P2" }; } [Test] public void Tie() { P1.SetHand("2D", "3D", "4D", "5D", "6D"); P2.SetHand("2S", "3S", "4S", "5S", "6S"); var winners = Poker.DetermineWinner(P1, P2); Assert.That(winners.Length, Is.EqualTo(2)); Assert.That(winners[0].Hand, Is.EqualTo(Hand.StraightFlush)); } } }
Now we'll need the Card, Player, and PokerHand helper classes. These are simple POCO helpers just to hold some information for whatever is utilizing the code.
Card.csnamespace Poker_Kata { public enum Faces { Club, Diamond, Spade, Heart } public class Card { public Card() { } public Card(int value, Faces face) { Value = value; Face = face; } public Card(string card) { switch (card[0]) { case '2': Value = 2; break; case '3': Value = 3; break; case '4': Value = 4; break; case '5': Value = 5; break; case '6': Value = 6; break; case '7': Value = 7; break; case '8': Value = 8; break; case '9': Value = 9; break; case 'T': Value = 10; break; case 'J': Value = 11; break; case 'Q': Value = 12; break; case 'K': Value = 13; break; case 'A': Value = 14; break; } switch (card[1]) { case 'H': Face = Faces.Heart; break; case 'D': Face = Faces.Diamond; break; case 'C': Face = Faces.Club; break; case 'S': Face = Faces.Spade; break; } } public Faces Face { get; set; } public int Value { get; set; } public override string ToString() { return Value + " " + Face.ToString(); } } }Player.cs
using System.Collections.Generic; namespace Poker_Kata { public class Player { public Card[] Hand { get; set; } public string Name { get; set; } public void SetHand(params string[] cards) { ListPokerHand.cshand = new List (); foreach (string card in cards) { hand.Add(new Card(card)); } Hand = hand.ToArray(); } } }
namespace Poker_Kata { public class PokerHand { public Card[] Cards { get; set; } public Hand Hand { get; set; } public Player Player { get; set; } } }
Next is the heart of the project, the Poker class. This class will hold all the of the logic and rules of poker and allow you to pass in a number of players with their hands and will determine what hands they hold along with who won the hand. I didn't make this work with just any poker game, although it wouldn't take much work to do so, I only did enough to handle each player having 5 cards.
Poker.csusing System.Linq; namespace Poker_Kata { public enum Hand { HighCard, Pair, TwoPair, ThreeKind, FullHouse, Straight, Flush, FourKind, StraightFlush } public static class Poker { public static PokerHand[] DetermineWinner(params Player[] players) { PokerHand[] pokerHands = new PokerHand[players.Length]; for (int i = 0; i < players.Length; i++) { var hand = GetHand(players[i].Hand); hand.Player = players[i]; pokerHands[i] = hand; } pokerHands = pokerHands.Where(p => p.Hand == pokerHands.Max(p1 => p1.Hand)).ToArray(); if (pokerHands.Length > 1) { pokerHands = FilterByHigherHand(pokerHands); if (pokerHands.Length > 1) { pokerHands = FilterByHighCard(pokerHands); } } return pokerHands; } public static PokerHand GetHand(Card[] hand) { PokerHand pokerHand = new PokerHand(); pokerHand.Cards = hand = hand.OrderByDescending(c => c.Value).ToArray(); pokerHand.Hand = (hand.CountBy(c => c.Face == hand[0].Face) >= 5) ? Hand.Flush : Hand.HighCard; if (CheckIsStraight(hand)) { pokerHand.Hand = pokerHand.Hand == Hand.Flush ? Hand.StraightFlush : Hand.Straight; } int previousCard = 0; foreach (var card in hand) { if (previousCard == card.Value) continue; int valueCount = hand.CountBy(c => c.Value == card.Value); if (valueCount == 2) { if (pokerHand.Hand == Hand.Pair) { pokerHand.Hand = Hand.TwoPair; } else if (pokerHand.Hand == Hand.ThreeKind) { pokerHand.Hand = Hand.FullHouse; } else { pokerHand.Hand = Hand.Pair; } } else if (valueCount == 3) { if (pokerHand.Hand == Hand.Pair) { pokerHand.Hand = Hand.FullHouse; } else { pokerHand.Hand = Hand.ThreeKind; } } else if (valueCount == 4) { pokerHand.Hand = Hand.FourKind; } previousCard = card.Value; } return pokerHand; } private static bool CheckIsStraight(Card[] hand) { int previousValue = 0; foreach (var card in hand.OrderBy(c => c.Value)) { if (previousValue > 0) { if (card.Value == 14 && previousValue == 5) break; if (card.Value != previousValue + 1) { return false; } } previousValue = card.Value; } return true; } private static PokerHand[] FilterByHighCard(PokerHand[] pokerHands) { foreach (var hand in pokerHands) { hand.Cards = hand.Cards.OrderByDescending(c => c.Value).ToArray(); if ((hand.Hand == Hand.Straight || hand.Hand == Hand.StraightFlush) && (hand.Cards.Max(c => c.Value) == 14 && hand.Cards.Min(c => c.Value) == 2)) { hand.Cards[0].Value = 1; hand.Cards = hand.Cards.OrderByDescending(c => c.Value).ToArray(); } } for (int i = 0; i < pokerHands[0].Cards.Length; i++) { int highCard = pokerHands.Select(p => p.Cards[i]).Max(c => c.Value); pokerHands = pokerHands.Where(p => p.Cards[i].Value == highCard).ToArray(); if (pokerHands.Length == 1) break; } return pokerHands; } private static PokerHand[] FilterByHigherHand(PokerHand[] pokerHands) { switch (pokerHands[0].Hand) { case Hand.Pair: pokerHands = FilterBySet(pokerHands, 2); break; case Hand.TwoPair: pokerHands = FilterBySet(pokerHands, 2); if (pokerHands.Length > 1) { pokerHands = FilterBySet(pokerHands, 2, false); } break; case Hand.ThreeKind: case Hand.FullHouse: pokerHands = FilterBySet(pokerHands, 3); break; case Hand.FourKind: pokerHands = FilterBySet(pokerHands, 4); break; } return pokerHands; } private static PokerHand[] FilterBySet(PokerHand[] pokerHands, int setCount, bool checkMax = true) { var groupedCards = pokerHands.Select(p => p.Cards.GroupBy(c => c.Value).Where(g => g.Count() == setCount)); int checkDigit; if (checkMax) { checkDigit = groupedCards.SelectMany(g => g).Max(g => g.Key); } else { checkDigit = groupedCards.SelectMany(g => g).Min(g => g.Key); } pokerHands = pokerHands.Where(p => p.Cards.CountBy(c => c.Value == checkDigit) == setCount).ToArray(); return pokerHands; } } }
In order for this code to compile, you'll need one more helper class that holds the extension CountBy.
Extensions.csusing System; using System.Collections.Generic; namespace Poker_Kata { public static class Extensions { public static int CountBy(this IEnumerable source, Func comparer) { int count = 0; foreach (var s in source) { if (comparer(s)) { count++; } } return count; } } }
I believe this logic should be able to handle any 5 card poker hand, if not, please post a comment on what hand it fails on and I will happily take a look.
Now that we have all the logic and the unit tests passing, we need to read in the .txt document that the challenge provides that holds all the hands. I did this in the Program class, so when the program executes, it completes the challenge assuming you give the argument of the .txt document.
Program.cs
using System; using System.IO; using System.Linq; namespace Poker_Kata { internal class Program { private static string[] LoadFile(string file) { string[] contents; using (var fs = new FileStream(file, FileMode.Open, FileAccess.Read)) using (var sr = new StreamReader(fs)) { contents = sr.ReadToEnd().Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); } return contents; } private static void Main(string[] args) { if (args.Length > 0) { int p1Wins = 0; int p2Wins = 0; int ties = 0; var cards = LoadFile(args[0]); Player p1 = new Player() { Name = "P1" }; Player p2 = new Player() { Name = "P2" }; foreach (var hand in cards) { var game = hand.Split(' '); p1.SetHand(game.Take(5).ToArray()); p2.SetHand(game.Skip(5).Take(5).ToArray()); var winners = Poker.DetermineWinner(p1, p2); p1.Hand = p1.Hand.OrderBy(c => c.Value).ToArray(); p2.Hand = p2.Hand.OrderBy(c => c.Value).ToArray(); if (winners.Length == 2) { ties++; } else if (winners[0].Player.Equals(p1)) { p1Wins++; } else { p2Wins++; } } Console.WriteLine("P1: {0}\r\nP2: {1}\r\nTies: {2}", p1Wins, p2Wins, ties); } } } }
I do hope, once again, that you attempt this challenge properly without just using my code to get the answer. I know that my code could be optimized further, but I see no need to do so at this time. Thanks for reading and happy coding.