Hands evaluator first commit

hh
Laurent 6 years ago
parent 2b8096f209
commit 736bc0d543
  1. 22
      app/src/main/java/net/pokeranalytics/android/model/realm/handhistory/Card.kt
  2. 15
      app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/HandHistoryActivity.kt
  3. 143
      app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/evaluator/Card.java
  4. 66
      app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/evaluator/EvaluatorBridge.kt
  5. 115
      app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/evaluator/Hand.java
  6. 1446
      app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/evaluator/Tables.java

@ -124,6 +124,18 @@ open class Card : RealmObject() {
override val viewType: Int = RowViewType.TITLE_GRID.ordinal override val viewType: Int = RowViewType.TITLE_GRID.ordinal
val evualuatorSuit: Int?
get() {
return when (this) {
SPADES -> net.pokeranalytics.android.ui.modules.handhistory.evaluator.Card.SPADES
CLOVER -> net.pokeranalytics.android.ui.modules.handhistory.evaluator.Card.CLUBS
DIAMOND -> net.pokeranalytics.android.ui.modules.handhistory.evaluator.Card.DIAMONDS
HEART -> net.pokeranalytics.android.ui.modules.handhistory.evaluator.Card.HEARTS
else -> null
}
}
} }
/*** /***
@ -209,4 +221,14 @@ open class Card : RealmObject() {
return textView return textView
} }
fun toEvaluatorCard():net.pokeranalytics.android.ui.modules.handhistory.evaluator.Card? {
val rank = this.value
val suit = this.suit?.evualuatorSuit
return if (rank != null && suit != null) {
net.pokeranalytics.android.ui.modules.handhistory.evaluator.Card(rank, suit)
} else {
null
}
}
} }

@ -7,6 +7,8 @@ import net.pokeranalytics.android.R
import net.pokeranalytics.android.model.realm.Session import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.ui.activity.components.BaseActivity import net.pokeranalytics.android.ui.activity.components.BaseActivity
import net.pokeranalytics.android.ui.activity.components.RequestCode import net.pokeranalytics.android.ui.activity.components.RequestCode
import net.pokeranalytics.android.ui.modules.handhistory.evaluator.Hand
import timber.log.Timber
class HandHistoryActivity : BaseActivity() { class HandHistoryActivity : BaseActivity() {
@ -55,6 +57,19 @@ class HandHistoryActivity : BaseActivity() {
fragmentTransaction.add(R.id.container, fragment) fragmentTransaction.add(R.id.container, fragment)
fragmentTransaction.commit() fragmentTransaction.commit()
// test()
}
fun test() {
val h1 = Hand.fromString("Kd Ts Jc Ah Qc")
val h2 = Hand.fromString("Kd 5d Jd Ad Qd")
val r1 = Hand.evaluate(h1)
val r2 = Hand.evaluate(h2)
Timber.d(">>> Hand evaluation = $r1")
Timber.d(">>> Hand evaluation = $r2")
} }
} }

@ -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];
}
}
Loading…
Cancel
Save