Poker Logic

Posted on: June 7, 2016 1:31:50 AM

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.

Comments


No comments yet, be the first to leave one.

Leave a Comment