Project 2: Poker

Due: Thursday, March 10, 2016 by 9:00PM


Objective

The objectives of this project are 1) to practice using C++ class syntax, 2) to practice implementing member functions, and 3) to practice object-oriented thinking.


Background

For this project, you will implement member functions of two C++ classes that can be used to play the game of poker. In object-oriented design, the classes should model things in the problem domain. In this situation, the two classes that you will work with are Card (which models a single playing card) and PokerHand (which models a five-card poker hand).

Card is the simpler class. It has just two data members. One stores the suit of the playing card and another stores the point value. Besides the constructors, the Card class only has accessors and a member function to print the card to standard output.

The PokerHand class is more complicated. First, it has an array of five Card objects. There is a member function to print a PokerHand and a member function to get the rank of a PokerHand. There is also a member function that compares two PokerHand objects and figures out which one is better according to the rules of poker. (If you are not familiar with poker, please see the list of poker hands.) There are several more member functions of PokerHand that you will have to implement. (See below.)

C++ class definitions include data members and member functions. They can also include type definitions and constants. For example, in the definition of the Card class, we have:

typedef enum {Invalid, Clubs, Diamonds, Hearts, Spades} cSuit ;

This defines a new type called cSuit. Variables of type cSuit can be assigned a constant value like Clubs. (Please see the section on enumeration types in your textbook.) For example, the data member in Card that holds the suit of a card is declared as:

cSuit m_suit ;

Note that within the class definition or within the body of a member function, you may use cSuit, Invalid, Clubs, Diamonds, ... without any qualification. Outside the class definition and the body of a member function, you must use Card::cSuit, Card::Clubs... For example, even in the implementation of the Card class, the return value of accessor function for m_suit, must be declared as:

Card::cSuit Card::suit() { ... }

Similarly, there is a type definition for the point value of a card. These are stored as an unsigned int.

typedef unsigned int cPoints ; The purpose of having such a type definition is that if we were to make a change from unsigned int to unsigned char, we would not have to scan through hundreds of lines of code to determine which unsigned int refers to the points of a playing card and should be changed to unsigned char and which unsigned int are unsigned integers for another reason. Defining the cPoints type also helps the readability of your program. The purpose of a variable with type cPoints is much clearer than if the variable were defined as an int.

In the PokerHand class, we also have a type definition for an enumeration type:

typedef enum { NoRank, HighCard, OnePair, TwoPair, ThreeOfAKind, Straight, Flush, FullHouse, FourOfAKind, StraightFlush, RoyalFlush } pRank ;

Again, within the PokerHand class definition and within the body of PokerHand member functions, variables that store the rank of a poker hand should be declared using pRank. Outside, PokerHand::pRank should be used. Note that pRank variables hold int values and may be compared. For example,

PokerHand::FullHouse < PokerHand::RoyalFlush evaluates to true. You may rely on the enumeration and assume that better poker hands have ranks with bigger int values.

In the PokerHand class, we also have the constant:

static const int FIVE = 5 ; This is a bit pedantic, but it allows us to avoid the use of magic numbers. If we see PokerHand::FIVE anywhere in our code, we know that the 5 is there because there are five cards in a poker hand and not for some other reason.


Assignment

Your assignment is to implement the member functions of the Card class and the PokerHand class. The class definitions are given in separate files:

For the Card class, you are also provided with a skeleton for the implementation card.cpp that has the function declaration of each function. For the PokerHand class, you are given pokerhand.cpp that just has the default constructor and part of the alternate constructor implemented. You have to fill in the rest yourself.

You have also been provided two main programs that use the Card and PokerHand classes: play5.cpp and play7.cpp. The main program in play5.cpp simply creates two five-card poker hands and uses the cmp() member function to compare them. The main program in play7.cpp is more elaborate and uses PokerHand member functions to determine which two cards should be dropped at the end of a game of seven-card stud. It tries all possible ways of dropping two cards from a hand of seven cards so that the remaining five cards form the best possible poker hand.

These two programs do not fully exercise the member functions that you will implement. They do not even call every function. You should write your own test programs to check the correctness of all of the functions. Even if your code compiles and runs with these two programs, it might not compile and run with the programs that we will use for grading. You are responsible for testing your own code.

The header file for each class has comments describing what each member function has to accomplish. We make some additional comments here:

For the Card class:

For the PokerHand class, there are some complications. First, it is much easier to determine the rank of a poker hand if the cards are sorted by points. For example, if you want to check if a hand is a Two Pair, there are only 3 possible patterns when the cards are sorted. A Two Pair hand must have a 2-2-1, 2-1-2 or 1-2-2 pattern. The 2-2-1 pattern means the first two cards are a pair with the same point value. The next two cards are a pair with a different (higher) point value. Finally, the last card is by itself and has a point value higher than the second pair. Because the cards are sorted, there are no other patterns possible to make a Two Pair. Since sorting is so useful, it should be done by the constructors. That way, all member functions can assume that the hand is already sorted.

Note that poker ranks are mutually exclusive. A Full House is not a Two Pair, even though there are two pairs in a Full House. Similarly, a Straight Flush is not a Straight and a Royal Flush is not a Straight Flush. Thus, if two poker hands have different ranks, we can quickly determine which hand is better. Your implementation should store the rank of the hand in the data member m_rank after it has been computed for the first time. This way you can avoid having to recompute the rank when the client calls getRank(). Before the rank has been computed, m_rank should hold the value NoRank. That is how you can tell if the rank has been computed previously. The constructors must make sure that m_rank is initialized to NoRank.

If two PokerHand objects have the same rank, your cmp() function should still determine which hand is better according to the rules of poker. A tie is possible, but this is not determined just by the rank. For example, if two hands are both Two Pair, the hand whose higher pair has higher point value wins. If that's a tie, then the hand whose lower pair has higher point value wins. If that's a tie, then the fifth card, the one that is not in any pair, is used to break the tie. If the fifth cards are once again tied, then the two hands are tied. Thus, to determine which of the Two Pair hands is better, we need three values: the point value of the higher pair, the point value of the lower pair and the point value of the last card. These three values can be determined easily when you first discovered that the rank of the hand is two pair. For example, when you check if the hand is a Two Pair with the 2-2-1 pattern, you can do the following:

// Check 2-2-1 if ( m_cards[0].points() == m_cards[1].points() && m_cards[1].points() != m_cards[2].points() && m_cards[2].points() == m_cards[3].points() && m_cards[3].points() != m_cards[4].points() ) { m_firstPairPoints = m_cards[3].points() ; m_secondPairPoints = m_cards[1].points() ; m_lastCardPoints = m_cards[4].points() ; m_rank = TwoPair ; return true ; }

Then, the cmp() function can use the data members m_firstPairPoints, m_secondPairPoints and m_lastCardPoints to determine which of two hands with rank of Two Pair is better.

Overall, we just need five data members to store this "additional information" in order the compare any two poker hands:

m_lastCardPoints m_firstPairPoints m_secondPairPoints m_tripletPoints m_quadrupletPoints

Full explanation for these data members are in pokerhand.h.

Here is a list of the member functions in PokerHand that you must implement:


Implementation Issues

Here are some issues to consider and pitfalls to avoid. (You should read these before coding!)


Submitting your program

You should submit these files to the proj2 subdirectory:

You do not need to submit card.h, play5.cpp or play7.cpp because those files should not have changed.

If you followed the directions in setting up shared directories, then you can copy your code to the submission directory with:

cp card.cpp pokerhand.h pokerhand.cpp mytest.cpp ~/cs202proj/proj2/ You can check that your program compiles and runs in the proj2 directory, but please clean up any .o and executable files. Again, do not develop your code in this directory and you should not have the only copy of your program here.

If you are submitting late, you need to copy to proj2-late1, or proj2-late2 instead of proj2. Make sure you understand the course late submission policy.