@pokertools/engine 1.0.0 → 1.0.4

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 (41) hide show
  1. package/README.md +590 -444
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/actions/betting.js +83 -50
  4. package/dist/actions/dealing.js +118 -27
  5. package/dist/actions/management.d.ts +12 -1
  6. package/dist/actions/management.js +86 -5
  7. package/dist/actions/special.d.ts +18 -0
  8. package/dist/actions/special.js +20 -0
  9. package/dist/actions/validation.js +27 -2
  10. package/dist/browser.d.ts +27 -0
  11. package/dist/browser.js +73 -0
  12. package/dist/engine/PokerEngine.d.ts +23 -2
  13. package/dist/engine/PokerEngine.js +54 -2
  14. package/dist/engine/gameReducer.js +6 -0
  15. package/dist/errors/ErrorCodes.d.ts +4 -35
  16. package/dist/errors/ErrorCodes.js +7 -41
  17. package/dist/errors/index.d.ts +0 -1
  18. package/dist/errors/index.js +1 -1
  19. package/dist/history/exporter.d.ts +1 -2
  20. package/dist/history/formats/json.d.ts +1 -1
  21. package/dist/history/formats/pokerstars.d.ts +1 -1
  22. package/dist/history/handHistoryBuilder.d.ts +1 -2
  23. package/dist/history/handHistoryBuilder.js +4 -1
  24. package/dist/index.d.ts +1 -1
  25. package/dist/rules/actionOrder.js +4 -4
  26. package/dist/rules/blinds.d.ts +2 -0
  27. package/dist/rules/blinds.js +27 -3
  28. package/dist/rules/headsUp.js +18 -0
  29. package/dist/rules/showdown.js +10 -0
  30. package/dist/utils/cardUtils.d.ts +2 -1
  31. package/dist/utils/cardUtils.js +2 -1
  32. package/dist/utils/invariants.js +4 -0
  33. package/dist/utils/positioning.js +2 -2
  34. package/dist/utils/serialization.d.ts +1 -0
  35. package/dist/utils/serialization.js +2 -0
  36. package/dist/utils/viewMasking.d.ts +2 -1
  37. package/dist/utils/viewMasking.js +9 -1
  38. package/package.json +30 -12
  39. package/dist/history/types.d.ts +0 -73
  40. package/dist/history/types.js +0 -5
  41. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -34,6 +34,12 @@ function gameReducer(state, action) {
34
34
  case "STAND" /* ActionType.STAND */:
35
35
  newState = (0, management_1.handleStand)(state, action);
36
36
  break;
37
+ case "ADD_CHIPS" /* ActionType.ADD_CHIPS */:
38
+ newState = (0, management_1.handleAddChips)(state, action);
39
+ break;
40
+ case "RESERVE_SEAT" /* ActionType.RESERVE_SEAT */:
41
+ newState = (0, management_1.handleReserveSeat)(state, action);
42
+ break;
37
43
  // Dealing
38
44
  case "DEAL" /* ActionType.DEAL */:
39
45
  newState = (0, dealing_1.handleDeal)(state, action);
@@ -1,38 +1,7 @@
1
1
  /**
2
- * Standardized error codes for IllegalActionError
2
+ * ErrorCodes are now managed in @pokertools/types for consistency across packages.
3
+ * This file re-exports them for backwards compatibility.
3
4
  *
4
- * Using a const enum pattern for:
5
- * - Type safety at compile time
6
- * - Autocomplete in IDEs
7
- * - Documentation of all possible error codes
8
- * - Easy refactoring
5
+ * @deprecated Import from "@pokertools/types" instead
9
6
  */
10
- export declare const ErrorCodes: {
11
- readonly INVALID_ACTION: "INVALID_ACTION";
12
- readonly PLAYER_NOT_FOUND: "PLAYER_NOT_FOUND";
13
- readonly PLAYER_NOT_ACTIVE: "PLAYER_NOT_ACTIVE";
14
- readonly NOT_YOUR_TURN: "NOT_YOUR_TURN";
15
- readonly NO_CHIPS: "NO_CHIPS";
16
- readonly CANNOT_CHECK: "CANNOT_CHECK";
17
- readonly NOTHING_TO_CALL: "NOTHING_TO_CALL";
18
- readonly CANNOT_BET: "CANNOT_BET";
19
- readonly BET_TOO_SMALL: "BET_TOO_SMALL";
20
- readonly CANNOT_RAISE: "CANNOT_RAISE";
21
- readonly CANNOT_RERAISE: "CANNOT_RERAISE";
22
- readonly RAISE_TOO_SMALL: "RAISE_TOO_SMALL";
23
- readonly CANNOT_DEAL: "CANNOT_DEAL";
24
- readonly NOT_ENOUGH_PLAYERS: "NOT_ENOUGH_PLAYERS";
25
- readonly INVALID_SEAT: "INVALID_SEAT";
26
- readonly SEAT_OCCUPIED: "SEAT_OCCUPIED";
27
- readonly INVALID_STACK: "INVALID_STACK";
28
- readonly INVALID_AMOUNT: "INVALID_AMOUNT";
29
- readonly INVALID_TIMESTAMP: "INVALID_TIMESTAMP";
30
- };
31
- /**
32
- * Type representing all valid error codes
33
- */
34
- export type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];
35
- /**
36
- * Helper to check if an error message contains a specific error code
37
- */
38
- export declare function hasErrorCode(error: Error, code: ErrorCode): boolean;
7
+ export { ErrorCodes, type ErrorCode, hasErrorCode } from "@pokertools/types";
@@ -1,46 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ErrorCodes = void 0;
4
- exports.hasErrorCode = hasErrorCode;
3
+ exports.hasErrorCode = exports.ErrorCodes = void 0;
5
4
  /**
6
- * Standardized error codes for IllegalActionError
5
+ * ErrorCodes are now managed in @pokertools/types for consistency across packages.
6
+ * This file re-exports them for backwards compatibility.
7
7
  *
8
- * Using a const enum pattern for:
9
- * - Type safety at compile time
10
- * - Autocomplete in IDEs
11
- * - Documentation of all possible error codes
12
- * - Easy refactoring
8
+ * @deprecated Import from "@pokertools/types" instead
13
9
  */
14
- exports.ErrorCodes = {
15
- // Generic errors
16
- INVALID_ACTION: "INVALID_ACTION",
17
- // Player errors
18
- PLAYER_NOT_FOUND: "PLAYER_NOT_FOUND",
19
- PLAYER_NOT_ACTIVE: "PLAYER_NOT_ACTIVE",
20
- NOT_YOUR_TURN: "NOT_YOUR_TURN",
21
- NO_CHIPS: "NO_CHIPS",
22
- // Betting action errors
23
- CANNOT_CHECK: "CANNOT_CHECK",
24
- NOTHING_TO_CALL: "NOTHING_TO_CALL",
25
- CANNOT_BET: "CANNOT_BET",
26
- BET_TOO_SMALL: "BET_TOO_SMALL",
27
- CANNOT_RAISE: "CANNOT_RAISE",
28
- CANNOT_RERAISE: "CANNOT_RERAISE",
29
- RAISE_TOO_SMALL: "RAISE_TOO_SMALL",
30
- // Deal errors
31
- CANNOT_DEAL: "CANNOT_DEAL",
32
- NOT_ENOUGH_PLAYERS: "NOT_ENOUGH_PLAYERS",
33
- // Seat errors
34
- INVALID_SEAT: "INVALID_SEAT",
35
- SEAT_OCCUPIED: "SEAT_OCCUPIED",
36
- INVALID_STACK: "INVALID_STACK",
37
- // Validation errors
38
- INVALID_AMOUNT: "INVALID_AMOUNT",
39
- INVALID_TIMESTAMP: "INVALID_TIMESTAMP",
40
- };
41
- /**
42
- * Helper to check if an error message contains a specific error code
43
- */
44
- function hasErrorCode(error, code) {
45
- return error.message.includes(code);
46
- }
10
+ var types_1 = require("@pokertools/types");
11
+ Object.defineProperty(exports, "ErrorCodes", { enumerable: true, get: function () { return types_1.ErrorCodes; } });
12
+ Object.defineProperty(exports, "hasErrorCode", { enumerable: true, get: function () { return types_1.hasErrorCode; } });
@@ -2,4 +2,3 @@ export * from "./PokerEngineError";
2
2
  export * from "./CriticalStateError";
