@pokertools/engine 1.0.9 → 1.0.11
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 +16 -68
- package/dist/actions/dealing.js +24 -52
- package/dist/actions/management.js +0 -1
- package/dist/actions/showdown-actions.js +12 -26
- package/dist/actions/special.js +0 -4
- package/dist/actions/street-progression.js +9 -24
- package/dist/actions/tournament.js +1 -4
- package/dist/actions/validation.js +30 -35
- package/dist/browser.js +0 -1
- package/dist/engine/game-reducer.js +5 -24
- package/dist/engine/poker-engine.js +6 -20
- package/dist/errors/illegal-action-error.d.ts +1 -1
- package/dist/errors/index.js +0 -2
- package/dist/errors/poker-engine-error.js +2 -0
- package/dist/history/formats/pokerstars.js +0 -8
- package/dist/history/hand-history-builder.js +1 -2
- package/dist/index.js +0 -1
- package/dist/rules/action-order.js +2 -7
- package/dist/rules/blinds.js +4 -3
- package/dist/rules/heads-up.js +1 -2
- package/dist/rules/showdown.js +12 -29
- package/dist/rules/side-pots.js +5 -18
- package/dist/utils/deck.js +1 -4
- package/dist/utils/invariants.js +8 -0
- package/dist/utils/rake.js +6 -8
- package/dist/utils/serialization.js +2 -3
- package/dist/utils/validation.js +7 -7
- package/package.json +6 -6
- package/dist/errors/error-codes.d.ts +0 -7
- package/dist/errors/error-codes.js +0 -12
|
@@ -11,7 +11,6 @@ const blinds_1 = require("./blinds");
|
|
|
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
14
|
if ((0, heads_up_1.isHeadsUp)(state)) {
|
|
16
15
|
return getNextToActHeadsUp(state);
|
|
17
16
|
}
|
|
@@ -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);
|
|
@@ -125,7 +122,6 @@ 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
127
|
if (player?.status === "ACTIVE" /* PlayerStatus.ACTIVE */ && player.stack > 0 && !player.isSittingOut) {
|
|
@@ -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
|
@@ -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
|
|
@@ -76,10 +76,11 @@ function calculateAntes(state) {
|
|
|
76
76
|
if (state.ante === 0) {
|
|
77
77
|
return antes;
|
|
78
78
|
}
|
|
79
|
-
|
|
79
|
+
const isTournament = !!state.config.blindStructure;
|
|
80
|
+
// Tournament players blind/ante off while sitting out; cash-game sit-outs do not.
|
|
80
81
|
for (let seat = 0; seat < state.players.length; seat++) {
|
|
81
82
|
const player = state.players[seat];
|
|
82
|
-
if (player && player.stack > 0) {
|
|
83
|
+
if (player && player.stack > 0 && (isTournament || !player.isSittingOut)) {
|
|
83
84
|
const anteAmount = Math.min(player.stack, state.ante);
|
|
84
85
|
antes.set(seat, anteAmount);
|
|
85
86
|
}
|
package/dist/rules/heads-up.js
CHANGED
|
@@ -25,14 +25,13 @@ function getHeadsUpActionOrder(state, street) {
|
|
|
25
25
|
const buttonSeat = state.buttonSeat;
|
|
26
26
|
const activePlayers = state.players
|
|
27
27
|
.map((p, seat) => ({ player: p, seat }))
|
|
28
|
-
.filter(({ player }) => player
|
|
28
|
+
.filter(({ player }) => player?.status === "ACTIVE" /* PlayerStatus.ACTIVE */)
|
|
29
29
|
.map(({ seat }) => seat);
|
|
30
30
|
if (activePlayers.length !== 2) {
|
|
31
31
|
return activePlayers;
|
|
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
|
package/dist/rules/showdown.js
CHANGED
|
@@ -34,8 +34,8 @@ function determineWinners(state) {
|
|
|
34
34
|
// Distribute pot among winners
|
|
35
35
|
const share = Math.floor(potAfterRake / potWinners.length);
|
|
36
36
|
const remainder = potAfterRake % potWinners.length;
|
|
37
|
-
// Sort winners by position
|
|
38
|
-
// TDA Rule:
|
|
37
|
+
// Sort winners by position for odd chip distribution.
|
|
38
|
+
// TDA Rule: odd chips go to first player(s) clockwise from the button.
|
|
39
39
|
const sortedWinners = [...potWinners].sort((a, b) => {
|
|
40
40
|
if (state.buttonSeat === null)
|
|
41
41
|
return 0;
|
|
@@ -47,20 +47,17 @@ function determineWinners(state) {
|
|
|
47
47
|
for (let i = 0; i < sortedWinners.length; i++) {
|
|
48
48
|
const evaluation = sortedWinners[i];
|
|
49
49
|
let award = share;
|
|
50
|
-
//
|
|
51
|
-
// (first N winners in sorted order get the extra chips)
|
|
50
|
+
// First N winners (worst position) each get one extra chip.
|
|
52
51
|
if (i < remainder) {
|
|
53
52
|
award += 1;
|
|
54
53
|
}
|
|
55
|
-
// Track winner seats
|
|
56
54
|
winnerSeats.add(evaluation.seat);
|
|
57
|
-
// Award chips to player
|
|
58
55
|
const player = newPlayers[evaluation.seat];
|
|
59
56
|
newPlayers[evaluation.seat] = {
|
|
60
57
|
...player,
|
|
61
58
|
stack: player.stack + award,
|
|
62
59
|
};
|
|
63
|
-
// Record winner
|
|
60
|
+
// Record winner with hand description.
|
|
64
61
|
winners.push({
|
|
65
62
|
seat: evaluation.seat,
|
|
66
63
|
amount: award,
|
|
@@ -77,7 +74,7 @@ function determineWinners(state) {
|
|
|
77
74
|
// Winners must show all cards
|
|
78
75
|
newPlayers[seat] = {
|
|
79
76
|
...player,
|
|
80
|
-
shownCards:
|
|
77
|
+
shownCards: Array.from({ length: player.hand.length }, (_, i) => i),
|
|
81
78
|
};
|
|
82
79
|
}
|
|
83
80
|
else {
|
|
@@ -104,11 +101,10 @@ function determineWinners(state) {
|
|
|
104
101
|
* Evaluate a single pot and return winner(s)
|
|
105
102
|
*/
|
|
106
103
|
function evaluatePot(state, pot) {
|
|
107
|
-
// Get eligible players (not folded)
|
|
108
104
|
const eligible = pot.eligibleSeats
|
|
109
105
|
.map((seat) => state.players[seat])
|
|
110
106
|
.filter((player) => player && (player.status === "ACTIVE" /* PlayerStatus.ACTIVE */ || player.status === "ALL_IN" /* PlayerStatus.ALL_IN */));
|
|
111
|
-
//
|
|
107
|
+
// Single remaining player wins uncontested.
|
|
112
108
|
if (eligible.length === 1) {
|
|
113
109
|
const player = eligible[0];
|
|
114
110
|
return [
|
|
@@ -120,22 +116,17 @@ function evaluatePot(state, pot) {
|
|
|
120
116
|
},
|
|
121
117
|
];
|
|
122
118
|
}
|
|
123
|
-
// Evaluate all hands
|
|
124
119
|
const evaluations = [];
|
|
125
120
|
for (const player of eligible) {
|
|
126
121
|
if (!player?.hand)
|
|
127
122
|
continue;
|
|
128
|
-
// Skip masked hands
|
|
123
|
+
// Skip masked hands in client mode.
|
|
129
124
|
if (player.hand.some((c) => c === null)) {
|
|
130
125
|
continue;
|
|
131
126
|
}
|
|
132
|
-
// Combine hole cards + board (7 cards total for river)
|
|
133
127
|
const allCards = [...player.hand, ...state.board];
|
|
134
|
-
if (allCards.length < 5)
|
|
135
|
-
// Not enough cards (shouldn't happen)
|
|
128
|
+
if (allCards.length < 5)
|
|
136
129
|
continue;
|
|
137
|
-
}
|
|
138
|
-
// Evaluate using @pokertools/evaluator
|
|
139
130
|
const cardCodes = (0, evaluator_1.getCardCodes)(allCards);
|
|
140
131
|
const score = (0, evaluator_1.evaluate)(cardCodes);
|
|
141
132
|
const handRank = (0, evaluator_1.rank)(cardCodes);
|
|
@@ -150,10 +141,8 @@ function evaluatePot(state, pot) {
|
|
|
150
141
|
if (evaluations.length === 0) {
|
|
151
142
|
return [];
|
|
152
143
|
}
|
|
153
|
-
// Find best hand(s)
|
|
154
144
|
const bestScore = Math.min(...evaluations.map((e) => e.score));
|
|
155
|
-
|
|
156
|
-
return winners;
|
|
145
|
+
return evaluations.filter((e) => e.score === bestScore);
|
|
157
146
|
}
|
|
158
147
|
/**
|
|
159
148
|
* Return the concrete five cards that produce the best evaluator score.
|
|
@@ -192,16 +181,10 @@ function getBestFiveCardHand(cards) {
|
|
|
192
181
|
* Check if hand should go to showdown
|
|
193
182
|
*/
|
|
194
183
|
function shouldShowdown(state) {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
// 3. Winners haven't been determined yet
|
|
199
|
-
if (state.street !== "SHOWDOWN" /* Street.SHOWDOWN */) {
|
|
184
|
+
if (state.street !== "SHOWDOWN" /* Street.SHOWDOWN */)
|
|
185
|
+
return false;
|
|
186
|
+
if (state.winners !== null)
|
|
200
187
|
return false;
|
|
201
|
-
}
|
|
202
|
-
if (state.winners !== null) {
|
|
203
|
-
return false; // Already determined winners
|
|
204
|
-
}
|
|
205
188
|
const activePlayers = state.players.filter((p) => p && (p.status === "ACTIVE" /* PlayerStatus.ACTIVE */ || p.status === "ALL_IN" /* PlayerStatus.ALL_IN */));
|
|
206
189
|
return activePlayers.length >= 2;
|
|
207
190
|
}
|
package/dist/rules/side-pots.js
CHANGED
|
@@ -36,11 +36,9 @@ function calculateSidePots(state) {
|
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
-
// If no investments, return empty
|
|
40
39
|
if (investments.length === 0) {
|
|
41
40
|
return [];
|
|
42
41
|
}
|
|
43
|
-
// Sort by investment (ascending)
|
|
44
42
|
investments.sort((a, b) => a.amount - b.amount);
|
|
45
43
|
const pots = [];
|
|
46
44
|
let prevAmount = 0;
|
|
@@ -48,14 +46,11 @@ function calculateSidePots(state) {
|
|
|
48
46
|
const current = investments[i];
|
|
49
47
|
const allAtThisLevel = investments.slice(i); // Current + all higher investors
|
|
50
48
|
const increment = current.amount - prevAmount;
|
|
51
|
-
// Create pot for this level
|
|
52
49
|
if (increment > 0) {
|
|
53
|
-
// Pot includes
|
|
50
|
+
// Pot includes ALL players at this level (folded players' chips stay in).
|
|
54
51
|
const potAmount = increment * allAtThisLevel.length;
|
|
55
|
-
//
|
|
52
|
+
// Only non-folded players are eligible to win.
|
|
56
53
|
const eligibleSeats = allAtThisLevel.filter((inv) => !inv.folded).map((inv) => inv.seat);
|
|
57
|
-
// Must have at least one eligible player
|
|
58
|
-
// If everyone folded at this level, something went wrong in the game logic
|
|
59
54
|
if (eligibleSeats.length === 0) {
|
|
60
55
|
throw new critical_state_error_1.CriticalStateError(`Side pot has no eligible players - all ${allAtThisLevel.length} players at this level have folded`, {
|
|
61
56
|
potAmount,
|
|
@@ -86,10 +81,8 @@ function calculateSidePots(state) {
|
|
|
86
81
|
* @returns Tuple of [uncalled amount, seat to return to]
|
|
87
82
|
*/
|
|
88
83
|
function calculateUncalledBet(state) {
|
|
89
|
-
if (state.currentBets.size === 0)
|
|
84
|
+
if (state.currentBets.size === 0)
|
|
90
85
|
return null;
|
|
91
|
-
}
|
|
92
|
-
// Find highest bet
|
|
93
86
|
let maxBet = 0;
|
|
94
87
|
let maxBetSeat = -1;
|
|
95
88
|
let secondMaxBet = 0;
|
|
@@ -119,21 +112,17 @@ function returnUncalledBet(state) {
|
|
|
119
112
|
}
|
|
120
113
|
const [amount, seat] = uncalled;
|
|
121
114
|
const player = state.players[seat];
|
|
122
|
-
if (!player)
|
|
115
|
+
if (!player)
|
|
123
116
|
return state;
|
|
124
|
-
}
|
|
125
|
-
// Return chips to player
|
|
126
117
|
const newPlayers = [...state.players];
|
|
127
118
|
newPlayers[seat] = {
|
|
128
119
|
...player,
|
|
129
120
|
stack: player.stack + amount,
|
|
130
121
|
totalInvestedThisHand: player.totalInvestedThisHand - amount,
|
|
131
122
|
};
|
|
132
|
-
// Reduce current bet
|
|
133
123
|
const newCurrentBets = new Map(state.currentBets);
|
|
134
124
|
const currentBet = newCurrentBets.get(seat) ?? 0;
|
|
135
125
|
newCurrentBets.set(seat, currentBet - amount);
|
|
136
|
-
// Record to action history
|
|
137
126
|
const actionRecord = {
|
|
138
127
|
action: {
|
|
139
128
|
type: "UNCALLED_BET_RETURNED" /* ActionType.UNCALLED_BET_RETURNED */,
|
|
@@ -158,11 +147,9 @@ function returnUncalledBet(state) {
|
|
|
158
147
|
* This is called before progressing to next street
|
|
159
148
|
*/
|
|
160
149
|
function recalculatePots(state) {
|
|
161
|
-
// First, return any uncalled bet
|
|
162
150
|
const newState = returnUncalledBet(state);
|
|
163
|
-
// Calculate side pots based on all investments
|
|
164
151
|
const pots = calculateSidePots(newState);
|
|
165
|
-
// Reset betThisStreet
|
|
152
|
+
// Reset betThisStreet after collecting bets into pots.
|
|
166
153
|
const newPlayers = newState.players.map((p) => (p ? { ...p, betThisStreet: 0 } : null));
|
|
167
154
|
return {
|
|
168
155
|
...newState,
|
package/dist/utils/deck.js
CHANGED
|
@@ -28,10 +28,8 @@ function createDeck() {
|
|
|
28
28
|
* be used for production poker games. Always provide a secure RNG.
|
|
29
29
|
*/
|
|
30
30
|
function getSecureRandom() {
|
|
31
|
-
// Check if we're in Node.js environment
|
|
32
31
|
if (typeof process !== "undefined" && process.versions?.node) {
|
|
33
32
|
try {
|
|
34
|
-
// Use Node.js crypto for production
|
|
35
33
|
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
|
|
36
34
|
const crypto = require("crypto");
|
|
37
35
|
return () => {
|
|
@@ -81,11 +79,10 @@ function getSecureRandom() {
|
|
|
81
79
|
*/
|
|
82
80
|
function shuffle(deck, rng) {
|
|
83
81
|
const random = rng ?? getSecureRandom();
|
|
84
|
-
const shuffled = [...deck];
|
|
82
|
+
const shuffled = [...deck];
|
|
85
83
|
// Fisher-Yates shuffle
|
|
86
84
|
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
87
85
|
const j = Math.floor(random() * (i + 1));
|
|
88
|
-
// Swap elements
|
|
89
86
|
const temp = shuffled[i];
|
|
90
87
|
shuffled[i] = shuffled[j];
|
|
91
88
|
shuffled[j] = temp;
|
package/dist/utils/invariants.js
CHANGED
|
@@ -160,6 +160,14 @@ function validateGameStateIntegrity(state) {
|
|
|
160
160
|
actionTo: state.actionTo,
|
|
161
161
|
});
|
|
162
162
|
}
|
|
163
|
+
if (player.status !== "ACTIVE" /* PlayerStatus.ACTIVE */ || player.stack <= 0 || player.isSittingOut) {
|
|
164
|
+
throw new critical_state_error_1.CriticalStateError(`ActionTo points to non-actionable seat: ${state.actionTo}`, {
|
|
165
|
+
actionTo: state.actionTo,
|
|
166
|
+
status: player.status,
|
|
167
|
+
stack: player.stack,
|
|
168
|
+
isSittingOut: player.isSittingOut,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
163
171
|
}
|
|
164
172
|
// 6. Button must be valid or null
|
|
165
173
|
if (state.buttonSeat !== null) {
|
package/dist/utils/rake.js
CHANGED
|
@@ -10,25 +10,23 @@ exports.calculateRake = calculateRake;
|
|
|
10
10
|
* @returns Object with rake amount and whether cap was hit
|
|
11
11
|
*/
|
|
12
12
|
function calculateRake(state, potAmount, rakeTakenSoFar = 0) {
|
|
13
|
-
// No rake for tournaments (identified by presence of
|
|
13
|
+
// No rake for tournaments (identified by presence of blindStructure)
|
|
14
14
|
if (state.config.blindStructure) {
|
|
15
15
|
return { rake: 0, capReached: false };
|
|
16
16
|
}
|
|
17
|
-
// No rake if not configured
|
|
18
17
|
const rakePercent = state.config.rakePercent ?? 0;
|
|
19
18
|
if (rakePercent === 0) {
|
|
20
19
|
return { rake: 0, capReached: false };
|
|
21
20
|
}
|
|
22
|
-
// "No Flop, No Drop" rule (standard in most cash games)
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
const noFlopNoDrop = state.config.noFlopNoDrop !== false;
|
|
21
|
+
// "No Flop, No Drop" rule (standard in most cash games).
|
|
22
|
+
// When enabled (default), no rake is taken if no flop was dealt,
|
|
23
|
+
// regardless of whether the hand ended preflop or all-in preflop.
|
|
24
|
+
const noFlopNoDrop = state.config.noFlopNoDrop !== false;
|
|
26
25
|
if (noFlopNoDrop && state.board.length === 0) {
|
|
27
26
|
return { rake: 0, capReached: false };
|
|
28
27
|
}
|
|
29
|
-
// Calculate rake as percentage
|
|
30
28
|
let rake = Math.floor((potAmount * rakePercent) / 100);
|
|
31
|
-
// Apply
|
|
29
|
+
// Apply per-hand rake cap (not per-pot).
|
|
32
30
|
let capReached = false;
|
|
33
31
|
if (state.config.rakeCap !== undefined) {
|
|
34
32
|
const rakeAllowed = state.config.rakeCap - rakeTakenSoFar;
|
|
@@ -75,10 +75,10 @@ function restoreFromSnapshot(snapshot) {
|
|
|
75
75
|
...snapshot,
|
|
76
76
|
currentBets,
|
|
77
77
|
timeBanks,
|
|
78
|
-
timeBankActiveSeat: snapshot.timeBankActiveSeat
|
|
78
|
+
timeBankActiveSeat: snapshot.timeBankActiveSeat,
|
|
79
79
|
previousStates,
|
|
80
80
|
initialChips: snapshot.initialChips,
|
|
81
|
-
rakeThisHand: snapshot.rakeThisHand
|
|
81
|
+
rakeThisHand: snapshot.rakeThisHand,
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
84
|
/**
|
|
@@ -99,7 +99,6 @@ function deserializeSnapshot(json) {
|
|
|
99
99
|
*/
|
|
100
100
|
function validateSnapshot(snapshot) {
|
|
101
101
|
try {
|
|
102
|
-
// Basic validation
|
|
103
102
|
if (!snapshot.handId)
|
|
104
103
|
return false;
|
|
105
104
|
if (snapshot.maxPlayers < 2 || snapshot.maxPlayers > 10)
|
package/dist/utils/validation.js
CHANGED
|
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.validateChipAmount = validateChipAmount;
|
|
7
7
|
exports.validateTimestamp = validateTimestamp;
|
|
8
8
|
const illegal_action_error_1 = require("../errors/illegal-action-error");
|
|
9
|
-
const
|
|
9
|
+
const types_1 = require("@pokertools/types");
|
|
10
10
|
const constants_1 = require("./constants");
|
|
11
11
|
/**
|
|
12
12
|
* Validate that a chip amount is a non-negative integer
|
|
@@ -18,13 +18,13 @@ const constants_1 = require("./constants");
|
|
|
18
18
|
*/
|
|
19
19
|
function validateChipAmount(amount, context) {
|
|
20
20
|
if (!Number.isFinite(amount)) {
|
|
21
|
-
throw new illegal_action_error_1.IllegalActionError(
|
|
21
|
+
throw new illegal_action_error_1.IllegalActionError(types_1.ErrorCodes.INVALID_AMOUNT, `${context}: ${amount} is not a valid number`, { amount, context });
|
|
22
22
|
}
|
|
23
23
|
if (!Number.isInteger(amount)) {
|
|
24
|
-
throw new illegal_action_error_1.IllegalActionError(
|
|
24
|
+
throw new illegal_action_error_1.IllegalActionError(types_1.ErrorCodes.INVALID_AMOUNT, `${context}: ${amount} must be an integer (fractional chips not allowed)`, { amount, context });
|
|
25
25
|
}
|
|
26
26
|
if (amount < 0) {
|
|
27
|
-
throw new illegal_action_error_1.IllegalActionError(
|
|
27
|
+
throw new illegal_action_error_1.IllegalActionError(types_1.ErrorCodes.INVALID_AMOUNT, `${context}: ${amount} cannot be negative`, { amount, context });
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
@@ -36,17 +36,17 @@ function validateChipAmount(amount, context) {
|
|
|
36
36
|
*/
|
|
37
37
|
function validateTimestamp(timestamp, previousTimestamp) {
|
|
38
38
|
if (!Number.isFinite(timestamp) || timestamp < 0) {
|
|
39
|
-
throw new illegal_action_error_1.IllegalActionError(
|
|
39
|
+
throw new illegal_action_error_1.IllegalActionError(types_1.ErrorCodes.INVALID_TIMESTAMP, `Invalid timestamp: ${timestamp}`, {
|
|
40
40
|
timestamp,
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
43
|
// Allow some clock drift tolerance for "future" timestamps
|
|
44
44
|
const now = Date.now() + constants_1.TIMESTAMP_FUTURE_TOLERANCE_MS;
|
|
45
45
|
if (timestamp > now) {
|
|
46
|
-
throw new illegal_action_error_1.IllegalActionError(
|
|
46
|
+
throw new illegal_action_error_1.IllegalActionError(types_1.ErrorCodes.INVALID_TIMESTAMP, `Timestamp ${timestamp} is in the future (current: ${Date.now()})`, { timestamp, currentTime: Date.now() });
|
|
47
47
|
}
|
|
48
48
|
// Ensure timestamps are monotonically increasing (or equal for same-time actions)
|
|
49
49
|
if (previousTimestamp !== undefined && timestamp < previousTimestamp) {
|
|
50
|
-
throw new illegal_action_error_1.IllegalActionError(
|
|
50
|
+
throw new illegal_action_error_1.IllegalActionError(types_1.ErrorCodes.INVALID_TIMESTAMP, `Timestamp ${timestamp} is before previous action timestamp ${previousTimestamp}`, { timestamp, previousTimestamp });
|
|
51
51
|
}
|
|
52
52
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pokertools/engine",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
4
4
|
"description": "Enterprise-grade Texas Hold'em poker engine",
|
|
5
5
|
"author": "A.Aurelius",
|
|
6
6
|
"license": "MIT",
|
|
@@ -63,14 +63,14 @@
|
|
|
63
63
|
"access": "public"
|
|
64
64
|
},
|
|
65
65
|
"engines": {
|
|
66
|
-
"node": ">=
|
|
66
|
+
"node": ">=24.0.0"
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"@pokertools/evaluator": "1.0.
|
|
70
|
-
"@pokertools/types": "1.0.
|
|
69
|
+
"@pokertools/evaluator": "1.0.11",
|
|
70
|
+
"@pokertools/types": "1.0.11"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
|
-
"@pokertools/evaluator": "1.0.
|
|
74
|
-
"@pokertools/types": "1.0.
|
|
73
|
+
"@pokertools/evaluator": "1.0.11",
|
|
74
|
+
"@pokertools/types": "1.0.11"
|
|
75
75
|
}
|
|
76
76
|
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ErrorCodes are now managed in @pokertools/types for consistency across packages.
|
|
3
|
-
* This file re-exports them for backwards compatibility.
|
|
4
|
-
*
|
|
5
|
-
* @deprecated Import from "@pokertools/types" instead
|
|
6
|
-
*/
|
|
7
|
-
export { ErrorCodes, type ErrorCode, hasErrorCode } from "@pokertools/types";
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.hasErrorCode = exports.ErrorCodes = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* ErrorCodes are now managed in @pokertools/types for consistency across packages.
|
|
6
|
-
* This file re-exports them for backwards compatibility.
|
|
7
|
-
*
|
|
8
|
-
* @deprecated Import from "@pokertools/types" instead
|
|
9
|
-
*/
|
|
10
|
-
var types_1 = require("@pokertools/types");
|
|
11
|
-
Object.defineProperty(exports, "ErrorCodes", { enumerable: true, get: function () { return types_1.ErrorCodes; } });
|
|
12
|
-
Object.defineProperty(exports, "hasErrorCode", { enumerable: true, get: function () { return types_1.hasErrorCode; } });
|