package common.handeval.klaatu; import java.util.HashMap; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; /** * Fast (Finite accepting state transducer) poker hand evaluation methods. * <p> * Each evaluation method takes 5, 6, or 7 cards represented as ints in the range [0..51] and * encoded as described in the {@link #encode(int, int)} method. * The return value of each evaluation method is an {@code int} representing the equivalence class of * the hand, ordered such that better hands have higher equivalence class numbers. * <p> * The evaluation algorithm uses finite state tranducers that are generated from all possible * combinations of the ranks of the cards, using the code in {@code HandFST}. The output of * each tranducer is the equivalence class of the best 5-card poker hand, chosen from the number of * argument cards, ignoring the suits of the cards. The minimized tranducers are compactly * stored as {@code char} (Java's unsigned short type) arrays, one array for cards one through three * and one array for each of the remaining cards. * <p> * Evaluation begins by combining the ranks of the first three cards to form an index to * lookup a value in the first array. For each remaining card, the lookup value obtained from * the last array is added to the rank of the card to obtain an index into the next lookup array. * The values in the final array are the equivalence class values for the hand, assuming that the cards * do not form a flush or straight flush. * <p> * Independently, suit counts of each of the seven cards are tallied. If the cards do not * from a flush or straight flush, then the equivalence class value from the last FST array is returned. * Otherwise, the cards of the flush suit are combined to form an index that is used to lookup an * equivalence class value from a separate flush array. * <p> * The transducer and flush arrays are read from files HandFST<n>.ser which are by default located * in the current directory. If the files do not exist, they are generated on first use. The directory * of the HandFST<n>.ser files may be changed by invoking the method {@code HandFST.setDirectory} * prior to the first reference to the {@code FastEval} class. * <p> * Return values from the different eval<n> methods are not comparable. However, for compatibility, * return values may be passed as arguments to the methods brecher<n> which will convert the * return values into "Steve Brecher" {@code com.stevebrecher.poker.HandEval} format. Brecher-format * values are comparable across different hand lengths and may also be used to determine the type of * hand that the return value represents. * <p> * All methods are thread-safe. * <p> * For better performance when running the Sun JDK Java, use the -server option. * <p> * @author Klaatu */ public final class PartialStageFastEval { // An arbitrary suit designation private static final int CLUBS = 0; private static final int DIAMONDS = 1; private static final int HEARTS = 2; private static final int SPADES = 3; private static final int SUIT_COUNT = 4; private static final int SUIT_MASK = SUIT_COUNT - 1; private static final int LOG2_SUIT_COUNT = 2; private static final int FLUSH_COUNT = 5; // cards needed for a flush // SUIT_INIT value chosen to push made-flush suit counts into next nibble bits private static final int SUIT_INIT = 3; private static final int CLUB_FLUSH = (1 << (CLUBS << LOG2_SUIT_COUNT)) * (SUIT_INIT + FLUSH_COUNT); private static final int DIAMOND_FLUSH = (1 << (DIAMONDS << LOG2_SUIT_COUNT)) * (SUIT_INIT + FLUSH_COUNT); private static final int HEART_FLUSH = (1 << (HEARTS << LOG2_SUIT_COUNT)) * (SUIT_INIT + FLUSH_COUNT); private static final int SPADE_FLUSH = (1 << (SPADES << LOG2_SUIT_COUNT)) * (SUIT_INIT + FLUSH_COUNT); private static final int FLUSH_MASK = CLUB_FLUSH | DIAMOND_FLUSH | HEART_FLUSH | SPADE_FLUSH; private static final int SUIT_INIT_MASK = ((1 << (CLUBS << LOG2_SUIT_COUNT)) * SUIT_INIT) | ((1 << (DIAMONDS << LOG2_SUIT_COUNT)) * SUIT_INIT) | ((1 << (HEARTS << LOG2_SUIT_COUNT)) * SUIT_INIT) | ((1 << (SPADES << LOG2_SUIT_COUNT)) * SUIT_INIT); private static final int RANK_COUNT = 13; // Number of different card ranks // The next power of two above RANK_COUNT -- used when we can't pre-multiply lookup array values // by RANK_COUNT because we'd overflow an unsigned short (or when we choose not to for the first // two cards) private static final int WIDE_RANK = 16; private static final int WIDE_RANK_SHIFT = 4; // log2(WIDE_RANK) private static final int RANK_MASK = ~SUIT_MASK; private static final int ONE_CARD_WIDE_RANK_SHIFT = WIDE_RANK_SHIFT - LOG2_SUIT_COUNT; private int card1, card2, card3, card4, card5, card6; private int sval1, sval2, sval3, sval4, sval5, sval6, sval7; private int rval123, rval4, rval5, rval6; public final void setCard1(int card) { card1 = card; sval1 = SUIT_INIT_MASK + (1 << ((card & SUIT_MASK) << LOG2_SUIT_COUNT)); } public final void setCard2(int card) { card2 = card; sval2 = sval1 + (1 << ((card & SUIT_MASK) << LOG2_SUIT_COUNT)); } public final void setCard3(int card) { card3 = card; sval3 = sval2 + (1 << ((card & SUIT_MASK) << LOG2_SUIT_COUNT)); rval123 = fst7123[((card1 & RANK_MASK) << (ONE_CARD_WIDE_RANK_SHIFT + WIDE_RANK_SHIFT)) + ((card2 >> LOG2_SUIT_COUNT) << WIDE_RANK_SHIFT) + (card3 >> LOG2_SUIT_COUNT)]; } public final void setCard4(int card) { card4 = card; sval4 = sval3 + (1 << ((card & SUIT_MASK) << LOG2_SUIT_COUNT)); rval4 = fst74[rval123 + (card >> LOG2_SUIT_COUNT)]; } public final void setCard5(int card) { card5 = card; sval5 = sval4 + (1 << ((card & SUIT_MASK) << LOG2_SUIT_COUNT)); rval5 = fst75[rval4 + (card >> LOG2_SUIT_COUNT)]; } public final void setCard6(int card) { card6 = card; sval6 = sval5 + (1 << ((card & SUIT_MASK) << LOG2_SUIT_COUNT)); rval6 = fst76[(rval5 * HandFST.OPT_WIDE_RANK_SIZE) + (card >> LOG2_SUIT_COUNT)] * HandFST.OPT_WIDE_RANK_SIZE; } public final int setHand7(int card) { sval7 = sval6 + (1 << ((card & SUIT_MASK) << LOG2_SUIT_COUNT)); if ((sval7 & FLUSH_MASK) == 0) { return fst77[rval6 + (card >> LOG2_SUIT_COUNT)]; } // Find the cards that form the flush int suit = (((sval7 & (CLUB_FLUSH | DIAMOND_FLUSH)) != 0) ? (((sval7 & CLUB_FLUSH) != 0) ? CLUBS : DIAMONDS) : (((sval7 & SPADE_FLUSH) != 0) ? SPADES : HEARTS)); int suitedCards = 0; if ((card1 & SUIT_MASK) == suit) suitedCards |= (1 << (card1 >> LOG2_SUIT_COUNT)); if ((card2 & SUIT_MASK) == suit) suitedCards |= (1 << (card2 >> LOG2_SUIT_COUNT)); if ((card3 & SUIT_MASK) == suit) suitedCards |= (1 << (card3 >> LOG2_SUIT_COUNT)); if ((card4 & SUIT_MASK) == suit) suitedCards |= (1 << (card4 >> LOG2_SUIT_COUNT)); if ((card5 & SUIT_MASK) == suit) suitedCards |= (1 << (card5 >> LOG2_SUIT_COUNT)); if ((card6 & SUIT_MASK) == suit) suitedCards |= (1 << (card6 >> LOG2_SUIT_COUNT)); if ((card & SUIT_MASK) == suit) suitedCards |= (1 << (card >> LOG2_SUIT_COUNT)); return flush7[suitedCards]; } /** * Returns the suit of an encoded card, a value from [0..3], representing an arbitrary card suit. * <p> * The {@code suitOf} method returns the value {@code (card & 3)}. * * @param card an encoded card * @return the suit of the card */ public static int suitOf(int card) { return card & SUIT_MASK; } /** * Returns the rank of an encoded card, a value from [0..12], representing the values [2..A]. * <p> * The {@code rankOf} method returns the value {@code (card >> 2)}. * * @param card an encoded card * @return the rank of the card */ public static int rankOf(int card) { return card >> LOG2_SUIT_COUNT; } /** * Returns an encoded card value suitable for passing as an argument to one of the {@code eval} * methods. * The rank argument is an {@code int} from [0..12] representing the cards [2..A]. * The suit argument is an {@code int} from [0..3], chosen arbitrarily. * The return value is an {@code int} from [0..51]. * <p> * The {@code encode} method returns the value {@code ((rank << 2) | suit)}. * * @param rank the rank of the card, a value from 0 to 12, inclusive * @param suit the suit of the card, a value from 0 to 3, inclusive * @return an encoded card value suitable for passing as an argument to one of * the {@code eval} methods. */ public static int encode(int rank, int suit) { return (rank << LOG2_SUIT_COUNT) | suit; } /** * Returns the equivalence class value of the best five-card high poker hand chosen * from the specified seven cards, ordered such that better hands have higher equivalence * class numbers. * The returned equivalence class value is a number between 0 and 4823, inclusive. * Each card is an {@code int} encoded as described in the {@code encode} method. * Cards may be specified in any order. * <p> * The evaluation algorithm uses a finite state transducer that is generated from all possible * combinations of the ranks of the seven cards. The output of the transducer is a number * representing one of the 3537 possible distinct five-card poker hands chosen from seven cards * that are not flushes or straight flushes. The minimized transducer contains 23890 non-final * states and is stored as five {@code char} arrays, one array for cards one through * three and one array for each of the remaining four cards. * <p> * Evaluation begins by combining the ranks of the first three cards to form an index to * lookup a value in the first array. For each remaining card, the lookup value obtained from * the last array is added to the rank of the card to obtain an index into the next lookup array. * The values in the final array are the equivalence class values for the hand, assuming that * the cards do not form a flush or straight flush. * <p> * Independently, suit counts of each of the seven cards are tallied. If the cards do not * from a flush or straight flush (this is the case 96.94% of the time for randomly chosen hands), * then the equivalence class value from the last FST array is returned. Otherwise, the * cards of the flush suit are combined to form an index that is used to lookup an equivalence class * value from a separate flush array. * <p> * The five transducer arrays and the flush array together total 641350 bytes in memory. * The transducer and flush arrays are read from the file HandFST7.ser which is by default * located in the current directory. If the file does not exist, it is generated on first use. * The directory of HandFST7.ser may be changed by invoking the method {@code HandFST.setDirectory} * prior to the first reference to the {@code FastEval} class. * * @param card1 the first encoded card value * @param card2 the second encoded card value * @param card3 the third encoded card value * @param card4 the fourth encoded card value * @param card5 the fifth encoded card value * @param card6 the sixth encoded card value * @param card7 the seventh encoded card value * @see #encode(int, int) * @return the equivalence class value of the best five-card high poker hand chosen * from the specified seven cards, ordered such that better hands have higher equivalence * class numbers. The returned equivalence class value is a number between 0 and 4823, * inclusive. */ public static final int eval7(int card1, int card2, int card3, int card4, int card5, int card6, int card7) { int sval = SUIT_INIT_MASK + (1 << ((card1 & SUIT_MASK) << LOG2_SUIT_COUNT)) + (1 << ((card2 & SUIT_MASK) << LOG2_SUIT_COUNT)) + (1 << ((card3 & SUIT_MASK) << LOG2_SUIT_COUNT)) + (1 << ((card4 & SUIT_MASK) << LOG2_SUIT_COUNT)) + (1 << ((card5 & SUIT_MASK) << LOG2_SUIT_COUNT)) + (1 << ((card6 & SUIT_MASK) << LOG2_SUIT_COUNT)) + (1 << ((card7 & SUIT_MASK) << LOG2_SUIT_COUNT)); if ((sval & FLUSH_MASK) == 0) { // done with the 96.94% of all hands that aren't flushes or straight flushes // The java -client (default) compiler generates less optimal code if all of the array // accesses are combined into one statement (which is what java -server prefers, on the // other hand). The following is a compromise. int rval = fst7123[((card1 & RANK_MASK) << (ONE_CARD_WIDE_RANK_SHIFT + WIDE_RANK_SHIFT)) + ((card2 >> LOG2_SUIT_COUNT) << WIDE_RANK_SHIFT) + (card3 >> LOG2_SUIT_COUNT)]; rval = fst77[(fst76[(fst75[fst74[rval + (card4 >> LOG2_SUIT_COUNT)] + (card5 >> LOG2_SUIT_COUNT)] * HandFST.OPT_WIDE_RANK_SIZE) + (card6 >> LOG2_SUIT_COUNT)] * HandFST.OPT_WIDE_RANK_SIZE) + (card7 >> LOG2_SUIT_COUNT)]; return rval; } // Find the cards that form the flush int suit = (((sval & (CLUB_FLUSH | DIAMOND_FLUSH)) != 0) ? (((sval & CLUB_FLUSH) != 0) ? CLUBS : DIAMONDS) : (((sval & SPADE_FLUSH) != 0) ? SPADES : HEARTS)); int suitedCards = 0; if ((card1 & SUIT_MASK) == suit) suitedCards |= (1 << (card1 >> LOG2_SUIT_COUNT)); if ((card2 & SUIT_MASK) == suit) suitedCards |= (1 << (card2 >> LOG2_SUIT_COUNT)); if ((card3 & SUIT_MASK) == suit) suitedCards |= (1 << (card3 >> LOG2_SUIT_COUNT)); if ((card4 & SUIT_MASK) == suit) suitedCards |= (1 << (card4 >> LOG2_SUIT_COUNT)); if ((card5 & SUIT_MASK) == suit) suitedCards |= (1 << (card5 >> LOG2_SUIT_COUNT)); if ((card6 & SUIT_MASK) == suit) suitedCards |= (1 << (card6 >> LOG2_SUIT_COUNT)); if ((card7 & SUIT_MASK) == suit) suitedCards |= (1 << (card7 >> LOG2_SUIT_COUNT)); return flush7[suitedCards]; } /** * Returns the equivalence class value of the best five-card high poker hand chosen * from the specified six cards, ordered such that better hands have higher equivalence class * numbers. The returned equivalence class value is a number between 0 and 6074, inclusive. * Each card is an {@code int} encoded as described in the {@code encode} method. * Cards may be specified in any order. * <p> * The evaluation algorithm uses a finite state transducer that is generated from all possible * combinations of the ranks of the six cards. The output of the transducer is a number * representing one of the 4788 possible distinct five-card poker hands chosen from six cards * that are not flushes or straight flushes. The transducer contains 8554 non-final states * and is stored as four {@code char} arrays, one array for cards one through * three and one array for each of the remaining three cards. * <p> * Evaluation begins by combining the ranks of the first three cards to form an index to * lookup a value in the first array. For each remaining card, the lookup value obtained from * the last array is added to the rank of the card to obtain an index into the next lookup array. * The values in the final array are the equivalence class values for the hand, assuming that the * cards do not form a flush or straight flush. * <p> * Independently, suit counts of each of the six cards are tallied. If the cards do not * from a flush or straight flush (this is the case 98.98% of the time for randomly chosen hands), * then the equivalence class value from the last FST array is returned. * Otherwise, the cards of the flush suit are combined to form an index that is used to lookup * an equivalence class value from a separate flush array. * <p> * The four transducer arrays and the flush array together total 242614 bytes in memory, 18486 * bytes of which are shared with the other {@code eval} methods. The transducer and flush arrays * are read from the file HandFST6.ser which is by default located in the current directory. If * the file does not exist, it is generated on first use. The directory of HandFST6.ser may be * changed by invoking the method {@code HandFST.setDirectory} prior to the * first reference to the {@code FastEval} class. * * @param card1 the first encoded card value * @param card2 the second encoded card value * @param card3 the third encoded card value * @param card4 the fourth encoded card value * @param card5 the fifth encoded card value * @param card6 the sixth encoded card value * @see #encode(int, int) * @return the equivalence class value of the best five-card high poker hand chosen * from the six cards, ordered such that better hands have higher equivalence class numbers. * The returned equivalence class value is a number between 0 and 6074, inclusive. */ public static final int eval6(int card1, int card2, int card3, int card4, int card5, int card6) { // One fewer nested access (than in eval7) doesn't cause nearly the same performance issues // for java -client. Go figure. int rval = fst66[(fst65[fst64[fst6123[((card1 & RANK_MASK) << (ONE_CARD_WIDE_RANK_SHIFT + WIDE_RANK_SHIFT)) + ((card2 >> LOG2_SUIT_COUNT) << WIDE_RANK_SHIFT) + (card3 >> LOG2_SUIT_COUNT)] + (card4 >> LOG2_SUIT_COUNT)] + (card5 >> LOG2_SUIT_COUNT)] * HandFST.OPT_WIDE_RANK_SIZE) + (card6 >> LOG2_SUIT_COUNT)]; int sval = SUIT_INIT_MASK + (1 << ((card1 & SUIT_MASK) << LOG2_SUIT_COUNT)) + (1 << ((card2 & SUIT_MASK) << LOG2_SUIT_COUNT)) + (1 << ((card3 & SUIT_MASK) << LOG2_SUIT_COUNT)) + (1 << ((card4 & SUIT_MASK) << LOG2_SUIT_COUNT)) + (1 << ((card5 & SUIT_MASK) << LOG2_SUIT_COUNT)) + (1 << ((card6 & SUIT_MASK) << LOG2_SUIT_COUNT)); if ((sval & FLUSH_MASK) == 0) // done with all hands that aren't flushes or straight flushes return rval; // Find the cards that form the flush int suit = (((sval & (CLUB_FLUSH | DIAMOND_FLUSH)) != 0) ? (((sval & CLUB_FLUSH) != 0) ? CLUBS : DIAMONDS) : (((sval & SPADE_FLUSH) != 0) ? SPADES : HEARTS)); int suitedCards = 0; if ((card1 & SUIT_MASK) == suit) suitedCards |= (1 << (card1 >> LOG2_SUIT_COUNT)); if ((card2 & SUIT_MASK) == suit) suitedCards |= (1 << (card2 >> LOG2_SUIT_COUNT)); if ((card3 & SUIT_MASK) == suit) suitedCards |= (1 << (card3 >> LOG2_SUIT_COUNT)); if ((card4 & SUIT_MASK) == suit) suitedCards |= (1 << (card4 >> LOG2_SUIT_COUNT)); if ((card5 & SUIT_MASK) == suit) suitedCards |= (1 << (card5 >> LOG2_SUIT_COUNT)); if ((card6 & SUIT_MASK) == suit) suitedCards |= (1 << (card6 >> LOG2_SUIT_COUNT)); return flush6[suitedCards]; } /** * Returns the equivalence class value of the best five-card high poker hand from the * specified five cards, ordered such that better hands have higher equivalence class numbers. * The returned equivalence class value is a number between 0 and 7461, inclusive. * Each card is an {@code int} encoded as described in the {@code encode} method. * Cards may be specified in any order. * <p> * The evaluation algorithm uses a finite state transducer that is generated from all possible * combinations of the ranks of the five cards. The output of the transducer is a number * representing one of the 6175 possible distinct five-card poker hands that are not flushes * or straight flushes. The transducer contains 2379 non-final states and is stored as three * {@code char} arrays, one array for cards one through three and one array for each of the * remaining two cards. * <p> * Evaluation begins by combining the ranks of the first three cards to form an index to * lookup a value in the first array. For the remaining two cards, the lookup value obtained from * the last array is added to the rank of the card to obtain an index into the next lookup array. * The values in the final array are the equivalence class values for the hand, assuming that * the cards do not form a flush or straight flush. * <p> * Independently, suit counts of each of the five cards are tallied. If the cards do not * from a flush or straight flush (this is the case 99.8% of the time for randomly chosen hands), * then the equivalence class value from the last FST array is returned. * Otherwise, the cards are combined to form an index that is used to lookup an equivalence * class value from a separate flush array. * <p> * The three transducer arrays and the flush array together total 82064 bytes in memory, 18486 * bytes of which are shared with the other {@code eval} methods. The transducer and flush * arrays are read from the file HandFST5.ser which is by default located in the current directory. * If the file does not exist, it is generated on first use. The directory of HandFST5.ser may * be changed by invoking the method {@code HandFST.setDirectory} prior to the * first reference to the {@code FastEval} class. * * @param card1 the first encoded card value * @param card2 the second encoded card value * @param card3 the third encoded card value * @param card4 the fourth encoded card value * @param card5 the fifth encoded card value * @see #encode(int, int) * @return the equivalence class value of the best five-card high poker hand made from the specified * five cards, ordered such that better hands have higher equivalence class numbers. * The returned equivalence class value is a number between 0 and 7461, inclusive. */ public static final int eval5(int card1, int card2, int card3, int card4, int card5) { int rval = fst55[fst54[fst5123[((card1 & RANK_MASK) << (ONE_CARD_WIDE_RANK_SHIFT + WIDE_RANK_SHIFT)) + ((card2 >> LOG2_SUIT_COUNT) << WIDE_RANK_SHIFT) + (card3 >> LOG2_SUIT_COUNT)] + (card4 >> LOG2_SUIT_COUNT)] + (card5 >> LOG2_SUIT_COUNT)]; int sval = SUIT_INIT_MASK + (1 << ((card1 & SUIT_MASK) << LOG2_SUIT_COUNT)) + (1 << ((card2 & SUIT_MASK) << LOG2_SUIT_COUNT)) + (1 << ((card3 & SUIT_MASK) << LOG2_SUIT_COUNT)) + (1 << ((card4 & SUIT_MASK) << LOG2_SUIT_COUNT)) + (1 << ((card5 & SUIT_MASK) << LOG2_SUIT_COUNT)); if ((sval & FLUSH_MASK) == 0) // done with all hands that aren't flushes or straight flushes return rval; int suitedCards = (1 << (card1 >> LOG2_SUIT_COUNT)) | (1 << (card2 >> LOG2_SUIT_COUNT)) | (1 << (card3 >> LOG2_SUIT_COUNT)) | (1 << (card4 >> LOG2_SUIT_COUNT)) | (1 << (card5 >> LOG2_SUIT_COUNT)); return flush5[suitedCards]; } /** * Returns the {@code com.stevebrecher.poker.HandEval} return value that correspdonds to the * specified {@code eval5} equivalence class. * @param eqvClass an equivalence class value such as returned by {@code eval5}. * @return the equivalent {@code com.stevebrecher.poker.HandEval} return value. */ public static final int toBrecher5(int eqvClass) { return toBrecher5[eqvClass]; } /** * Returns the {@code com.stevebrecher.poker.HandEval} return value that correspdonds to the * specified {@code eval6} equivalence class. * @param eqvClass an equivalence class value such as returned by {@code eval6}. * @return the equivalent {@code com.stevebrecher.poker.HandEval} return value. */ public static final int toBrecher6(int eqvClass) { return toBrecher6[eqvClass]; } /** * Returns the {@code com.stevebrecher.poker.HandEval} return value that correspdonds to the * specified {@code eval7} equivalence class. * @param eqvClass an equivalence class value such as returned by {@code eval7}. * @return the equivalent {@code com.stevebrecher.poker.HandEval} return value. */ public static final int toBrecher7(int eqvClass) { return toBrecher7[eqvClass]; } /** * Returns the {@code eval5} equivalence class value that corresponds to the specified * {@code com.stevebrecher.poker.HandEval} return value. * @param val a {@code com.stevebrecher.poker.HandEval} return value. * @return the corresponding equivalence class value that would be returned by {@code eval5}. */ public static final int fromBrecher5(int val) { return fromBrecher5(new Integer(val)); } /** * Returns the {@code eval5} equivalence class value that corresponds to the specified * {@code com.stevebrecher.poker.HandEval} return value. * @param val a {@code com.stevebrecher.poker.HandEval} return value. * @return the corresponding equivalence class value that would be returned by {@code eval5}. */ public static final int fromBrecher5(Integer val) { return ((Integer) fromBrecher5.get(val)).intValue(); } /** * Returns the {@code eval6} equivalence class value that corresponds to the specified * {@code com.stevebrecher.poker.HandEval} return value. * @param val a {@code com.stevebrecher.poker.HandEval} return value. * @return the corresponding equivalence class value that would be returned by {@code eval6}. */ public static final int fromBrecher6(int val) { return fromBrecher6(new Integer(val)); } /** * Returns the {@code eval6} equivalence class value that corresponds to the specified * {@code com.stevebrecher.poker.HandEval} return value. * @param val a {@code com.stevebrecher.poker.HandEval} return value. * @return the corresponding equivalence class value that would be returned by {@code eval6}. */ public static final int fromBrecher6(Integer val) { return ((Integer) fromBrecher6.get(val)).intValue(); } /** * Returns the {@code eval7} equivalence class value that corresponds to the specified * {@code com.stevebrecher.poker.HandEval} return value. * @param val a {@code com.stevebrecher.poker.HandEval} return value. * @return the corresponding equivalence class value that would be returned by {@code eval7}. */ public static final int fromBrecher7(int val) { return fromBrecher7(new Integer(val)); } /** * Returns the {@code eval7} equivalence class value that corresponds to the specified * {@code com.stevebrecher.poker.HandEval} return value. * @param val a {@code com.stevebrecher.poker.HandEval} return value. * @return the corresponding equivalence class value that would be returned by {@code eval7}. */ public static final int fromBrecher7(Integer val) { return ((Integer) fromBrecher7.get(val)).intValue(); } /* * Initalization */ /* * The FSTs for each 5, 6, or 7-card game are represented as a set of arrays, * one per card (with cards 1 and 2 combined for efficiency purposes). * * MAYBE * The arrays are stored in separate inner classes so that only the arrays * for the games actually being used are loaded into memory * * The six seven-card hand FST arrays total approximately 600000 bytes. * The five six-card hand arrays total approximately ?00000 bytes. * The four five-card hand arrays total approximately ?00000 bytes. * */ // These 'char' arrays don't contain actual characters. The 'char' type // is java's version of an unsigned short, which is what we want to use to // be space-efficient. // Five card hand evalutaion arrays private static final char[] fst5123; // cards 1-3 private static final char[] fst54; private static final char[] fst55; private static final char[] flush5; // Six card hand evalutaion arrays private static final char[] fst6123; // cards 1-3 private static final char[] fst64; private static final char[] fst65; private static final char[] fst66; private static final char[] flush6; // Seven card hand evalutaion arrays private static final char[] fst7123; // cards 1-3 private static final char[] fst74; private static final char[] fst75; private static final char[] fst76; private static final char[] fst77; private static final char[] flush7; /** * An array containing {@code int} values that map {@code eval5} equivalence classes to * "Steve Brecher" {@code com.stevebrecher.poker.HandEval} format hand values */ private static final int[] toBrecher5; /** * A HashMap whose {@code Integer} keys map "Steve Brecher" {@code com.stevebrecher.poker.HandEval} * format hand values to {@code eval5} equivalence classes. */ private static final HashMap fromBrecher5; /** * An array containing {@code int} values that map {@code eval6} equivalence classes to * "Steve Brecher" {@code com.stevebrecher.poker.HandEval} format hand values */ private static final int[] toBrecher6; /** * A HashMap whose {@code Integer} keys map "Steve Brecher" {@code com.stevebrecher.poker.HandEval} * format hand values to {@code eval6} equivalence classes. */ private static final HashMap fromBrecher6; /** * An array containing {@code int} values that map {@code eval7} equivalence classes to * "Steve Brecher" {@code com.stevebrecher.poker.HandEval} format hand values */ private static final int[] toBrecher7; /** * A HashMap whose {@code Integer} keys map "Steve Brecher" {@code com.stevebrecher.poker.HandEval} * format hand values to {@code eval7} equivalence classes. */ private static final HashMap fromBrecher7; // Read in the arrays. Normally, these are read from serialized files // but they will be rebuilt from scratch if the files are not found static { HandFST fst7 = new HandFST(7); fst7123 = fst7.evalCards123(); fst74 = fst7.evalCard4(); fst75 = fst7.evalCard5(); fst76 = fst7.evalCard6(); fst77 = fst7.evalCard7(); flush7 = fst7.evalFlush(); toBrecher7 = fst7.toBrecher(); fromBrecher7 = fst7.fromBrecher(); HandFST fst6 = new HandFST(6); // Share first two arrays fst6123 = fst7.evalCards123(); // equiv to fst6.evalCards123(), so share fst64 = fst7.evalCard4(); // equiv to fst6.evalCard4(), so share fst65 = fst6.evalCard5(); fst66 = fst6.evalCard6(); flush6 = fst6.evalFlush(); toBrecher6 = fst6.toBrecher(); fromBrecher6 = fst6.fromBrecher(); HandFST fst5 = new HandFST(5); // Share first two arrays fst5123 = fst7.evalCards123(); // equiv to fst5.evalCards123(), so share fst54 = fst7.evalCard4(); // equiv to fst5.evalCard4(), so share fst55 = fst5.evalCard5(); flush5 = fst5.evalFlush(); toBrecher5 = fst5.toBrecher(); fromBrecher5 = fst5.fromBrecher(); } /* * Debugging routines (not written to be particularly efficient) * * See Brecher's HandEval.java for a description of the format of the brecher value argument */ static String[] _cardStrings = { "bad1", "bad2", "Deuce", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Jack", "Queen", "King", "Ace" }; /** * A constant (that may be used in a {@code switch} statement) indicating a high-card hand * in the {@code com.stevebrecher.poker.HandEval} return value format. */ public static final int NO_PAIR = 0; // HandEval.HandCategory.NO_PAIR.ordinal(); /** * A constant (that may be used in a {@code switch} statement) indicating a one-pair hand * in the {@code com.stevebrecher.poker.HandEval} return value format. */ public static final int PAIR = 1; // HandEval.HandCategory.PAIR.ordinal(); /** * A constant (that may be used in a {@code switch} statement) indicating a two-pair hand * in the {@code com.stevebrecher.poker.HandEval} return value format. */ public static final int TWO_PAIR = 2; // HandEval.HandCategory.TWO_PAIR.ordinal(); /** * A constant (that may be used in a {@code switch} statement) indicating a trips hand * in the {@code com.stevebrecher.poker.HandEval} return value format. */ public static final int THREE_OF_A_KIND = 3; // HandEval.HandCategory.THREE_OF_A_KIND.ordinal(); /** * A constant (that may be used in a {@code switch} statement) indicating a straight hand * in the {@code com.stevebrecher.poker.HandEval} return value format. */ public static final int STRAIGHT = 4; // HandEval.HandCategory.STRAIGHT.ordinal(); /** * A constant (that may be used in a {@code switch} statement) indicating a flush hand * in the {@code com.stevebrecher.poker.HandEval} return value format. */ public static final int FLUSH = 5; // HandEval.HandCategory.FLUSH.ordinal(); /** * A constant (that may be used in a {@code switch} statement) indicating a full house hand * in the {@code com.stevebrecher.poker.HandEval} return value format. */ public static final int FULL_HOUSE = 6; // HandEval.HandCategory.FULL_HOUSE.ordinal(); /** * A constant (that may be used in a {@code switch} statement) indicating a quads hand * in the {@code com.stevebrecher.poker.HandEval} return value format. */ public static final int FOUR_OF_A_KIND = 7; // HandEval.HandCategory.FOUR_OF_A_KIND.ordinal(); /** * A constant (that may be used in a {@code switch} statement) indicating a straight flush hand * in the {@code com.stevebrecher.poker.HandEval} return value format. */ public static final int STRAIGHT_FLUSH = 8; // HandEval.HandCategory.STRAIGHT_FLUSH.ordinal(); /** * Given a {@code com.stevebrecher.poker.HandEval} return value, return a string that * describes the made hand. * @param brecherValue a {@code com.stevebrecher.poker.HandEval} return value. * @return a string that describes the made hand. */ public static String handString(int brecherValue) { StringBuffer sb = new StringBuffer(); int category = brecherValue >> 24; int topNibble = (brecherValue >> 20) & 0xf; int botNibble = (brecherValue >> 16) & 0xf; int kickers = brecherValue & 0xffff; switch (category) { case PAIR: sb.append("a pair of "); sb.append(plural(botNibble)); sb.append(" with a "); sb.append(_cardStrings[kicker(kickers, 1)]); sb.append(" "); sb.append(_cardStrings[kicker(kickers, 2)]); sb.append(" "); sb.append(_cardStrings[kicker(kickers, 3)]); sb.append(" kicker"); break; case TWO_PAIR: sb.append("two pair, "); sb.append(plural(topNibble)); sb.append(" and "); sb.append(plural(botNibble)); sb.append(" with a "); sb.append(_cardStrings[kicker(kickers, 1)]); sb.append(" kicker"); break; case THREE_OF_A_KIND: sb.append("three of a kind, "); sb.append(plural(botNibble)); sb.append(" with a "); sb.append(_cardStrings[kicker(kickers, 1)]); sb.append(" "); sb.append(_cardStrings[kicker(kickers, 2)]); sb.append(" kicker"); break; case STRAIGHT: sb.append("a straight, "); sb.append(_cardStrings[botNibble]); sb.append(" high "); break; case FLUSH: sb.append("a flush, "); sb.append(_cardStrings[kicker(kickers, 1)]); sb.append(" "); sb.append(_cardStrings[kicker(kickers, 2)]); sb.append(" "); sb.append(_cardStrings[kicker(kickers, 3)]); sb.append(" "); sb.append(_cardStrings[kicker(kickers, 4)]); sb.append(" "); sb.append(_cardStrings[kicker(kickers, 5)]); break; case FULL_HOUSE: sb.append("a full house, "); sb.append(plural(botNibble)); sb.append(" full of "); sb.append(plural(kicker(kickers, 1))); break; case FOUR_OF_A_KIND: sb.append("four of a kind, "); sb.append(plural(botNibble)); sb.append(" with a "); sb.append(_cardStrings[kicker(kickers, 1)]); sb.append(" kicker"); break; case STRAIGHT_FLUSH: sb.append("a straight flush, "); sb.append(_cardStrings[botNibble]); sb.append(" high "); default: sb.append("high card "); sb.append(_cardStrings[kicker(kickers, 1)]); sb.append(" "); sb.append(_cardStrings[kicker(kickers, 2)]); sb.append(" "); sb.append(_cardStrings[kicker(kickers, 3)]); sb.append(" "); sb.append(_cardStrings[kicker(kickers, 4)]); sb.append(" "); sb.append(_cardStrings[kicker(kickers, 5)]); } return sb.toString(); } private static int kicker(int mask, int pos) { int found = 0; int bit = 0x1000; int bnum = 14; while (bit != 0) { if ((bit & mask) != 0 && ++found == pos) return bnum; bnum--; bit >>= 1; } return 0; } private static String plural(int val) { return _cardStrings[val] + (val == 6 ? "es" : "s"); } }