@pokertools/engine 1.0.8 → 1.0.10

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 (55) hide show
  1. package/README.md +8 -5
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/actions/betting.js +23 -73
  4. package/dist/actions/dealing.js +29 -56
  5. package/dist/actions/management.js +3 -1
  6. package/dist/actions/{showdownActions.js → showdown-actions.js} +13 -27
  7. package/dist/actions/special.js +20 -7
  8. package/dist/actions/{streetProgression.js → street-progression.js} +16 -31
  9. package/dist/actions/tournament.js +1 -4
  10. package/dist/actions/validation.js +48 -33
  11. package/dist/browser.d.ts +5 -5
  12. package/dist/browser.js +6 -7
  13. package/dist/engine/{gameReducer.js → game-reducer.js} +16 -32
  14. package/dist/engine/{PokerEngine.js → poker-engine.js} +16 -30
  15. package/dist/errors/{ConfigError.d.ts → config-error.d.ts} +1 -1
  16. package/dist/errors/{ConfigError.js → config-error.js} +2 -2
  17. package/dist/errors/{CriticalStateError.d.ts → critical-state-error.d.ts} +1 -1
  18. package/dist/errors/{CriticalStateError.js → critical-state-error.js} +2 -2
  19. package/dist/errors/{IllegalActionError.d.ts → illegal-action-error.d.ts} +2 -2
  20. package/dist/errors/{IllegalActionError.js → illegal-action-error.js} +2 -2
  21. package/dist/errors/index.d.ts +4 -4
  22. package/dist/errors/index.js +4 -6
  23. package/dist/errors/{PokerEngineError.js → poker-engine-error.js} +2 -0
  24. package/dist/history/exporter.js +4 -4
  25. package/dist/history/formats/pokerstars.js +0 -8
  26. package/dist/history/{handHistoryBuilder.js → hand-history-builder.js} +1 -2
  27. package/dist/index.d.ts +2 -2
  28. package/dist/index.js +4 -5
  29. package/dist/rules/{actionOrder.js → action-order.js} +12 -17
  30. package/dist/rules/blinds.js +3 -3
  31. package/dist/rules/{headsUp.js → heads-up.js} +0 -1
  32. package/dist/rules/showdown.js +45 -29
  33. package/dist/rules/{sidePots.js → side-pots.js} +7 -20
  34. package/dist/utils/deck.js +1 -4
  35. package/dist/utils/invariants.js +16 -10
  36. package/dist/utils/rake.js +6 -8
  37. package/dist/utils/serialization.d.ts +1 -0
  38. package/dist/utils/serialization.js +10 -5
  39. package/dist/utils/validation.js +8 -8
  40. package/dist/utils/{viewMasking.js → view-masking.js} +16 -6
  41. package/package.json +6 -6
  42. package/dist/errors/ErrorCodes.d.ts +0 -7
  43. package/dist/errors/ErrorCodes.js +0 -12
  44. /package/dist/actions/{showdownActions.d.ts → showdown-actions.d.ts} +0 -0
  45. /package/dist/actions/{streetProgression.d.ts → street-progression.d.ts} +0 -0
  46. /package/dist/engine/{gameReducer.d.ts → game-reducer.d.ts} +0 -0
  47. /package/dist/engine/{PokerEngine.d.ts → poker-engine.d.ts} +0 -0
  48. /package/dist/errors/{PokerEngineError.d.ts → poker-engine-error.d.ts} +0 -0
  49. /package/dist/history/{handHistoryBuilder.d.ts → hand-history-builder.d.ts} +0 -0
  50. /package/dist/rules/{actionOrder.d.ts → action-order.d.ts} +0 -0
  51. /package/dist/rules/{headsUp.d.ts → heads-up.d.ts} +0 -0
  52. /package/dist/rules/{sidePots.d.ts → side-pots.d.ts} +0 -0
  53. /package/dist/utils/{cardUtils.d.ts → card-utils.d.ts} +0 -0
  54. /package/dist/utils/{cardUtils.js → card-utils.js} +0 -0
  55. /package/dist/utils/{viewMasking.d.ts → view-masking.d.ts} +0 -0
@@ -5,11 +5,11 @@ const validation_1 = require("../actions/validation");
5
5
  const dealing_1 = require("../actions/dealing");
6
6
  const betting_1 = require("../actions/betting");
7
7
  const management_1 = require("../actions/management");
8
- const showdownActions_1 = require("../actions/showdownActions");
8
+ const showdown_actions_1 = require("../actions/showdown-actions");
9
9
  const tournament_1 = require("../actions/tournament");
10
10
  const special_1 = require("../actions/special");
11
- const streetProgression_1 = require("../actions/streetProgression");
12
- const sidePots_1 = require("../rules/sidePots");
11
+ const street_progression_1 = require("../actions/street-progression");
12
+ const side_pots_1 = require("../rules/side-pots");
13
13
  const showdown_1 = require("../rules/showdown");
14
14
  const invariants_1 = require("../utils/invariants");
15
15
  const constants_1 = require("../utils/constants");
@@ -22,12 +22,9 @@ const constants_1 = require("../utils/constants");
22
22
  * @returns New game state
23
23
  */
24
24
  function gameReducer(state, action) {
25
- // Validate action
26
25
  (0, validation_1.validateAction)(state, action);
27
- // Apply action based on type
28
26
  let newState;
29
27
  switch (action.type) {
30
- // Management actions
31
28
  case "SIT" /* ActionType.SIT */:
32
29
  newState = (0, management_1.handleSit)(state, action);
33
30
  break;
@@ -40,11 +37,9 @@ function gameReducer(state, action) {
40
37
  case "RESERVE_SEAT" /* ActionType.RESERVE_SEAT */:
41
38
  newState = (0, management_1.handleReserveSeat)(state, action);
42
39
  break;
43
- // Dealing
44
40
  case "DEAL" /* ActionType.DEAL */:
45
41
  newState = (0, dealing_1.handleDeal)(state, action);
46
42
  break;
47
- // Betting actions
48
43
  case "FOLD" /* ActionType.FOLD */:
49
44
  newState = (0, betting_1.handleFold)(state, action);
50
45
  break;
@@ -54,14 +49,12 @@ function gameReducer(state, action) {
54
49
  case "CALL" /* ActionType.CALL */:
55
50
  newState = (0, betting_1.handleCall)(state, action);
56
51
  break;
52
+ // Auto-convert BET to RAISE/CALL when there's already a wager.
53
+ // Note: action.amount is the TOTAL bet, not the increment.
57
54
  case "BET" /* ActionType.BET */:
58
- // Auto-convert BET to appropriate action if there's already a bet
59
- // This handles UI implementations that don't distinguish between BET/CALL/RAISE buttons
60
- // Note: action.amount is the TOTAL bet size, not the amount to add
61
55
  const currentBet = Math.max(...Array.from(state.currentBets.values()), 0);
62
56
  if (currentBet > 0 && "amount" in action) {
63
57
  if (action.amount === currentBet) {
64
- // Amount equals current bet -> Convert to CALL
65
58
  const callAction = {
66
59
  type: "CALL" /* ActionType.CALL */,
67
60
  playerId: action.playerId,
@@ -70,7 +63,6 @@ function gameReducer(state, action) {
70
63
  newState = (0, betting_1.handleCall)(state, callAction);
71
64
  }
72
65
  else if (action.amount > currentBet) {
73
- // Amount exceeds current bet -> Convert to RAISE
74
66
  const raiseAction = {
75
67
  type: "RAISE" /* ActionType.RAISE */,
76
68
  playerId: action.playerId,
@@ -80,7 +72,7 @@ function gameReducer(state, action) {
80
72
  newState = (0, betting_1.handleRaise)(state, raiseAction);
81
73
  }
82
74
  else {
83
- // Amount is less than current bet -> Keep as BET (will fail validation)
75
+ // amount < currentBet: pass through (will fail validation)
84
76
  newState = (0, betting_1.handleBet)(state, action);
85
77
  }
86
78
  }
@@ -91,14 +83,12 @@ function gameReducer(state, action) {
91
83
  case "RAISE" /* ActionType.RAISE */:
92
84
  newState = (0, betting_1.handleRaise)(state, action);
93
85
  break;
94
- // Showdown actions
95
86
  case "SHOW" /* ActionType.SHOW */:
96
- newState = (0, showdownActions_1.handleShow)(state, action);
87
+ newState = (0, showdown_actions_1.handleShow)(state, action);
97
88
  break;
98
89
  case "MUCK" /* ActionType.MUCK */:
99
- newState = (0, showdownActions_1.handleMuck)(state, action);
90
+ newState = (0, showdown_actions_1.handleMuck)(state, action);
100
91
  break;
101
- // Special actions
102
92
  case "TIMEOUT" /* ActionType.TIMEOUT */:
103
93
  newState = (0, special_1.handleTimeout)(state, action);
104
94
  break;
@@ -109,30 +99,24 @@ function gameReducer(state, action) {
109
99
  newState = (0, tournament_1.handleNextBlindLevel)(state, action);
110
100
  break;
111
101
  default:
112
- // Unknown action type
113
102
  newState = state;
114
103
  break;
115
104
  }
116
- // Check if we should progress to next street
117
- if ((0, streetProgression_1.shouldProgressStreet)(newState)) {
118
- // Recalculate pots before progressing
119
- newState = (0, sidePots_1.recalculatePots)(newState);
120
- newState = (0, streetProgression_1.progressStreet)(newState);
105
+ // Recalculate pots, then progress street.
106
+ if ((0, street_progression_1.shouldProgressStreet)(newState)) {
107
+ newState = (0, side_pots_1.recalculatePots)(newState);
108
+ newState = (0, street_progression_1.progressStreet)(newState);
121
109
  }
122
- // Check if we should go to showdown
123
110
  if ((0, showdown_1.shouldShowdown)(newState)) {
124
- newState = {
125
- ...newState,
126
- street: newState.street, // Keep current street for showdown
127
- };
128
111
  newState = (0, showdown_1.determineWinners)(newState);
129
112
  }
130
- // Audit chip conservation (throws on failure)
131
- // Enabled by default, can be disabled via config (not recommended for production)
113
+ if (newState.actionTo !== null && newState.players[newState.actionTo]?.isSittingOut) {
114
+ newState = { ...newState, actionTo: null };
115
+ }
116
+ // Integrity check; can be disabled via config (not recommended for production).
132
117
  if (newState.config.validateIntegrity !== false) {
133
118
  (0, invariants_1.validateGameStateIntegrity)(newState);
134
119
  }
135
- // Add to previous states for undo
136
120
  const previousStates = [...newState.previousStates, state].slice(-constants_1.MAX_UNDO_HISTORY);
137
121
  return {
138
122
  ...newState,
@@ -1,10 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PokerEngine = void 0;
4
- const gameReducer_1 = require("./gameReducer");
5
- const viewMasking_1 = require("../utils/viewMasking");
4
+ const game_reducer_1 = require("./game-reducer");
5
+ const view_masking_1 = require("../utils/view-masking");
6
6
  const serialization_1 = require("../utils/serialization");
7
- const ConfigError_1 = require("../errors/ConfigError");
7
+ const config_error_1 = require("../errors/config-error");
8
8
  const exporter_1 = require("../history/exporter");
9
9
  const validation_1 = require("../utils/validation");
10
10
  /**
@@ -12,20 +12,18 @@ const validation_1 = require("../utils/validation");
12
12
  * Wraps the pure reducer with a stateful API
13
13
  */
14
14
  class PokerEngine {
15
+ currentState;
16
+ listeners = [];
17
+ timeProvider;
15
18
  constructor(config, timeProvider = () => Date.now()) {
16
- this.listeners = [];
17
- // Validate config
18
19
  this.validateConfig(config);
19
- // Initialize time provider
20
20
  this.timeProvider = timeProvider;
21
- // Initialize state
22
21
  this.currentState = this.createInitialState(config);
23
22
  }
24
23
  /**
25
24
  * Add a player to the table
26
25
  */
27
26
  sit(seat, id, name, stack) {
28
- // Validate chip amount is a non-negative integer
29
27
  (0, validation_1.validateChipAmount)(stack, "Sit stack");
30
28
  const action = {
31
29
  type: "SIT" /* ActionType.SIT */,
@@ -63,13 +61,10 @@ class PokerEngine {
63
61
  * If action.timestamp is not provided, the engine will automatically set it
64
62
  */
65
63
  act(action) {
66
- // Ensure timestamp is set
67
64
  const timestamp = action.timestamp ?? this.timeProvider();
68
- // Validate timestamp if provided by caller
69
65
  if (action.timestamp !== undefined) {
70
66
  (0, validation_1.validateTimestamp)(timestamp, this.currentState.timestamp);
71
67
  }
72
- // Validate chip amounts for betting actions
73
68
  if ("amount" in action && typeof action.amount === "number") {
74
69
  (0, validation_1.validateChipAmount)(action.amount, `${action.type} amount`);
75
70
  }
@@ -86,10 +81,8 @@ class PokerEngine {
86
81
  */
87
82
  validate(action) {
88
83
  try {
89
- // Dry-run the reducer
90
- // We don't need to deep clone state because reducer is immutable
91
- // and pure, and we discard the result.
92
- (0, gameReducer_1.gameReducer)(this.currentState, action);
84
+ // Dry-run the reducer; we discard the result since it's immutable and pure.
85
+ (0, game_reducer_1.gameReducer)(this.currentState, action);
93
86
  return { valid: true };
94
87
  }
95
88
  catch (err) {
@@ -110,11 +103,8 @@ class PokerEngine {
110
103
  // Hydrate PublicState into GameState if needed
111
104
  const newState = {
112
105
  ...serverState,
113
- // Ensure deck exists (empty for client/public state)
114
106
  deck: "deck" in serverState ? serverState.deck : [],
115
- // Ensure players map correctly (PublicPlayer.hand is compatible with Player.hand)
116
- players: serverState.players, // Type assertion needed due to deep readonly/mutable mismatch potential
117
- // Ensure config carries isClient flag if set locally
107
+ players: serverState.players,
118
108
  config: {
119
109
  ...serverState.config,
120
110
  isClient: this.currentState.config.isClient,
@@ -129,7 +119,7 @@ class PokerEngine {
129
119
  optimisticAct(action) {
130
120
  const timestamp = action.timestamp ?? this.timeProvider();
131
121
  const actionWithTimestamp = { ...action, timestamp };
132
- return (0, gameReducer_1.gameReducer)(this.currentState, actionWithTimestamp);
122
+ return (0, game_reducer_1.gameReducer)(this.currentState, actionWithTimestamp);
133
123
  }
134
124
  /**
135
125
  * Undo last action
@@ -152,7 +142,7 @@ class PokerEngine {
152
142
  * Get player view (masked)
153
143
  */
154
144
  view(playerId, version) {
155
- return (0, viewMasking_1.createPublicView)(this.currentState, playerId ?? null, version ?? 0);
145
+ return (0, view_masking_1.createPublicView)(this.currentState, playerId ?? null, version ?? 0);
156
146
  }
157
147
  /**
158
148
  * Get snapshot for serialization
@@ -174,7 +164,6 @@ class PokerEngine {
174
164
  */
175
165
  on(callback) {
176
166
  this.listeners.push(callback);
177
- // Return unsubscribe function
178
167
  return () => {
179
168
  const index = this.listeners.indexOf(callback);
180
169
  if (index > -1) {
@@ -191,9 +180,8 @@ class PokerEngine {
191
180
  }
192
181
  const nextLevel = this.currentState.blindLevel + 1;
193
182
  if (nextLevel >= this.currentState.config.blindStructure.length) {
194
- return; // At max level
183
+ return;
195
184
  }
196
- // Dispatch NEXT_BLIND_LEVEL action to notify listeners
197
185
  this.dispatch({
198
186
  type: "NEXT_BLIND_LEVEL" /* ActionType.NEXT_BLIND_LEVEL */,
199
187
  timestamp: this.timeProvider(),
@@ -222,14 +210,12 @@ class PokerEngine {
222
210
  dispatch(action) {
223
211
  const oldState = this.currentState;
224
212
  try {
225
- this.currentState = (0, gameReducer_1.gameReducer)(this.currentState, action);
226
- // Notify listeners
213
+ this.currentState = (0, game_reducer_1.gameReducer)(this.currentState, action);
227
214
  for (const listener of this.listeners) {
228
215
  listener(action, oldState, this.currentState);
229
216
  }
230
217
  }
231
218
  catch (error) {
232
- // Re-throw error but keep old state
233
219
  throw error;
234
220
  }
235
221
  }
@@ -238,19 +224,19 @@ class PokerEngine {
238
224
  */
239
225
  validateConfig(config) {
240
226
  if (config.smallBlind <= 0) {
241
- throw new ConfigError_1.ConfigError("Small blind must be positive", {
227
+ throw new config_error_1.ConfigError("Small blind must be positive", {
242
228
  smallBlind: config.smallBlind,
243
229
  });
244
230
  }
245
231
  if (config.bigBlind <= config.smallBlind) {
246
- throw new ConfigError_1.ConfigError("Big blind must be greater than small blind", {
232
+ throw new config_error_1.ConfigError("Big blind must be greater than small blind", {
247
233
  smallBlind: config.smallBlind,
248
234
  bigBlind: config.bigBlind,
249
235
  });
250
236
  }
251
237
  const maxPlayers = config.maxPlayers ?? 9;
252
238
  if (maxPlayers < 2 || maxPlayers > 10) {
253
- throw new ConfigError_1.ConfigError("Max players must be between 2 and 10", {
239
+ throw new config_error_1.ConfigError("Max players must be between 2 and 10", {
254
240
  maxPlayers,
255
241
  });
256
242
  }
@@ -1,4 +1,4 @@
1
- import { PokerEngineError } from "./PokerEngineError";
1
+ import { PokerEngineError } from "./poker-engine-error";
2
2
  /**
3
3
  * Error indicating invalid configuration
4
4
  * Should fail at engine initialization
@@ -1,12 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ConfigError = void 0;
4
- const PokerEngineError_1 = require("./PokerEngineError");
4
+ const poker_engine_error_1 = require("./poker-engine-error");
5
5
  /**
6
6
  * Error indicating invalid configuration
7
7
  * Should fail at engine initialization
8
8
  */
9
- class ConfigError extends PokerEngineError_1.PokerEngineError {
9
+ class ConfigError extends poker_engine_error_1.PokerEngineError {
10
10
  constructor(message, context = {}) {
11
11
  super("INVALID_CONFIG", message, context);
12
12
  this.name = "ConfigError";
@@ -1,4 +1,4 @@
1
- import { PokerEngineError } from "./PokerEngineError";
1
+ import { PokerEngineError } from "./poker-engine-error";
2
2
  /**
3
3
  * Critical error indicating state corruption or invariant violation
4
4
  * When this occurs, the table should be FROZEN immediately
@@ -1,12 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CriticalStateError = void 0;
4
- const PokerEngineError_1 = require("./PokerEngineError");
4
+ const poker_engine_error_1 = require("./poker-engine-error");
5
5
  /**
6
6
  * Critical error indicating state corruption or invariant violation
7
7
  * When this occurs, the table should be FROZEN immediately
8
8
  */
9
- class CriticalStateError extends PokerEngineError_1.PokerEngineError {
9
+ class CriticalStateError extends poker_engine_error_1.PokerEngineError {
10
10
  constructor(message, context = {}) {
11
11
  super("CRITICAL_INVARIANT_FAILURE", message, context);
12
12
  this.name = "CriticalStateError";
@@ -1,5 +1,5 @@
1
- import { PokerEngineError } from "./PokerEngineError";
2
- import { ErrorCode } from "./ErrorCodes";
1
+ import { PokerEngineError } from "./poker-engine-error";
2
+ import { ErrorCode } from "@pokertools/types";
3
3
  /**
4
4
  * Error indicating an illegal or invalid action
5
5
  * Action should be rejected and error sent to client
@@ -1,12 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.IllegalActionError = void 0;
4
- const PokerEngineError_1 = require("./PokerEngineError");
4
+ const poker_engine_error_1 = require("./poker-engine-error");
5
5
  /**
6
6
  * Error indicating an illegal or invalid action
7
7
  * Action should be rejected and error sent to client
8
8
  */
9
- class IllegalActionError extends PokerEngineError_1.PokerEngineError {
9
+ class IllegalActionError extends poker_engine_error_1.PokerEngineError {
10
10
  constructor(code, message, context = {}) {
11
11
  super(code, message, context);
12
12
  this.name = "IllegalActionError";
@@ -1,4 +1,4 @@
1
- export * from "./PokerEngineError";
2
- export * from "./CriticalStateError";
3
- export * from "./IllegalActionError";
4
- export * from "./ConfigError";
1
+ export * from "./poker-engine-error";
2
+ export * from "./critical-state-error";
3
+ export * from "./illegal-action-error";
4
+ export * from "./config-error";
@@ -14,9 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- // Export all errors
18
- __exportStar(require("./PokerEngineError"), exports);
19
- __exportStar(require("./CriticalStateError"), exports);
20
- __exportStar(require("./IllegalActionError"), exports);
21
- __exportStar(require("./ConfigError"), exports);
22
- // ErrorCodes now exported from @pokertools/types
17
+ __exportStar(require("./poker-engine-error"), exports);
18
+ __exportStar(require("./critical-state-error"), exports);
19
+ __exportStar(require("./illegal-action-error"), exports);
20
+ __exportStar(require("./config-error"), exports);
@@ -5,6 +5,8 @@ exports.PokerEngineError = void 0;
5
5
  * Base error class for all poker engine errors
6
6
  */
7
7
  class PokerEngineError extends Error {
8
+ code;
9
+ context;
8
10
  constructor(code, message, context = {}) {
9
11
  super(message);
10
12
  this.name = "PokerEngineError";
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.exportHandHistory = exportHandHistory;
7
7
  exports.getHandHistory = getHandHistory;
8
8
  exports.exportMultipleHands = exportMultipleHands;
9
- const handHistoryBuilder_1 = require("./handHistoryBuilder");
9
+ const hand_history_builder_1 = require("./hand-history-builder");
10
10
  const pokerstars_1 = require("./formats/pokerstars");
11
11
  const json_1 = require("./formats/json");
12
12
  /**
@@ -18,7 +18,7 @@ const json_1 = require("./formats/json");
18
18
  */
19
19
  function exportHandHistory(state, options = { format: "json" }) {
20
20
  // Build structured history
21
- const history = (0, handHistoryBuilder_1.buildHandHistory)(state);
21
+ const history = (0, hand_history_builder_1.buildHandHistory)(state);
22
22
  // Export to requested format
23
23
  switch (options.format) {
24
24
  case "pokerstars":
@@ -38,7 +38,7 @@ function exportHandHistory(state, options = { format: "json" }) {
38
38
  * @returns Structured hand history object
39
39
  */
40
40
  function getHandHistory(state) {
41
- return (0, handHistoryBuilder_1.buildHandHistory)(state);
41
+ return (0, hand_history_builder_1.buildHandHistory)(state);
42
42
  }
43
43
  /**
44
44
  * Export multiple hands to a single file
@@ -54,7 +54,7 @@ function exportMultipleHands(states, options = { format: "json" }) {
54
54
  }
55
55
  else {
56
56
  // JSON format: array of hands
57
- const histories = states.map((state) => (0, handHistoryBuilder_1.buildHandHistory)(state));
57
+ const histories = states.map((state) => (0, hand_history_builder_1.buildHandHistory)(state));
58
58
  return JSON.stringify(histories, null, options.format === "compact" ? 0 : 2);
59
59
  }
60
60
  }
@@ -11,25 +11,19 @@ exports.exportToPokerStars = exportToPokerStars;
11
11
  */
12
12
  function exportToPokerStars(history, options = { format: "pokerstars" }) {
13
13
  const lines = [];
14
- // Header line
15
14
  lines.push(buildHeader(history));
16
15
  lines.push(buildTableInfo(history));
17
- // Button and seats
18
16
  lines.push(`Button: seat #${history.buttonSeat + 1}`);
19
17
  lines.push("");
20
- // Player stacks
21
18
  for (const player of history.players) {
22
19
  lines.push(`Seat ${player.seat + 1}: ${player.name} ($${player.startingStack.toFixed(2)} in chips)`);
23
20
  }
24
21
  lines.push("");
25
- // Blinds and antes
26
22
  lines.push(buildBlindsLine(history));
27
23
  if (history.stakes.ante > 0) {
28
24
  lines.push(buildAntesLine(history));
29
25
  }
30
- // Hole cards (dealt to)
31
26
  lines.push(buildHoleCardsLine(history, options));
32
- // Each street
33
27
  for (const street of history.streets) {
34
28
  lines.push("");
35
29
  lines.push(buildStreetHeader(street));
@@ -37,7 +31,6 @@ function exportToPokerStars(history, options = { format: "pokerstars" }) {
37
31
  lines.push(buildActionLine(action, history));
38
32
  }
39
33
  }
40
- // Summary
41
34
  lines.push("");
42
35
  lines.push(buildSummary(history));
43
36
  return lines.join("\n");
@@ -61,7 +54,6 @@ function buildTableInfo(history) {
61
54
  */
62
55
  function buildBlindsLine(history) {
63
56
  const lines = [];
64
- // Find SB and BB seats (button+1 and button+2)
65
57
  const sbSeat = (history.buttonSeat + 1) % history.maxPlayers;
66
58
  const bbSeat = (history.buttonSeat + 2) % history.maxPlayers;
67
59
  const sbPlayer = history.players.find((p) => p.seat === sbSeat);
@@ -84,10 +84,9 @@ function groupActionsByStreet(state) {
84
84
  grouped.set("TURN" /* Street.TURN */, []);
85
85
  grouped.set("RIVER" /* Street.RIVER */, []);
86
86
  grouped.set("SHOWDOWN" /* Street.SHOWDOWN */, []);
87
- // Group action history by street
88
87
  for (const record of state.actionHistory) {
89
88
  if (record.seat === null)
90
- continue; // Skip table-level actions
89
+ continue;
91
90
  const street = record.street ?? state.street;
92
91
  const existing = grouped.get(street) ?? [];
93
92
  const actionRecord = {
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- export { PokerEngine } from "./engine/PokerEngine";
1
+ export { PokerEngine } from "./engine/poker-engine";
2
2
  export * from "@pokertools/types";
3
3
  export * from "./errors";
4
4
  export { createSnapshot, restoreFromSnapshot, Snapshot } from "./utils/serialization";
5
- export { createPublicView } from "./utils/viewMasking";
5
+ export { createPublicView } from "./utils/view-masking";
6
6
  export { calculateTotalChips, auditChipConservation } from "./utils/invariants";
7
7
  export { exportHandHistory, getHandHistory, exportMultipleHands } from "./history/exporter";
8
8
  export { HandHistory, HandHistoryPlayer, StreetHistory, ExportOptions } from "@pokertools/types";
package/dist/index.js CHANGED
@@ -15,9 +15,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  exports.exportMultipleHands = exports.getHandHistory = exports.exportHandHistory = exports.auditChipConservation = exports.calculateTotalChips = exports.createPublicView = exports.restoreFromSnapshot = exports.createSnapshot = exports.PokerEngine = void 0;
18
- // Main Engine Class
19
- var PokerEngine_1 = require("./engine/PokerEngine");
20
- Object.defineProperty(exports, "PokerEngine", { enumerable: true, get: function () { return PokerEngine_1.PokerEngine; } });
18
+ var poker_engine_1 = require("./engine/poker-engine");
19
+ Object.defineProperty(exports, "PokerEngine", { enumerable: true, get: function () { return poker_engine_1.PokerEngine; } });
21
20
  // Types (re-exported from @pokertools/types for convenience)
22
21
  __exportStar(require("@pokertools/types"), exports);
23
22
  // Errors
@@ -26,8 +25,8 @@ __exportStar(require("./errors"), exports);
26
25
  var serialization_1 = require("./utils/serialization");
27
26
  Object.defineProperty(exports, "createSnapshot", { enumerable: true, get: function () { return serialization_1.createSnapshot; } });
28
27
  Object.defineProperty(exports, "restoreFromSnapshot", { enumerable: true, get: function () { return serialization_1.restoreFromSnapshot; } });
29
- var viewMasking_1 = require("./utils/viewMasking");
30
- Object.defineProperty(exports, "createPublicView", { enumerable: true, get: function () { return viewMasking_1.createPublicView; } });
28
+ var view_masking_1 = require("./utils/view-masking");
29
+ Object.defineProperty(exports, "createPublicView", { enumerable: true, get: function () { return view_masking_1.createPublicView; } });
31
30
  var invariants_1 = require("./utils/invariants");
32
31
  Object.defineProperty(exports, "calculateTotalChips", { enumerable: true, get: function () { return invariants_1.calculateTotalChips; } });
33
32
  Object.defineProperty(exports, "auditChipConservation", { enumerable: true, get: function () { return invariants_1.auditChipConservation; } });
@@ -4,15 +4,14 @@ 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
11
11
  * Returns seat number or null if action is complete
12
12
  */
13
13
  function getNextToAct(state) {
14
- // Special case: heads-up has different rules
15
- if ((0, headsUp_1.isHeadsUp)(state)) {
14
+ if ((0, heads_up_1.isHeadsUp)(state)) {
16
15
  return getNextToActHeadsUp(state);
17
16
  }
18
17
  return getNextToActNormal(state);
@@ -32,8 +31,8 @@ function getNextToActNormal(state) {
32
31
  // Search for next player who can act
33
32
  while (seat !== startSeat) {
34
33
  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) {
34
+ // Skip if: no player, folded, all-in, busted, or sitting out
35
+ if (player?.status !== "ACTIVE" /* PlayerStatus.ACTIVE */ || player.stack === 0 || player.isSittingOut) {
37
36
  seat = (0, positioning_1.getNextSeat)(seat, state.maxPlayers);
38
37
  continue;
39
38
  }
@@ -42,12 +41,10 @@ function getNextToActNormal(state) {
42
41
  // 2. Current bet is higher than their bet
43
42
  const playerBet = state.currentBets.get(seat) ?? 0;
44
43
  if (playerBet < currentBet) {
45
- return seat; // Player needs to respond to bet
44
+ return seat;
46
45
  }
47
- // Player has matched current bet
48
- // Check if they've already acted
49
46
  if (!hasActedThisStreet(state, seat)) {
50
- return seat; // Player hasn't acted yet
47
+ return seat;
51
48
  }
52
49
  foundActionable = true;
53
50
  seat = (0, positioning_1.getNextSeat)(seat, state.maxPlayers);
@@ -65,7 +62,7 @@ function getNextToActHeadsUp(state) {
65
62
  if (state.buttonSeat === null) {
66
63
  return null;
67
64
  }
68
- const actionOrder = (0, headsUp_1.getHeadsUpActionOrder)(state, state.street);
65
+ const actionOrder = (0, heads_up_1.getHeadsUpActionOrder)(state, state.street);
69
66
  if (actionOrder.length === 0) {
70
67
  return null;
71
68
  }
@@ -77,7 +74,7 @@ function getNextToActHeadsUp(state) {
77
74
  // Check both players
78
75
  for (const seat of actionOrder) {
79
76
  const player = state.players[seat];
80
- if (player?.status !== "ACTIVE" /* PlayerStatus.ACTIVE */ || player.stack === 0) {
77
+ if (player?.status !== "ACTIVE" /* PlayerStatus.ACTIVE */ || player.stack === 0 || player.isSittingOut) {
81
78
  continue;
82
79
  }
83
80
  const playerBet = state.currentBets.get(seat) ?? 0;
@@ -94,8 +91,8 @@ function getFirstToAct(state) {
94
91
  if (state.buttonSeat === null) {
95
92
  return null;
96
93
  }
97
- if ((0, headsUp_1.isHeadsUp)(state)) {
98
- const order = (0, headsUp_1.getHeadsUpActionOrder)(state, state.street);
94
+ if ((0, heads_up_1.isHeadsUp)(state)) {
95
+ const order = (0, heads_up_1.getHeadsUpActionOrder)(state, state.street);
99
96
  if (order.length > 0) {
100
97
  return order[0];
101
98
  }
@@ -125,10 +122,9 @@ function getFirstToAct(state) {
125
122
  function getNextActionableSeat(startSeat, state) {
126
123
  let seat = (0, positioning_1.getNextSeat)(startSeat, state.maxPlayers);
127
124
  const endSeat = startSeat;
128
- // Scan full circle
129
125
  while (seat !== endSeat) {
130
126
  const player = state.players[seat];
131
- if (player?.status === "ACTIVE" /* PlayerStatus.ACTIVE */ && player.stack > 0) {
127
+ if (player?.status === "ACTIVE" /* PlayerStatus.ACTIVE */ && player.stack > 0 && !player.isSittingOut) {
132
128
  return seat;
133
129
  }
134
130
  seat = (0, positioning_1.getNextSeat)(seat, state.maxPlayers);
@@ -159,7 +155,7 @@ function isActionComplete(state) {
159
155
  if (!player)
160
156
  continue;
161
157
  // Count active players who can still act
162
- if (player.status === "ACTIVE" /* PlayerStatus.ACTIVE */ && player.stack > 0) {
158
+ if (player.status === "ACTIVE" /* PlayerStatus.ACTIVE */ && player.stack > 0 && !player.isSittingOut) {
163
159
  activeCount++;
164
160
  const playerBet = state.currentBets.get(seat) ?? 0;
165
161
  // Player has matched bet and acted
@@ -183,7 +179,6 @@ function isActionComplete(state) {
183
179
  * This is tracked by checking if they appear in the action history for this street
184
180
  */
185
181
  function hasActedThisStreet(state, seat) {
186
- // Find actions from current street
187
182
  const streetStartIndex = findStreetStartIndex(state);
188
183
  for (let i = streetStartIndex; i < state.actionHistory.length; i++) {
189
184
  if (state.actionHistory[i].seat === seat) {
@@ -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;
@@ -66,7 +66,7 @@ function getNextActiveOrOccupiedSeat(currentSeat, players, maxPlayers, isTournam
66
66
  }
67
67
  seat = (0, positioning_1.getNextSeat)(seat, maxPlayers);
68
68
  }
69
- return null; // No eligible seats
69
+ return null;
70
70
  }
71
71
  /**
72
72
  * Calculate blind amounts for antes
@@ -32,7 +32,6 @@ 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
35
  const isButtonActive = activePlayers.includes(buttonSeat);
37
36
  if (!isButtonActive) {
38
37
  // Dead button scenario - button is not one of the active players