package common.handeval.klaatu; import java.io.*; import java.util.*; import common.handeval.stevebrecher.HandEval; // We use Steve Brecher's HandEval during FST generation, although any correct hand evaluator would work as well /** * FST (Finite State Transducer) generator helper class for the {@code FSTEval} poker hand evaluation methods. * <p> * This class generates the finite state transducer arrays used by {@code FSTEval} to evaluate poker hands. * The evaulation arrays and associated utility map data are serialized and stored in files. When a request * is made for the arrays, an attempt is made to read them from the serialized files. If the files * are not found, the arrays are regenerated. * <p> * Input to the FST are the poker cards that form the hand. In order to keep the FST at a manageable * size, only the ranks of the cards are considered. The initial state of the FST represents a zero-card hand. * Each dealt card causes a transition to another valid FST state (there are no invalid inputs in a poker * hand). Because card order does not affect the value of the hand, states with different card permutations * are coalesced. States with identical transistions for every input card (e.g. the states for the six-card * hands [2 2 2 3 5 5] and [2 2 2 4 5 5] in a seven-card game) are also coalesced. The final FST states are the * equivalence class numbers of the best 5-card high poker hand that can be made from the input cards * (5, 6, and 7-card games are supported). * <p> * The FST states are compactly written as {@code char} arrays (Java's {@code unsigned short} type). For an n-card * game, Each array entry in the first through n-1 arrays, when added to the next card rank value, gives the * index of the entry into the next-level array. The entries in the last array are the * equivalence class values ([0..4823] for a 7-card game, [0..6074] for a 6-card game and [0..7461] for a * 5-card game). * <p> * A separate flush array is also generated. For each encoded {@code int} bit vector array index of * the 5-n suited cards of the hand, the array entry at that index is the equivalence class value of the * flush or straight flush hand. * <p> * As an optimization, a separate array is written that combines the rank values of the first three * cards. That is, for each index value {@code ((((rankOf(card1)) << 8) | (rankOf(card2) << 4)) | rankOf(card3))}, * the array entry at that index contains a value that may be used to index into the fourth-card array. * <p> * For seven card hands, the five evaluation arrays (four single card arrays and the first three-card array) * together with the flush array total 641350 bytes in memory. * <p> * For six card hands, the five evaluation arrays (four single card arrays and the first three-card array) * together with the flush array total 242614 bytes in memory. * <p> * For five card hands, the five evaluation arrays (four single card arrays and the first three-card array) * together with the flush array total 82064 bytes in memory. * <p> * The generation routines use the {@code com.stevebrecher.poker.HandEval} evaulation routines * to generate the equivalence class data, although any correct hand evaluator would suffice. * An array and HashMap are also generated that convert equivalence class numbers to * {@code HandEval} return values and vice versa. * <p> * After being generated, the arrays and map data are serialized and written to the files HandFST<n>.ser * which are by default located in the current directory. The directory of the HandFST<n>.ser files * may be changed by invoking the method {@code setDirectory} prior to being used. * <p> * @author Klaatu */ public class HandFST { private static final int RANK_SIZE = 13; private static final int SUIT_COUNT = 4; private static final int WIDE_RANK_SIZE = 16; // next power of 2 above RANK_SIZE /** * Controls whether "wide arrays" are used when the lookup value overflows Character.MAX_VALUE. * When this overflow occurs the lookup value cannot be pre-multiplied by the rank count (13). * If OPT_WIDE_RANK_SIZE is set to the next power of two (16), these array sizes * are rounded up so that the eval routines can use shift instructions instead of multiplications. * In the 7-card FST, this adds an additional ~125K of memory to the arrays but allows the eval7 routine * to replace two multiplication instructions with shift instructions. * <p> * Using wide arrays speeds up hand enumerations by around 5%, but slows down random evaluations * due to the extra memory required. It's a close call, but they're turned off by default for now. * If you would like to turn them on, set the value of OPT_WIDE_RANK_SIZE to be WIDE_RANK_SIZE, * and recompile {@code FSTEval.java} and {@code HandFST.java}. * <p> * We continue to use a "wide" index for the (small) card123 jump start array, however, * since the additional memory cost is negligible. */ public static final int OPT_WIDE_RANK_SIZE = /* WIDE_RANK_SIZE */RANK_SIZE; private static String _cardValues = "23456789TJQKA"; // for debugging private static final boolean DEBUG = false; // the default directory for the serialized arrays private static String _directory = ""; private int _handLength; private int _numStates = 0; private String _filename; // states of the transducer private Vector<HandState> _allStatesVec = new Vector<HandState>(); private HandState[] _allStates; private HashMap<HandState, HandState> _stateSet = new HashMap<HandState, HandState>(); private int[] _equivalentStates; // set by minimize(), used to coalesce equivalent states // states, partitioned by "level" (i.e., card count) private HandState[][] _levelStates; // the exported arrays and map private char[][] _levelArrays; private char[] _jumpStartArray; private char[] _wide6Array; private char[] _wide7Array; private char[] _flushArray; private int[] _toBrecherArray; private HashMap<Integer, Integer> _fromBrecherMap; // Package-visible constructors HandFST(int handLength, String filename) { if (handLength < 5) throw new UnsupportedOperationException("Too few cards for a poker hand: " + handLength); if (handLength > 7) throw new UnsupportedOperationException("Unsupported hand size: " + handLength); _handLength = handLength; _filename = filename; } HandFST(int handLength) { this(handLength, _directory + "HandFST" + handLength + ".ser"); } HandFST(String filename) { this(7, filename); } HandFST() { this(7); } /** * Sets the current default directory in which to store serialized FST data. The * default directory is used by the {@code HandFST} constructors that do not take * a filename argument. * @param dir the current default directory in which to store serialized FST data. */ public static void setDirectory(String dir) { if (dir == null) dir = ""; if (!dir.equals("") && !dir.endsWith(File.separator)) dir = dir + File.separator; _directory = dir; } /** * Returns the current default directory in which to store serialized FST data. * @return the current default directory in which to store serialized FST data. */ public static String directory() { return _directory; } // Package-visible methods to return the FST evaluation and utility arrays char[] evalCard1() { ensureArrays(1); return _levelArrays[0]; } char[] evalCard2() { ensureArrays(2); return _levelArrays[1]; } char[] evalCard3() { ensureArrays(3); return _levelArrays[2]; } char[] evalCard4() { ensureArrays(4); return _levelArrays[3]; } char[] evalCard5() { ensureArrays(5); return _levelArrays[4]; } char[] evalCard6() { ensureArrays(6); if (OPT_WIDE_RANK_SIZE == WIDE_RANK_SIZE) return _wide6Array; else return _levelArrays[5]; } char[] evalCard7() { ensureArrays(7); if (OPT_WIDE_RANK_SIZE == WIDE_RANK_SIZE) return _wide7Array; else return _levelArrays[6]; } char[] evalFlush() { ensureArrays(0); return _flushArray; } int[] toBrecher() { ensureArrays(0); return _toBrecherArray; } HashMap fromBrecher() { ensureArrays(0); return _fromBrecherMap; } char[] evalCards123() { ensureArrays(3); return _jumpStartArray; } // Have the exported arrays been initialized private boolean haveArrays() { return _jumpStartArray != null && _levelArrays != null && _flushArray != null && _toBrecherArray != null && _toBrecherArray != null && _fromBrecherMap != null; } // Ensure that the exported arrays been initialized, else try to rebuild them, // else throw an exception private void ensureArrays(int level) { if (level > _handLength) throw new IllegalStateException("Illegal to request card " + level + " arrays from an " + _handLength + " FST"); try { if (!haveArrays()) readArrays(); } catch (FileNotFoundException e) { System.out.println("FST array file " + _filename + " not found, rebuilding..."); } catch (IOException e) { System.out.println("IO Exception + e + reading FST array file " + _filename + ", rebuilding..."); } catch (ClassNotFoundException e) { // not really possible } catch (IllegalStateException e) { System.out.println("FST array file " + _filename + " built with different OPT_WIDE_RANK_SIZE, rebuilding..."); } try { if (!haveArrays()) rebuild(true); } catch (Exception e) { System.out.println("Unexpected exception " + e); e.printStackTrace(); } if (!haveArrays()) throw new IllegalStateException("Unable to find or rebuild FST arrays for " + _handLength + "-card hands"); } /* * This class represents a state in the FST that occurs after the array of _cards * (actually just card ranks) has been dealt. */ private abstract class HandState implements Serializable { int[] _cards; // ranks only int _index; // into _allStates array char _levelIndex; // into _levelArrays[n] abstract boolean isFinal(); abstract char value(); // the generated array value public String toString() { StringBuffer sb = new StringBuffer(); sb.append("["); for (int i = 0; i < _cards.length; i++) { int card = _cards[i]; sb.append(_cardValues.charAt(card)); if (i != _cards.length - 1) sb.append(" "); } sb.append("]"); return sb.toString(); } boolean legalTransistion(int card) { int rankCount = 0; for (int i = 0; i < _cards.length; i++) { if (_cards[i] == card) { rankCount++; } } return rankCount != SUIT_COUNT; } void init(HandState prev, int card) { int prevlen = prev._cards.length; _cards = new int[prevlen + 1]; System.arraycopy(prev._cards, 0, _cards, 0, prevlen); _cards[prevlen] = card; Arrays.sort(_cards); } } /* * This class represents a final FST state for a made hand. Its array * value is the hand equivalence class. */ private class FinalHandState extends HandState { int _value = -1; char _eqvClass = (char) -1; FinalHandState(int[] cards) { _cards = cards.clone(); } private FinalHandState(HandState prev, int card) { init(prev, card); } boolean isFinal() { return true; } char value() { return _eqvClass; } public boolean equals(Object o) { if (!(o instanceof FinalHandState)) return false; FinalHandState other = (FinalHandState) o; return _value == other._value; } public int hashCode() { return _value; } } /* * This class represents a final FST state for a made hand. Its array * value is the index of the state in the corresponding level array * (i.e. the same-number-of-cards state array). */ private class NonFinalHandState extends HandState { HandState[] _transistions; private NonFinalHandState(int[] cards) { _cards = cards.clone(); } private NonFinalHandState(NonFinalHandState prev, int card) { init(prev, card); } private NonFinalHandState() { _cards = new int[0]; _index = _allStatesVec.size(); _transistions = new HandState[RANK_SIZE]; _allStatesVec.add(this); } boolean isFinal() { return false; } char value() { return _levelIndex; } public boolean equals(Object o) { if (!(o instanceof NonFinalHandState)) return false; NonFinalHandState other = (NonFinalHandState) o; return Arrays.equals(_cards, other._cards); } public int hashCode() { return Arrays.hashCode(_cards); } } // Intern a non final state that occurs after the transisition of card from // the state prev private NonFinalHandState internNonFinalState(NonFinalHandState prev, int card) { NonFinalHandState testState = new NonFinalHandState(prev, card); NonFinalHandState currState = (NonFinalHandState) _stateSet.get(testState); if (currState == null) { testState._transistions = new HandState[RANK_SIZE]; testState._index = _allStatesVec.size(); _allStatesVec.add(testState); _stateSet.put(testState, testState); currState = testState; } prev._transistions[card] = currState; return currState; } // Intern a final state that occurs after the transisition of card from // the state prev private FinalHandState internFinalState(NonFinalHandState prev, int card) { FinalHandState testState = new FinalHandState(prev, card); testState._value = makeHand(testState._cards); // after cards are sorted FinalHandState currState = internFinalState(testState); prev._transistions[card] = currState; return currState; } // Given an array of card ranks, return an int representing the evaluation // of that hand, assuming no flushes occur. We use Steve Brecher's HandEval, // although any correct hand evaluator would do. // Requires that ranks are sorted (in order to avoid spurious flushes) private static int makeHand(int[] ranks) { long bval = bval(ranks); return ranks.length == 7 ? HandEval.hand7Eval(bval) : ranks.length == 6 ? HandEval.hand6Eval(bval) : HandEval.hand5Eval(bval); } // Return a HandEval-encoded int for the specified card ranks. Choose // suits in a way to avoid flushes. private static long bval(int[] ranks) { int suit = 0; // avoid spurious flushes long bval = 0L; for (int i = 0; i < ranks.length; i++) bval |= 0x1L << (++suit & 3) * RANK_SIZE + ranks[i]; return bval; } // Intern a final state with the same cards as testState. private FinalHandState internFinalState(FinalHandState testState) { FinalHandState currState = (FinalHandState) _stateSet.get(testState); if (currState == null) { testState._index = _allStatesVec.size(); _allStatesVec.add(testState); _stateSet.put(testState, testState); currState = testState; } return currState; } // Intern a final flush state with the given cards (card ranks) private FinalHandState makeFlush(int[] cards) { FinalHandState testState = new FinalHandState(cards); testState._value = makeFlushHand(testState._cards); return internFinalState(testState); } // Given an array of card ranks, return an int representing the evaluation // of that hand, assuming that all cards are in the same suit. We use Steve // Brecher's HandEval to do the evaluation. private static int makeFlushHand(int[] ranks) { long bfval = bfval(ranks); return ranks.length == 7 ? HandEval.hand7Eval(bfval) : ranks.length == 6 ? HandEval.hand6Eval(bfval) : HandEval.hand5Eval(bfval); } // Return a HandEval-encoded int for the specified card ranks, assuming that // all cards are in the same suit. private static long bfval(int[] ranks) { long bfval = 0L; for (int i = 0; i < ranks.length; i++) bfval |= 0x1L << ranks[i]; return bfval; } // Rebuild the FST as needed (when the file containing the FST array data is not found) private void rebuild(boolean recalculate) { if (recalculate) calculate(); minimize(); buildLevels(); makeEqvClasses(); buildLevelArrays(); makeFlushArray(); makeToFrom(); try { writeArrays(); } catch (IOException e) { System.out.println("Unable to write FST to " + _filename + ": " + e + ", will rebuild on next use"); } catch (ClassNotFoundException e) { // not really possible } } // Calculate the states of the FST private void calculate() { long start, stop; start = System.currentTimeMillis(); if (_handLength == 7) System.out.println("Calculating 7-card FST (this is done one time only and will take a minute or so) ..."); else System.out.print("Calculating " + _handLength + "-card FST..."); makeRankStates(); makeFlushStates(); _allStates = new HandState[_allStatesVec.size()]; _allStatesVec.toArray(_allStates); _allStatesVec = null; // help GC stop = System.currentTimeMillis(); if (_handLength == 7) System.out.println("7-card FST calculated in " + (stop - start) + " ms"); else System.out.println("done"); } // Make the states of the FST for all the possible card rank values of the hand private void makeRankStates() { NonFinalHandState initialState = new NonFinalHandState(); for (int cn0 = 0; cn0 < RANK_SIZE; cn0++) { NonFinalHandState state0 = internNonFinalState(initialState, cn0); if (_handLength == 7 && cn0 > 0 && ((cn0 * 10) / RANK_SIZE > ((cn0 - 1) * 10) / RANK_SIZE)) System.out.print((cn0 * 10 / RANK_SIZE) * 10 + "% "); for (int cn1 = 0; cn1 < RANK_SIZE; cn1++) { NonFinalHandState state1 = internNonFinalState(state0, cn1); for (int cn2 = 0; cn2 < RANK_SIZE; cn2++) { NonFinalHandState state2 = internNonFinalState(state1, cn2); for (int cn3 = 0; cn3 < RANK_SIZE; cn3++) { NonFinalHandState state3 = internNonFinalState(state2, cn3); for (int cn4 = 0; cn4 < RANK_SIZE; cn4++) { if (state3.legalTransistion(cn4)) { if (_handLength == 5) { internFinalState(state3, cn4); } else { NonFinalHandState state4 = internNonFinalState(state3, cn4); for (int cn5 = 0; cn5 < RANK_SIZE; cn5++) { if (state4.legalTransistion(cn5)) { if (_handLength == 6) { internFinalState(state4, cn5); } else { NonFinalHandState state5 = internNonFinalState(state4, cn5); for (int cn6 = 0; cn6 < RANK_SIZE; cn6++) { if (state5.legalTransistion(cn6)) { internFinalState(state5, cn6); } } } } } } } } } } } } } // Make the final flush states. These aren't part of the FST (there are no transistions // to them) but we add them to the final state array anyway for convenience private void makeFlushStates() { for (int cn0 = 0; cn0 < RANK_SIZE; cn0++) { for (int cn1 = cn0 + 1; cn1 < RANK_SIZE; cn1++) { for (int cn2 = cn1 + 1; cn2 < RANK_SIZE; cn2++) { for (int cn3 = cn2 + 1; cn3 < RANK_SIZE; cn3++) { for (int cn4 = cn3 + 1; cn4 < RANK_SIZE; cn4++) { int[] cards5 = { cn0, cn1, cn2, cn3, cn4 }; makeFlush(cards5); if (_handLength > 5) { for (int cn5 = cn4 + 1; cn5 < RANK_SIZE; cn5++) { int[] cards6 = { cn0, cn1, cn2, cn3, cn4, cn5 }; makeFlush(cards6); if (_handLength > 6) { for (int cn6 = cn4 + 1; cn6 < RANK_SIZE; cn6++) { int[] cards7 = { cn0, cn1, cn2, cn3, cn4, cn5, cn6 }; makeFlush(cards7); } } } } } } } } } } // Minimize the FST by coalescing states with identical transistions. private void minimize() { long start, stop; int numStates = _allStates.length; if (DEBUG) { start = System.currentTimeMillis(); System.out.println("Minimizing FST ..."); } _equivalentStates = new int[_allStates.length]; Arrays.fill(_equivalentStates, -1); ArrayList<LinkedList<HandState>> partitions = new ArrayList<LinkedList<HandState>>(numStates); for (int i = 0; i < numStates; i++) partitions.add(i, new LinkedList<HandState>()); int deletedCount = 0; int partitionCount = 1; int[] partIndexes = new int[numStates + 1]; for (int i = 0; i < numStates; i++) { // Place each final state in its own partition, since we want more than a yes/no answer partIndexes[i] = (_allStates[i].isFinal() ? partitionCount++ : 0); partitions.get(partIndexes[i]).addFirst(_allStates[i]); } int[] nextPartIndexes = new int[numStates + 1]; Arrays.fill(nextPartIndexes, -1); Stack<Integer> reverse = new Stack<Integer>(); int[] nextIndexes = new int[partitions.get(0).size()]; boolean didSomething = true; while (didSomething) { didSomething = false; for (int pn = 0; pn < partitionCount; pn++) { if (partitions.get(pn).size() > 1) { for (int tn = 0; tn < RANK_SIZE; tn++) { if (partitions.get(pn).size() > 1) { int bin = 0; for (Iterator it = partitions.get(pn).iterator(); it.hasNext();) { HandState state = (HandState) it.next(); int nxtIdx = numStates; if (!state.isFinal()) { NonFinalHandState nfhs = (NonFinalHandState) state; HandState[] trans = nfhs._transistions; if (trans != null) { HandState nhs = trans[tn]; if (nhs != null) nxtIdx = nhs._index; } } int iNext = partIndexes[nxtIdx]; if (nextPartIndexes[iNext] == -1) { reverse.push(iNext); nextPartIndexes[iNext] = bin; nextIndexes[bin] = (bin == 0 ? pn : partitionCount++); bin++; } if (nextPartIndexes[iNext] > 0) { it.remove(); int npn = nextIndexes[nextPartIndexes[iNext]]; partitions.get(npn).addFirst(state); didSomething = true; } } while (--bin >= 0) { if (bin > 0) { for (Iterator it = partitions.get(nextIndexes[bin]).iterator(); it.hasNext();) partIndexes[((HandState) it.next())._index] = nextIndexes[bin]; } nextPartIndexes[((Integer) reverse.pop()).intValue()] = -1; } } } } } } // Coalesce states by setting an index in _equivalentStates for (int bn = 0; bn < partitionCount; bn++) { if (partitions.get(bn).size() == 0) continue; Iterator it = partitions.get(bn).iterator(); int eqvnum = ((HandState) it.next())._index; _equivalentStates[eqvnum] = eqvnum; while (it.hasNext()) { int stateNum = ((HandState) it.next())._index; if (stateNum == numStates) continue; if (DEBUG) System.out.println("Coalescing state " + _allStates[stateNum] + " with " + _allStates[eqvnum]); _equivalentStates[stateNum] = eqvnum; deletedCount++; } } // Replace all transistion indexes with equivalents for (int i = 0; i < _allStates.length; i++) { HandState hs = _allStates[i]; if (!hs.isFinal()) { NonFinalHandState nfhs = (NonFinalHandState) hs; if (nfhs._transistions != null) { for (int j = 0; j < nfhs._transistions.length; j++) { HandState trans = nfhs._transistions[j]; if (trans != null) { int txidx = trans._index; if (_equivalentStates != null && _equivalentStates[txidx] != -1 && _equivalentStates[txidx] != txidx) // This state has been deleted, replace with the equiv state nfhs._transistions[j] = _allStates[_equivalentStates[txidx]]; } } } } } if (DEBUG) { stop = System.currentTimeMillis(); System.out.println("Removed " + deletedCount + " states"); System.out.println("FST minimized in " + (stop - start) + " ms"); } } // Split the states into separate arrays, one for each number of cards. This is // possible because we never coalesce states with different card numbers. The FST is // ultimately stored as a set of arrays, one per level, to make array index sizes // as small as posssible private void buildLevels() { ArrayList<Vector<HandState>> levelStatesList = new ArrayList<Vector<HandState>>(_handLength + 1); for (int i = 0; i < _handLength + 1; i++) { levelStatesList.add(i, new Vector<HandState>()); } for (int i = 0; i < _allStates.length; i++) { HandState hs = _allStates[i]; if (hs._cards != null && _equivalentStates[hs._index] == hs._index) { //int level = hs._transistions == null ? _handLength : hs._cards.length; int level = hs.isFinal() ? _handLength : hs._cards.length; hs._levelIndex = (char) levelStatesList.get(level).size(); levelStatesList.get(level).add(hs); } } _levelStates = new HandState[_handLength + 1][]; for (int i = 0; i < _handLength + 1; i++) { _levelStates[i] = new HandState[levelStatesList.get(i).size()]; levelStatesList.get(i).toArray(_levelStates[i]); if (DEBUG) System.out.println("level " + i + " length = " + _levelStates[i].length); } } // Final state comparison class used by makeEqvClasses private class StateComp<T> implements Comparator<T> { public int compare(Object o1, Object o2) { FinalHandState hs1 = (FinalHandState) o1; FinalHandState hs2 = (FinalHandState) o2; return hs1._value < hs2._value ? -1 : 1; } } // Order all of the final states. Once ordered, the index into the sorted array // is the eqivalence class of the hand (which is the result of the eval methods). private void makeEqvClasses() { HandState[] finalStates = _levelStates[_handLength]; if (DEBUG) System.out.println("Final state len = " + finalStates.length); Arrays.sort(finalStates, new StateComp<HandState>()); for (int i = 0; i < finalStates.length; i++) { ((FinalHandState) finalStates[i])._eqvClass = (char) i; } } // For each level, create the exported form of the array. We try to keep the // arrays as small as possible by using the smallest array type in which all entries will // fit. If OPT_WIDE_RANK_SIZE == WIDE_RANK_SIZE, we also build wider arrays to // avoid eval-time multiplications. private void buildLevelArrays() { _levelArrays = new char[_handLength][]; for (int i = _handLength - 1; i >= 0; i--) { _levelArrays[i] = buildLevelArray(i); } _jumpStartArray = buildJumpStartArray(); if (OPT_WIDE_RANK_SIZE == WIDE_RANK_SIZE) { if (_handLength > 5) { _wide6Array = buildWideArray(6); if (_handLength > 6) { _wide7Array = buildWideArray(7); } } } } // Build the arrays for a single level. Non-final level arrays contain indexes into // the next level arrays, pre-multiplied by RANK_SIZE if the index will fit in a char. // The final level array contains the equivalence class values for the hand. private char[] buildLevelArray(int n) { HandState[] levelStates = _levelStates[n]; int entryCount = levelStates.length; boolean useEqvClass = n == _levelStates.length - 2; boolean preMultiply = !useEqvClass && _levelStates[n + 1].length * RANK_SIZE <= Character.MAX_VALUE; char[] arr = new char[entryCount * RANK_SIZE]; for (int i = 0; i < levelStates.length; i++) { NonFinalHandState hs = (NonFinalHandState) levelStates[i]; if (hs._cards.length == n) { HandState[] trans = hs._transistions; int tlen = trans.length; for (int j = 0; j < tlen; j++) { HandState ns = trans[j]; if (ns != null) { char nsval = ns.value(); if (preMultiply) nsval *= RANK_SIZE; arr[i * tlen + j] = nsval; } } } } return arr; } // As an optimization, we build a single array for cards one through three. // There isn't much fanout yet, so this doesn't waste much space, and it // saves eval a couple of memory indirections. private char[] buildJumpStartArray() { char[] arr = new char[WIDE_RANK_SIZE * WIDE_RANK_SIZE * RANK_SIZE]; char[] fst1 = _levelArrays[0]; char[] fst2 = _levelArrays[1]; char[] fst3 = _levelArrays[2]; for (int i = 0; i < RANK_SIZE; i++) { // card1 for (int j = 0; j < RANK_SIZE; j++) { // card2 for (int k = 0; k < RANK_SIZE; k++) { // card3 arr[(((i << 4) + j) << 4) + k] = fst3[fst2[fst1[i] + j] + k]; } } } if (DEBUG) System.out.println("jump start len = " + arr.length); return arr; } // Optionally build wider arrays to avoid eval-time multiplications. private char[] buildWideArray(int level) { char[] curr = _levelArrays[level - 1]; int rows = curr.length / RANK_SIZE; char[] arr = new char[WIDE_RANK_SIZE * rows]; for (int i = 0; i < rows; i++) { for (int j = 0; j < RANK_SIZE; j++) { arr[i * WIDE_RANK_SIZE + j] = (char) curr[i * RANK_SIZE + j]; } } if (DEBUG) System.out.println("wide array " + level + " len = " + arr.length); return arr; } // Construct a separate array that holds all of the possible flush and straight flush // hand equivalence class values. The index into the array is a bit vector that // contains all of the 5-n cards of the flush suit. private void makeFlushArray() { _flushArray = new char[0x1fc1]; for (int cn0 = 0; cn0 < RANK_SIZE; cn0++) { for (int cn1 = cn0 + 1; cn1 < RANK_SIZE; cn1++) { for (int cn2 = cn1 + 1; cn2 < RANK_SIZE; cn2++) { for (int cn3 = cn2 + 1; cn3 < RANK_SIZE; cn3++) { for (int cn4 = cn3 + 1; cn4 < RANK_SIZE; cn4++) { int[] cards5 = { cn0, cn1, cn2, cn3, cn4 }; FinalHandState flushState5 = makeFlush(cards5); int bfval5 = (int) bfval(cards5); _flushArray[bfval5] = flushState5._eqvClass; if (_handLength > 5) { for (int cn5 = cn4 + 1; cn5 < RANK_SIZE; cn5++) { int[] cards6 = { cn0, cn1, cn2, cn3, cn4, cn5 }; FinalHandState flushState6 = makeFlush(cards6); int bfval6 = (int) bfval(cards6); _flushArray[bfval6] = flushState6._eqvClass; if (_handLength > 6) { for (int cn6 = cn4 + 1; cn6 < RANK_SIZE; cn6++) { int[] cards7 = { cn0, cn1, cn2, cn3, cn4, cn5, cn6 }; FinalHandState flushState7 = makeFlush(cards7); int bfval7 = (int) bfval(cards7); _flushArray[bfval7] = flushState7._eqvClass; } } } } } } } } } } // Construct the array and HashMap that map equivalence class numbers to // Steve Brecher HandEval numbers. private void makeToFrom() { HandState[] finalStates = _levelStates[_handLength]; _toBrecherArray = new int[finalStates.length]; _fromBrecherMap = new HashMap<Integer, Integer>(); for (int i = 0; i < finalStates.length; i++) { FinalHandState finalState = (FinalHandState) finalStates[i]; _toBrecherArray[i] = finalState._value; _fromBrecherMap.put(new Integer(finalState._value), new Integer(i)); } } // Serialize the constructed eval arrays and utility maps and write them into a file // so that they don't have to be recalculated each time. private void writeArrays() throws IOException, ClassNotFoundException { FileOutputStream fos = new FileOutputStream(_filename); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeInt(OPT_WIDE_RANK_SIZE); oos.writeObject(_jumpStartArray); oos.writeObject(_levelArrays[3]); oos.writeObject(_levelArrays[4]); if (_handLength > 5) { if (OPT_WIDE_RANK_SIZE == WIDE_RANK_SIZE) oos.writeObject(_wide6Array); else oos.writeObject(_levelArrays[5]); if (_handLength > 6) { if (OPT_WIDE_RANK_SIZE == WIDE_RANK_SIZE) oos.writeObject(_wide7Array); else oos.writeObject(_levelArrays[6]); } } oos.writeObject(_flushArray); oos.writeObject(_toBrecherArray); oos.writeObject(_fromBrecherMap); oos.close(); } // Read the serialize the constructed eval arrays and utility maps from a file. @SuppressWarnings("unchecked") private void readArrays() throws IOException, ClassNotFoundException, IllegalStateException { FileInputStream fis = new FileInputStream(_filename); ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(fis)); int owrs = ois.readInt(); if (owrs != OPT_WIDE_RANK_SIZE) throw new IllegalStateException(); _jumpStartArray = (char[]) ois.readObject(); _levelArrays = new char[_handLength][]; _levelArrays[3] = (char[]) ois.readObject(); _levelArrays[4] = (char[]) ois.readObject(); if (_handLength > 5) { if (OPT_WIDE_RANK_SIZE == WIDE_RANK_SIZE) _wide6Array = (char[]) ois.readObject(); else _levelArrays[5] = (char[]) ois.readObject(); if (_handLength > 6) { if (OPT_WIDE_RANK_SIZE == WIDE_RANK_SIZE) _wide7Array = (char[]) ois.readObject(); else _levelArrays[6] = (char[]) ois.readObject(); } } _flushArray = (char[]) ois.readObject(); _toBrecherArray = (int[]) ois.readObject(); _fromBrecherMap = (HashMap<Integer, Integer>) ois.readObject(); ois.close(); } }