@pokertools/engine 1.0.7 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/actions/betting.js +8 -8
- package/dist/actions/dealing.js +6 -4
- package/dist/actions/management.js +3 -0
- package/dist/actions/{showdownActions.js → showdown-actions.js} +10 -10
- package/dist/actions/special.js +20 -3
- package/dist/actions/{streetProgression.js → street-progression.js} +7 -7
- package/dist/actions/validation.js +48 -27
- package/dist/browser.d.ts +5 -5
- package/dist/browser.js +6 -6
- package/dist/engine/{gameReducer.js → game-reducer.js} +11 -8
- package/dist/engine/{PokerEngine.js → poker-engine.js} +10 -10
- 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 -4
- package/dist/history/exporter.js +4 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -4
- package/dist/rules/{actionOrder.js → action-order.js} +10 -10
- package/dist/rules/blinds.js +2 -2
- package/dist/rules/showdown.js +34 -1
- package/dist/rules/{sidePots.js → side-pots.js} +2 -2
- package/dist/utils/invariants.js +16 -10
- package/dist/utils/serialization.d.ts +1 -0
- package/dist/utils/serialization.js +9 -3
- package/dist/utils/validation.js +8 -8
- package/dist/utils/{viewMasking.js → view-masking.js} +16 -6
- package/package.json +5 -5
- /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/{ErrorCodes.d.ts → error-codes.d.ts} +0 -0
- /package/dist/errors/{ErrorCodes.js → error-codes.js} +0 -0
- /package/dist/errors/{PokerEngineError.d.ts → poker-engine-error.d.ts} +0 -0
- /package/dist/errors/{PokerEngineError.js → poker-engine-error.js} +0 -0
- /package/dist/history/{handHistoryBuilder.d.ts → hand-history-builder.d.ts} +0 -0
- /package/dist/history/{handHistoryBuilder.js → hand-history-builder.js} +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/{headsUp.js → heads-up.js} +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
|
@@ -3,8 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.progressStreet = progressStreet;
|
|
4
4
|
exports.shouldProgressStreet = shouldProgressStreet;
|
|
5
5
|
const deck_1 = require("../utils/deck");
|
|
6
|
-
const
|
|
7
|
-
const
|
|
6
|
+
const card_utils_1 = require("../utils/card-utils");
|
|
7
|
+
const action_order_1 = require("../rules/action-order");
|
|
8
8
|
/**
|
|
9
9
|
* Progress to next street
|
|
10
10
|
* - Collects bets into pots
|
|
@@ -38,7 +38,7 @@ function progressStreet(state) {
|
|
|
38
38
|
// Keep timestamp from last action (street progression is not a user action)
|
|
39
39
|
};
|
|
40
40
|
// Set first to act
|
|
41
|
-
const firstToAct = (0,
|
|
41
|
+
const firstToAct = (0, action_order_1.getFirstToAct)(newState);
|
|
42
42
|
return {
|
|
43
43
|
...newState,
|
|
44
44
|
actionTo: firstToAct,
|
|
@@ -69,21 +69,21 @@ function dealCommunityCards(state, street) {
|
|
|
69
69
|
// Burn 1, deal 3
|
|
70
70
|
const [flopCards, flopDeck] = (0, deck_1.burnAndDeal)(deck, 3);
|
|
71
71
|
return {
|
|
72
|
-
board: [...currentBoard, ...(0,
|
|
72
|
+
board: [...currentBoard, ...(0, card_utils_1.cardCodesToStrings)(flopCards)],
|
|
73
73
|
deck: flopDeck,
|
|
74
74
|
};
|
|
75
75
|
case "TURN" /* Street.TURN */:
|
|
76
76
|
// Burn 1, deal 1
|
|
77
77
|
const [turnCards, turnDeck] = (0, deck_1.burnAndDeal)(deck, 1);
|
|
78
78
|
return {
|
|
79
|
-
board: [...currentBoard, ...(0,
|
|
79
|
+
board: [...currentBoard, ...(0, card_utils_1.cardCodesToStrings)(turnCards)],
|
|
80
80
|
deck: turnDeck,
|
|
81
81
|
};
|
|
82
82
|
case "RIVER" /* Street.RIVER */:
|
|
83
83
|
// Burn 1, deal 1
|
|
84
84
|
const [riverCards, riverDeck] = (0, deck_1.burnAndDeal)(deck, 1);
|
|
85
85
|
return {
|
|
86
|
-
board: [...currentBoard, ...(0,
|
|
86
|
+
board: [...currentBoard, ...(0, card_utils_1.cardCodesToStrings)(riverCards)],
|
|
87
87
|
deck: riverDeck,
|
|
88
88
|
};
|
|
89
89
|
default:
|
|
@@ -153,5 +153,5 @@ function shouldProgressStreet(state) {
|
|
|
153
153
|
return false; // Already at showdown
|
|
154
154
|
}
|
|
155
155
|
// Check if all active players have acted
|
|
156
|
-
return (0,
|
|
156
|
+
return (0, action_order_1.isActionComplete)(state);
|
|
157
157
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.validateAction = validateAction;
|
|
4
|
-
const
|
|
5
|
-
const
|
|
4
|
+
const illegal_action_error_1 = require("../errors/illegal-action-error");
|
|
5
|
+
const error_codes_1 = require("../errors/error-codes");
|
|
6
6
|
const positioning_1 = require("../utils/positioning");
|
|
7
7
|
/**
|
|
8
8
|
* Validate that an action is legal in the current game state
|
|
@@ -44,16 +44,16 @@ function validateAction(state, action) {
|
|
|
44
44
|
}
|
|
45
45
|
function validateBettingAction(state, action) {
|
|
46
46
|
if (!("playerId" in action)) {
|
|
47
|
-
throw new
|
|
47
|
+
throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.INVALID_ACTION, "Action missing playerId");
|
|
48
48
|
}
|
|
49
49
|
const result = (0, positioning_1.getPlayerById)(state, action.playerId);
|
|
50
50
|
if (!result) {
|
|
51
|
-
throw new
|
|
51
|
+
throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.PLAYER_NOT_FOUND, `Player ${action.playerId} not found`, { playerId: action.playerId });
|
|
52
52
|
}
|
|
53
53
|
const { player, seat } = result;
|
|
54
54
|
// Check if it's player's turn
|
|
55
55
|
if (state.actionTo !== seat) {
|
|
56
|
-
throw new
|
|
56
|
+
throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.NOT_YOUR_TURN, `Player ${action.playerId} attempted to act, but action is on seat ${state.actionTo}`, {
|
|
57
57
|
playerId: action.playerId,
|
|
58
58
|
playerSeat: seat,
|
|
59
59
|
actionTo: state.actionTo,
|
|
@@ -62,11 +62,11 @@ function validateBettingAction(state, action) {
|
|
|
62
62
|
}
|
|
63
63
|
// Check player status
|
|
64
64
|
if (player.status !== "ACTIVE" /* PlayerStatus.ACTIVE */) {
|
|
65
|
-
throw new
|
|
65
|
+
throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.PLAYER_NOT_ACTIVE, `Player ${action.playerId} cannot act with status ${player.status}`, { playerId: action.playerId, status: player.status });
|
|
66
66
|
}
|
|
67
67
|
// Check player has chips
|
|
68
68
|
if (player.stack === 0 && action.type !== "FOLD" /* ActionType.FOLD */) {
|
|
69
|
-
throw new
|
|
69
|
+
throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.NO_CHIPS, `Player ${action.playerId} has no chips`, {
|
|
70
70
|
playerId: action.playerId,
|
|
71
71
|
});
|
|
72
72
|
}
|
|
@@ -77,25 +77,29 @@ function validateBettingAction(state, action) {
|
|
|
77
77
|
switch (action.type) {
|
|
78
78
|
case "CHECK" /* ActionType.CHECK */:
|
|
79
79
|
if (toCall > 0) {
|
|
80
|
-
throw new
|
|
80
|
+
throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.CANNOT_CHECK, `Player ${action.playerId} cannot check with ${toCall} to call`, { playerId: action.playerId, toCall });
|
|
81
81
|
}
|
|
82
82
|
break;
|
|
83
83
|
case "CALL" /* ActionType.CALL */:
|
|
84
84
|
if (toCall === 0) {
|
|
85
|
-
throw new
|
|
85
|
+
throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.NOTHING_TO_CALL, `Player ${action.playerId} has nothing to call`, { playerId: action.playerId });
|
|
86
86
|
}
|
|
87
87
|
break;
|
|
88
88
|
case "BET" /* ActionType.BET */:
|
|
89
89
|
// Note: We allow BET even when currentBet > 0 because the reducer will auto-convert it to RAISE or CALL
|
|
90
90
|
// This handles UI implementations that don't distinguish between BET and RAISE buttons
|
|
91
91
|
if ("amount" in action) {
|
|
92
|
+
if (currentBet > 0) {
|
|
93
|
+
validateBetAsRaise(state, action, seat, playerBet, currentBet, player.stack);
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
92
96
|
// Reject bets below the current bet (string bet exploit)
|
|
93
|
-
if (
|
|
94
|
-
throw new
|
|
97
|
+
if (action.amount < currentBet) {
|
|
98
|
+
throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.BET_TOO_SMALL, `Bet of ${action.amount} is below current bet ${currentBet}`, { amount: action.amount, currentBet });
|
|
95
99
|
}
|
|
96
100
|
// Reject bets below big blind (when no current bet)
|
|
97
101
|
if (action.amount < state.bigBlind && action.amount < player.stack) {
|
|
98
|
-
throw new
|
|
102
|
+
throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.BET_TOO_SMALL, `Bet of ${action.amount} is below minimum ${state.bigBlind}`, { amount: action.amount, minimum: state.bigBlind });
|
|
99
103
|
}
|
|
100
104
|
}
|
|
101
105
|
break;
|
|
@@ -104,26 +108,26 @@ function validateBettingAction(state, action) {
|
|
|
104
108
|
// intermediate actions (like calls or incomplete all-in raises) did NOT
|
|
105
109
|
// reopen the betting. Therefore, they cannot re-raise their own bet.
|
|
106
110
|
if (state.lastAggressorSeat === seat) {
|
|
107
|
-
throw new
|
|
111
|
+
throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.CANNOT_RERAISE, "Betting has not been re-opened to you (incomplete raise or no action)", {
|
|
108
112
|
playerId: action.playerId,
|
|
109
113
|
seat,
|
|
110
114
|
lastAggressor: state.lastAggressorSeat,
|
|
111
115
|
});
|
|
112
116
|
}
|
|
113
117
|
if (currentBet === 0) {
|
|
114
|
-
throw new
|
|
118
|
+
throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.CANNOT_RAISE, `Player ${action.playerId} cannot raise when there's no bet`, { playerId: action.playerId });
|
|
115
119
|
}
|
|
116
120
|
if ("amount" in action) {
|
|
117
121
|
// Check if player is going all-in (incomplete raise exception)
|
|
118
122
|
const isAllIn = action.amount >= playerBet + player.stack;
|
|
119
123
|
// Reject raises that don't exceed current bet (unless all-in)
|
|
120
124
|
if (action.amount <= currentBet && !isAllIn) {
|
|
121
|
-
throw new
|
|
125
|
+
throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.RAISE_TOO_SMALL, `Raise to ${action.amount} must be greater than current bet ${currentBet}`, { amount: action.amount, currentBet });
|
|
122
126
|
}
|
|
123
127
|
// Check minimum raise requirement (unless player is going all-in)
|
|
124
128
|
const raiseIncrement = action.amount - currentBet;
|
|
125
129
|
if (!isAllIn && action.amount < state.minRaise) {
|
|
126
|
-
throw new
|
|
130
|
+
throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.RAISE_TOO_SMALL, `Raise to ${action.amount} is below minimum ${state.minRaise}`, {
|
|
127
131
|
amount: action.amount,
|
|
128
132
|
currentBet,
|
|
129
133
|
raiseIncrement,
|
|
@@ -134,63 +138,80 @@ function validateBettingAction(state, action) {
|
|
|
134
138
|
break;
|
|
135
139
|
}
|
|
136
140
|
}
|
|
141
|
+
function validateBetAsRaise(state, action, seat, playerBet, currentBet, playerStack) {
|
|
142
|
+
if (!("amount" in action))
|
|
143
|
+
return;
|
|
144
|
+
const amount = action.amount;
|
|
145
|
+
if (typeof amount !== "number")
|
|
146
|
+
return;
|
|
147
|
+
if (state.lastAggressorSeat === seat) {
|
|
148
|
+
throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.CANNOT_RERAISE, "Betting has not been re-opened to you (incomplete raise or no action)", { playerId: action.playerId, seat, lastAggressor: state.lastAggressorSeat });
|
|
149
|
+
}
|
|
150
|
+
const isAllIn = amount >= playerBet + playerStack;
|
|
151
|
+
if (amount <= currentBet && !isAllIn) {
|
|
152
|
+
throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.RAISE_TOO_SMALL, `Raise to ${amount} must be greater than current bet ${currentBet}`, { amount, currentBet });
|
|
153
|
+
}
|
|
154
|
+
if (!isAllIn && amount < state.minRaise) {
|
|
155
|
+
throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.RAISE_TOO_SMALL, `Raise to ${amount} is below minimum ${state.minRaise}`, { amount, currentBet, minRaise: state.minRaise });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
137
158
|
function validateDealAction(state) {
|
|
138
159
|
if (state.street !== "PREFLOP" /* Street.PREFLOP */ || state.handNumber > 0) {
|
|
139
160
|
// Allow dealing if we're at showdown (hand complete) or haven't started
|
|
140
161
|
if (state.street !== "SHOWDOWN" /* Street.SHOWDOWN */ && state.handNumber > 0) {
|
|
141
|
-
throw new
|
|
162
|
+
throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.CANNOT_DEAL, "Cannot deal while hand is in progress", { street: state.street });
|
|
142
163
|
}
|
|
143
164
|
}
|
|
144
165
|
// Check we have enough players
|
|
145
166
|
const activePlayers = state.players.filter((p) => p && p.stack > 0 && !p.isSittingOut);
|
|
146
167
|
if (activePlayers.length < 2) {
|
|
147
|
-
throw new
|
|
168
|
+
throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.NOT_ENOUGH_PLAYERS, `Need at least 2 players to deal, found ${activePlayers.length}`, { playerCount: activePlayers.length });
|
|
148
169
|
}
|
|
149
170
|
}
|
|
150
171
|
function validateSitAction(state, action) {
|
|
151
172
|
if (action.seat < 0 || action.seat >= state.maxPlayers) {
|
|
152
|
-
throw new
|
|
173
|
+
throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.INVALID_SEAT, `Seat ${action.seat} is invalid (max: ${state.maxPlayers - 1})`, { seat: action.seat, maxPlayers: state.maxPlayers });
|
|
153
174
|
}
|
|
154
175
|
const existingPlayer = state.players[action.seat];
|
|
155
176
|
if (existingPlayer !== null) {
|
|
156
177
|
// Allow claiming the seat if it is RESERVED by THIS player
|
|
157
178
|
const isMyReservation = existingPlayer.status === "RESERVED" /* PlayerStatus.RESERVED */ && existingPlayer.id === action.playerId;
|
|
158
179
|
if (!isMyReservation) {
|
|
159
|
-
throw new
|
|
180
|
+
throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.SEAT_OCCUPIED, `Seat ${action.seat} is already occupied`, { seat: action.seat });
|
|
160
181
|
}
|
|
161
182
|
}
|
|
162
183
|
if (action.stack <= 0) {
|
|
163
|
-
throw new
|
|
184
|
+
throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.INVALID_STACK, `Stack must be positive, got ${action.stack}`, { stack: action.stack });
|
|
164
185
|
}
|
|
165
186
|
}
|
|
166
187
|
function validateStandAction(state, action) {
|
|
167
188
|
const result = (0, positioning_1.getPlayerById)(state, action.playerId);
|
|
168
189
|
if (!result) {
|
|
169
|
-
throw new
|
|
190
|
+
throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.PLAYER_NOT_FOUND, `Player ${action.playerId} not found`, { playerId: action.playerId });
|
|
170
191
|
}
|
|
171
192
|
}
|
|
172
193
|
function validateTimeAction(state, action) {
|
|
173
194
|
const result = (0, positioning_1.getPlayerById)(state, action.playerId);
|
|
174
195
|
if (!result) {
|
|
175
|
-
throw new
|
|
196
|
+
throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.PLAYER_NOT_FOUND, `Player ${action.playerId} not found`, { playerId: action.playerId });
|
|
176
197
|
}
|
|
177
198
|
const { seat } = result;
|
|
178
199
|
if (state.actionTo !== seat) {
|
|
179
|
-
throw new
|
|
200
|
+
throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.NOT_YOUR_TURN, `Player ${action.playerId} cannot use time action when it's not their turn`, { playerId: action.playerId, actionTo: state.actionTo });
|
|
180
201
|
}
|
|
181
202
|
}
|
|
182
203
|
function validateAddChipsAction(state, action) {
|
|
183
204
|
const result = (0, positioning_1.getPlayerById)(state, action.playerId);
|
|
184
205
|
if (!result) {
|
|
185
|
-
throw new
|
|
206
|
+
throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.PLAYER_NOT_FOUND, `Player ${action.playerId} not found`, { playerId: action.playerId });
|
|
186
207
|
}
|
|
187
208
|
}
|
|
188
209
|
function validateReserveSeatAction(state, action) {
|
|
189
210
|
if (action.seat < 0 || action.seat >= state.maxPlayers) {
|
|
190
|
-
throw new
|
|
211
|
+
throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.INVALID_SEAT, `Seat ${action.seat} is invalid (max: ${state.maxPlayers - 1})`, { seat: action.seat, maxPlayers: state.maxPlayers });
|
|
191
212
|
}
|
|
192
213
|
if (state.players[action.seat] !== null) {
|
|
193
|
-
throw new
|
|
214
|
+
throw new illegal_action_error_1.IllegalActionError(error_codes_1.ErrorCodes.SEAT_OCCUPIED, `Seat ${action.seat} is already occupied`, { seat: action.seat });
|
|
194
215
|
}
|
|
195
216
|
}
|
|
196
217
|
/**
|
package/dist/browser.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* This file provides a browser-safe RNG using Web Crypto API
|
|
5
5
|
* and re-exports all engine functionality for use in web applications.
|
|
6
6
|
*/
|
|
7
|
-
import { PokerEngine } from "./engine/
|
|
7
|
+
import { PokerEngine } from "./engine/poker-engine";
|
|
8
8
|
import type { TableConfig } from "@pokertools/types";
|
|
9
9
|
/**
|
|
10
10
|
* Browser-compatible RNG using Web Crypto API
|
|
@@ -15,13 +15,13 @@ export declare function getBrowserRNG(): () => number;
|
|
|
15
15
|
* Create a PokerEngine instance with browser-compatible RNG
|
|
16
16
|
*/
|
|
17
17
|
export declare function createBrowserEngine(config: TableConfig): PokerEngine;
|
|
18
|
-
export * from "./engine/
|
|
18
|
+
export * from "./engine/poker-engine";
|
|
19
19
|
export * from "./actions/betting";
|
|
20
20
|
export * from "./actions/dealing";
|
|
21
21
|
export * from "./actions/management";
|
|
22
|
-
export * from "./actions/
|
|
22
|
+
export * from "./actions/showdown-actions";
|
|
23
23
|
export * from "./actions/special";
|
|
24
|
-
export * from "./utils/
|
|
24
|
+
export * from "./utils/view-masking";
|
|
25
25
|
export * from "./utils/serialization";
|
|
26
|
-
export * from "./utils/
|
|
26
|
+
export * from "./utils/card-utils";
|
|
27
27
|
export * from "./history/exporter";
|
package/dist/browser.js
CHANGED
|
@@ -22,7 +22,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
22
22
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
23
|
exports.getBrowserRNG = getBrowserRNG;
|
|
24
24
|
exports.createBrowserEngine = createBrowserEngine;
|
|
25
|
-
const
|
|
25
|
+
const poker_engine_1 = require("./engine/poker-engine");
|
|
26
26
|
/**
|
|
27
27
|
* Browser-compatible RNG using Web Crypto API
|
|
28
28
|
* Falls back to Math.random() only in environments without crypto
|
|
@@ -55,19 +55,19 @@ function getBrowserRNG() {
|
|
|
55
55
|
* Create a PokerEngine instance with browser-compatible RNG
|
|
56
56
|
*/
|
|
57
57
|
function createBrowserEngine(config) {
|
|
58
|
-
return new
|
|
58
|
+
return new poker_engine_1.PokerEngine({
|
|
59
59
|
...config,
|
|
60
60
|
randomProvider: getBrowserRNG(),
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
63
|
// Re-export everything from main engine
|
|
64
|
-
__exportStar(require("./engine/
|
|
64
|
+
__exportStar(require("./engine/poker-engine"), exports);
|
|
65
65
|
__exportStar(require("./actions/betting"), exports);
|
|
66
66
|
__exportStar(require("./actions/dealing"), exports);
|
|
67
67
|
__exportStar(require("./actions/management"), exports);
|
|
68
|
-
__exportStar(require("./actions/
|
|
68
|
+
__exportStar(require("./actions/showdown-actions"), exports);
|
|
69
69
|
__exportStar(require("./actions/special"), exports);
|
|
70
|
-
__exportStar(require("./utils/
|
|
70
|
+
__exportStar(require("./utils/view-masking"), exports);
|
|
71
71
|
__exportStar(require("./utils/serialization"), exports);
|
|
72
|
-
__exportStar(require("./utils/
|
|
72
|
+
__exportStar(require("./utils/card-utils"), exports);
|
|
73
73
|
__exportStar(require("./history/exporter"), exports);
|
|
@@ -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");
|
|
@@ -93,10 +93,10 @@ function gameReducer(state, action) {
|
|
|
93
93
|
break;
|
|
94
94
|
// Showdown actions
|
|
95
95
|
case "SHOW" /* ActionType.SHOW */:
|
|
96
|
-
newState = (0,
|
|
96
|
+
newState = (0, showdown_actions_1.handleShow)(state, action);
|
|
97
97
|
break;
|
|
98
98
|
case "MUCK" /* ActionType.MUCK */:
|
|
99
|
-
newState = (0,
|
|
99
|
+
newState = (0, showdown_actions_1.handleMuck)(state, action);
|
|
100
100
|
break;
|
|
101
101
|
// Special actions
|
|
102
102
|
case "TIMEOUT" /* ActionType.TIMEOUT */:
|
|
@@ -114,10 +114,10 @@ function gameReducer(state, action) {
|
|
|
114
114
|
break;
|
|
115
115
|
}
|
|
116
116
|
// Check if we should progress to next street
|
|
117
|
-
if ((0,
|
|
117
|
+
if ((0, street_progression_1.shouldProgressStreet)(newState)) {
|
|
118
118
|
// Recalculate pots before progressing
|
|
119
|
-
newState = (0,
|
|
120
|
-
newState = (0,
|
|
119
|
+
newState = (0, side_pots_1.recalculatePots)(newState);
|
|
120
|
+
newState = (0, street_progression_1.progressStreet)(newState);
|
|
121
121
|
}
|
|
122
122
|
// Check if we should go to showdown
|
|
123
123
|
if ((0, showdown_1.shouldShowdown)(newState)) {
|
|
@@ -127,6 +127,9 @@ function gameReducer(state, action) {
|
|
|
127
127
|
};
|
|
128
128
|
newState = (0, showdown_1.determineWinners)(newState);
|
|
129
129
|
}
|
|
130
|
+
if (newState.actionTo !== null && newState.players[newState.actionTo]?.isSittingOut) {
|
|
131
|
+
newState = { ...newState, actionTo: null };
|
|
132
|
+
}
|
|
130
133
|
// Audit chip conservation (throws on failure)
|
|
131
134
|
// Enabled by default, can be disabled via config (not recommended for production)
|
|
132
135
|
if (newState.config.validateIntegrity !== false) {
|
|
@@ -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
|
/**
|
|
@@ -89,7 +89,7 @@ class PokerEngine {
|
|
|
89
89
|
// Dry-run the reducer
|
|
90
90
|
// We don't need to deep clone state because reducer is immutable
|
|
91
91
|
// and pure, and we discard the result.
|
|
92
|
-
(0,
|
|
92
|
+
(0, game_reducer_1.gameReducer)(this.currentState, action);
|
|
93
93
|
return { valid: true };
|
|
94
94
|
}
|
|
95
95
|
catch (err) {
|
|
@@ -129,7 +129,7 @@ class PokerEngine {
|
|
|
129
129
|
optimisticAct(action) {
|
|
130
130
|
const timestamp = action.timestamp ?? this.timeProvider();
|
|
131
131
|
const actionWithTimestamp = { ...action, timestamp };
|
|
132
|
-
return (0,
|
|
132
|
+
return (0, game_reducer_1.gameReducer)(this.currentState, actionWithTimestamp);
|
|
133
133
|
}
|
|
134
134
|
/**
|
|
135
135
|
* Undo last action
|
|
@@ -152,7 +152,7 @@ class PokerEngine {
|
|
|
152
152
|
* Get player view (masked)
|
|
153
153
|
*/
|
|
154
154
|
view(playerId, version) {
|
|
155
|
-
return (0,
|
|
155
|
+
return (0, view_masking_1.createPublicView)(this.currentState, playerId ?? null, version ?? 0);
|
|
156
156
|
}
|
|
157
157
|
/**
|
|
158
158
|
* Get snapshot for serialization
|
|
@@ -222,7 +222,7 @@ class PokerEngine {
|
|
|
222
222
|
dispatch(action) {
|
|
223
223
|
const oldState = this.currentState;
|
|
224
224
|
try {
|
|
225
|
-
this.currentState = (0,
|
|
225
|
+
this.currentState = (0, game_reducer_1.gameReducer)(this.currentState, action);
|
|
226
226
|
// Notify listeners
|
|
227
227
|
for (const listener of this.listeners) {
|
|
228
228
|
listener(action, oldState, this.currentState);
|
|
@@ -238,19 +238,19 @@ class PokerEngine {
|
|
|
238
238
|
*/
|
|
239
239
|
validateConfig(config) {
|
|
240
240
|
if (config.smallBlind <= 0) {
|
|
241
|
-
throw new
|
|
241
|
+
throw new config_error_1.ConfigError("Small blind must be positive", {
|
|
242
242
|
smallBlind: config.smallBlind,
|
|
243
243
|
});
|
|
244
244
|
}
|
|
245
245
|
if (config.bigBlind <= config.smallBlind) {
|
|
246
|
-
throw new
|
|
246
|
+
throw new config_error_1.ConfigError("Big blind must be greater than small blind", {
|
|
247
247
|
smallBlind: config.smallBlind,
|
|
248
248
|
bigBlind: config.bigBlind,
|
|
249
249
|
});
|
|
250
250
|
}
|
|
251
251
|
const maxPlayers = config.maxPlayers ?? 9;
|
|
252
252
|
if (maxPlayers < 2 || maxPlayers > 10) {
|
|
253
|
-
throw new
|
|
253
|
+
throw new config_error_1.ConfigError("Max players must be between 2 and 10", {
|
|
254
254
|
maxPlayers,
|
|
255
255
|
});
|
|
256
256
|
}
|
|
@@ -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 "./error-codes";
|
|
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
|
@@ -15,8 +15,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
// Export all errors
|
|
18
|
-
__exportStar(require("./
|
|
19
|
-
__exportStar(require("./
|
|
20
|
-
__exportStar(require("./
|
|
21
|
-
__exportStar(require("./
|
|
18
|
+
__exportStar(require("./poker-engine-error"), exports);
|
|
19
|
+
__exportStar(require("./critical-state-error"), exports);
|
|
20
|
+
__exportStar(require("./illegal-action-error"), exports);
|
|
21
|
+
__exportStar(require("./config-error"), exports);
|
|
22
22
|
// ErrorCodes now exported from @pokertools/types
|
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
|
}
|
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
|
@@ -16,8 +16,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
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
18
|
// Main Engine Class
|
|
19
|
-
var
|
|
20
|
-
Object.defineProperty(exports, "PokerEngine", { enumerable: true, get: function () { return
|
|
19
|
+
var poker_engine_1 = require("./engine/poker-engine");
|
|
20
|
+
Object.defineProperty(exports, "PokerEngine", { enumerable: true, get: function () { return poker_engine_1.PokerEngine; } });
|
|
21
21
|
// Types (re-exported from @pokertools/types for convenience)
|
|
22
22
|
__exportStar(require("@pokertools/types"), exports);
|
|
23
23
|
// Errors
|
|
@@ -26,8 +26,8 @@ __exportStar(require("./errors"), exports);
|
|
|
26
26
|
var serialization_1 = require("./utils/serialization");
|
|
27
27
|
Object.defineProperty(exports, "createSnapshot", { enumerable: true, get: function () { return serialization_1.createSnapshot; } });
|
|
28
28
|
Object.defineProperty(exports, "restoreFromSnapshot", { enumerable: true, get: function () { return serialization_1.restoreFromSnapshot; } });
|
|
29
|
-
var
|
|
30
|
-
Object.defineProperty(exports, "createPublicView", { enumerable: true, get: function () { return
|
|
29
|
+
var view_masking_1 = require("./utils/view-masking");
|
|
30
|
+
Object.defineProperty(exports, "createPublicView", { enumerable: true, get: function () { return view_masking_1.createPublicView; } });
|
|
31
31
|
var invariants_1 = require("./utils/invariants");
|
|
32
32
|
Object.defineProperty(exports, "calculateTotalChips", { enumerable: true, get: function () { return invariants_1.calculateTotalChips; } });
|
|
33
33
|
Object.defineProperty(exports, "auditChipConservation", { enumerable: true, get: function () { return invariants_1.auditChipConservation; } });
|