@block52/poker-vm-sdk 1.12.0 → 1.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -57116,6 +57116,10 @@ class PokerSolver {
57116
57116
  /**
57117
57117
  * Evaluate a 5-card poker hand
57118
57118
  */
57119
+ /**
57120
+ * Evaluate exactly 5 cards (a winner's best hand, WinnerDTO.cards). Use
57121
+ * findBestHand for 7.
57122
+ */
57119
57123
  static evaluateHand(cards) {
57120
57124
  if (cards.length !== 5) {
57121
57125
  throw new Error("Hand evaluation requires exactly 5 cards");
@@ -58193,37 +58197,116 @@ class PokerGameIntegration {
58193
58197
  };
58194
58198
  }
58195
58199
  /**
58196
- * Create a formatted hand description for UI display
58200
+ * The winning hand in words, from the winner's best five cards
58201
+ * (WinnerDTO.cards) and the board (communityCards): "Full House, Eights
58202
+ * over Sevens", "Diamond Flush, Six high", "Set of Sevens". The board tells
58203
+ * a set (pocket pair plus one on the board) from trips (one hole card plus a
58204
+ * board pair, or all three on the board), which the five cards alone can't.
58205
+ * @param cards The winner's five card mnemonics ("6D")
58206
+ * @param board The community cards
58207
+ * @returns The hand in words, or "" for an uncontested pot (no cards)
58208
+ */
58209
+ static describeWinningHand(cards, board) {
58210
+ if (!cards || cards.length === 0)
58211
+ return "";
58212
+ if (cards.length !== 5) {
58213
+ throw new Error(`describeWinningHand expects the winning five cards, got ${cards.length}`);
58214
+ }
58215
+ const evaluation = PokerSolver.evaluateHand(cards.map(c => Deck.fromString(c)));
58216
+ if (evaluation.handType === exports.HandType.THREE_OF_A_KIND) {
58217
+ const tripRank = evaluation.rankValues[0];
58218
+ const onBoard = board.map(c => Deck.fromString(c)).filter(c => (c.rank === 1 ? 14 : c.rank) === tripRank).length;
58219
+ if (onBoard === 1)
58220
+ return `Set of ${this.rankWord(tripRank, true)}`;
58221
+ }
58222
+ return this.formatHandDescription(evaluation);
58223
+ }
58224
+ /**
58225
+ * Fill in each winner's name and description from its cards and the board.
58226
+ * The engine no longer writes the hand's wording into chain state
58227
+ * (poker-vm#2721), so callers run game state through this as it arrives and
58228
+ * keep reading winner.name / winner.description as before.
58229
+ *
58230
+ * A winner with five cards gets "Full House, Eights over Sevens" in both
58231
+ * fields. A winner without five cards (an uncontested pot, or an older state
58232
+ * that recorded hole cards) is returned unchanged.
58233
+ *
58234
+ * Returns a new state object; the input is not modified.
58235
+ * @param state Any game state with winners and community cards
58236
+ */
58237
+ static withWinnerDescriptions(state) {
58238
+ const winners = state.winners.map(winner => {
58239
+ if (!winner.cards || winner.cards.length !== 5)
58240
+ return winner;
58241
+ const description = this.describeWinningHand(winner.cards, state.communityCards);
58242
+ return { ...winner, name: description, description };
58243
+ });
58244
+ return { ...state, winners };
58245
+ }
58246
+ /**
58247
+ * The hand description in words: "Full House, Eights over Sevens", "Quad
58248
+ * Fours", "Diamond Flush, Six high", "Ace high". A flush names its suit from
58249
+ * the best hand. The rank and suit words mirror @block52/deck's rankName /
58250
+ * suitName.
58197
58251
  * @param evaluation Hand evaluation result
58198
58252
  * @returns Human-readable hand description
58199
58253
  */
58200
58254
  static formatHandDescription(evaluation) {
58201
- const { handType, rankValues, description } = evaluation;
58255
+ const { handType, rankValues, description, bestHand } = evaluation;
58256
+ const suit = this.flushSuitWord(bestHand);
58257
+ const suited = (hand) => (suit ? `${suit} ${hand}` : hand);
58202
58258
  switch (handType) {
58203
58259
  case exports.HandType.ROYAL_FLUSH:
58204
- return "Royal Flush";
58260
+ return suit ? `Royal Flush in ${suit}s` : "Royal Flush";
58205
58261
  case exports.HandType.STRAIGHT_FLUSH:
58206
- return `Straight Flush, ${PokerSolver.formatRank(rankValues[0])} high`;
58262
+ return `${suited("Straight Flush")}, ${this.rankWord(rankValues[0])} high`;
58207
58263
  case exports.HandType.FOUR_OF_A_KIND:
58208
- return `Four ${PokerSolver.formatRank(rankValues[0])}s`;
58264
+ return `Quad ${this.rankWord(rankValues[0], true)}`;
58209
58265
  case exports.HandType.FULL_HOUSE:
58210
- return `Full House, ${PokerSolver.formatRank(rankValues[0])}s over ${PokerSolver.formatRank(rankValues[1])}s`;
58266
+ return `Full House, ${this.rankWord(rankValues[0], true)} over ${this.rankWord(rankValues[1], true)}`;
58211
58267
  case exports.HandType.FLUSH:
58212
- return `Flush, ${PokerSolver.formatRank(rankValues[0])} high`;
58268
+ return `${suited("Flush")}, ${this.rankWord(rankValues[0])} high`;
58213
58269
  case exports.HandType.STRAIGHT:
58214
- return `Straight, ${PokerSolver.formatRank(rankValues[0])} high`;
58270
+ return `Straight, ${this.rankWord(rankValues[0])} high`;
58215
58271
  case exports.HandType.THREE_OF_A_KIND:
58216
- return `Three ${PokerSolver.formatRank(rankValues[0])}s`;
58272
+ return `Trip ${this.rankWord(rankValues[0], true)}`;
58217
58273
  case exports.HandType.TWO_PAIR:
58218
- return `Two Pair, ${PokerSolver.formatRank(rankValues[0])}s and ${PokerSolver.formatRank(rankValues[1])}s`;
58274
+ return `Two Pair, ${this.rankWord(rankValues[0], true)} and ${this.rankWord(rankValues[1], true)}`;
58219
58275
  case exports.HandType.PAIR:
58220
- return `Pair of ${PokerSolver.formatRank(rankValues[0])}s`;
58276
+ return `Pair of ${this.rankWord(rankValues[0], true)}`;
58221
58277
  case exports.HandType.HIGH_CARD:
58222
- return `${PokerSolver.formatRank(rankValues[0])} high`;
58278
+ return `${this.rankWord(rankValues[0])} high`;
58223
58279
  default:
58224
58280
  return description;
58225
58281
  }
58226
58282
  }
58283
+ /** A high-ace rank spelled out ("Ace", "Ten"); plural gives "Aces", "Sixes". */
58284
+ static rankWord(rank, plural = false) {
58285
+ const words = this.RANK_WORDS[rank === 1 ? 14 : rank];
58286
+ if (!words)
58287
+ return plural ? `${PokerSolver.formatRank(rank)}s` : PokerSolver.formatRank(rank);
58288
+ return plural ? words[1] : words[0];
58289
+ }
58290
+ /** The suit word every card of a flush shares ("Diamond"), or "" when the hand is empty or mixed. */
58291
+ static flushSuitWord(cards) {
58292
+ if (!cards || cards.length === 0)
58293
+ return "";
58294
+ const suit = cards[0].suit;
58295
+ if (cards.some(c => c.suit !== suit))
58296
+ return "";
58297
+ switch (suit) {
58298
+ case exports.SUIT.CLUBS:
58299
+ return "Club";
58300
+ case exports.SUIT.DIAMONDS:
58301
+ return "Diamond";
58302
+ case exports.SUIT.HEARTS:
58303
+ return "Heart";
58304
+ case exports.SUIT.SPADES:
58305
+ return "Spade";
58306
+ default:
58307
+ return "";
58308
+ }
58309
+ }
58227
58310
  /**
58228
58311
  * Example usage with string cards (for testing/debugging)
58229
58312
  * @param playerCardStrings Array of player hands as mnemonic strings
@@ -58244,6 +58327,21 @@ class PokerGameIntegration {
58244
58327
  return { winners, results };
58245
58328
  }
58246
58329
  }
58330
+ PokerGameIntegration.RANK_WORDS = {
58331
+ 14: ["Ace", "Aces"],
58332
+ 13: ["King", "Kings"],
58333
+ 12: ["Queen", "Queens"],
58334
+ 11: ["Jack", "Jacks"],
58335
+ 10: ["Ten", "Tens"],
58336
+ 9: ["Nine", "Nines"],
58337
+ 8: ["Eight", "Eights"],
58338
+ 7: ["Seven", "Sevens"],
58339
+ 6: ["Six", "Sixes"],
58340
+ 5: ["Five", "Fives"],
58341
+ 4: ["Four", "Fours"],
58342
+ 3: ["Three", "Threes"],
58343
+ 2: ["Two", "Twos"]
58344
+ };
58247
58345
  // Example usage:
58248
58346
  /*
58249
58347
  const example = PokerGameIntegration.exampleShowdown([