How DD Poker's Hand Evaluator Works: Calculating Hand Strength in Java

DD Poker calculates hand strength by enumerating all 1,235 possible pocket card combinations against the current board, converting raw hand scores into percentile rankings that represent the fraction of opponent hands beaten or tied.

The hand evaluator in the dougdonohoe/ddpoker repository implements a complete Texas Hold'em evaluation pipeline. It transforms community cards and hole cards into actionable intelligence for AI decision-making through a multi-stage scoring system that balances computational accuracy with runtime performance.

Core Components of the Hand Evaluation Pipeline

DD Poker's evaluation system operates through three tightly coupled stages:

  1. Score Matrix GenerationPocketScores calculates raw integer rankings for every legal pocket against the current board using HandInfoFaster.
  2. Percentile ConversionPocketRanks transforms those scores into 0.0–1.0 probabilities representing hand strength against the field.
  3. AI IntegrationV2Player and RuleEngine consume these values, combining raw strength with positive hand potential to drive betting logic.

Step 1: Scoring Individual Hands with HandInfoFaster

The foundation of DD Poker's evaluator is HandInfoFaster, a Java class that evaluates 5-card or 7-card hands and returns a numeric score where higher values represent stronger hands.

Hand Classification Logic

HandInfoFaster.getScore(Hand pocket, Hand board) analyzes hand composition through several counting mechanisms:

  • Rank counting (nNumRank_, nGroupings_) detects pairs, trips, and quads
  • Suit counting (nNumSuit_, bFlush) identifies flush possibilities
  • Straight detection uses a sliding window over rank counts (bStraight, nStraightHigh_)
  • Straight-flush validation intersects flush suits with straight ranks (isStraightFlush)

Numeric Score Calculation

After classification, the method assembles a composite score using constants from HandInfo. The scoring hierarchy places royal flushes and straight flushes at the top, with base multipliers ensuring proper hand ranking:

// HandInfoFaster core – see lines 145-210
int score;
if ( bStraight && bFlush && isStraightFlush(h)) {
    if (nStraightHigh_ == Card.ACE) score = HandInfo.ROYAL_FLUSH * HandInfo.BASE;
    else score = HandInfo.STRAIGHT_FLUSH * HandInfo.BASE;
    score += nStraightHigh_ * HandInfo.H0;
}
...
return score;

Source: code/poker/src/main/java/com/donohoedigital/games/poker/HandInfoFaster.java (lines 145-210)

Step 2: Building the Pocket Score Matrix

PocketScores constructs a complete evaluation matrix for all possible hole card combinations against the current community cards.

The constructor iterates through all 1,235 legal pocket combinations (52 × 51 / 2), skipping any cards that appear on the board:

// PocketScores ctor – see lines 106-132
for (int i = 1; i < 52; ++i) {
    if (community.containsCard(i)) continue;
    Card card1 = Card.getCard(i);
    pocket.setCard(0, card1);
    for (int j = 0; j < i; ++j) {
        if (community.containsCard(j)) continue;
        Card card2 = Card.getCard(j);
        pocket.setCard(1, card2);
        score_.set(i, j, info.getScore(pocket, community));
    }
}

Source: code/poker/src/main/java/com/donohoedigital/games/poker/ai/PocketScores.java (lines 106-132)

Caching Strategy for Performance

To avoid recomputing expensive matrices, PocketScores implements flop-based caching. A static fingerprint of the first three community cards triggers cache invalidation only when the flop changes, allowing cheap turn and river recalculations:

// Cache handling – lines 78-88
long fpFlop = community.fingerprint(3);
if (fpFlop != fpFlop_) { cache_.clear(); fpFlop_ = fpFlop; }
Object key = community.fingerprint();

Source: code/poker/src/main/java/com/donohoedigital/games/poker/ai/PocketScores.java (lines 78-88)

Step 3: Calculating Raw Hand Strength Percentages

PocketRanks transforms the integer score matrix into probabilistic hand strength values representing the likelihood of beating a random opponent.

Converting Scores to Percentiles

The constructor compares every pocket against all other legal pockets, calculating the percentage of hands that are worse or equal:

// Core of PocketRanks ctor – lines 17-48 (excerpt)
score = scores.getScore(i, j);
worse = equal = count = 0;
for (int k = 1; k < 52; ++k) {
    if ((k == i) || (k == j) || community.containsCard(k)) continue;
    for (int m = 0; m < k; ++m) {
        if ((m == i) || (m == j) || community.containsCard(m)) continue;
        other = scores.getScore(k, m);
        if (other < score) ++worse;
        else if (other == score) ++equal;
        ++count;
    }
}
rhs_.set(i, j, (short)(10000.0f * (worse + equal) / count));

