parent
2b8096f209
commit
736bc0d543
@ -0,0 +1,143 @@ |
||||
package net.pokeranalytics.android.ui.modules.handhistory.evaluator; |
||||
|
||||
/** |
||||
* An immutable class representing a card from a normal 52-card deck. |
||||
*/ |
||||
public class Card { |
||||
private final int value; // Format: xxxAKQJT 98765432 CDHSrrrr xxPPPPPP
|
||||
|
||||
// Ranks
|
||||
public static final int DEUCE = 0; |
||||
public static final int TREY = 1; |
||||
public static final int FOUR = 2; |
||||
public static final int FIVE = 3; |
||||
public static final int SIX = 4; |
||||
public static final int SEVEN = 5; |
||||
public static final int EIGHT = 6; |
||||
public static final int NINE = 7; |
||||
public static final int TEN = 8; |
||||
public static final int JACK = 9; |
||||
public static final int QUEEN = 10; |
||||
public static final int KING = 11; |
||||
public static final int ACE = 12; |
||||
|
||||
// Suits
|
||||
public static final int CLUBS = 0x8000; |
||||
public static final int DIAMONDS = 0x4000; |
||||
public static final int HEARTS = 0x2000; |
||||
public static final int SPADES = 0x1000; |
||||
|
||||
// Rank symbols
|
||||
private static final String RANKS = "23456789TJQKA"; |
||||
private static final String SUITS = "shdc"; |
||||
|
||||
/** |
||||
* Creates a new card with the given rank and suit. |
||||
* @param rank the rank of the card, e.g. {@link Card#SIX} |
||||
* @param suit the suit of the card, e.g. {@link Card#CLUBS} |
||||
*/ |
||||
public Card(int rank, int suit) { |
||||
if (!isValidRank(rank)) { |
||||
throw new IllegalArgumentException("Invalid rank."); |
||||
} |
||||
|
||||
if (!isValidSuit(suit)) { |
||||
throw new IllegalArgumentException("Invalid suit."); |
||||
} |
||||
|
||||
value = (1 << (rank + 16)) | suit | (rank << 8) | Tables.PRIMES[rank]; |
||||
} |
||||
|
||||
/** |
||||
* Create a new {@link Card} instance from the given string. |
||||
* The string should be a two-character string where the first character |
||||
* is the rank and the second character is the suit. For example, "Kc" means |
||||
* the king of clubs, and "As" means the ace of spades. |
||||
* @param string Card to create as a string. |
||||
* @return a new {@link Card} instance corresponding to the given string. |
||||
*/ |
||||
public static Card fromString(String string) { |
||||
if (string == null || string.length() != 2) { |
||||
throw new IllegalArgumentException("Card string must be non-null with length of exactly 2."); |
||||
} |
||||
|
||||
final int rank = RANKS.indexOf(string.charAt(0)); |
||||
final int suit = SPADES << SUITS.indexOf(string.charAt(1)); |
||||
|
||||
return new Card(rank, suit); |
||||
} |
||||
|
||||
/** |
||||
* Returns the rank of the card. |
||||
* @return rank of the card as an integer. |
||||
* @see Card#ACE |
||||
* @see Card#DEUCE |
||||
* @see Card#TREY |
||||
* @see Card#FOUR |
||||
* @see Card#FIVE |
||||
* @see Card#SIX |
||||
* @see Card#SEVEN |
||||
* @see Card#EIGHT |
||||
* @see Card#NINE |
||||
* @see Card#TEN |
||||
* @see Card#JACK |
||||
* @see Card#QUEEN |
||||
* @see Card#KING |
||||
*/ |
||||
public int getRank() { |
||||
return (value >> 8) & 0xF; |
||||
} |
||||
|
||||
/** |
||||
* Returns the suit of the card. |
||||
* @return Suit of the card as an integer. |
||||
* @see Card#SPADES |
||||
* @see Card#HEARTS |
||||
* @see Card#DIAMONDS |
||||
* @see Card#CLUBS |
||||
*/ |
||||
public int getSuit() { |
||||
return value & 0xF000; |
||||
} |
||||
|
||||
/** |
||||
* Returns a string representation of the card. |
||||
* For example, the king of spades is "Ks", and the jack of hearts is "Jh". |
||||
* @return a string representation of the card. |
||||
*/ |
||||
public String toString() { |
||||
char rank = RANKS.charAt(getRank()); |
||||
char suit = SUITS.charAt((int) (Math.log(getSuit()) / Math.log(2)) - 12); |
||||
return "" + rank + suit; |
||||
} |
||||
|
||||
/** |
||||
* Returns the value of the card as an integer. |
||||
* The value is represented as the bits <code>xxxAKQJT 98765432 CDHSrrrr xxPPPPPP</code>, |
||||
* where <code>x</code> means unused, <code>AKQJT 98765432</code> are bits turned on/off |
||||
* depending on the rank of the card, <code>CDHS</code> are the bits corresponding to the |
||||
* suit, and <code>PPPPPP</code> is the prime number of the card. |
||||
* @return the value of the card. |
||||
*/ |
||||
int getValue() { |
||||
return value; |
||||
} |
||||
|
||||
/** |
||||
* Returns whether the given rank is valid or not. |
||||
* @param rank rank to check. |
||||
* @return true if the rank is valid, false otherwise. |
||||
*/ |
||||
private static boolean isValidRank(int rank) { |
||||
return rank >= DEUCE && rank <= ACE; |
||||
} |
||||
|
||||
/** |
||||
* Returns whether the given suit is valid or not. |
||||
* @param suit suit to check. |
||||
* @return true if the suit is valid, false otherwise. |
||||
*/ |
||||
private static boolean isValidSuit(int suit) { |
||||
return suit == CLUBS || suit == DIAMONDS || suit == HEARTS || suit == SPADES; |
||||
} |
||||
} |
||||
@ -0,0 +1,66 @@ |
||||
package net.pokeranalytics.android.ui.modules.handhistory.evaluator |
||||
|
||||
import net.pokeranalytics.android.model.realm.handhistory.Card |
||||
import kotlin.math.min |
||||
|
||||
class EvaluatorBridge { |
||||
|
||||
object Combinator { |
||||
|
||||
fun <T>combinations( |
||||
arr: Array<T>, |
||||
len: Int, |
||||
startPosition: Int, |
||||
result: Array<T>, |
||||
callback: (Array<T>) -> (Unit) |
||||
) { |
||||
if (len == 0) { |
||||
callback.invoke(result) |
||||
// System.out.println(Arrays.toString(result)) |
||||
return |
||||
} |
||||
for (i in startPosition..arr.size - len) { |
||||
result[result.size - len] = arr[i] |
||||
combinations(arr, len - 1, i + 1, result, callback) |
||||
} |
||||
} |
||||
} |
||||
|
||||
companion object { |
||||
|
||||
fun playerHand(hand: List<Card>, board: List<Card>): Int { |
||||
|
||||
val handCards = hand.mapNotNull { it.toEvaluatorCard() }.toTypedArray() |
||||
val boardCards = board.mapNotNull { it.toEvaluatorCard() }.toTypedArray() |
||||
|
||||
val handSize = 5 |
||||
var result = Int.MAX_VALUE |
||||
when (hand.size) { |
||||
2 -> { // Hold'em |
||||
val allCards = arrayOf<net.pokeranalytics.android.ui.modules.handhistory.evaluator.Card>() |
||||
allCards.plus(handCards) |
||||
allCards.plus(boardCards) |
||||
|
||||
if (allCards.size >= handSize) { |
||||
Combinator.combinations(allCards, handSize, 0, arrayOf()) { |
||||
result = min(result, Hand.evaluate(it)) |
||||
} |
||||
} |
||||
} |
||||
else -> { // Omahas |
||||
Combinator.combinations(boardCards, 3, 0, arrayOf()) { bc -> |
||||
Combinator.combinations(handCards, 2, 0, arrayOf()) { hc -> |
||||
val fcc = arrayOf<net.pokeranalytics.android.ui.modules.handhistory.evaluator.Card>() |
||||
fcc.plus(bc) // Five Card Combination |
||||
fcc.plus(hc) |
||||
result = min(result, Hand.evaluate(fcc)) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return result |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,115 @@ |
||||
package net.pokeranalytics.android.ui.modules.handhistory.evaluator; |
||||
|
||||
import java.util.Arrays; |
||||
|
||||
/** Utility methods for evaluating or creating a hand of cards. */ |
||||
public abstract class Hand { |
||||
/** |
||||
* Private constructor to disable instantiation of an abstract class. |
||||
*/ |
||||
private Hand() { |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Evaluates the given hand and returns its value as an integer. |
||||
* Based on Kevin Suffecool's 5-card hand evaluator and with Paul Senzee's pre-computed hash. |
||||
* @param cards a hand of cards to evaluate |
||||
* @return the value of the hand as an integer between 1 and 7462 |
||||
*/ |
||||
public static int evaluate(Card[] cards) { |
||||
// Only 5-card hands are supported
|
||||
if (cards == null || cards.length != 5) { |
||||
throw new IllegalArgumentException("Exactly 5 cards are required."); |
||||
} |
||||
|
||||
// Binary representations of each card
|
||||
final int c1 = cards[0].getValue(); |
||||
final int c2 = cards[1].getValue(); |
||||
final int c3 = cards[2].getValue(); |
||||
final int c4 = cards[3].getValue(); |
||||
final int c5 = cards[4].getValue(); |
||||
|
||||
// No duplicate cards allowed
|
||||
if (hasDuplicates(new int[]{c1, c2, c3, c4, c5})) { |
||||
throw new IllegalArgumentException("Illegal hand."); |
||||
} |
||||
|
||||
// Calculate index in the flushes/unique table
|
||||
final int index = (c1 | c2 | c3 | c4 | c5) >> 16; |
||||
|
||||
// Flushes, including straight flushes
|
||||
if ((c1 & c2 & c3 & c4 & c5 & 0xF000) != 0) { |
||||
return Tables.Flushes.TABLE[index]; |
||||
} |
||||
|
||||
// Straight and high card hands
|
||||
final int value = Tables.Unique.TABLE[index]; |
||||
if (value != 0) { |
||||
return value; |
||||
} |
||||
|
||||
// Remaining cards
|
||||
final int product = (c1 & 0xFF) * (c2 & 0xFF) * (c3 & 0xFF) * (c4 & 0xFF) * (c5 & 0xFF); |
||||
return Tables.Hash.Values.TABLE[hash(product)]; |
||||
} |
||||
|
||||
/** |
||||
* Creates a new 5-card hand from the given string. |
||||
* @param string the string to create the hand from, such as "Kd 5s Jc Ah Qc" |
||||
* @return a new hand as an array of cards |
||||
* @see Card |
||||
*/ |
||||
public static Card[] fromString(String string) { |
||||
final String[] parts = string.split(" "); |
||||
final Card[] cards = new Card[parts.length]; |
||||
|
||||
if (parts.length != 5) |
||||
throw new IllegalArgumentException("Exactly 5 cards are required."); |
||||
|
||||
int index = 0; |
||||
for (String part : parts) |
||||
cards[index++] = Card.fromString(part); |
||||
|
||||
return cards; |
||||
} |
||||
|
||||
/** |
||||
* Converts the given hand into concatenation of their string representations |
||||
* @param cards a hand of cards |
||||
* @return a concatenation of the string representations of the given cards |
||||
*/ |
||||
public static String toString(Card[] cards) { |
||||
final StringBuilder builder = new StringBuilder(); |
||||
|
||||
for (int i = 0; i < cards.length; i++) { |
||||
builder.append(cards[i]); |
||||
if (i < cards.length - 1) |
||||
builder.append(" "); |
||||
} |
||||
|
||||
return builder.toString(); |
||||
} |
||||
|
||||
/** |
||||
* Checks if the given array of values has any duplicates. |
||||
* @param values the values to check |
||||
* @return true if the values contain duplicates, false otherwise |
||||
*/ |
||||
private static boolean hasDuplicates(int[] values) { |
||||
Arrays.sort(values); |
||||
for (int i = 1; i < values.length; i++) { |
||||
if (values[i] == values[i - 1]) |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
private static int hash(int key) { |
||||
key += 0xE91AAA35; |
||||
key ^= key >>> 16; |
||||
key += key << 8; |
||||
key ^= key >>> 4; |
||||
return ((key + (key << 2)) >>> 19) ^ Tables.Hash.Adjust.TABLE[(key >>> 8) & 0x1FF]; |
||||
} |
||||
} |
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue