Table of Contents
This project is my unique take on solving Problem #54 from Project Euler. It is a Poker card game.
So basically we have a file (which can be found in the resources folder) containing 1000 lines == 1000 rounds.
Each round is composed of 10 cards that should be divided into 2 hands related to 2 players. Each player should have 5 cards.
A card is represented by a string composed of two characters:
-
The first character denotes the cardβs rank (e.g., β5β for Five, βAβ for Ace).
-
The second character represents the cardβs suit (e.g., βSβ for Spades, βHβ for Hearts).
A hand in poker consists of five cards. For instance, the following string represents a hand: 8C TS KC 9H 4S
This hand includes:
-
8S ==> Eight of Spades
-
TS ==> Two of Spades
-
KC ==> King of Clubs
-
9H ==> Nine of Hearts
-
4S ==> Four of Spades
Name | Symbol | Character Value |
---|---|---|
Spade | β | S |
Heart | β₯ | H |
Club | β£ | C |
Diamond | β¦ | D |
Tab.1: Suits Symbol Representation.
Name | Symbol | Integer Value |
---|---|---|
Two | 2 | 2 |
Three | 3 | 3 |
Four | 4 | 4 |
Five | 5 | 5 |
Six | 6 | 6 |
Seven | 7 | 7 |
Eight | 8 | 8 |
Nine | 9 | 9 |
Ten | T | 10 |
Jack | J | 11 |
Queen | Q | 12 |
King | K | 13 |
Ace | A | 14 |
Tab.2: Card suits and ranks.
The goal of this task is to calculate the number of times that player 1 wins. The winner is determined based on the strength of their hands.
Hand Rank | Hand Name | Definition / Example | Consecutive | Same Suit |
---|---|---|---|---|
9 | Royal Flush | Cards of Ten, Jack, Queen, King, Ace, in the same suit | yes | yes |
8 | Straight Flush | Cards of the same suit and consecutive ranks: Jβ , Tβ , 9β , 8β , 7β | yes | yes |
7 | Four of a Kind | Four cards of the same rank: 9β , 9β¦, 9β₯, 9β | no | no |
6 | Full House | Three cards of the same rank, two of another: Kβ , Kβ₯, Kβ£, 9β₯, 9β£ | no | no |
5 | Flush | Cards of the same suit but not consecutive: Aβ₯, Kβ₯, Qβ₯, Tβ₯, 2β₯ | no | yes |
4 | Straight | Cards of consecutive ranks but in the same suit: 7β , 6β¦, 5β¦, 4β₯, 3β£ | yes | no |
3 | Three of a Kind | Three cards of the same rank, others distinct: Aβ , Aβ¦, Aβ£, Tβ¦, 4β¦ | no | no |
2 | Two Pair | Two cards of the same rank and two of another: Aβ , Aβ£, Qβ£, Qβ¦, 4β£ | no | no |
1 | One Pair | Two cards of the same rank, others distinct: Jβ₯, Jβ , Tβ¦, 4β , 2β₯ | no | no |
0 | High Card | Any kind of hand not mentioned above: highest value card: Aβ₯, Kβ₯, Qβ¦, Jβ , 9β₯ => A in this case | - | - |
Tab.3: Poker hand ranks.
The solution employs anΒ object-oriented programmingΒ approach to simulate a poker game andΒ determine the winner of each hand. At the core of my design are two main classes: theΒ Card
Β class and theΒ HandΒ
class. The Card class encapsulates the properties of individual cards, such as suit and number, providing a blueprint for creating and managing card objects. On the other hand, the Hand class represents a list of cards, offering functionalities to add or remove cards, evaluate hand ranks, and compare hands to determine winners. At a high level, the program parses input hands represented as strings, evaluates the rank of each hand, and determines the winner based on the ranks and card values. It employs various methods to analyze hands, such as checking for pairs, flushes, straights, and high cards. Moreover, this solution takes into account scenarios where ties occur (hands of equal strength).
TheΒ getHandRank
Β method assesses the rank of each hand, allowing for comparisons to determine the winner.
One aspect I appreciate about my solution is its clarity and organization. By dividing functionality into separate classes (such as Card and Hand), the solution becomes more modular and easier to understand. Here are the key aspects I focused on:
- Modular Design: I structured the solution into separate utility methods for parsing, analyzing, and comparing hands. This modular approach makes the solution easier to maintain and understand.
- Use of Streams: While I had only a theoretical understanding of Java Streams before, I found them incredibly valuable in certain methods for their ability to enhance readability and conciseness. They were extremely useful, especially for tasks like counting occurrences and getting the highest card value, etc improving code clarity.
- Error Handling: I tried to implement error handling to ensure robustness by validating input and invalid scenarios.
However, one potential drawback of the solution is itsΒ complexity in Hand Ranking. While I acknowledge the current complexity, I believe it could be further reduced by breaking down the logic into smaller, more focused methods. One alternative approach I considered, but didn't have time to implement, was assigning a numerical value to each hand type with additional values assigned based on factors such as the highest card in the hand. This would allow for direct comparison based on assigned values, potentially simplifying the comparison process.
Initially, I struggled with understanding the game of poker as I wasn't familiar with card games. However, I found the learning process to be enjoyable and interesting! I was fascinated by the logic involved in evaluating poker hands. The problem itself wasn't too difficult, but what mattered the most was how I structured the code to ensure simplicity, and readability, and reduce complexity. Here are the things I employed:
- Java Streams: This helped me with tasks like mapping, filtering, and collecting data and introduced a more functional programming approach to certain parts of the solution.
- Unit Testing: This helped to validate the functionality of various methods, ensuring that changes to the codebase maintain expected behavior.
- Code Analysis Tool: I used it to detect and rectify coding issues, code smells, and potential vulnerabilities in my code. So, I utilized these tools to calculate the complexity of the code and address any areas of concern. Although I had intended to work with SonarLint for code analysis, I resorted to the Codalyze tool due to limitations in installing extensions. Also, I used Visual Studio Code as my primary IDE.
poker-hands-54_euler/
βββ src/ # Source code directory
| βββ main/
| | βββ java/
| | βββ com/
| | βββ poker/
| | | βββ application/ # Package for utility classes
| | | | βββ LaunchGame.java # Main class for running the application
| | | |
| | | βββ common/ # Package for common classes
| | | | βββ FileResource.java # Class for file operation
| | | |
| | | βββ domains/
| | | βββ Card.java # Class representing a playing card
| | | βββ Hand.java # Class representing a hand of cards
| | | βββ HandRankEvaluator.java # Class for hand comparison and evaluation
| | |
| | βββ resources/
| | βββ 0054_poker.txt # File that contains one thousand random hands dealt to two players
| |
| |
| βββ test # Test source code containing automated tests (JUnit5)
| βββ java/
| βββ com/
| βββ poker/
| βββ application/
| | βββ LaunchGameTest.java
| |
| βββ common/
| | βββ FileResourceTest.java
| |
| βββ domains/
| βββ CardTest.java
| βββ HandTest.java
| βββ HandRankEvaluatorTest.java
βββ pom.xml
βββ README.md
-
Phase 1: Understanding the Poker Hands Problem
- Problem Definition: Defined the problem of Euler #54 of the poker hands and its goals.
- Poker Rules Review: I needed to understand the rules of poker, especially the ranking and comparison of poker hands.
- Algorithm Design: I developed an algorithm for evaluating poker hands (just handwriting of a high level).
-
Phase 2: Building Basic Classes
- Card and Hand Classes: Implemented the Card and Hand classes.
-
Phase 3: Parsing Poker Hands from Text File
- File Reading: Implemented logic to read the poker hands text file.
- Hand Parsing: Developed a method to parse each line into two sets of poker hands, one for each player.
- Round Representation: Created a method to represent a round of poker using the Card and Hand objects (using parseCard and parseHand).
-
Phase 4: Evaluating Poker Hands
- Utility functions: Created functions like
isSameSuit
,isConsecutive
,getHandHighCard
, etc. - Hand Evaluation: Wrote down the logic to evaluate and rank poker hands according to the rules of poker.
- Utility functions: Created functions like
-
Phase 5: Testing and Optimization
- Solution Testing: Continuously tested all the elements thoroughly to ensure they worked as expected using JUnit5 tests.
- Optimization: Continuously refined and optimized the code for efficiency and readability.
Hereβs the description for theΒ CardΒ class:
Method | Description |
---|---|
Card(int rank, int suit) |
Constructor; creates a Card object from a string (e.g., β9Sβ or βAHβ). |
int getNumber() |
Returns the rank of the card as an integer (e.g., 9 for Nine, 14 for Ace). |
int getSuit() |
Returns the suit of the card (e.g., βSβ for Spades, βHβ for Hearts). |
String toString() |
Returns a String representation of the card. |
Tab.4: Card class description
This class represents a collection of cards in a poker game.
Method | Description |
---|---|
Hand(List<Card> hand) |
Constructor that takes a List object as input and initializes the internal card list. |
getCards() |
Returns List object containing the cards in the hand. |
toString() |
Returns a String representation of the hand. |
Tab.5: Hand class description
This class provides utility methods for parsing and interpreting card representations in a poker game.
Method Name | Method Description |
---|---|
mapSuitToNumber(char symbol) |
Maps a suit symbol (H, C, S, D) to a corresponding integer value (0-3). Throws IllegalArgumentException for invalid symbols. |
mapNumberToValue(char number) |
Maps a number symbol (2-9, T, J, Q, K, A) to its corresponding integer value (2-14). Throws IllegalArgumentException for invalid symbols. |
parseCard(String cardString) |
Parses a card string representation (e.g., "3S") into a Card object. Validates the string format (length must be 2) and throws IllegalArgumentException for invalid strings. |
Tab.6: CardUtils class description
This class offers a method to obtain a BufferedReader object for reading resource files. The resources folder is located within the src/main/resources
directory of your project structure. Files placed in this folder are accessible via this method:
Method Name | Method Description |
---|---|
getFileResource(String filePath) |
Retrieves a BufferedReader object for reading a file located in the resources folder. The filePath argument specifies the path to the file relative to the resources directory. Throws IllegalArgumentException if the file is not found. |
Tab.7: FileUtil class description
This class provides utility methods for parsing a hand string representation into a Hand object, evaluating hand properties like a same suit, consecutive cards, highest card value, etc, and determining the poker hand rank (Straight Flush, Four of a Kind, etc). and Identifying specific card combinations like triplets and pairs.
Method Name | Method Description |
---|---|
parseHand(String handString) |
Parses a hand string representation (e.g., "3S 3D 3H 9C 10H") into a Hand object. Handles invalid input and returns null if parsing fails. |
isSameSuit(Hand hand) |
Checks if all cards in the hand have the same suit (e.g., all Spades). |
isConsecutive(Hand hand) |
Checks if the cards in the hand have consecutive ranks (e.g., 3, 4, 5, 6, 7). Wraps around for high cards (e.g., T, J, Q, K, A). |
getHighCard(Hand hand) |
Returns the value (rank) of the highest card in the hand. |
countValuesOccurrences(Hand hand) |
Calculates the frequency of each card rank in the hand. The resulting Map associates each card rank (key) with the number of times it appears in the hand (value). |
getNumberOfPairs(Map<Integer, Integer> occurrenceValues) |
Counts the number of pairs (two cards with the same rank) in the hand based on the provided occurrenceValues Map. |
getHandRank(Hand hand) |
Analyzes the hand composition and determines the poker hand rank value (0-8) using a series of checks for different hand types (Straight Flush, Four of a Kind, etc.). |
getThreeOfAKindValue(Hand hand) |
Returns the value (rank) of the three cards of the same kind if a Three of a Kind exists in the hand, otherwise returns -1. |
getPairValue(Hand hand) |
Gets the value (rank) of the pair in the hand, if present. Otherwise, returns -1. |
getHighestNonPairCard(Hand hand) |
Determines the highest non-pair card value in a hand. |
Tab.8: HandUtil class description
This class provides functionalities for evaluating and comparing poker hands offering a way to determine the winner between two poker hands.
Method Name | Method Description |
---|---|
compareHands(Hand hand1, Hand hand2, Comparator<Hand> comparison) |
Determines the winner based on a provided Comparator function. This allows for flexible comparison logic based on different hand properties. |
compareCardValues(Hand hand1, Hand hand2, Function<Hand, Integer> cardValueFunction) |
Compares the hands based on a specified card value function. This function should extract the relevant card value for comparison (e.g., highest card, three of a kind value). |
compareFullHouse(Hand hand1, Hand hand2) |
Compares two Full House hands specifically, considering both the triplet and pair values within each hand. |
evaluateHands(Hand hand1, Hand hand2) |
The main entry point for hand evaluation. Analyzes hand ranks and uses appropriate comparison methods based on the rank (e.g., comparing pairs, full houses, or highest non-pair cards). |
Tab.9: HandService class description