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