3
3
  export * from "./IllegalActionError";
4
4
  export * from "./ConfigError";
5
- export * from "./ErrorCodes";
@@ -19,4 +19,4 @@ __exportStar(require("./PokerEngineError"), exports);
19
19
  __exportStar(require("./CriticalStateError"), exports);
20
20
  __exportStar(require("./IllegalActionError"), exports);
21
21
  __exportStar(require("./ConfigError"), exports);
22
- __exportStar(require("./ErrorCodes"), exports);
22
+ // ErrorCodes now exported from @pokertools/types
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * Main hand history exporter
3
3
  */
4
- import { GameState } from "@pokertools/types";
5
- import { HandHistory, ExportOptions } from "./types";
4
+ import { GameState, HandHistory, ExportOptions } from "@pokertools/types";
6
5
  /**
7
6
  * Export hand history from game state
8
7
  *
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Machine-readable format for analysis and storage
5
5
  */
6
- import { HandHistory, ExportOptions } from "../types";
6
+ import { HandHistory, ExportOptions } from "@pokertools/types";
7
7
  /**
8
8
  * Export hand history to JSON format
9
9
  */
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Format specification based on PokerStars hand history text format
5
5
  */
6
- import { HandHistory, ExportOptions } from "../types";
6
+ import { HandHistory, ExportOptions } from "@pokertools/types";
7
7
  /**
8
8
  * Export hand history to PokerStars format
9
9
  */
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * Build hand history from game state and action history
3
3
  */
