@pokertools/engine 1.0.8 → 1.0.9

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.
Files changed (50) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/actions/betting.js +8 -8
  3. package/dist/actions/dealing.js +6 -4
  4. package/dist/actions/management.js +3 -0
  5. package/dist/actions/{showdownActions.js → showdown-actions.js} +10 -10
  6. package/dist/actions/special.js +20 -3
  7. package/dist/actions/{streetProgression.js → street-progression.js} +7 -7
  8. package/dist/actions/validation.js +48 -27
  9. package/dist/browser.d.ts +5 -5
  10. package/dist/browser.js +6 -6
  11. package/dist/engine/{gameReducer.js → game-reducer.js} +11 -8
  12. package/dist/engine/{PokerEngine.js → poker-engine.js} +10 -10
  13. package/dist/errors/{ConfigError.d.ts → config-error.d.ts} +1 -1
  14. package/dist/errors/{ConfigError.js → config-error.js} +2 -2
  15. package/dist/errors/{CriticalStateError.d.ts → critical-state-error.d.ts} +1 -1
  16. package/dist/errors/{CriticalStateError.js → critical-state-error.js} +2 -2
  17. package/dist/errors/{IllegalActionError.d.ts → illegal-action-error.d.ts} +2 -2
  18. package/dist/errors/{IllegalActionError.js → illegal-action-error.js} +2 -2
  19. package/dist/errors/index.d.ts +4 -4
  20. package/dist/errors/index.js +4 -4
  21. package/dist/history/exporter.js +4 -4
  22. package/dist/index.d.ts +2 -2
  23. package/dist/index.js +4 -4
  24. package/dist/rules/{actionOrder.js → action-order.js} +10 -10
  25. package/dist/rules/blinds.js +2 -2
  26. package/dist/rules/showdown.js +34 -1
  27. package/dist/rules/{sidePots.js → side-pots.js} +2 -2
  28. package/dist/utils/invariants.js +16 -10
  29. package/dist/utils/serialization.d.ts +1 -0
  30. package/dist/utils/serialization.js +9 -3
  31. package/dist/utils/validation.js +8 -8
  32. package/dist/utils/{viewMasking.js → view-masking.js} +16 -6
  33. package/package.json +5 -5
  34. /package/dist/actions/{showdownActions.d.ts → showdown-actions.d.ts} +0 -0
  35. /package/dist/actions/{streetProgression.d.ts → street-progression.d.ts} +0 -0
  36. /package/dist/engine/{gameReducer.d.ts → game-reducer.d.ts} +0 -0
  37. /package/dist/engine/{PokerEngine.d.ts → poker-engine.d.ts} +0 -0
  38. /package/dist/errors/{ErrorCodes.d.ts → error-codes.d.ts} +0 -0
  39. /package/dist/errors/{ErrorCodes.js → error-codes.js} +0 -0
  40. /package/dist/errors/{PokerEngineError.d.ts → poker-engine-error.d.ts} +0 -0
  41. /package/dist/errors/{PokerEngineError.js → poker-engine-error.js} +0 -0
  42. /package/dist/history/{handHistoryBuilder.d.ts → hand-history-builder.d.ts} +0 -0
  43. /package/dist/history/{handHistoryBuilder.js → hand-history-builder.js} +0 -0
  44. /package/dist/rules/{actionOrder.d.ts → action-order.d.ts} +0 -0
  45. /package/dist/rules/{headsUp.d.ts → heads-up.d.ts} +0 -0
  46. /package/dist/rules/{headsUp.js → heads-up.js} +0 -0
  47. /package/dist/rules/{sidePots.d.ts → side-pots.d.ts} +0 -0
  48. /package/dist/utils/{cardUtils.d.ts → card-utils.d.ts} +0 -0
  49. /package/dist/utils/{cardUtils.js → card-utils.js} +0 -0
  50. /package/dist/utils/{viewMasking.d.ts → view-masking.d.ts} +0 -0
@@ -4,7 +4,7 @@ exports.getNextToAct = getNextToAct;
4
4
  exports.getFirstToAct = getFirstToAct;
5
5
  exports.isActionComplete = isActionComplete;
6
6
  const positioning_1 = require("../utils/positioning");
7
- const headsUp_1 = require("./headsUp");
7
+ const heads_up_1 = require("./heads-up");
8
8
  const blinds_1 = require("./blinds");
