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.
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
- DTOs: Data Transfer Objects, or simply Data Objects
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.
void ArrangeCards(PokerHand pokerHand)
{
bool straight = IsStraight(pokerHand);
if (!straight ||
pokerHand[4].Value == CardValue.King)
{
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.
bool IsStraight(PokerHand pokerHand)
{
pokerHand.Sort((pokerCard1, pokerCard2) =>
pokerCard1.Value.CompareTo(pokerCard2.Value));
return
pokerHand[1].Value == pokerHand[2].Value - 1 &&
pokerHand[2].Value == pokerHand[3].Value - 1 &&
pokerHand[3].Value == pokerHand[4].Value - 1
&&
(
pokerHand[0].Value == pokerHand[1].Value - 1
|| 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).
public PokerHandType DeterminePokerHandType(PokerHand pokerHand)
{
bool allSameSuit = pokerHand.GroupBy(card => card.Suit).Count() == 1;
bool straight = IsStraight(pokerHand);
if (allSameSuit && straight)
return PokerHandType.StraightFlush;
if (allSameSuit)
return PokerHandType.Flush;
if (straight)
return PokerHandType.Straight;
List<int> sameCardSet1, sameCardSet2;
FindSetsOfCardsWithSameValue(pokerHand, out sameCardSet1, out sameCardSet2);
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.
Hello World Code
These are simply two helper functions to decrease the amount of testing code.
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));
}
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.
static void Main(string[] args)
{
Console.WriteLine("Hello World - SOLID Poker!");
Console.WriteLine("");
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 }
));
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 }
));
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 }
));
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 }
));
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 }
));
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 }
));
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 }
));
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 }
));
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.