I am developing a poker game as college project and our current assignment is to write an algorithm to score a hand of 5 cards, so that the scores of two hands can be compared to each other to determine which is the better hand. The score of a hand has nothing to do with the probability of what hands could be made upon the draw being dealt with random cards, etc. - The score of a hand is based solely on the 5 cards in the hand, and no other cards in the deck.
The example solution we were given was to give a default score for each type of Poker hand, with the score reflecting how good the hand is - like this for instance:
//HAND TYPES:
ROYAL_FLUSH = 900000
STRAIGHT_FLUSH = 800000
...
TWO_PAIR = 200000
ONE_PAR = 100000
Then if two hands of the same type are compared, the values of the cards in the hands should be factored into the hand's score.
So for example, the following formula could be used to score a hand:
HAND_TYPE + (each card value in the hand)^(the number of occurences of that value)
So, for a Full House of three Queens and two 7s, the score would be:
600000 + 12^3 + 7^2
This formula works for the most part, but I have determined that in some instances, two similar hands can return the exact same score, when one should actually beat the other. An example of this is:
hand1 = 4C, 6C, 6H, JS, KC
hand2 = 3H, 4H, 7C, 7D, 8H
These two hands both have one pair, so their respective scores are:
100000 + 4^1 + 6^2 + 11^1 + 13^1 = 100064
100000 + 3^1 + 4^1 + 7^2 + 8^1 = 100064
This results in a draw, when clearly a pair of 7s trumps a pair of 6s.
How can I improve this formula, or even, what is a better formula I can use?
By the way, in my code, hands are stored in an array of each card's value in ascending order, for example:
[2H, 6D, 10C, KS, AS]
EDIT:
Here is my final solution thanks to the answers below:
/**
* Sorts cards by putting the "most important" cards first, and the rest in decreasing order.
* e.g. High Hand: KS, 9S, 8C, 4D, 2H
* One Pair: 3S, 3D, AH, 7S, 2C
* Full House: 6D, 6C, 6S, JC, JH
* Flush: KH, 9H, 7H, 6H, 3H
*/
private void sort() {
Arrays.sort(hand, Collections.reverseOrder()); // Initially sorts cards in descending order of game value
if (isFourOfAKind()) { // Then adjusts for hands where the "most important" cards
sortFourOfAKind(); // must come first
} else if (isFullHouse()) {
sortFullHouse();
} else if (isThreeOfAKind()) {
sortThreeOfAKind();
} else if (isTwoPair()) {
sortTwoPair();
} else if (isOnePair()){
sortOnePair();
}
}
private void sortFourOfAKind() {
if (hand[0].getGameValue() != hand[HAND_SIZE - 4].getGameValue()) { // If the four of a kind are the last four cards
swapCardsByIndex(0, HAND_SIZE - 1); // swap the first and last cards
} // e.g. AS, 9D, 9H, 9S, 9C => 9C, 9D, 9H, 9S, AS
}
private void sortFullHouse() {
if (hand[0].getGameValue() != hand[HAND_SIZE - 3].getGameValue()) { // If the 3 of a kind cards are the last three
swapCardsByIndex(0, HAND_SIZE - 2); // swap cards 1 and 4, 2 and 5
swapCardsByIndex(HAND_SIZE - 4, HAND_SIZE - 1); // e.g. 10D, 10C, 6H, 6S, 6D => 6S, 6D, 6H, 10D, 10C
}
}
private void sortThreeOfAKind() { // If the 3 of a kind cards are the middle 3 cards
if (hand[0].getGameValue() != hand[HAND_SIZE - 3].getGameValue() && hand[HAND_SIZE - 1].getGameValue() != hand[HAND_SIZE - 3].getGameValue()) { // swap cards 1 and 4
swapCardsByIndex(0, HAND_SIZE - 2); // e.g. AH, 8D, 8S, 8C, 7D => 8C, 8D, 8S, AH, 7D
} else if (hand[0].getGameValue() != hand[HAND_SIZE - 3].getGameValue() && hand[HAND_SIZE - 4].getGameValue() != hand[HAND_SIZE - 3].getGameValue()) {
Arrays.sort(hand); // If the 3 of a kind cards are the last 3,
swapCardsByIndex(HAND_SIZE - 1, HAND_SIZE - 2); // reverse the order (smallest game value to largest)
} // then swap the last two cards (maintain the large to small ordering)
} // e.g. KS, 9D, 3C, 3S, 3H => 3H, 3S, 3C, 9D, KS => 3H, 3S, 3C, KS, 9D
private void sortTwoPair() {
if (hand[0].getGameValue() != hand[HAND_SIZE - 4].getGameValue()) { // If the two pairs are the last 4 cards
for (int i = 0; i < HAND_SIZE - 1; i++) { // "bubble" the first card to the end
swapCardsByIndex(i, i + 1); // e.g. AH, 7D, 7S, 6H, 6C => 7D, 7S, 6H, 6C, AH
}
} else if (hand[0].getGameValue() == hand[HAND_SIZE - 4].getGameValue() && hand[HAND_SIZE - 2].getGameValue() == hand[HAND_SIZE - 1].getGameValue()) { // If the two pairs are the first and last two cards
swapCardsByIndex(HAND_SIZE - 3, HAND_SIZE - 1); // swap the middle and last card
} // e.g. JS, JC, 8D, 4H, 4S => JS, JC, 4S, 4H, 8D
}
private void sortOnePair() { // If the pair are cards 2 and 3, swap cards 1 and 3
if (hand[HAND_SIZE - 4].getGameValue() == hand[HAND_SIZE - 3].getGameValue()) { // e.g QD, 8H, 8C, 6S, 4J => 8C, 8H, QD, 6S, 4J
swapCardsByIndex(0, HAND_SIZE - 3);
} else if (hand[HAND_SIZE - 3].getGameValue() == hand[HAND_SIZE - 2].getGameValue()) { // If the pair are cards 3 and 4, swap 1 and 3, 2 and 4
swapCardsByIndex(0, HAND_SIZE - 3); // e.g. 10S, 8D, 4C, 4H, 2H => 4C, 4H, 10S, 8D, 2H
swapCardsByIndex(HAND_SIZE - 4, HAND_SIZE - 2);
} else if (hand[HAND_SIZE - 2].getGameValue() == hand[HAND_SIZE - 1].getGameValue()) { // If the pair are the last 2 cards, reverse the order
Arrays.sort(hand); // and then swap cards 3 and 5
swapCardsByIndex(HAND_SIZE - 3, HAND_SIZE - 1); // e.g. 9H, 7D, 6C, 3D, 3S => 3S, 3D, 6C, 7D, 9H => 3S, 3D, 9H, 7D, 6C
}
}
/**
* Swaps the two cards of the hand at the indexes taken as parameters
* @param index1
* @param index2
*/
private void swapCardsByIndex(int index1, int index2) {
PlayingCard temp = hand[index1];
hand[index1] = hand[index2];
hand[index2] = temp;
}
/**
* Gives a unique value of any hand, based firstly on the type of hand, and then on the cards it contains
* @return The Game Value of this hand
*
* Firstly, a 24 bit binary string is created where the most significant 4 bits represent the value of the type of hand
* (defined as constants private to this class), the last 20 bits represent the values of the 5 cards in the hand, where
* the "most important" cards are at greater significant places. Finally, the binary string is converter to an integer.
*/
public int getGameValue() {
String handValue = addPaddingToBinaryString(Integer.toBinaryString(getHandValue()));
for (int i = 0; i < HAND_SIZE; i++) {
handValue += addPaddingToBinaryString(Integer.toBinaryString(getCardValue(hand[i])));
}
return Integer.parseInt(handValue, 2);
}
/**
* @param binary
* @return the same binary string padded to 4 bits long
*/
private String addPaddingToBinaryString(String binary) {
switch (binary.length()) {
case 1: return "000" + binary;
case 2: return "00" + binary;
case 3: return "0" + binary;
default: return binary;
}
}
/**
* @return Default value for the type of hand
*/
private int getHandValue() {
if (isRoyalFlush()) { return ROYAL_FLUSH_VALUE; }
if (isStraightFlush()) { return STRAIGHT_FLUSH_VALUE; }
if (isFourOfAKind()) { return FOUR_OF_A_KIND_VALUE; }
if (isFullHouse()) { return FULL_HOUSE_VALUE; }
if (isFlush()) { return FLUSH_VALUE; }
if (isStraight()) { return STRAIGHT_VALUE; }
if (isThreeOfAKind()) { return THREE_OF_A_KIND_VALUE; }
if (isTwoPair()) { return TWO_PAIR_VALUE; }
if (isOnePair()) { return ONE_PAIR_VALUE; }
return 0;
}
/**
* @param card
* @return the value for a given card type, used to calculate the Hand's Game Value
* 2H = 0, 3D = 1, 4S = 2, ... , KC = 11, AH = 12
*/
private int getCardValue(PlayingCard card) {
return card.getGameValue() - 2;
}