9
9
  /**
10
10
  * Determine the next player to act
@@ -12,7 +12,7 @@ const blinds_1 = require("./blinds");
12
12
  */
13
13
  function getNextToAct(state) {
14
14
  // Special case: heads-up has different rules
15
- if ((0, headsUp_1.isHeadsUp)(state)) {
15
+ if ((0, heads_up_1.isHeadsUp)(state)) {
16
16
  return getNextToActHeadsUp(state);
17
17
  }
18
18
  return getNextToActNormal(state);
@@ -32,8 +32,8 @@ function getNextToActNormal(state) {
32
32
  // Search for next player who can act
33
33
  while (seat !== startSeat) {
34
34
  const player = state.players[seat];
35
- // Skip if: no player, folded, all-in, or busted
36
- if (player?.status !== "ACTIVE" /* PlayerStatus.ACTIVE */ || player.stack === 0) {
35
+ // Skip if: no player, folded, all-in, busted, or sitting out
36
+ if (player?.status !== "ACTIVE" /* PlayerStatus.ACTIVE */ || player.stack === 0 || player.isSittingOut) {
37
37
  seat = (0, positioning_1.getNextSeat)(seat, state.maxPlayers);
38
38
  continue;
39
39
  }
@@ -65,7 +65,7 @@ function getNextToActHeadsUp(state) {
65
65
  if (state.buttonSeat === null) {
66
66
  return null;
67
67
  }
68
- const actionOrder = (0, headsUp_1.getHeadsUpActionOrder)(state, state.street);
68
+ const actionOrder = (0, heads_up_1.getHeadsUpActionOrder)(state, state.street);
69
69
  if (actionOrder.length === 0) {
70
70
  return null;
71
71
  }
@@ -77,7 +77,7 @@ function getNextToActHeadsUp(state) {
77
77
  // Check both players
78
78
  for (const seat of actionOrder) {
79
79
  const player = state.players[seat];
80
- if (player?.status !== "ACTIVE" /* PlayerStatus.ACTIVE */ || player.stack === 0) {
80
+ if (player?.status !== "ACTIVE" /* PlayerStatus.ACTIVE */ || player.stack === 0 || player.isSittingOut) {
81
81
  continue;
82
82
  }
83
83
  const playerBet = state.currentBets.get(seat) ?? 0;
@@ -94,8 +94,8 @@ function getFirstToAct(state) {
94
94
  if (state.buttonSeat === null) {
95
95
  return null;
96
96
  }
97
- if ((0, headsUp_1.isHeadsUp)(state)) {
98
- const order = (0, headsUp_1.getHeadsUpActionOrder)(state, state.street);
97
+ if ((0, heads_up_1.isHeadsUp)(state)) {
98
+ const order = (0, heads_up_1.getHeadsUpActionOrder)(state, state.street);
99
99
  if (order.length > 0) {
100
100
  return order[0];
101
101
  }
@@ -128,7 +128,7 @@ function getNextActionableSeat(startSeat, state) {
128
128
  // Scan full circle
129
129
  while (seat !== endSeat) {
130
130
  const player = state.players[seat];
131
- if (player?.status === "ACTIVE" /* PlayerStatus.ACTIVE */ && player.stack > 0) {
131
+ if (player?.status === "ACTIVE" /* PlayerStatus.ACTIVE */ && player.stack > 0 && !player.isSittingOut) {
132
132
  return seat;
133
133
  }
134
134
  seat = (0, positioning_1.getNextSeat)(seat, state.maxPlayers);
@@ -159,7 +159,7 @@ function isActionComplete(state) {
159
159
  if (!player)
160
160
  continue;
161
161
  // Count active players who can still act
162
- if (player.status === "ACTIVE" /* PlayerStatus.ACTIVE */ && player.stack > 0) {
162
+ if (player.status === "ACTIVE" /* PlayerStatus.ACTIVE */ && player.stack > 0 && !player.isSittingOut) {
163
163
  activeCount++;
164
164
  const playerBet = state.currentBets.get(seat) ?? 0;
165
165
  // Player has matched bet and acted
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getBlindPositions = getBlindPositions;
4
4
  exports.calculateAntes = calculateAntes;
5
5
  const positioning_1 = require("../utils/positioning");
6
- const headsUp_1 = require("./headsUp");
6
+ const heads_up_1 = require("./heads-up");
7
7
  /**
8
8
  * Determine which seats should post blinds
9
9
  *
@@ -24,7 +24,7 @@ function getBlindPositions(state) {
24
24
  const buttonSeat = state.buttonSeat;
25
25
  const isTournament = !!state.config.blindStructure;
26
26
  // Heads-up specific logic (Button is SB)
27
- if ((0, headsUp_1.isHeadsUp)(state)) {
27
+ if ((0, heads_up_1.isHeadsUp)(state)) {
28
28
  const bbSeat = getNextActiveOrOccupiedSeat(buttonSeat, state.players, state.maxPlayers, isTournament);
29
29
  if (bbSeat === null) {
30
30
  return null;
@@ -143,7 +143,7 @@ function evaluatePot(state, pot) {
143
143
  evaluations.push({
144
144
  seat: player.seat,
145
145
  score,
146
- hand: [...player.hand], // Store hole cards (copy to mutable array)
146
+ hand: getBestFiveCardHand(allCards),
147
147
  description,
148
148
  });
149
149
  }
@@ -155,6 +155,39 @@ function evaluatePot(state, pot) {
155
155
  const winners = evaluations.filter((e) => e.score === bestScore);
156
156
  return winners;
157
157
  }
158
+ /**
159
+ * Return the concrete five cards that produce the best evaluator score.
160
+ * The evaluator score is lower-is-better, so enumerate all 5-card subsets and
161
+ * keep the first subset with the minimum score. Hold'em has at most 7 cards,
162
+ * making this deterministic brute-force path only 21 evaluations per player.
163
+ */
164
+ function getBestFiveCardHand(cards) {
165
+ if (cards.length === 5) {
166
+ return [...cards];
167
+ }
168
+ let bestHand = null;
169
+ let bestScore = Number.POSITIVE_INFINITY;
170
+ for (let a = 0; a < cards.length - 4; a++) {
171
+ for (let b = a + 1; b < cards.length - 3; b++) {
172
+ for (let c = b + 1; c < cards.length - 2; c++) {
173
+ for (let d = c + 1; d < cards.length - 1; d++) {
174
+ for (let e = d + 1; e < cards.length; e++) {
175
+ const candidate = [cards[a], cards[b], cards[c], cards[d], cards[e]];
176
+ const score = (0, evaluator_1.evaluate)((0, evaluator_1.getCardCodes)(candidate));
177
+ if (score < bestScore) {
178
+ bestScore = score;
179
+ bestHand = candidate;
180
+ }
181
+ }
182
+ }
183
+ }
184
+ }
185
+ }
186
+ if (bestHand === null) {
187
+ return [...cards.slice(0, 5)];
188
+ }
189
+ return bestHand;
190
+ }
158
191
  /**
159
192
  * Check if hand should go to showdown
160
193
  */
@@ -4,7 +4,7 @@ exports.calculateSidePots = calculateSidePots;
4
4
  exports.calculateUncalledBet = calculateUncalledBet;
5
5
  exports.returnUncalledBet = returnUncalledBet;
6
6
  exports.recalculatePots = recalculatePots;
7
- const CriticalStateError_1 = require("../errors/CriticalStateError");
7
+ const critical_state_error_1 = require("../errors/critical-state-error");
8
8
  /**
9
9
  * Calculate side pots using iterative subtraction method
10
10
  *
@@ -57,7 +57,7 @@ function calculateSidePots(state) {
57
57
  // Must have at least one eligible player
58
58
  // If everyone folded at this level, something went wrong in the game logic
59
59
  if (eligibleSeats.length === 0) {
60
- throw new CriticalStateError_1.CriticalStateError(`Side pot has no eligible players - all ${allAtThisLevel.length} players at this level have folded`, {
60
+ throw new critical_state_error_1.CriticalStateError(`Side pot has no eligible players - all ${allAtThisLevel.length} players at this level have folded`, {
61
61
  potAmount,
62
62
  potLevel: i,
63
63
  investmentLevel: current.amount,
@@ -7,7 +7,7 @@ exports.calculatePotTotal = calculatePotTotal;
7
7
  exports.calculateBetTotal = calculateBetTotal;
8
8
  exports.getInitialChips = getInitialChips;
9
9
  exports.validateGameStateIntegrity = validateGameStateIntegrity;
10
- const CriticalStateError_1 = require("../errors/CriticalStateError");
10
+ const critical_state_error_1 = require("../errors/critical-state-error");
11
11
  /**
12
12
  * Audit chip conservation
13
13
  * Formula: ∑(player.stack) + ∑(pot.amount) + ∑(currentBets) = initialChips
@@ -17,9 +17,10 @@ const CriticalStateError_1 = require("../errors/CriticalStateError");
17
17
  * @throws CriticalStateError if chips don't match
18
18
  */
19
19
  function auditChipConservation(state, initialChips) {
20
- const currentChips = getInitialChips(state);
20
+ const handComplete = state.winners !== null && state.pots.length === 0 && state.currentBets.size === 0;
21
+ const currentChips = calculateTotalChips(state) + (handComplete ? state.rakeThisHand : 0);
21
22
  if (currentChips !== initialChips) {
22
- throw new CriticalStateError_1.CriticalStateError(`Chip conservation violated: expected ${initialChips}, found ${currentChips}`, {
23
+ throw new critical_state_error_1.CriticalStateError(`Chip conservation violated: expected ${initialChips}, found ${currentChips}`, {
23
24
  expected: initialChips,
24
25
  actual: currentChips,
25
26
  difference: currentChips - initialChips,
@@ -77,6 +78,10 @@ function calculateBetTotal(state) {
77
78
  * - After hand complete: stack + rake (all chips have been distributed, rake removed)
78
79
  */
79
80
  function getInitialChips(state) {
81
+ const baseline = state.initialChips;
82
+ if (typeof baseline === "number") {
83
+ return baseline;
84
+ }
80
85
  let total = 0;
81
86
  // Hand is complete if winners are declared AND pots/bets have been distributed
82
87
  // This ensures we don't switch modes mid-hand
@@ -110,12 +115,13 @@ function validateGameStateIntegrity(state) {
110
115
  return;
111
116
  }
112
117
  // 1. Chip conservation
113
- const initialChips = getInitialChips(state);
118
+ const baseline = state.initialChips;
119
+ const initialChips = typeof baseline === "number" ? baseline : getInitialChips(state);
114
120
  auditChipConservation(state, initialChips);
115
121
  // 2. No negative stacks
116
122
  for (const player of state.players) {
117
123
  if (player && player.stack < 0) {
118
- throw new CriticalStateError_1.CriticalStateError(`Player ${player.id} has negative stack: ${player.stack}`, {
124
+ throw new critical_state_error_1.CriticalStateError(`Player ${player.id} has negative stack: ${player.stack}`, {
119
125
  playerId: player.id,
120
126
  stack: player.stack,
121
127
  });
@@ -124,7 +130,7 @@ function validateGameStateIntegrity(state) {
124
130
  // 3. No negative bets
125
131
  for (const [seat, bet] of state.currentBets.entries()) {
126
132
  if (bet < 0) {
127
- throw new CriticalStateError_1.CriticalStateError(`Seat ${seat} has negative bet: ${bet}`, {
133
+ throw new critical_state_error_1.CriticalStateError(`Seat ${seat} has negative bet: ${bet}`, {
128
134
  seat,
129
135
  bet,
130
136
  });
@@ -134,7 +140,7 @@ function validateGameStateIntegrity(state) {
134
140
  for (let i = 0; i < state.pots.length; i++) {
135
141
  const pot = state.pots[i];
136
142
  if (pot.amount < 0) {
137
- throw new CriticalStateError_1.CriticalStateError(`Pot ${i} has negative amount: ${pot.amount}`, {
143
+ throw new critical_state_error_1.CriticalStateError(`Pot ${i} has negative amount: ${pot.amount}`, {
138
144
  potIndex: i,
139
145
  amount: pot.amount,
140
146
  });
@@ -143,14 +149,14 @@ function validateGameStateIntegrity(state) {
143
149
  // 5. ActionTo must be valid seat or null
144
150
  if (state.actionTo !== null) {
145
151
  if (state.actionTo < 0 || state.actionTo >= state.maxPlayers) {
146
- throw new CriticalStateError_1.CriticalStateError(`Invalid actionTo: ${state.actionTo}`, {
152
+ throw new critical_state_error_1.CriticalStateError(`Invalid actionTo: ${state.actionTo}`, {
147
153
  actionTo: state.actionTo,
148
154
  maxPlayers: state.maxPlayers,
149
155
  });
150
156
  }
151
157
  const player = state.players[state.actionTo];
152
158
  if (!player) {
153
- throw new CriticalStateError_1.CriticalStateError(`ActionTo points to empty seat: ${state.actionTo}`, {
159
+ throw new critical_state_error_1.CriticalStateError(`ActionTo points to empty seat: ${state.actionTo}`, {
154
160
  actionTo: state.actionTo,
155
161
  });
156
162
  }
@@ -158,7 +164,7 @@ function validateGameStateIntegrity(state) {
158
164
  // 6. Button must be valid or null
159
165
  if (state.buttonSeat !== null) {
160
166
  if (state.buttonSeat < 0 || state.buttonSeat >= state.maxPlayers) {
161
- throw new CriticalStateError_1.CriticalStateError(`Invalid buttonSeat: ${state.buttonSeat}`, {
167
+ throw new critical_state_error_1.CriticalStateError(`Invalid buttonSeat: ${state.buttonSeat}`, {
162
168
  buttonSeat: state.buttonSeat,
163
169
  maxPlayers: state.maxPlayers,
164
170
  });
@@ -28,6 +28,7 @@ export interface Snapshot {
28
28
  readonly timeBankActiveSeat: number | null;
29
29
  readonly actionHistory: ActionRecord[];
30
30
  readonly previousStates: Snapshot[];
31
+ readonly initialChips?: number;
31
32
  readonly timestamp: number;
32
33
  readonly handId: string;
33
34
  }
@@ -19,8 +19,12 @@ function createSnapshot(state) {
19
19
  for (const [seat, time] of state.timeBanks.entries()) {
20
20
  timeBanks[seat] = time;
21
21
  }
22
- // Truncate previous states (keep last 10 only)
23
- const previousStates = state.previousStates.slice(-10).map((s) => createSnapshot(s));
22
+ // Truncate previous states and do not recursively serialize nested undo
23
+ // histories, avoiding exponential snapshot growth.
24
+ const previousStates = state.previousStates.slice(-10).map((s) => createSnapshot({
25
+ ...s,
26
+ previousStates: [],
27
+ }));
24
28
  return {
25
29
  config: state.config,
26
30
  players: [...state.players],
@@ -47,6 +51,7 @@ function createSnapshot(state) {
47
51
  timeBankActiveSeat: state.timeBankActiveSeat,
48
52
  actionHistory: Array.from(state.actionHistory),
49
53
  previousStates,
54
+ initialChips: state.initialChips,
50
55
  timestamp: state.timestamp,
51
56
  handId: state.handId,
52
57
  };
@@ -72,7 +77,8 @@ function restoreFromSnapshot(snapshot) {
72
77
  timeBanks,
73
78
  timeBankActiveSeat: snapshot.timeBankActiveSeat ?? null, // Backward compatibility
74
79
  previousStates,
75
- rakeThisHand: snapshot.rakeThisHand || 0, // Add missing field with default
80
+ initialChips: snapshot.initialChips,
81
+ rakeThisHand: snapshot.rakeThisHand ?? 0, // Add missing field with default
76
82
  };
77
83
  }
78
84
  /**
@@ -5,8 +5,8 @@
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.validateChipAmount = validateChipAmount;
7
7
  exports.validateTimestamp = validateTimestamp;
8
- const IllegalActionError_1 = require("../errors/IllegalActionError");
9
- const ErrorCodes_1 = require("../errors/ErrorCodes");
8
+ const illegal_action_error_1 = require("../errors/illegal-action-error");
9
+ const error_codes_1 = require("../errors/error-codes");
10
10
  const constants_1 = require("./constants");
11
11
  /**
12
12
  * Validate that a chip amount is a non-negative integer
@@ -18,13 +18,13 @@ const constants_1 = require("./constants");
18
18
  */
19
19
  function validateChipAmount(amount, context) {
20
20
  if (!Number.isFinite(amount)) {
21
- throw new IllegalActionError_1.IllegalActionError(ErrorCodes_1.ErrorCodes.INVALID_AMOUNT, `${context}: ${amount} is not a valid number`, { amount, context });
21
+ throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.INVALID_AMOUNT, `${context}: ${amount} is not a valid number`, { amount, context });
22
22
  }
23
23
  if (!Number.isInteger(amount)) {
24
- throw new IllegalActionError_1.IllegalActionError(ErrorCodes_1.ErrorCodes.INVALID_AMOUNT, `${context}: ${amount} must be an integer (fractional chips not allowed)`, { amount, context });
24
+ throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.INVALID_AMOUNT, `${context}: ${amount} must be an integer (fractional chips not allowed)`, { amount, context });
25
25
  }
26
26
  if (amount < 0) {
27
- throw new IllegalActionError_1.IllegalActionError(ErrorCodes_1.ErrorCodes.INVALID_AMOUNT, `${context}: ${amount} cannot be negative`, { amount, context });
27
+ throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.INVALID_AMOUNT, `${context}: ${amount} cannot be negative`, { amount, context });
28
28
  }
29
29
  }
30
30
  /**
@@ -36,17 +36,17 @@ function validateChipAmount(amount, context) {
36
36
  */
37
37
  function validateTimestamp(timestamp, previousTimestamp) {
38
38
  if (!Number.isFinite(timestamp) || timestamp < 0) {
39
- throw new IllegalActionError_1.IllegalActionError(ErrorCodes_1.ErrorCodes.INVALID_TIMESTAMP, `Invalid timestamp: ${timestamp}`, {
39
+ throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.INVALID_TIMESTAMP, `Invalid timestamp: ${timestamp}`, {
40
40
  timestamp,
41
41
  });
42
42
  }
43
43
  // Allow some clock drift tolerance for "future" timestamps
44
44
  const now = Date.now() + constants_1.TIMESTAMP_FUTURE_TOLERANCE_MS;
45
45
  if (timestamp > now) {
46
- throw new IllegalActionError_1.IllegalActionError(ErrorCodes_1.ErrorCodes.INVALID_TIMESTAMP, `Timestamp ${timestamp} is in the future (current: ${Date.now()})`, { timestamp, currentTime: Date.now() });
46
+ throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.INVALID_TIMESTAMP, `Timestamp ${timestamp} is in the future (current: ${Date.now()})`, { timestamp, currentTime: Date.now() });
47
47
  }
48
48
  // Ensure timestamps are monotonically increasing (or equal for same-time actions)
49
49
  if (previousTimestamp !== undefined && timestamp < previousTimestamp) {
50
- throw new IllegalActionError_1.IllegalActionError(ErrorCodes_1.ErrorCodes.INVALID_TIMESTAMP, `Timestamp ${timestamp} is before previous action timestamp ${previousTimestamp}`, { timestamp, previousTimestamp });
50
+ throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.INVALID_TIMESTAMP, `Timestamp ${timestamp} is before previous action timestamp ${previousTimestamp}`, { timestamp, previousTimestamp });
51
51
  }
52
52
  }
@@ -24,20 +24,30 @@ function createPublicView(state, playerId = null, version = 0) {
24
24
  hand: visibleHand,
25
25
  };
26
26
  });
27
- // Convert Map to plain object for JSON serialization
28
- const currentBetsObj = {};
29
- for (const [seat, amount] of state.currentBets.entries()) {
30
- currentBetsObj[seat] = amount;
31
- }
27
+ const currentBets = createSerializableReadonlyMap(state.currentBets);
32
28
  return {
33
29
  ...state,
34
30
  deck: [], // Always hide deck
35
31
  players: maskedPlayers,
36
- currentBets: currentBetsObj, // Serializable record
32
+ currentBets,
37
33
  viewingPlayerId: playerId,
38
34
  version,
39
35
  };
40
36
  }
37
+ function createSerializableReadonlyMap(source) {
38
+ const map = new Map(source);
39
+ Object.defineProperty(map, "toJSON", {
40
+ value: () => {
41
+ const record = {};
42
+ for (const [seat, amount] of map.entries()) {
43
+ record[seat] = amount;
44
+ }
45
+ return record;
46
+ },
47
+ enumerable: false,
48
+ });
49
+ return map;
50
+ }
41
51
  /**
42
52
  * Get visible cards for a player based on shownCards and viewer permissions
43
53
  * Returns null (all hidden), full hand, or partial hand with positional context preserved
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pokertools/engine",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "Enterprise-grade Texas Hold'em poker engine",
5
5
  "author": "A.Aurelius",
6
6
  "license": "MIT",
@@ -66,11 +66,11 @@
66
66
  "node": ">=18.0.0"
67
67
  },
68
68
  "dependencies": {
69
- "@pokertools/evaluator": "1.0.8",
70
- "@pokertools/types": "1.0.8"
69
+ "@pokertools/evaluator": "1.0.9",
70
+ "@pokertools/types": "1.0.9"
71
71
  },
72
72
  "peerDependencies": {
73
- "@pokertools/evaluator": "1.0.8",
74
- "@pokertools/types": "1.0.8"
73
+ "@pokertools/evaluator": "1.0.9",
74
+ "@pokertools/types": "1.0.9"
75
75
  }
76
76
  }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes