package netgame.fivecarddraw;
import java.util.ArrayList;
/**
* This is a utility class that can be used to assign ranks
* to poker hands containing up to five cards. It does not
* handle jokers or hands of more than five cards, but a rank
* can be computed for a hand with fewer than five cards.
* A numerical rank is assigned to a hand. One poker hand
* beats another if and only if the rank for the first hand
* is greater than the rank for the second hand. If the
* ranks are equal, then two hands are tied. Note that a hand
* with fewer than five cards is never considered to be
* a straight or flush.
*
Once cards have been added to a PokerRank object, the
* numerical rank can be obtained by calling the getRank()
* method. Call getDescription() to get a verbal description
* of the hand that is good enough for most purposes (such as
* "Pair of Kings"), but that does not include enough information
* to fully rank the hand. Call getLongDescription() to get a
* verbal description with enough detail to fully rank the hand.
*/
public class PokerRank {
/* This main routine is meant only to test this class.
* this will only do something if the output statements at
* the end of computeRank() are uncommented.
*/
public static void main(String[] args) {
System.out.println("(No cards)");
System.out.println(new PokerRank().getRank());
System.out.println(new PokerRank().getDescription());
System.out.println(new PokerRank().getLongDescription());
System.out.println("\n");
new PokerRank(new PokerCard(12,3)).getRank();
new PokerRank(new PokerCard(2,2), new PokerCard(12, 1)).getRank();
new PokerRank(new PokerCard(12,3), new PokerCard(12, 1)).getRank();
new PokerRank(new PokerCard(12,3), new PokerCard(2,2), new PokerCard(12, 1)).getRank();
new PokerRank(new PokerCard(12,3), new PokerCard(12,2), new PokerCard(12, 1)).getRank();
new PokerRank(new PokerCard(12,3), new PokerCard(2,2), new PokerCard(2,3), new PokerCard(12, 1)).getRank();
new PokerRank(new PokerCard(12,3), new PokerCard(2,2), new PokerCard(12, 1), new PokerCard(12,2)).getRank();
new PokerRank(new PokerCard(12,3), new PokerCard(2,2), new PokerCard(12, 1), new PokerCard(12,2)).getRank();
new PokerRank(new PokerCard(12,3), new PokerCard(12, 1), new PokerCard(12,2), new PokerCard(2,3)).getRank();
new PokerRank(new PokerCard(12,3), new PokerCard(2,2), new PokerCard(12, 1), new PokerCard(12,2), new PokerCard(12,0)).getRank();
new PokerRank(new PokerCard(3,1), new PokerCard(2,1), new PokerCard(5, 1), new PokerCard(6,2), new PokerCard(4,3)).getRank();
new PokerRank(new PokerCard(3,1), new PokerCard(2,1), new PokerCard(5, 1), new PokerCard(14,2), new PokerCard(4,3)).getRank();
new PokerRank(new PokerCard(3,1), new PokerCard(2,1), new PokerCard(5, 1), new PokerCard(14,1), new PokerCard(4,1)).getRank();
new PokerRank(new PokerCard(12,1), new PokerCard(10,1), new PokerCard(13, 1), new PokerCard(14,2), new PokerCard(11,3)).getRank();
new PokerRank(new PokerCard(12,1), new PokerCard(10,1), new PokerCard(13, 1), new PokerCard(14,1), new PokerCard(11,1)).getRank();
new PokerRank(new PokerCard(12,1), new PokerCard(10,1), new PokerCard(13, 1), new PokerCard(4,1), new PokerCard(11,1)).getRank();
new PokerRank(new PokerCard(12,1), new PokerCard(10,2), new PokerCard(13, 1), new PokerCard(4,1), new PokerCard(11,1)).getRank();
new PokerRank(new PokerCard(10,2), new PokerCard(10,1), new PokerCard(13, 1), new PokerCard(14,1), new PokerCard(13,3)).getRank();
new PokerRank(new PokerCard(10,2), new PokerCard(10,1), new PokerCard(13, 1), new PokerCard(13,1), new PokerCard(10,3)).getRank();
}
public static final int NOTHING = 0; // Codes for the basic types of poker hand.
public static final int PAIR = 1;
public static final int TWO_PAIR = 2;
public static final int TRIPLE = 3;
public static final int STRAIGHT = 4;
public static final int FLUSH = 5;
public static final int FULL_HOUSE = 6;
public static final int FOUR_OF_A_KIND = 7;
public static final int STRAIGHT_FLUSH = 8;
public static final int ROYAL_FLUSH = 9;
private ArrayList cards = new ArrayList(); // The cards in this hand.
/**
* Numerical rank of the hand consisting of cards that have
* been added to this object. -1 is a signal than the rank
* has to be computed.
*/
private int rank = -1;
/**
* The verbal description of the hand. Computed at the same
* time the rank is computed.
*/
private String description;
/**
* The full verbal description of the hand. Computed at the same
* time the rank is computed.
*/
private String longDescription;
/**
* Construct a PokerRank object from a list of zero or more
* cards. It is possible to add more cards later.
* @throws IllegalArgumentException if any of the cards are
* null, or if any are jokers, or if the number of cards
* is greater than five.
*/
public PokerRank(PokerCard... card) {
if (card != null) {
for (PokerCard c : card)
add(c);
}
}
/**
* Construct a PokerRank object from a list of zero or more
* cards. It is possible to add more cards later.
* @param cards the list of cards to be added. A null value
* is OK and means that no cards are added initially.
* @throws IllegalArgumentException if any cards in the list are
* null, or if any are jokers, or if the number of cards
* is greater than five.
*/
public PokerRank(ArrayList cards) {
if (cards != null) {
for (PokerCard c : cards)
add(c);
}
}
/**
* Add a card to the hand. This will change the ranking of the hand.
* @throws IllegalArgumentException if the card is null or is a joker
* or if there were already five cards in the hand.
*/
public void add(PokerCard card) {
if (card == null)
throw new IllegalArgumentException("Cards can't be null for class PokerRank");
if (card.getSuit() == PokerCard.JOKER)
throw new IllegalArgumentException("Class PokerRank does not support jokers.");
if (cards.size() == 5)
throw new IllegalArgumentException("PokerRank does not support hands with more than five cards.");
cards.add(card);
rank = -1;
}
/**
* Get the numerical rank of the hand. One hand beats another in poker
* if and only if it has a higher numerical rank. (The rank is determined
* as follows: The basic type of hand is given by one of the constants
* NOTHING, PAIR, TWO_PAIR, TRIPLE, STRAIGHT, FLUSH, FULL_HOUSE, FOUR_OF_A_KIND,
* STRAIGHT_FLUSH, ROYAL_FLUSH. These constants are integers in the range
* 0 through 9. That integer is stored in bits 20 through 23 of the numerical
* rank (and all higher order bits are zero). So, if the basic type of one hand
* is higher than the basic type of another hand then the rank of the first
* will be higher than the rank of the second (no matter what is in bits 0 -- 19).
* If the basic types are the same, the comparison is based on bit positions
* 0 through 19, which are used to record the values of the cards, with 4 bits per
* card in the order that the values would have to be considered to break ties.
* The cards are ordered highest value to lowest value, except that, for example,
* when there is a four-of-a-kind, the cards that make up the four-of-a-kind
* come first even if their value is less than the value of the remaining
* card. This is because 5-5-5-5-2 beats 2-2-2-2-9. Similarly, the cards
* that make up a triple are moved to the front, since the value of the
* triple has to be considered before the values of the other cards in the
* hand.)
*/
public int getRank() {
if (rank == -1)
computeRank();
return rank;
}
/**
* Returns a description of the rank of the hand, such as
* "Pair of Threes", "High Card (Queen)", "Full House, Threes and Sevens",
* "Seven-high Straight," or "Royal Flush". The description does
* not necessarily contain enough information to fully rank the
* hand.
*/
public String getDescription() {
if (rank == -1)
computeRank();
return description;
}
/**
* Returns a long description of the rank of the hand, containing
* enough information to fully rank the hand. For example:
* "Pair of Threes (plus Ace, Ten, Seven)".
*/
public String getLongDescription() {
if (rank == -1)
computeRank();
return longDescription;
}
/**
* Returns one of the constants NOTHING, PAIR, TWO_PAIR, TRIPLE, STRAIGHT,
* FLUSH, FULL_HOUSE, FOUR_OF_A_KIND, STRAIGHT_FLUSH, or ROYAL_FLUSH.
* The constant represents the basic type of the hand, but without
* the information about card values that is needed to compare
* hands of the same type.
*/
public int getHandType() {
if (rank == -1)
computeRank();
return rank >> 20;
}
/**
* Returns the type of hand as a string, such as "Two pairs", "Straight",
* or "Nothing". This is a string representation of the value returned
* by getHandType(). This is the basic type of the hand, but without
* the information about card values that is needed to compare
* hands of the same type.
*/
public String getHandTypeAsString() {
if (cards.size() == 0)
return "Empty Hand";
int type = getHandType();
if (type == PAIR)
return "Pair";
if (type == TWO_PAIR)
return "Two pairs";
if (type == TRIPLE)
return "Triple";
if (type == STRAIGHT)
return "Straight";
if (type == FLUSH)
return "Flush";
if (type == FULL_HOUSE)
return "Full House";
if (type == FOUR_OF_A_KIND)
return "Four of a kind";
if (type == STRAIGHT_FLUSH)
return "Straight Flush";
if (type == ROYAL_FLUSH)
return "Royal Flush";
return "Nothing";
}
/**
* Returns the cards that have been added to this hand.
* @return a newly created ArrayList containing the cards.
* The list can be empty but will never be null. Cards in
* the list have been sorted into the order in which they
* have to be considered when evaluating the hand.
*/
public ArrayList getCards() {
if ( rank == -1)
computeRank();
return new ArrayList(cards);
}
/**
* Returns the same string as does the getDescription() method.
* That is, the return value is a string that describes the hand
* but does not contain all the information that is needed to
* completely rank hands of the same type. See also the
* method getLongDescription().
*/
public String toString() {
return getDescription();
}
// --------------------- the private implementation section -------------------
private String valueName(PokerCard c) {
switch ( c.getValue() ) {
case 2: return "Two";
case 3: return "Three";
case 4: return "Four";
case 5: return "Five";
case 6: return "Six";
case 7: return "Seven";
case 8: return "Eight";
case 9: return "Nine";
case 10: return "Ten";
case 11: return "Jack";
case 12: return "Queen";
case 13: return "King";
default: return "Ace";
}
}
private String pluralValueName(PokerCard c) {
if (c.getValue() == 6)
return "Sixes";
else
return valueName(c) + "s";
}
private String cardValueNames() {
StringBuffer s = new StringBuffer(valueName(cards.get(0)));
for (int i = 1; i < cards.size(); i++) {
s.append(',');
s.append(valueName(cards.get(i)));
}
return s.toString();
}
/**
* Computes the rank, description, and longDescription of the hand.
* We know that there are 0 to 5 cards, that none of them are null
* and that none of them are jokers.
*/
private void computeRank() {
if (cards.size() == 0) {
rank = 0;
description = longDescription = "Empty Hand";
return;
}
/* Sort the cards by value. Within the same value, sort them by
* suit just to be neat; the suit order has no effect on the rank. */
ArrayList newCards = new ArrayList();
while (cards.size() > 0) {
PokerCard maxCard = cards.get(0);
for (int i = 1; i < cards.size(); i++)
if (cards.get(i).getValue() > maxCard.getValue() ||
cards.get(i).getValue() == maxCard.getValue() && cards.get(i).getSuit() > maxCard.getSuit())
maxCard = cards.get(i);
cards.remove(maxCard);
newCards.add(maxCard);
}
cards = newCards;
/* Cards are now sorted by value. They might have to be rearranged. Use
* a try..finally statement here to add the values of the cards in bit positions
* 0 through 19. That has to be done last, not first, because it has be
* done after the cards have been, possibly, rearranged.
*/
try {
/* Check if the card is a straight and/or flush. A partial hand,
* with fewer than five cards, can never be considered to be a
* straight or a flush.
*/
boolean isFlush = false;
if (cards.size() == 5) {
isFlush = cards.get(0).getSuit() == cards.get(1).getSuit()
&& cards.get(1).getSuit() == cards.get(2).getSuit()
&& cards.get(1).getSuit() == cards.get(3).getSuit()
&& cards.get(1).getSuit() == cards.get(4).getSuit();
}
boolean isStraight = false;
if (cards.size() == 5) {
// Handle the case of a 5-4-3-2-A straight. This hand would currently
// be in the order A-5-4-3-2, but must be rearranged, since the Ace
// counts as 1 in this case.
if (cards.get(0).getValue() == PokerCard.ACE && cards.get(1).getValue() == 5
&& cards.get(2).getValue() == 4 && cards.get(3).getValue() == 3
&& cards.get(4).getValue() == 2 ) {
isStraight = true;
cards.add(cards.remove(0)); // Move the ace to the end, by removing it then adding it.
}
else { // An ordinary straight.
isStraight = cards.get(0).getValue() == cards.get(1).getValue() + 1
&& cards.get(1).getValue() == cards.get(2).getValue() + 1
&& cards.get(2).getValue() == cards.get(3).getValue() + 1
&& cards.get(3).getValue() == cards.get(4).getValue() + 1;
}
}
if (isFlush) {
if (isStraight) {
if (cards.get(0).getValue() == PokerCard.ACE) {
rank = ROYAL_FLUSH;
description = longDescription = "Royal Flush";
}
else {
rank = STRAIGHT_FLUSH;
description = longDescription = valueName(cards.get(0)) + "-high Straight Flush";
}
}
else {
rank = FLUSH;
description = "Flush";
longDescription = "Flush (" + cardValueNames() + ")";
}
return;
}
if (isStraight) {
rank = STRAIGHT;
description = longDescription = valueName(cards.get(0)) + "-high Straight";
return;
}
/* Check for four-of-a-kind, first one in which the four-of-a-kind
* occurs in the first four cards, then in the case where the first
* card is not part of the four-of-a-kind. In the latter case, the
* first card has to be moved to the end.
*/
if (cards.size() >= 4) {
if (cards.get(0).getValue() == cards.get(1).getValue()
&& cards.get(1).getValue() == cards.get(2).getValue()
&& cards.get(2).getValue() == cards.get(3).getValue()) {
rank = FOUR_OF_A_KIND;
description = longDescription = "Four " + pluralValueName(cards.get(0));
if (cards.size() == 5)
longDescription = description + " (plus " + valueName(cards.get(4)) + ")";
return;
}
}
if (cards.size() == 5
&& cards.get(1).getValue() == cards.get(2).getValue()
&& cards.get(2).getValue() == cards.get(3).getValue()
&& cards.get(3).getValue() == cards.get(4).getValue()) {
cards.add(cards.remove(0)); // Move first card -- not part of the Quad -- to the end.
rank = FOUR_OF_A_KIND;
description = "Four " + pluralValueName(cards.get(0));
longDescription = description + " (plus " + valueName(cards.get(4)) + ")";
return;
}
/* Check for triples and pairs. */
int tripleValue = 0; // If greater than 0, then there is a triple with this value.
int tripleLocation = -1; // If tripleValue is greater than 0, this gives the index in cards of the first card in the triple.
for (int i = 0; i <= cards.size() - 3; i++) {
if (cards.get(i).getValue() == cards.get(i+1).getValue()
&& cards.get(i+1).getValue() == cards.get(i+2).getValue()) {
tripleLocation = i;
tripleValue = cards.get(i).getValue();
break;
}
}
int pairValue1 = 0; // If greater than 0, then there is a pair with this value. If two pairs, this is the first (so highest) value.
int pairLoc1 = -1; // If pairValue1 is greater than 0, then this is the index in cards of the first card in the pair.
int pairValue2 = 0; // If greater than 0, there are two pairs and this is the value of the cards in the second pair.
int pairLoc2 = -1; // If pairValue2 is greater than 0, this is the index in cards of the first card in the second pair.
for (int i = 0; i <= cards.size() - 2; i++) {
// Look for a pair at position i. Be careful not to count two cards that
// are part of a triple as being a pair
if (cards.get(i).getValue() == cards.get(i+1).getValue() && cards.get(i).getValue() != tripleValue) {
// Found a pair at position i. Record it and look a second pair later in the hand.
pairValue1 = cards.get(i).getValue();
pairLoc1 = i;
for (int j = i+2; j <= cards.size() -2; j++) {
// Found a second pair.
if (cards.get(j).getValue() == cards.get(j+1).getValue() && cards.get(j).getValue() != tripleValue) {
pairValue2 = cards.get(j).getValue();
pairLoc2 = j;
break;
}
}
break;
}
}
if (tripleValue == 0 && pairValue1 == 0) {
// No triple or pair in the hand. The hand is ranked primarily on its high card.
rank = NOTHING;
description = "High Card (" + valueName(cards.get(0)) + ")";
longDescription = "High Card (" + cardValueNames() + ")";
return;
}
if (tripleValue > 0) {
// There is a triple.
for (int i = 0; i < tripleLocation; i++) {
// Move the cards that precede the triple to the end of the hand.
// by rotating the first card to last position for the number of
// times given by the original location of the triple in the hand.
cards.add(cards.remove(0));
}
if (pairValue1 > 0) {
// There is also a pair, so the hand is a full house. The pair
// has been moved to the end of the hand, if it wasn't there in
// the first place.
rank = FULL_HOUSE;
description = longDescription = "Full House, " + pluralValueName(cards.get(0))
+ " and " + pluralValueName(cards.get(4));
return;
}
else {
rank = TRIPLE;
description = longDescription = "Three " + pluralValueName(cards.get(0));
if (cards.size() == 4)
longDescription = description + " (plus " + valueName(cards.get(3)) + ")";
else if (cards.size() == 5)
longDescription = description + " (plus " + valueName(cards.get(3))
+ " and " + valueName(cards.get(4)) + ")";
return;
}
}
if (pairLoc1 > 0) {
// The first pair that was found is not at the start of the hand,
// so move it there by removing the cards that make up the pair
// from the hand and then adding them back into the hand at
// the start.
PokerCard p2 = cards.remove(pairLoc1+1);
PokerCard p1 = cards.remove(pairLoc1);
cards.add(0,p2);
cards.add(0,p1);
}
if (pairValue2 == 0) { // There was only one pair.
rank = PAIR;
description = longDescription = "Pair of " + pluralValueName(cards.get(0));
if (cards.size() == 5)
longDescription = description + " (plus " + valueName(cards.get(2)) + ","
+ valueName(cards.get(3)) + "," + valueName(cards.get(4)) + ")";
else if (cards.size() == 4)
longDescription = description + " (plus " + valueName(cards.get(2)) + ","
+ valueName(cards.get(3)) + ")";
else if (cards.size() == 3)
longDescription = description + " (plus " + valueName(cards.get(2)) + ")";
return;
}
// If we reach this point, there are two pairs.
if (pairLoc2 > 2) {
// The second pair should at position 2. If not, move the second
// pair to that position by removing the cards that make up the
// pair from the hand and then adding them back at position 2.
PokerCard p2 = cards.remove(pairLoc2+1);
PokerCard p1 = cards.remove(pairLoc2);
cards.add(2,p2);
cards.add(2,p1);
}
rank = TWO_PAIR;
description = longDescription = "Two Pairs, " + pluralValueName(cards.get(0)) + " and "
+ pluralValueName(cards.get(2));
if (cards.size() == 5)
longDescription = description + " (plus " + valueName(cards.get(4)) + ")";
}
finally {
// The finally clause adds the values of the cards to the rank, in
// bit positions 19 down to 0. The current value of rank contains the
// basic hand type in bits 0-4. This value is first moved into
// bits 20-23, then the card values are added. The first card goes in bits
// 19-16, the second in bits 15-12, and so on. (This is true even if there
// are fewer than five cards; in that case, lower order bits will
// be zero.
rank <<= 20;
for (int i = 0; i < cards.size(); i++) {
rank |= cards.get(i).getValue() << 4*(4-i);
}
// For testing, print out the cards, the hand type, the rank, and the descriptions.
// The main() routine in this class is meant for testing, but only works if the
// following lines are uncommented.
/*
for (PokerCard c : cards)
System.out.println(c);
System.out.println("Hand Type: " + (rank >> 20));
System.out.println(description);
System.out.println(longDescription);
System.out.printf("Rank: %X\n\n", rank);
*/
}
}
}