@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.
- package/README.md +8 -5
- package/dist/.tsbuildinfo +1 -1
- package/dist/actions/betting.js +23 -73
- package/dist/actions/dealing.js +29 -56
- package/dist/actions/management.js +3 -1
- package/dist/actions/{showdownActions.js → showdown-actions.js} +13 -27
- package/dist/actions/special.js +20 -7
- package/dist/actions/{streetProgression.js → street-progression.js} +16 -31
- package/dist/actions/tournament.js +1 -4
- package/dist/actions/validation.js +48 -33
- package/dist/browser.d.ts +5 -5
- package/dist/browser.js +6 -7
- package/dist/engine/{gameReducer.js → game-reducer.js} +16 -32
- package/dist/engine/{PokerEngine.js → poker-engine.js} +16 -30
- package/dist/errors/{ConfigError.d.ts → config-error.d.ts} +1 -1
- package/dist/errors/{ConfigError.js → config-error.js} +2 -2
- package/dist/errors/{CriticalStateError.d.ts → critical-state-error.d.ts} +1 -1
- package/dist/errors/{CriticalStateError.js → critical-state-error.js} +2 -2
- package/dist/errors/{IllegalActionError.d.ts → illegal-action-error.d.ts} +2 -2
- package/dist/errors/{IllegalActionError.js → illegal-action-error.js} +2 -2
- package/dist/errors/index.d.ts +4 -4
- package/dist/errors/index.js +4 -6
- package/dist/errors/{PokerEngineError.js → poker-engine-error.js} +2 -0
- package/dist/history/exporter.js +4 -4
- package/dist/history/formats/pokerstars.js +0 -8
- package/dist/history/{handHistoryBuilder.js → hand-history-builder.js} +1 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -5
- package/dist/rules/{actionOrder.js → action-order.js} +12 -17
- package/dist/rules/blinds.js +3 -3
- package/dist/rules/{headsUp.js → heads-up.js} +0 -1
- package/dist/rules/showdown.js +45 -29
- package/dist/rules/{sidePots.js → side-pots.js} +7 -20
- package/dist/utils/deck.js +1 -4
- package/dist/utils/invariants.js +16 -10
- package/dist/utils/rake.js +6 -8
- package/dist/utils/serialization.d.ts +1 -0
- package/dist/utils/serialization.js +10 -5
- package/dist/utils/validation.js +8 -8
- package/dist/utils/{viewMasking.js → view-masking.js} +16 -6
- package/package.json +6 -6
- package/dist/errors/ErrorCodes.d.ts +0 -7
- package/dist/errors/ErrorCodes.js +0 -12
- /package/dist/actions/{showdownActions.d.ts → showdown-actions.d.ts} +0 -0
- /package/dist/actions/{streetProgression.d.ts → street-progression.d.ts} +0 -0
- /package/dist/engine/{gameReducer.d.ts → game-reducer.d.ts} +0 -0
- /package/dist/engine/{PokerEngine.d.ts → poker-engine.d.ts} +0 -0
- /package/dist/errors/{PokerEngineError.d.ts → poker-engine-error.d.ts} +0 -0
- /package/dist/history/{handHistoryBuilder.d.ts → hand-history-builder.d.ts} +0 -0
- /package/dist/rules/{actionOrder.d.ts → action-order.d.ts} +0 -0
- /package/dist/rules/{headsUp.d.ts → heads-up.d.ts} +0 -0
- /package/dist/rules/{sidePots.d.ts → side-pots.d.ts} +0 -0
- /package/dist/utils/{cardUtils.d.ts → card-utils.d.ts} +0 -0
- /package/dist/utils/{cardUtils.js → card-utils.js} +0 -0
- /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
|
|
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
|
|
12
|
-
const
|
|
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
|
-
//
|
|
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,
|
|
87
|
+
newState = (0, showdown_actions_1.handleShow)(state, action);
|
|
97
88
|
break;
|
|
98
89
|
case "MUCK" /* ActionType.MUCK */:
|
|
99
|
-
newState = (0,
|
|
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
|
-
//
|
|
117
|
-
if ((0,
|
|
118
|
-
|
|
119
|
-
newState = (0,
|
|
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
|
-
|
|
131
|
-
|
|
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
|
|
5
|
-
const
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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;
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
239
|
+
throw new config_error_1.ConfigError("Max players must be between 2 and 10", {
|
|
254
240
|
maxPlayers,
|
|
255
241
|
});
|
|
256
242
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ConfigError = void 0;
|
|
4
|
-
const
|
|
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
|
|
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,12 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CriticalStateError = void 0;
|
|
4
|
-
const
|
|
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
|
|
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 "./
|
|
2
|
-
import { ErrorCode } from "
|
|
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
|
|
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
|
|
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";
|
package/dist/errors/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from "./
|
|
2
|
-
export * from "./
|
|
3
|
-
export * from "./
|
|
4
|
-
export * from "./
|
|
1
|
+
export * from "./poker-engine-error";
|
|
2
|
+
export * from "./critical-state-error";
|
|
3
|
+
export * from "./illegal-action-error";
|
|
4
|
+
export * from "./config-error";
|
package/dist/errors/index.js
CHANGED
|
@@ -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
|
-
|
|
18
|
-
__exportStar(require("./
|
|
19
|
-
__exportStar(require("./
|
|
20
|
-
__exportStar(require("./
|
|
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);
|
package/dist/history/exporter.js
CHANGED
|
@@ -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
|
|
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,
|
|
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,
|
|
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,
|
|
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;
|
|
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/
|
|
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/
|
|
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
|
-
|
|
19
|
-
|
|
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
|
|
30
|
-
Object.defineProperty(exports, "createPublicView", { enumerable: true, get: function () { return
|
|
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
|
|
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
|
-
|
|
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
|
|
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;
|
|
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;
|
|
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,
|
|
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,
|
|
98
|
-
const order = (0,
|
|
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) {
|
package/dist/rules/blinds.js
CHANGED
|
@@ -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
|
|
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,
|
|
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;
|
|
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
|