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.cs
using 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.cs
namespace 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)
{
List hand = new List();
foreach (string card in cards)
{
hand.Add(new Card(card));
}
Hand = hand.ToArray();
}
}
}
PokerHand.cs
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.cs
using 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.cs
using 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.