Source: code/poker/src/main/java/com/donohoedigital/games/poker/ai/PocketRanks.java (lines 17-48)

The matrix stores values as shorts scaled by 10,000 (0-10000), preserving two decimal places of precision while minimizing memory footprint.

Accessing Hand Strength Values

The public API converts the stored short back to a 0.0-1.0 float:

public float getRawHandStrength(int c1, int c2) {
    return ((float)rhs_.get(c1, c2)) / 10000.0f;
}

Source: code/poker/src/main/java/com/donohoedigital/games/poker/ai/PocketRanks.java (lines 78-81)

Caching follows the same flop-fingerprint pattern as PocketScores, ensuring that turn and river calculations reuse existing matrices when possible.

Step 4: AI Decision Making with Hand Strength

The AI components consume raw hand strength through PocketRanks and combine it with predictive metrics to make betting decisions.

Integrating Positive Hand Potential

V2Player retrieves the cached PocketRanks instance and queries its own pocket strength:

PocketRanks ranks = PocketRanks.getInstance(community);
float raw = ranks.getRawHandStrength(myPocket);

Source: code/poker/src/main/java/com/donohoedigital/games/poker/ai/V2Player.java (lines 372-380)

The final hand strength calculation incorporates positive hand potential—the probability of improving on future streets:

float handStrength = (float)Math.pow(
        raw + (1 - raw) * getPositiveHandPotential(),
        getPokerPlayer().getHoldemHand().getNumWithCards() - 1);

Source: code/poker/src/main/java/com/donohoedigital/games/poker/ai/V2Player.java (lines 620-628)

Betting Logic and Rule Engine

RuleEngine and PocketWeights read the same raw strength values to adjust folding thresholds, bet sizing, and steal-blind strategies. The evaluation pipeline ensures that every AI decision rests on mathematically precise hand strength calculations rather than heuristic approximations.

Summary

  • HandInfoFaster evaluates individual 5-card or 7-card hands using rank counting, suit detection, and straight-flush validation, returning integer scores where higher values indicate stronger hands.
  • PocketScores builds a complete matrix of raw scores for all 1,235 legal pocket combinations against the current board, caching results per flop to optimize turn and river calculations.
  • PocketRanks converts score matrices into percentile hand strength (0.0–1.0), representing the fraction of opponent hands beaten or tied, storing values as scaled shorts for memory efficiency.
  • V2Player and supporting AI classes consume raw hand strength through getRawHandStrength(), combining it with positive hand potential to drive mathematically sound betting decisions.

Frequently Asked Questions

How does DD Poker handle the computational cost of evaluating all 1,235 pocket combinations?

DD Poker mitigates computational overhead through aggressive caching in PocketScores and PocketRanks. Both classes use a flop fingerprinting mechanism (fingerprint(3)) to detect when the first three community cards change. When the flop remains constant, the cached matrices persist, allowing cheap recalculation for turn and river scenarios without re-enumerating all combinations.

What is the difference between raw hand score and raw hand strength in DD Poker?

The raw hand score is an integer value produced by HandInfoFaster that ranks a specific 5-card or 7-card hand on an absolute scale (higher is better). The raw hand strength is a floating-point value between 0.0 and 1.0 produced by PocketRanks that represents the percentile rank of a pocket pair against all possible opponent holdings, indicating the probability of winning against a random hand.

How does the AI use hand strength to make betting decisions?

The AI class V2Player retrieves raw hand strength from PocketRanks.getRawHandStrength() and combines it with positive hand potential—the statistical likelihood of improving on future streets. This composite value is raised to a power based on the number of active players, producing a final hand strength metric that drives folding, calling, and raising decisions through the RuleEngine and betting heuristics.

Where is the hand evaluation logic located in the DD Poker repository?

The core evaluation logic resides in code/poker/src/main/java/com/donohoedigital/games/poker/HandInfoFaster.java for individual hand scoring. The matrix construction and caching logic lives in code/poker/src/main/java/com/donohoedigital/games/poker/ai/PocketScores.java and code/poker/src/main/java/com/donohoedigital/games/poker/ai/PocketRanks.java. AI consumption of these values occurs primarily in code/poker/src/main/java/com/donohoedigital/games/poker/ai/V2Player.java.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →