/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/**
* I have no problem at all for the code to be used in a GPL program.
* - Ray Wotton
*/
package org.cspoker.common.handeval.spears2p2;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import org.apache.log4j.Logger;
import org.cspoker.common.elements.cards.Card;
/**
*
* http://forumserver.twoplustwo.com/showthreaded.php?Cat=0&Number=9765615&page=0&vc=1
* http://forumserver.twoplustwo.com/showthreaded.php?Cat=0&Number=9774228&page=0&vc=1
*
* To evaluate 6 card hand make the last card a zero.
*
*/
public class StateTableEvaluator {
private final static Logger logger = Logger.getLogger(StateTableEvaluator.class);
private final int HAND_RANKS_SIZE = 32487834;
/*
* Card to integer conversions: 2c = 1 2d = 2 2h = 3 2s = 4 3c = 5 3d = 6 3h
* = 7 3s = 8 4c = 9 4d = 10 4h = 11 4s = 12 5c = 13 5d = 14 5h = 15 5s = 16
* 6c = 17 6d = 18 6h = 19 6s = 20 7c = 21 7d = 22 7h = 23 7s = 24 8c = 25
* 8d = 26 8h = 27 8s = 28 9c = 29 9d = 30 9h = 31 9s = 32 Tc = 33 Td = 34
* Th = 35 Ts = 36 Jc = 37 Jd = 38 Jh = 39 Js = 40 Qc = 41 Qd = 42 Qh = 43
* Qs = 44 Kc = 45 Kd = 46 Kh = 47 Ks = 48 Ac = 49 Ad = 50 Ah = 51 As = 52
*/
private final int NUM_SUITS = 4;
private final int NUM_RANKS = 13;
public int[] handRanks = new int[HAND_RANKS_SIZE]; // array to hold hand
// rank lookup table
private final boolean verbose = true; // toggles verbose mode
private int[] hand; // re-usable array to hold cards in a hand
private final int size = 612978; // lookup table size
private final long[] keys = new long[size]; // array to hold key lookup
// table
private int numKeys = 1; // counter for number of defined keys in key array
private long maxKey = 0; // holds current maximum key value
private int numCards = 0; // re-usable counter for number of cards in a hand
private int cardIndex = 0; // re-usable index for cards in a hands
private int maxHandRankIndex = 0;
private long startTimer;
private long stopTimer;
private final String HAND_RANKS_FILE = "handRanks.zip";
private StateTableEvaluator() throws FileNotFoundException, IOException,
ClassNotFoundException {
logger.info("Loading evaluation tables ...");
File f = new File(HAND_RANKS_FILE);
if (!f.exists()) {
logger.info("Evaluation tables do not exist, this is first time run. Generating them ...");
handRanks = new int[HAND_RANKS_SIZE];
generateTables();
saveTables();
logger.info("Loading evaluation tables (again) ...");
}
long t = System.currentTimeMillis();
ZipInputStream zipStream = new ZipInputStream(new FileInputStream(
HAND_RANKS_FILE));
zipStream.getNextEntry();
ObjectInputStream s = new ObjectInputStream(zipStream);
handRanks = (int[]) s.readObject();
t = System.currentTimeMillis() - t;
logger.info("Evaluation tables loaded in " + t / 1000.0
+ " seconds");
}
private static StateTableEvaluator instance = null;
public synchronized static StateTableEvaluator getInstance() {
if (instance == null) {
try {
instance = new StateTableEvaluator();
} catch (FileNotFoundException e) {
throw new IllegalStateException(e);
} catch (IOException e) {
throw new IllegalStateException(e);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e);
}
}
return instance;
}
// Inserts a key into the key array and returns the insertion index.
private int insertKey(long key) {
// check to see if key is valid
if (key == 0) {
return 0;
}
// short circuit insertion for most common cases
if (key >= maxKey) {
if (key > maxKey) {
keys[numKeys++] = key; // appends the new key to the key array
maxKey = key;
}
return numKeys - 1;
}
// use binary search to find insertion point for new key
int low = -1;
int high = numKeys;
int pivot;
long difference;
while (high - low > 1) {
pivot = low + high >>> 1;
difference = keys[pivot] - key;
if (difference > 0) {
high = pivot;
} else if (difference < 0) {
low = pivot;
} else {
return pivot; // key already exists
}
}
// key does not exist so must be inserted
System.arraycopy(keys, high, keys, high + 1, numKeys - high);
keys[high] = key;
numKeys++;
return high;
} // END insertKey method
// Returns a key for the hand created by adding a new card to the hand
// represented by the given key. Returns 0 if new card already appears in
// hand.
private long makeKey(long baseKey, int newCard) {
int[] suitCount = new int[NUM_SUITS + 1]; // number of times a suit
// appears in a hand
int[] rankCount = new int[NUM_RANKS + 1]; // number of times a rank
// appears in a hand
hand = new int[8];
// extract the hand represented by the key value
for (cardIndex = 0; cardIndex < 6; cardIndex++) {
// hand[0] is used to hold the new card
hand[cardIndex + 1] = (int) (baseKey >>> 8 * cardIndex & 0xFF);
}
hand[0] = formatCard8bit(newCard);
// examine the hand to determine number of cards and rank/suit counts
for (numCards = 0; hand[numCards] != 0; numCards++) {
suitCount[hand[numCards] & 0xF]++;
rankCount[hand[numCards] >>> 4 & 0xF]++;
// check to see if new card is already contained in hand (rank and
// suit considered)
if (numCards != 0 && hand[0] == hand[numCards]) {
return 0;
}
}
// check to see if we already have four of a particular rank
if (numCards > 4) {
for (int rank = 1; rank < 14; rank++) {
if (rankCount[rank] > 4) {
return 0;
}
}
}
// determine the minimum number of suits required for a flush to be
// possible
int minSuitCount = numCards - 2;
// check to see if suit is significant
if (minSuitCount > 1) {
// examine each card in the hand
for (cardIndex = 0; cardIndex < numCards; cardIndex++) {
// if the suit is not significant then strip it from the card
if (suitCount[hand[cardIndex] & 0xF] < minSuitCount) {
hand[cardIndex] &= 0xF0;
}
}
}
sortHand();
long key = 0;
for (int i = 0; i < 7; i++) {
key += (long) hand[i] << i * 8;
}
return key;
} // END makeKey method
// Formats and returns a card in 8-bit packed representation.
private int formatCard8bit(int card) {
// 8-Bit Packed Card Representation
// +--------+
// |rrrr--ss|
// +--------+
// r = rank of card (deuce = 1, trey = 2, four = 3, five = 4,..., ace =
// 13)
// s = suit of card (suits are arbitrary, can take value from 0 to 3)
card--;
return ((card >>> 2) + 1 << 4) + (card & 3) + 1;
} // END formatCard8bit method
// Sorts the hand using Bose-Nelson Sorting Algorithm (N = 7).
private void sortHand() {
swapCard(0, 4);
swapCard(1, 5);
swapCard(2, 6);
swapCard(0, 2);
swapCard(1, 3);
swapCard(4, 6);
swapCard(2, 4);
swapCard(3, 5);
swapCard(0, 1);
swapCard(2, 3);
swapCard(4, 5);
swapCard(1, 4);
swapCard(3, 6);
swapCard(1, 2);
swapCard(3, 4);
swapCard(5, 6);
} // End sortHand method
// Swaps card i with card j.
private void swapCard(int i, int j) {
if (hand[i] < hand[j]) {
hand[i] ^= hand[j];
hand[j] ^= hand[i];
hand[i] ^= hand[j];
}
} // END swapCard method
// Determines the relative strength of a hand (the hand is given by its
// unique key value).
private int getHandRank(long key) {
// The following method implements a modified version of "Cactus Kev's
// Five-Card
// Poker Hand Evaluator" to determine the relative strength of two
// five-card hands.
// Reference: http://www.suffecool.net/poker/evaluator.html
hand = new int[8];
int currentCard;
int rank;
int handRank = 9999;
int holdrank = 9999;
int suit = 0;
int numCards = 0;
final int[] primes = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41 };
if (key != 0) {
for (cardIndex = 0; cardIndex < 7; cardIndex++) {
currentCard = (int) (key >>> 8 * cardIndex & 0xFF);
if (currentCard == 0) {
break;
}
numCards++;
// Cactus Kev Card Representation
// +--------+--------+--------+--------+
// |xxxbbbbb|bbbbbbbb|cdhsrrrr|xxpppppp|
// +--------+--------+--------+--------+
// p = prime number of rank (deuce = 2, trey = 3, four = 5, five
// = 7,..., ace = 41)
// r = rank of card (deuce = 0, trey = 1, four = 2, five =
// 3,..., ace = 12)
// cdhs = suit of card
// b = bit turned on depending on rank of card
// extract suit and rank from 8-bit packed representation
rank = (currentCard >>> 4) - 1;
suit = currentCard & 0xF;
// change card representation to Cactus Kev Representation
hand[cardIndex] = primes[rank] | rank << 8 | 1 << suit + 11
| 1 << 16 + rank;
}
switch (numCards) {
case 5:
holdrank = eval_5hand(hand[0], hand[1], hand[2], hand[3],
hand[4]);
break;
case 6:
// Cactus Kev's Evaluator ranks hands from 1 (Royal Flush) to
// 7462 (Seven High Card)
holdrank = eval_5hand(hand[0], hand[1], hand[2], hand[3],
hand[4]);
holdrank = Math.min(holdrank, eval_5hand(hand[0], hand[1],
hand[2], hand[3], hand[5]));
holdrank = Math.min(holdrank, eval_5hand(hand[0], hand[1],
hand[2], hand[4], hand[5]));
holdrank = Math.min(holdrank, eval_5hand(hand[0], hand[1],
hand[3], hand[4], hand[5]));
holdrank = Math.min(holdrank, eval_5hand(hand[0], hand[2],
hand[3], hand[4], hand[5]));
holdrank = Math.min(holdrank, eval_5hand(hand[1], hand[2],
hand[3], hand[4], hand[5]));
break;
case 7:
holdrank = eval_5hand(hand[0], hand[1], hand[2], hand[3],
hand[4]);
holdrank = Math.min(holdrank, eval_5hand(hand[0], hand[1],
hand[2], hand[3], hand[5]));
holdrank = Math.min(holdrank, eval_5hand(hand[0], hand[1],
hand[2], hand[3], hand[6]));
holdrank = Math.min(holdrank, eval_5hand(hand[0], hand[1],
hand[2], hand[4], hand[5]));
holdrank = Math.min(holdrank, eval_5hand(hand[0], hand[1],
hand[2], hand[4], hand[6]));
holdrank = Math.min(holdrank, eval_5hand(hand[0], hand[1],
hand[2], hand[5], hand[6]));
holdrank = Math.min(holdrank, eval_5hand(hand[0], hand[1],
hand[3], hand[4], hand[5]));
holdrank = Math.min(holdrank, eval_5hand(hand[0], hand[1],
hand[3], hand[4], hand[6]));
holdrank = Math.min(holdrank, eval_5hand(hand[0], hand[1],
hand[3], hand[5], hand[6]));
holdrank = Math.min(holdrank, eval_5hand(hand[0], hand[1],
hand[4], hand[5], hand[6]));
holdrank = Math.min(holdrank, eval_5hand(hand[0], hand[2],
hand[3], hand[4], hand[5]));
holdrank = Math.min(holdrank, eval_5hand(hand[0], hand[2],
hand[3], hand[4], hand[6]));
holdrank = Math.min(holdrank, eval_5hand(hand[0], hand[2],
hand[3], hand[5], hand[6]));
holdrank = Math.min(holdrank, eval_5hand(hand[0], hand[2],
hand[4], hand[5], hand[6]));
holdrank = Math.min(holdrank, eval_5hand(hand[0], hand[3],
hand[4], hand[5], hand[6]));
holdrank = Math.min(holdrank, eval_5hand(hand[1], hand[2],
hand[3], hand[4], hand[5]));
holdrank = Math.min(holdrank, eval_5hand(hand[1], hand[2],
hand[3], hand[4], hand[6]));
holdrank = Math.min(holdrank, eval_5hand(hand[1], hand[2],
hand[3], hand[5], hand[6]));
holdrank = Math.min(holdrank, eval_5hand(hand[1], hand[2],
hand[4], hand[5], hand[6]));
holdrank = Math.min(holdrank, eval_5hand(hand[1], hand[3],
hand[4], hand[5], hand[6]));
holdrank = Math.min(holdrank, eval_5hand(hand[2], hand[3],
hand[4], hand[5], hand[6]));
break;
default:
logger.error("ERROR: Invalid hand in GetRank method.");
break;
}
// Hand Rank Representation
// +--------+--------+
// |hhhheeee|eeeeeeee|
// +--------+--------+
// h = poker hand (1 = High Card, 2 = One Pair, 3 = Two Pair,..., 9
// = Straight Flush)
// e = equivalency class (Rank of equivalency class relative to base
// hand)
// +-----------------------------------+----------------------------------+-----------------+
// 5-Card Equivalency Classes 7-Card Equivalency Classes
// +-----------------------------------+----------------------------------+-----------------+
// 1277 407 High Card
// 2860 1470 One Pair
// 858 763 Two Pair
// 858 575 Three of a Kind
// 10 10 Straight
// 1277 1277 Flush
// 156 156 Full House
// 156 156 Four of a Kind
// 10 10 Straight Flush
// +----------+------------------------+----------------------------------+-----------------+
// Total: 7462 4824
// +----------+------------------------+----------------------------------+-----------------+
handRank = 7463 - holdrank; // Invert ranking metric (1 is now worst
// hand)
if (handRank < 1278) {
handRank = handRank - 0 + 4096 * 1; // High Card
} else if (handRank < 4138) {
handRank = handRank - 1277 + 4096 * 2; // One Pair
} else if (handRank < 4996) {
handRank = handRank - 4137 + 4096 * 3; // Two Pair
} else if (handRank < 5854) {
handRank = handRank - 4995 + 4096 * 4; // Three of a Kind
} else if (handRank < 5864) {
handRank = handRank - 5853 + 4096 * 5; // Straight
} else if (handRank < 7141) {
handRank = handRank - 5863 + 4096 * 6; // Flush
} else if (handRank < 7297) {
handRank = handRank - 7140 + 4096 * 7; // Full House
} else if (handRank < 7453) {
handRank = handRank - 7296 + 4096 * 8; // Four of a Kind
} else {
handRank = handRank - 7452 + 4096 * 9; // Straight Flush
}
}
return handRank;
} // END getHandRank method
private final int[] offsets = new int[] { 0, 1277, 4137, 4995, 5853, 5863,
7140, 7296, 7452 };
private int getIndex(int key) {
// use binary search to find key
int low = -1;
int high = 4888;
int pivot;
while (high - low > 1) {
pivot = low + high >>> 1;
if (Products.table[pivot] > key) {
high = pivot;
} else if (Products.table[pivot] < key) {
low = pivot;
} else {
return pivot;
}
}
return -1;
} // END getIndex method
private int eval_5hand(int c1, int c2, int c3, int c4, int c5) {
int q = (c1 | c2 | c3 | c4 | c5) >> 16;
short s;
// check for Flushes and Straight Flushes
if ((c1 & c2 & c3 & c4 & c5 & 0xF000) != 0) {
return Flushes.table[q];
}
// check for Straights and High Card hands
if ((s = Unique.table[q]) != 0) {
return s;
}
q = (c1 & 0xFF) * (c2 & 0xFF) * (c3 & 0xFF) * (c4 & 0xFF) * (c5 & 0xFF);
q = getIndex(q);
return Values.table[q];
} // END eval_5hand method
private void generateTables() {
int card;
int handRank;
int keyIndex;
long key;
if (verbose) {
logger.info("Generating and sorting keys...");
startTimer = System.currentTimeMillis();
}
for (keyIndex = 0; keys[keyIndex] != 0 || keyIndex == 0; keyIndex++) {
if (keyIndex % (size / 100) == 0) {
logger.info(keyIndex / (size / 100) + "% generated.");
}
for (card = 1; card < 53; card++) { // add a card to each previously
// calculated key
key = makeKey(keys[keyIndex], card); // create the new key
if (numCards < 7) {
insertKey(key); // insert the new key into the key lookup
// table
}
}
}
if (verbose) {
stopTimer = System.currentTimeMillis();
logger.info("Generating hand ranks...");
startTimer = System.currentTimeMillis();
}
for (keyIndex = 0; keys[keyIndex] != 0 || keyIndex == 0; keyIndex++) {
if (keyIndex % (size / 100) == 0) {
logger.info(keyIndex / (size / 100) + "% generated.");
}
for (card = 1; card < 53; card++) {
key = makeKey(keys[keyIndex], card);
if (numCards < 7) {
handRank = insertKey(key) * 53 + 53; // if number of cards
// is < 7 insert key
} else {
handRank = getHandRank(key); // if number of cards is 7
// insert hand rank
}
maxHandRankIndex = keyIndex * 53 + card + 53; // calculate hand
// rank
// insertion
// index
handRanks[maxHandRankIndex] = handRank; // populate hand rank
// lookup table with
// appropriate value
}
if (numCards == 6 || numCards == 7) {
// insert the hand rank into the hand rank lookup table
handRanks[keyIndex * 53 + 53] = getHandRank(keys[keyIndex]);
}
}
} // END generateTables method
private void saveTables() throws FileNotFoundException, IOException {
ZipOutputStream zipStream = new ZipOutputStream(new FileOutputStream(
HAND_RANKS_FILE));
zipStream.setLevel(Deflater.BEST_COMPRESSION);
ZipEntry entry = new ZipEntry("handRanks.ser");
zipStream.putNextEntry(entry);
ObjectOutputStream s = new ObjectOutputStream(zipStream);
s.writeObject(handRanks);
s.close();
}
public int getRank(Card[] cards) {
// System.out.println("Getting rank..");
// Card[] cards = sevenCardHand.toCards();
int rank = 53;
for (Card card : cards) {
int c = card.ordinal() + 1;
rank = handRanks[c + rank];
}
// System.out.println("Got rank");
int type = (rank >>> 12) - 1;
rank = rank & 0xFFF;
return offsets[type] + rank - 1;
}
}