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