Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / programming / architecture

SOLID Poker - Part 1 - Determine Poker Hand Type

4.80/5 (28 votes)
30 Mar 2017CPOL7 min read 43K   382  
There are a surprising number of variations on poker rules, making poker an excellent candidate for a SOLID Project, a project illustrating SOLID Principles and related Patterns & Practices.

Image 1

As an added bonus, this project will certainly test and hone your poker skills.

Contents

Series of Articles

This project and documentation will span numerous articles to ensure every part of the project receives the necessary time and attention. This provides good structure and makes the information easier to consume.

Each article builds and depends on the preceding articles, and each article will further develop the code from preceding articles. Preceding articles give necessary background and context to the current article.

Following is the complete list of articles of this series published thus far:

How to Get a Job

This series of articles uses a poker project to cover Key Areas of Expertise required by most employers of .NET developers.

The job market has become very competitive. Being good is no longer good enough; potential employers must not have a single reason to say: NO. Not only do you need to be good, but you also need to check all the boxes, and know the latest theory & terminology.

In addition to the basics (talent, training, fundamental programming skills, experience, EQ - soft skills), most employers of .NET developers require experience in the following Key Areas:

  • DRY Principle
  • SOLID Principles
  • Unit Testing
  • MVC (Model–View–Controller) and related technologies
  • ORM (Object-relational mapping): Entity Framework, NHibernate, etc.
  • IoC (Inversion of Control) Containers: Unity, Spring.Net, etc.
  • DI (Dependency Injection)
  • Domain Driven Design (DDD): This may not be necessary for junior - mid level developers but it is definitely necessary for senior developers.
  • Aspect Oriented Programming (AOP): This may not be necessary for junior - mid level developers, but it is definitely necessary for senior developers.

DRY Principle

DRY (Don't Repeat Yourself) is very well known; however, it is absolutely fundamental and incorporated in all good/sound engineering patterns/techniques. Knowledge & Logic should not be repeated; it should be reused, and in this way Science & Technology grows and develops as great minds build upon work done by predecessors.

DRY is important at all levels: fields, functions, classes, components. Accordingly, a major goal of SOLID Principles is to increase reusability, and to decrease repetition.

SOLID Principles

This series of articles and the poker project focuses strongly on implementing and illustrating SOLID Principles and related techniques/patterns. Below is a brief, easy to understand introduction to SOLID Principles, in case you are not yet familiar enough with SOLID Principles.

  • S - SRP: Single Responsibility Principle: A class should have only a single responsibility (i.e., only one potential change in the software's specification should be able to affect the specification of the class). By definition, strong cohesion is required between all the code in the class; all the code must work together to fulfil a Single Responsibility. It is problematic to reuse Multi Responsibility classes as the class may fulfil unrequired responsibilities.
  • O - OCP: Open/Closed Principle: “software entities … should be open for extension, but closed for modification.” Extension/Improvement is good; however, subscribers/users of functionality need assurance that the original/core functionality remains unmodified. For example: except for deprecated functionality, it is easy to upgrade from one .NET Framework to the next because the original functionality remains unchanged.
  • L - LSP: Liskov Substitution Principle: “objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.” This principle is heavily dependent on the following two SOLID Principles: Open/Closed Principle, Dependency Inversion Principle.
  • I - ISP: Interface Segregation Principle: “many client-specific interfaces are better than one general-purpose interface.” Like classes, interfaces should also adhere to the Single Responsibility Principle; an interface should focus on a Single Responsibility.
  • D - DIP: Dependency Inversion Principle: Entities should “depend upon abstractions, not concretions.” Entities should be loosely bound, loosely coupled or decoupled. Higher level entities must not depend on lower level entities, but should instead depend on abstractions/contracts (interfaces, enums, data objects).

Reference: Wikipedia - SOLID - Object Oriented Design

SOLID Principles Summary

SOLID is a mnemonic that aids remembrance of key Object Orientated Design principles. The main goal of SOLID Principles and Object Oriented Design is to develop modular code, so that the modules can easily be maintained, extended and replaced.

As an example: Software engineers generally appreciate computer hardware and enjoy upgrading their personal computers. The goal is for code to be as modular as computer hardware and computer hardware subcomponents (circuit boards, capacitors, resistors, etc.). This same kind of modularity is implemented in all fields of engineering.

Poker Rules & Variants

There are numerous variations on Poker Rules. This project will start off with the following rules:

  • Gameplay: Five-card draw: This is considered the simplest and most well-known variant.
  • Hand Rank Rules: Determines the Poker Hand Types and how hands compare.
    • High Rules
    • No Jokers

Poker Reading Material:

Dependency Injection - Poker Variants

As the project progresses, dependencies implementing variants of the poker rules will be developed. The software can then be changed from one Poker Variant to another by injecting different dependencies; for example: the software can be changed from Five Card Draw to Texas Hold 'em.

Implementation of Principles, Patterns & Practices

At this early stage of the SOLID Poker Project, only the DRY & SOLID Principles are being implemented.

NOTE: SOLID Principles don't apply to private methods, but the private methods do illustrate the DRY Principle.

Project Code: Commentary

The in-code commentary is extensive and the Variable & Function Names are descriptive. In order not to duplicate content/commentary, additional commentary about the code is only given if there is in fact something to add.

Please comment on the article if something is unclear or needs to be added.

Project Code: Contracts

The dependencies implementing variants of the poker rules will adhere to the contracts contained in the SOLIDPoker.Contract project:

  • Enums
    • CardSuit: Club, Diamond, Heart, etc.
    • CardValue: Jack, Queen, King, Ace etc.
    • PokerHandType: Straight Flush, Four of a Kind, etc.
  • Interfaces
    • IPokerHandAssessor
  • DTOs: Data Transfer Objects, or simply Data Objects
    • Card
    • PokerHand

These contracts allow higher level entities to depend on abstractions, not concretions, in accordance with the Dependency Inversion Principle (last SOLID Principle). The contracts will be expanded as development continues.

Project Code: Determine Poker Hand Type

This code is in the following project: SOLIDPoker. PokerHandMachine.

Arrange Cards

This is simply a private function that will be reused by the public functions, which in turn satisfy the contracts between entities.

C#
/// <summary>
/// The cards are arranged in ascending order, so that the Hand Type is easily seen.
/// If an Ace is to be used as a 1 (before 2) in a Straight, 
/// then the Ace will be at the beginning, otherwise, it will be at the end.
/// </summary>
/// <param name="pokerHand">The poker hand to be arranged.</param>
void ArrangeCards(PokerHand pokerHand)
{
    //First determine whether the poker hand is a Straight or a Straight Flush.
    //The IsStraight function also sorts the Poker Hand in ascending order.
    bool straight = IsStraight(pokerHand);

    //Move Aces to the end if:
    if (!straight || //Not a straight
        pokerHand[4].Value == CardValue.King)//Straight with a king at the end
    {
        //Move all Aces To the End
        while (pokerHand[0].Value == CardValue.Ace)
        {
            pokerHand.Add(pokerHand[0]);
            pokerHand.RemoveAt(0);
        }
    }
}

Is Straight

This is simply a private function that will be reused by the public functions, which in turn satisfy the contracts between entities.

C#
/// <summary>
/// Determines whether the card values are in sequence. 
/// The hand type would then be either Straight or Straight Flush.
/// </summary>
/// <param name="pokerHand">The poker hand to be evaluated.</param>
/// <returns>Boolean indicating whether the card values are in sequence.</returns>
bool IsStraight(PokerHand pokerHand)
{
    //Sort ascending
    pokerHand.Sort((pokerCard1, pokerCard2) => 
    pokerCard1.Value.CompareTo(pokerCard2.Value));

    //Determines whether the card values are in sequence.
    return
        //Check whether the last 4 cards are in sequence.
        pokerHand[1].Value == pokerHand[2].Value - 1 &&
        pokerHand[2].Value == pokerHand[3].Value - 1 &&
        pokerHand[3].Value == pokerHand[4].Value - 1
        &&
        (
        //Check that the first two cards are in sequence
        pokerHand[0].Value == pokerHand[1].Value - 1
        //or the first card is an Ace and the last card is a King.
        || pokerHand[0].Value==CardValue.Ace && pokerHand[4].Value==CardValue.King
        );
}

Determine Poker Hand Type

This function is public and is used to satisfy one of the requirements of the IPokerHandAssessor interface (contract).

C#
/// <summary>
/// Determines the poker hand type. For example: Straight Flush or Four of a Kind.
/// </summary>
/// <param name="pokerHand">The poker hand to be evaluated.</param>
/// <returns>The poker hand type.
/// For example: Straight Flush or Four of a Kind.</returns>
public PokerHandType DeterminePokerHandType(PokerHand pokerHand)
{
    //Check whether all cards are in the same suit
    bool allSameSuit = pokerHand.GroupBy(card => card.Suit).Count() == 1;

    //Check whether the Poker Hand Type is: Straight
    bool straight = IsStraight(pokerHand);

    //Determine Poker Hand Type
    if (allSameSuit && straight)
        return PokerHandType.StraightFlush;

    if (allSameSuit)
        return PokerHandType.Flush;

    if (straight)
        return PokerHandType.Straight;

    //Find sets of cards with the same value.
    //Example: QQQ KK
    List<int> sameCardSet1, sameCardSet2;
    FindSetsOfCardsWithSameValue(pokerHand, out sameCardSet1, out sameCardSet2);

    //Continue Determining Poker Hand Type
    if (sameCardSet1.Count == 4)
        return PokerHandType.FourOfAKind;

    if (sameCardSet1.Count + sameCardSet2.Count == 5)
        return PokerHandType.FullHouse;

    if (sameCardSet1.Count == 3)
        return PokerHandType.ThreeOfAKind;

    if (sameCardSet1.Count + sameCardSet2.Count == 4)
        return PokerHandType.TwoPair;

    if (sameCardSet1.Count == 2)
        return PokerHandType.Pair;

    return PokerHandType.HighCard;
}

Hello World

The sole purpose of this little application is to test and illustrate the functionality created thus far: Determine Poker Hand Type. Unit testing will come in future articles.

Image 2

Hello World Code

These are simply two helper functions to decrease the amount of testing code.

C#
/// <summary>
/// Determine the Poker Hand Type & write the result to the console.
/// </summary>
/// <param name="pokerHand"></param>
static void DeterminePokerHandType_and_writeToConsole(PokerHand pokerHand)
{
    IPokerHandAssessor assessor = new HighRules_NoJoker();
    PokerHandType handType = assessor.DeterminePokerHandType(pokerHand);
    Console.WriteLine("Poker hand type determined: " + EnumToTitle(handType));
}

/// <summary>
/// Converts an enum to a presentable title.
/// </summary>
/// <param name="enumToConvert">The enum to be converted.</param>
/// <returns>A presentable title.</returns>
static string EnumToTitle(Enum enumToConvert)
{
    return System.Text.RegularExpressions.Regex
    .Replace(enumToConvert.ToString(), "[A-Z]", " $0").Trim();
}

Hello World Constructor

The following code has too much boilerplate code; this problem will be solved in Part 3 of this series with the development of a Poker Hand Generator.

C#
static void Main(string[] args)
{
    Console.WriteLine("Hello World - SOLID Poker!");
    Console.WriteLine("");

    //Royal Flush
    DeterminePokerHandType_and_writeToConsole(new PokerHand(
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ten },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Jack },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Queen },
        new Card { Suit = CardSuit.Spade, Value = CardValue.King },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ace }
        ));

    //Flush
    DeterminePokerHandType_and_writeToConsole(new PokerHand(
        new Card { Suit = CardSuit.Spade, Value = CardValue.Queen },
        new Card { Suit = CardSuit.Spade, Value = CardValue.King },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Two },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Three }
        ));

    //Straight
    DeterminePokerHandType_and_writeToConsole(new PokerHand(
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ten },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Jack },
        new Card { Suit = CardSuit.Club, Value = CardValue.Queen },
        new Card { Suit = CardSuit.Spade, Value = CardValue.King },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ace }
        ));

    //Four of a Kind
    DeterminePokerHandType_and_writeToConsole(new PokerHand(
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Club, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Diamond, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Heart, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ten }
        ));

    //Full House
    DeterminePokerHandType_and_writeToConsole(new PokerHand(
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Club, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Diamond, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Heart, Value = CardValue.Ten },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ten }
        ));

    //Three of a Kind
    DeterminePokerHandType_and_writeToConsole(new PokerHand(
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Club, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Diamond, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Heart, Value = CardValue.Nine },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ten }
        ));

    //Two Pair
    DeterminePokerHandType_and_writeToConsole(new PokerHand(
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Club, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Diamond, Value = CardValue.King },
        new Card { Suit = CardSuit.Heart, Value = CardValue.Ten },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ten }
        ));

    //Pair
    DeterminePokerHandType_and_writeToConsole(new PokerHand(
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Club, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Diamond, Value = CardValue.King },
        new Card { Suit = CardSuit.Heart, Value = CardValue.Queen },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ten }
        ));

    //High Card
    DeterminePokerHandType_and_writeToConsole(new PokerHand(
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Club, Value = CardValue.Nine },
        new Card { Suit = CardSuit.Diamond, Value = CardValue.King },
        new Card { Suit = CardSuit.Heart, Value = CardValue.Queen },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ten }
        ));

    Console.Read();
}

See Something - Say Something

The goal is to have clear, error free content and your help in this regard is much appreciated. Be sure to comment if you see an error or potential improvement. All feedback is welcome.

Summary

SOLID Poker - Part 1 has served mostly as an introductory article, giving background and context for this series of articles. This article has provided a strong foundation for subsequent articles of this series, allowing subsequent articles to focus more on code.

Hopefully this article will help you ace your next interview, specifically on the topic of SOLID Principles.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)