4
- import { GameState } from "@pokertools/types";
5
- import { HandHistory } from "./types";
4
+ import { GameState, HandHistory } from "@pokertools/types";
6
5
  /**
7
6
  * Build complete hand history from final game state
8
7
  * Call this after a hand is complete (winners determined)
@@ -41,12 +41,15 @@ function buildPlayerHistory(state) {
41
41
  continue;
42
42
  // Calculate starting stack (current + invested)
43
43
  const startingStack = player.stack + player.totalInvestedThisHand;
44
+ // Only include cards if they are fully visible (no masked/null cards)
45
+ const hasMaskedCards = player.hand?.some((c) => c === null);
46
+ const cards = player.hand && !hasMaskedCards ? player.hand : undefined;
44
47
  players.push({
45
48
  seat: player.seat,
46
49
  name: player.name,
47
50
  startingStack,
48
51
  endingStack: player.stack,
49
- cards: player.hand ? [...player.hand] : undefined,
52
+ cards,
50
53
  });
51
54
  }
52
55
  return players;
package/dist/index.d.ts CHANGED
@@ -5,4 +5,4 @@ export { createSnapshot, restoreFromSnapshot, Snapshot } from "./utils/serializa
5
5
  export { createPublicView } from "./utils/viewMasking";
6
6
  export { calculateTotalChips, auditChipConservation } from "./utils/invariants";
7
7
  export { exportHandHistory, getHandHistory, exportMultipleHands } from "./history/exporter";
8
- export type { HandHistory, HandHistoryPlayer, StreetHistory, ExportOptions } from "./history/types";
8
+ export { HandHistory, HandHistoryPlayer, StreetHistory, ExportOptions } from "@pokertools/types";
@@ -33,7 +33,7 @@ function getNextToActNormal(state) {
33
33
  while (seat !== startSeat) {
34
34
  const player = state.players[seat];
35
35
  // Skip if: no player, folded, all-in, or busted
36
- if (!player || player.status !== "ACTIVE" /* PlayerStatus.ACTIVE */ || player.stack === 0) {
36
+ if (player?.status !== "ACTIVE" /* PlayerStatus.ACTIVE */ || player.stack === 0) {
37
37
  seat = (0, positioning_1.getNextSeat)(seat, state.maxPlayers);
38
38
  continue;
39
39
  }
@@ -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 || player.status !== "ACTIVE" /* PlayerStatus.ACTIVE */ || player.stack === 0) {
80
+ if (player?.status !== "ACTIVE" /* PlayerStatus.ACTIVE */ || player.stack === 0) {
81
81
  continue;
82
82
  }
83
83
  const playerBet = state.currentBets.get(seat) ?? 0;
@@ -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 && player.status === "ACTIVE" /* PlayerStatus.ACTIVE */ && player.stack > 0) {
131
+ if (player?.status === "ACTIVE" /* PlayerStatus.ACTIVE */ && player.stack > 0) {
132
132
  return seat;
133
133
  }
134
134
  seat = (0, positioning_1.getNextSeat)(seat, state.maxPlayers);
@@ -173,7 +173,7 @@ function isActionComplete(state) {
173
173
  if (activeCount === 0) {
174
174
  // Only return true if we're in a hand (not pre-deal)
175
175
  // Check: Are there all-in players with bets?
176
- const allInPlayers = state.players.filter((p) => p && p.status === "ALL_IN" /* PlayerStatus.ALL_IN */);
176
+ const allInPlayers = state.players.filter((p) => p?.status === "ALL_IN" /* PlayerStatus.ALL_IN */);
177
177
  return allInPlayers.length > 0 && state.currentBets.size > 0;
178
178
  }
179
179
  return activeCount > 0 && activeCount === actedCount;
@@ -16,6 +16,8 @@ export interface BlindPositions {
16
16
  * Normal (Dead Button Rule):
17
17
  * - SB = Button + 1 (Can be empty -> Dead Small Blind)
18
18
  * - BB = Next Occupied Seat after SB
19
+ *
20
+ * For cash games: Skips sitting-out players (unless they are in tournament mode)
19
21
  */
20
22
  export declare function getBlindPositions(state: GameState): BlindPositions | null;
21
23
  /**
@@ -14,15 +14,18 @@ const headsUp_1 = require("./headsUp");
14
14
  * Normal (Dead Button Rule):
15
15
  * - SB = Button + 1 (Can be empty -> Dead Small Blind)
16
16
  * - BB = Next Occupied Seat after SB
17
+ *
18
+ * For cash games: Skips sitting-out players (unless they are in tournament mode)
17
19
  */
18
20
  function getBlindPositions(state) {
19
21
  if (state.buttonSeat === null) {
20
22
  return null;
21
23
  }
22
24
  const buttonSeat = state.buttonSeat;
25
+ const isTournament = !!state.config.blindStructure;
23
26
  // Heads-up specific logic (Button is SB)
24
27
  if ((0, headsUp_1.isHeadsUp)(state)) {
25
- const bbSeat = (0, positioning_1.getNextOccupiedSeat)(buttonSeat, state.players, state.maxPlayers);
28
+ const bbSeat = getNextActiveOrOccupiedSeat(buttonSeat, state.players, state.maxPlayers, isTournament);
26
29
  if (bbSeat === null) {
27
30
  return null;
28
31
  }
@@ -34,8 +37,10 @@ function getBlindPositions(state) {
34
37
  // Normal Play (Dead Button / Dead Small Blind Logic)
35
38
  // 1. SB is ALWAYS the immediate next seat, even if empty
36
39
  const sbSeat = (0, positioning_1.getNextSeat)(buttonSeat, state.maxPlayers);
37
- // 2. BB is the next ACTIVE/OCCUPIED player after the SB position
38
- const bbSeat = (0, positioning_1.getNextOccupiedSeat)(sbSeat, state.players, state.maxPlayers);
40
+ // 2. BB is the next ACTIVE player after the SB position
41
+ // In cash games, skip sitting-out players
42
+ // In tournaments, include sitting-out players (they must post blinds)
43
+ const bbSeat = getNextActiveOrOccupiedSeat(sbSeat, state.players, state.maxPlayers, isTournament);
39
44
  if (bbSeat === null) {
40
45
  return null;
41
46
  }
@@ -44,6 +49,25 @@ function getBlindPositions(state) {
44
49
  bigBlindSeat: bbSeat,
45
50
  };
46
51
  }
52
+ /**
53
+ * Get next seat that is occupied and (if cash game) not sitting out
54
+ */
55
+ function getNextActiveOrOccupiedSeat(currentSeat, players, maxPlayers, isTournament) {
56
+ let seat = (0, positioning_1.getNextSeat)(currentSeat, maxPlayers);
57
+ const startSeat = currentSeat;
58
+ while (seat !== startSeat) {
59
+ const player = players[seat];
60
+ if (player !== null && player.stack > 0) {
61
+ // In tournaments, include sitting-out players (they must blind off)
62
+ // In cash games, skip sitting-out players
63
+ if (isTournament || !player.isSittingOut) {
64
+ return seat;
65
+ }
66
+ }
67
+ seat = (0, positioning_1.getNextSeat)(seat, maxPlayers);
68
+ }
69
+ return null; // No eligible seats
70
+ }
47
71
  /**
48
72
  * Calculate blind amounts for antes
49
73
  */
@@ -32,6 +32,24 @@ function getHeadsUpActionOrder(state, street) {
32
32
  }
33
33
  // Find the two seats
34
34
  const [seat1, seat2] = activePlayers.sort((a, b) => a - b);
35
+ // Check if button is one of the active players
36
+ const isButtonActive = activePlayers.includes(buttonSeat);
37
+ if (!isButtonActive) {
38
+ // Dead button scenario - button is not one of the active players
39
+ // In this case, the "button" for action purposes is the first active player
40
+ // after the actual button position
41
+ const effectiveButton = seat1 > buttonSeat || seat2 < buttonSeat ? seat1 : seat2;
42
+ const otherSeat = effectiveButton === seat1 ? seat2 : seat1;
43
+ if (street === "PREFLOP" /* Street.PREFLOP */) {
44
+ // Effective button acts first preflop
45
+ return [effectiveButton, otherSeat];
46
+ }
47
+ else {
48
+ // Effective button acts last postflop
49
+ return [otherSeat, effectiveButton];
50
+ }
51
+ }
52
+ // Normal case: button is one of the active players
35
53
  const otherSeat = seat1 === buttonSeat ? seat2 : seat1;
36
54
  if (street === "PREFLOP" /* Street.PREFLOP */) {
37
55
  // Button acts first preflop
@@ -23,6 +23,9 @@ function determineWinners(state) {
23
23
  });
24
24
  for (const pot of sortedPots) {
25
25
  const potWinners = evaluatePot(state, pot);
26
+ if (potWinners.length === 0) {
27
+ continue;
28
+ }
26
29
  // Calculate and deduct rake (cash games only)
27
30
  // Apply GLOBAL rake cap across all pots (per-hand, not per-pot)
28
31
  const { rake } = (0, rake_1.calculateRake)(state, pot.amount, totalRake);
@@ -122,6 +125,10 @@ function evaluatePot(state, pot) {
122
125
  for (const player of eligible) {
123
126
  if (!player?.hand)
124
127
  continue;
128
+ // Skip masked hands (client mode)
129
+ if (player.hand.some((c) => c === null)) {
130
+ continue;
131
+ }
125
132
  // Combine hole cards + board (7 cards total for river)
126
133
  const allCards = [...player.hand, ...state.board];
127
134
  if (allCards.length < 5) {
@@ -140,6 +147,9 @@ function evaluatePot(state, pot) {
140
147
  description,
141
148
  });
142
149
  }
150
+ if (evaluations.length === 0) {
151
+ return [];
152
+ }
143
153
  // Find best hand(s)
144
154
  const bestScore = Math.min(...evaluations.map((e) => e.score));
145
155
  const winners = evaluations.filter((e) => e.score === bestScore);
@@ -4,8 +4,9 @@
4
4
  export declare function cardCodesToStrings(codes: readonly number[]): string[];
5
5
  /**
6
6
  * Convert string card array to integer codes
7
+ * Filters out null (masked) cards
7
8
  */
8
- export declare function cardStringsToCards(cards: readonly string[]): number[];
9
+ export declare function cardStringsToCards(cards: ReadonlyArray<string | null>): number[];
9
10
  /**
10
11
  * Validate card string format
11
12
  */
@@ -12,9 +12,10 @@ function cardCodesToStrings(codes) {
12
12
  }
13
13
  /**
14
14
  * Convert string card array to integer codes
15
+ * Filters out null (masked) cards
15
16
  */
16
17
  function cardStringsToCards(cards) {
17
- return cards.map((card) => (0, evaluator_1.getCardCode)(card));
18
+ return cards.filter((c) => c !== null).map((card) => (0, evaluator_1.getCardCode)(card));
18
19
  }
19
20
  /**
20
21
  * Validate card string format
@@ -105,6 +105,10 @@ function getInitialChips(state) {
105
105
  * Checks multiple invariants beyond just chip conservation
106
106
  */
107
107
  function validateGameStateIntegrity(state) {
108
+ // Skip strict integrity checks in client mode to prevent UI crashes on minor sync issues
109
+ if (state.config.isClient) {
110
+ return;
111
+ }
108
112
  // 1. Chip conservation
109
113
  const initialChips = getInitialChips(state);
110
114
  auditChipConservation(state, initialChips);
@@ -30,7 +30,7 @@ function getActivePlayers(state) {
30
30
  const active = [];
31
31
  for (let i = 0; i < state.players.length; i++) {
32
32
  const player = state.players[i];
33
- if (player && player.status === "ACTIVE" /* PlayerStatus.ACTIVE */ && player.stack > 0) {
33
+ if (player?.status === "ACTIVE" /* PlayerStatus.ACTIVE */ && player.stack > 0) {
34
34
  active.push(i);
35
35
  }
36
36
  }
@@ -70,7 +70,7 @@ function getNextActionableSeat(currentSeat, state) {
70
70
  const startSeat = currentSeat;
71
71
  while (seat !== startSeat) {
72
72
  const player = state.players[seat];
73
- if (player && player.status === "ACTIVE" /* PlayerStatus.ACTIVE */ && player.stack > 0) {
73
+ if (player?.status === "ACTIVE" /* PlayerStatus.ACTIVE */ && player.stack > 0) {
74
74
  return seat;
75
75
  }
76
76
  seat = getNextSeat(seat, state.maxPlayers);
@@ -25,6 +25,7 @@ export interface Snapshot {
25
25
  readonly ante: number;
26
26
  readonly blindLevel: number;
27
27
  readonly timeBanks: Record<number, number>;
28
+ readonly timeBankActiveSeat: number | null;
28
29
  readonly actionHistory: ActionRecord[];
29
30
  readonly previousStates: Snapshot[];
30
31
  readonly timestamp: number;
@@ -44,6 +44,7 @@ function createSnapshot(state) {
44
44
  ante: state.ante,
45
45
  blindLevel: state.blindLevel,
46
46
  timeBanks,
47
+ timeBankActiveSeat: state.timeBankActiveSeat,
47
48
  actionHistory: Array.from(state.actionHistory),
48
49
  previousStates,
49
50
  timestamp: state.timestamp,
@@ -69,6 +70,7 @@ function restoreFromSnapshot(snapshot) {
69
70
  ...snapshot,
70
71
  currentBets,
71
72
  timeBanks,
73
+ timeBankActiveSeat: snapshot.timeBankActiveSeat ?? null, // Backward compatibility
72
74
  previousStates,
73
75
  rakeThisHand: snapshot.rakeThisHand || 0, // Add missing field with default
74
76
  };
@@ -6,9 +6,10 @@ import { GameState, PublicState } from "@pokertools/types";
6
6
  *
7
7
  * @param state Full game state
8
8
  * @param playerId Player requesting view (null = spectator)
9
+ * @param version State version number (defaults to 0 if not provided)
9
10
  * @returns Masked public state
10
11
  */
11
- export declare function createPublicView(state: GameState, playerId?: string | null): PublicState;
12
+ export declare function createPublicView(state: GameState, playerId?: string | null, version?: number): PublicState;
12
13
  /**
13
14
  * Create spectator view (no player-specific information)
14
15
  */
@@ -10,9 +10,10 @@ exports.sanitizeActionHistory = sanitizeActionHistory;
10
10
  *
11
11
  * @param state Full game state
12
12
  * @param playerId Player requesting view (null = spectator)
13
+ * @param version State version number (defaults to 0 if not provided)
13
14
  * @returns Masked public state
14
15
  */
15
- function createPublicView(state, playerId = null) {
16
+ function createPublicView(state, playerId = null, version = 0) {
16
17
  const maskedPlayers = state.players.map((player, _seat) => {
17
18
  if (!player)
18
19
  return null;
@@ -23,11 +24,18 @@ function createPublicView(state, playerId = null) {
23
24
  hand: visibleHand,
24
25
  };
25
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
+ }
26
32
  return {
27
33
  ...state,
28
34
  deck: [], // Always hide deck
29
35
  players: maskedPlayers,
36
+ currentBets: currentBetsObj, // Serializable record
30
37
  viewingPlayerId: playerId,
38
+ version,
31
39
  };
32
40
  }
33
41
  /**
package/package.json CHANGED
@@ -1,7 +1,9 @@
1
1
  {
2
2
  "name": "@pokertools/engine",
3
- "version": "1.0.0",
3
+ "version": "1.0.4",
4
4
  "description": "Enterprise-grade Texas Hold'em poker engine",
5
+ "author": "A.Aurelius",
6
+ "license": "MIT",
5
7
  "main": "dist/index.js",
6
8
  "types": "dist/index.d.ts",
7
9
  "exports": {
@@ -10,16 +12,32 @@
10
12
  "require": "./dist/index.js",
11
13
  "import": "./dist/index.js",
12
14
  "default": "./dist/index.js"
15
+ },
16
+ "./browser": {
17
+ "types": "./dist/browser.d.ts",
18
+ "browser": "./dist/browser.js",
19
+ "require": "./dist/browser.js",
20
+ "import": "./dist/browser.js",
21
+ "default": "./dist/browser.js"
13
22
  }
14
23
  },
24
+ "browser": "./dist/browser.js",
15
25
  "files": [
16
26
  "dist",
17
27
  "README.md",
18
28
  "LICENSE"
19
29
  ],
30
+ "sideEffects": false,
20
31
  "scripts": {
21
32
  "build": "tsc",
22
- "test": "NODE_OPTIONS='--no-warnings' jest"
33
+ "build:watch": "tsc --watch",
34
+ "clean": "rm -rf dist coverage",
35
+ "typecheck": "tsc --noEmit",
36
+ "lint": "eslint . --ext .ts",
37
+ "lint:fix": "eslint . --ext .ts --fix",
38
+ "test": "NODE_OPTIONS='--no-warnings' jest",
39
+ "test:watch": "NODE_OPTIONS='--no-warnings' jest --watch",
40
+ "test:coverage": "NODE_OPTIONS='--no-warnings' jest --coverage"
23
41
  },
24
42
  "keywords": [
25
43
  "poker",
@@ -27,10 +45,11 @@
27
45
  "engine",
28
46
  "game-engine",
29
47
  "redux",
30
- "immutable"
48
+ "immutable",
49
+ "state-management",
50
+ "tournament",
51
+ "cash-game"
31
52
  ],
32
- "author": "A.Aurelius",
33
- "license": "MIT",
34
53
  "repository": {
35
54
  "type": "git",
36
55
  "url": "https://github.com/aaurelions/pokertools.git",
@@ -43,16 +62,15 @@
43
62
  "publishConfig": {
44
63
  "access": "public"
45
64
  },
65
+ "engines": {
66
+ "node": ">=18.0.0"
67
+ },
46
68
  "dependencies": {
47
69
  "@pokertools/evaluator": "*",
48
70
  "@pokertools/types": "*"
49
71
  },
50
- "devDependencies": {
51
- "@types/jest": "^30.0.0",
52
- "@types/node": "^24.10.1",
53
- "fast-check": "^3.15.0",
54
- "jest": "^30.2.0",
55
- "ts-jest": "^29.4.5",
56
- "typescript": "^5.9.3"
72
+ "peerDependencies": {
73
+ "@pokertools/evaluator": "*",
74
+ "@pokertools/types": "*"
57
75
  }
58
76
  }
@@ -1,73 +0,0 @@
1
- /**
2
- * Hand history types
3
- */
4
- import { Street, Action } from "@pokertools/types";
5
- /**
6
- * Hand history record
7
- * Contains complete information about a single hand
8
- */
9
- export interface HandHistory {
10
- readonly handId: string;
11
- readonly timestamp: number;
12
- readonly tableName: string;
13
- readonly gameType: "Cash" | "Tournament";
14
- readonly stakes: {
15
- readonly smallBlind: number;
16
- readonly bigBlind: number;
17
- readonly ante: number;
18
- };
19
- readonly maxPlayers: number;
20
- readonly buttonSeat: number;
21
- readonly players: readonly HandHistoryPlayer[];
22
- readonly streets: readonly StreetHistory[];
23
- readonly winners: readonly WinnerRecord[];
24
- readonly totalPot: number;
25
- }
26
- /**
27
- * Player information in hand history
28
- */
29
- export interface HandHistoryPlayer {
30
- readonly seat: number;
31
- readonly name: string;
32
- readonly startingStack: number;
33
- readonly endingStack: number;
34
- readonly cards?: readonly string[];
35
- }
36
- /**
37
- * History for a single street
38
- */
39
- export interface StreetHistory {
40
- readonly street: Street;
41
- readonly board: readonly string[];
42
- readonly actions: readonly ActionRecord[];
43
- readonly pot: number;
44
- }
45
- /**
46
- * Action record in history
47
- */
48
- export interface ActionRecord {
49
- readonly seat: number;
50
- readonly playerName: string;
51
- readonly action: Action;
52
- readonly amount?: number;
53
- readonly isAllIn?: boolean;
54
- readonly timestamp: number;
55
- }
56
- /**
57
- * Winner record
58
- */
59
- export interface WinnerRecord {
60
- readonly seat: number;
61
- readonly playerName: string;
62
- readonly amount: number;
63
- readonly hand?: readonly string[];
64
- readonly handRank?: string;
65
- }
66
- /**
67
- * Hand history format options
68
- */
69
- export interface ExportOptions {
70
- readonly format: "pokerstars" | "json" | "compact";
71
- readonly includeHoleCards?: boolean;
72
- readonly timezone?: string;
73
- }
@@ -1,5 +0,0 @@
1
- "use strict";
2
- /**
3
- * Hand history types
4
- */
5
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1 +0,0 @@
1
- {"root":["../src/index.ts","../src/actions/betting.ts","../src/actions/dealing.ts","../src/actions/management.ts","../src/actions/showdownActions.ts","../src/actions/special.ts","../src/actions/streetProgression.ts","../src/actions/tournament.ts","../src/actions/validation.ts","../src/engine/PokerEngine.ts","../src/engine/gameReducer.ts","../src/errors/ConfigError.ts","../src/errors/CriticalStateError.ts","../src/errors/ErrorCodes.ts","../src/errors/IllegalActionError.ts","../src/errors/PokerEngineError.ts","../src/errors/index.ts","../src/history/exporter.ts","../src/history/handHistoryBuilder.ts","../src/history/types.ts","../src/history/formats/json.ts","../src/history/formats/pokerstars.ts","../src/rules/actionOrder.ts","../src/rules/blinds.ts","../src/rules/headsUp.ts","../src/rules/showdown.ts","../src/rules/sidePots.ts","../src/utils/cardUtils.ts","../src/utils/constants.ts","../src/utils/deck.ts","../src/utils/invariants.ts","../src/utils/positioning.ts","../src/utils/rake.ts","../src/utils/serialization.ts","../src/utils/validation.ts","../src/utils/viewMasking.ts"],"version":"5.9.3"}