Last active
July 17, 2017 14:24
-
-
Save snarkbait/f6852088447d5d4215ce to your computer and use it in GitHub Desktop.
Poker Hand Scoring example for /r/javaexamples
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package enumexample; | |
import java.util.*; | |
/** | |
* Cards class for playing cards. Uses simple integer-based system | |
* where the card face value is n mod 13 and the suit is n mod 4 | |
* @author /u/Philboyd_Studge on 3/26/2016. | |
*/ | |
public class Cards { | |
private static final int DECK_SIZE = 52; | |
private static final int FACES = 13; | |
private static final String[] FACE_NAMES = { "Ace", "Two", "Three", "Four", | |
"Five", "Six", "Seven", "Eight", | |
"Nine", "Ten", "Jack", "Queen", "King"}; | |
private static final String[] SUIT_NAMES = { "Diamonds", "Clubs", "Hearts", "Spades"}; | |
Random rand = new Random(); | |
private int[] deck; // deck itself | |
private int pointer; // current position in deck | |
/** | |
* Constructor | |
*/ | |
public Cards() { | |
deck = new int[DECK_SIZE]; | |
// initialize deck to values 0 - 51 | |
for (int i = 0; i < DECK_SIZE; i++) { | |
deck[i] = i; | |
} | |
} | |
/** | |
* Get card integer value at position n | |
* @param n position in deck | |
* @return integer card value | |
*/ | |
public int getCard(int n) { | |
return deck[n]; | |
} | |
/** | |
* Fisher-yates shuffle | |
*/ | |
public void shuffle() { | |
pointer = 0; | |
for (int i = DECK_SIZE - 1; i > 0; i--) { | |
int j = rand.nextInt(i); | |
if (j != i) { | |
swap(j, i); | |
} | |
} | |
} | |
private void swap(int first, int second) { | |
int temp = deck[first]; | |
deck[first] = deck[second]; | |
deck[second] = temp; | |
} | |
/** | |
* Get face value 0 - 12 for card | |
* note: off by one compared to actual card face | |
* @param card integer value of card | |
* @return face value | |
*/ | |
public static int getFaceValue(int card) { | |
return card % FACES; | |
} | |
/** | |
* Get suit 0 - 3 for card | |
* '& 3' a bitwise way of saying '% 4' | |
* @param card integer value of card | |
* @return suit | |
*/ | |
public static int getSuit(int card) { | |
return card & 3; | |
} | |
/** | |
* Get string representation of card | |
* @param card integer value of card | |
* @return string of card name | |
*/ | |
public static String getCardName(int card) { | |
return FACE_NAMES[getFaceValue(card)] + " of " + SUIT_NAMES[getSuit(card)]; | |
} | |
/** | |
* Deal n cards from the deck | |
* re-shuffles if not enough cards left for a full hand | |
* @param n number of cards to deal | |
* @return Linked list of integers representing the hand | |
*/ | |
public List<Integer> deal(int n) { | |
if (n < 1) return null; | |
// don't reshuffle in the middle of a hand | |
if (DECK_SIZE - pointer < n) shuffle(); | |
List<Integer> hand = new LinkedList<>(); | |
for (int i = 0; i < n; i++) { | |
hand.add(dealCard()); | |
} | |
return hand; | |
} | |
/** | |
* deal single card and increment pointer | |
* @return | |
*/ | |
private int dealCard() { | |
return deck[pointer++]; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package enumexample; | |
/** | |
* enum for ranking a poker hand | |
* @author /u/Philboyd_Studge on 3/26/2016. | |
*/ | |
public enum PokerHand { | |
HIGH_CARD("High Card"), | |
ONE_PAIR("One Pair"), | |
TWO_PAIR("Two Pairs"), | |
THREE_OF_A_KIND("Three of a Kind"), | |
STRAIGHT("Straight"), | |
ROYAL_STRAIGHT("Royal Straight"), | |
FLUSH("Flush"), | |
FULL_HOUSE("Full House"), | |
FOUR_OF_A_KIND("Four of a Kind"), | |
STRAIGHT_FLUSH("Straight Flush"), | |
ROYAL_FLUSH("Royal Flush"); | |
private String name; | |
PokerHand(String name) { | |
this.name = name; | |
} | |
public String getName() { return name; } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package enumexample; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
/** | |
* @author /u/Philboyd_Studge on 3/27/2016. | |
*/ | |
public class PokerHandTester { | |
public static void main(String[] args) { | |
Cards deck = new Cards(); | |
deck.shuffle(); | |
Map<PokerHand, Integer> frequencyMap = new HashMap<>(); | |
int total = 1000000; | |
for (int i = 0; i < total; i++) { | |
List<Integer> hand = deck.deal(5); | |
PokerHand temp = new ScoreHand(hand).getRank(); | |
frequencyMap.put(temp, frequencyMap.getOrDefault(temp, 0) + 1); | |
} | |
for (PokerHand each : PokerHand.values()) { | |
int count = frequencyMap.getOrDefault(each, 0); | |
System.out.println(each.getName() + " : " + count); | |
System.out.printf("%.4f%%%n", (( count / (double) total) * 100)); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package enumexample; | |
import java.util.Arrays; | |
import java.util.List; | |
/** | |
* Score a hand of poker | |
* for 5 cards, no wild cards. | |
* @author /u/Philboyd_Studge on 3/26/2016. | |
*/ | |
public class ScoreHand { | |
// constants for evaluating pairs | |
public static final int ONE_PAIR = 7; | |
public static final int TWO_PAIR = 9; | |
public static final int THREE_OF_A_KIND = 11; | |
public static final int FULL_HOUSE = 13; | |
public static final int FOUR_OF_A_KIND = 17; | |
List<Integer> hand; // the hand | |
int[] faceFrequency = new int[13]; // frequency table for face values | |
int[] suitFrequency = new int[4]; // frequency table for suits | |
boolean hasAce; // hand contains at least one Ace | |
boolean isRoyal; // hand contains a straight that is 'royal' i.e. A-10-J-Q-K | |
PokerHand rank; // calculated rank of the hand | |
int highCard; // highest card in hand for tiebreakers | |
/** | |
* Constructor | |
* @param hand list of integers in range 0 - 51, must have only 5 elements | |
*/ | |
public ScoreHand(List<Integer> hand) { | |
if (hand.size() != 5) { | |
throw new IllegalArgumentException("Hand incorrect size"); | |
} | |
this.hand = hand; | |
getFrequencies(); | |
rank = PokerHand.HIGH_CARD; | |
findHighCard(); | |
rankHand(); | |
} | |
public PokerHand getRank() { | |
return rank; | |
} | |
public int getHighCard() { | |
return highCard; | |
} | |
private void rankHand() { | |
// find all possibilities of straights first | |
if (isStraight()) { | |
if (isFlush()) { | |
rank = isRoyal ? PokerHand.ROYAL_FLUSH : PokerHand.STRAIGHT_FLUSH; | |
} else { | |
rank = isRoyal ? PokerHand.ROYAL_STRAIGHT : PokerHand.STRAIGHT; | |
} | |
} else { | |
if (isFlush()) rank = PokerHand.FLUSH; | |
} | |
// now find pairs/other multiples | |
int pairs = getPairSum(); | |
switch (pairs) { | |
case ONE_PAIR: | |
if (rank.compareTo(PokerHand.ONE_PAIR) < 0) rank = PokerHand.ONE_PAIR; | |
break; | |
case TWO_PAIR: | |
if (rank.compareTo(PokerHand.TWO_PAIR) < 0) rank = PokerHand.TWO_PAIR; | |
break; | |
case THREE_OF_A_KIND: | |
if (rank.compareTo(PokerHand.THREE_OF_A_KIND) < 0) rank = PokerHand.THREE_OF_A_KIND; | |
break; | |
case FULL_HOUSE: | |
if (rank.compareTo(PokerHand.FULL_HOUSE) < 0) rank = PokerHand.FULL_HOUSE; | |
break; | |
case FOUR_OF_A_KIND: | |
if (rank.compareTo(PokerHand.FOUR_OF_A_KIND) < 0) rank = PokerHand.FOUR_OF_A_KIND; | |
break; | |
default: | |
} | |
} | |
/** | |
* fill frequency tables with hand data | |
*/ | |
private void getFrequencies() { | |
for (int each : hand) { | |
// kill three birds with one for-loop | |
if (Cards.getFaceValue(each) == 0) hasAce = true; | |
faceFrequency[Cards.getFaceValue(each)]++; | |
suitFrequency[Cards.getSuit(each)]++; | |
} | |
} | |
/** | |
* use frequency of suits table to find a flush | |
* @return true if all cards the same suit | |
*/ | |
private boolean isFlush() { | |
for (int each : suitFrequency) { | |
if (each == 5) return true; | |
} | |
return false; | |
} | |
/** | |
* get a sorted int array for use in finding straights | |
* @return sorted array of card face values | |
*/ | |
private int[] getSortedArray() { | |
int[] sorted = new int[5]; | |
int i = 0; | |
for (int each : hand) { | |
sorted[i++] = Cards.getFaceValue(each); | |
} | |
Arrays.sort(sorted); | |
return sorted; | |
} | |
/** | |
* check for straights | |
* @return true if a 5-card run exists | |
*/ | |
private boolean isStraight() { | |
int[] sorted = getSortedArray(); | |
// ugly but why not | |
if (hasAce && sorted[1] > 1) { | |
if (sorted[1]==9 && sorted[2]==10 && | |
sorted[3]==11 && sorted[4]== 12) { | |
isRoyal = true; | |
return true; | |
} | |
} else { | |
for (int i = 1; i < 5; i++) { | |
if (sorted[i] - sorted[i - 1] > 1) return false; | |
} | |
return true; | |
} | |
return false; | |
} | |
/** | |
* use the face value frequency table to get a | |
* unique number for the different possibilities | |
* @return pair sum number that will correspond to the constants above | |
*/ | |
private int getPairSum() { | |
int sum = 0; | |
for (int each : faceFrequency) { | |
sum += each * each; | |
} | |
return sum; | |
} | |
/** | |
* find and set the high card for tiebreaker purposes | |
*/ | |
private void findHighCard() { | |
if (hasAce) { | |
for (int each : hand) { | |
if (Cards.getFaceValue(each) == 0) highCard = each; | |
} | |
} else { | |
int max = -1; | |
for (int each : hand) { | |
if (Cards.getFaceValue(each) > max) { | |
max = Cards.getFaceValue(each); | |
highCard = each; | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment