@pokertools/engine 1.0.0 → 1.0.1
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 +3 -3
- package/dist/actions/betting.js +76 -48
- package/dist/actions/dealing.js +72 -7
- package/dist/actions/management.d.ts +12 -1
- package/dist/actions/management.js +60 -0
- package/dist/actions/validation.js +27 -2
- package/dist/engine/gameReducer.js +6 -0
- package/package.json +1 -9
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# @pokertools/engine
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@pokertools/engine)
|
|
4
|
-
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://github.com/aaurelions/pokertools/actions)
|
|
6
|
+
[](https://codecov.io/gh/aaurelions/pokertools)
|
|
7
7
|
[](https://bundlephobia.com/package/@pokertools/engine)
|
|
8
8
|
[](https://www.typescriptlang.org/)
|
|
9
9
|
|
package/dist/actions/betting.js
CHANGED
|
@@ -274,10 +274,14 @@ function getTotalPot(state) {
|
|
|
274
274
|
}
|
|
275
275
|
/**
|
|
276
276
|
* Award pots to remaining eligible players when hand ends by folds
|
|
277
|
-
* Properly handles side pot eligibility and
|
|
277
|
+
* Properly handles side pot eligibility and uncalled bets
|
|
278
|
+
*
|
|
279
|
+
* Key principle: Uncalled bets are NOT raked and are returned to the bettor immediately.
|
|
280
|
+
* Only the contested portion of the pot (money actually at risk) is subject to rake.
|
|
278
281
|
*/
|
|
279
282
|
function awardPotToLastPlayer(state, winningSeat) {
|
|
280
283
|
const newPlayers = [...state.players];
|
|
284
|
+
const newActionHistory = [...state.actionHistory];
|
|
281
285
|
const winners = [];
|
|
282
286
|
// Process each pot separately, checking eligibility
|
|
283
287
|
let totalRakeFromPots = 0;
|
|
@@ -335,63 +339,87 @@ function awardPotToLastPlayer(state, winningSeat) {
|
|
|
335
339
|
});
|
|
336
340
|
}
|
|
337
341
|
}
|
|
338
|
-
//
|
|
339
|
-
|
|
340
|
-
let currentBetsTotal = 0;
|
|
341
|
-
for (const bet of state.currentBets.values()) {
|
|
342
|
-
currentBetsTotal += bet;
|
|
343
|
-
}
|
|
344
|
-
const newActionHistory = [...state.actionHistory];
|
|
345
|
-
let totalRake = 0;
|
|
346
|
-
if (currentBetsTotal > 0) {
|
|
347
|
-
const player = newPlayers[winningSeat];
|
|
342
|
+
// Handle current bets with proper uncalled bet logic
|
|
343
|
+
if (state.currentBets.size > 0) {
|
|
348
344
|
const winnersBet = state.currentBets.get(winningSeat) ?? 0;
|
|
349
|
-
//
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
345
|
+
// Find the second-highest bet (highest opponent bet)
|
|
346
|
+
// This determines how much of the winner's bet was actually "called"
|
|
347
|
+
let maxOpponentBet = 0;
|
|
348
|
+
for (const [seat, amount] of state.currentBets.entries()) {
|
|
349
|
+
if (seat !== winningSeat && amount > maxOpponentBet) {
|
|
350
|
+
maxOpponentBet = amount;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// Calculate uncalled and called portions
|
|
354
|
+
let uncalledAmount = 0;
|
|
355
|
+
let calledPortion = 0;
|
|
356
|
+
if (winnersBet > maxOpponentBet) {
|
|
357
|
+
uncalledAmount = winnersBet - maxOpponentBet;
|
|
358
|
+
calledPortion = maxOpponentBet;
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
calledPortion = winnersBet;
|
|
362
|
+
}
|
|
363
|
+
// Step 1: Return uncalled bet immediately (NO RAKE on uncalled bets)
|
|
364
|
+
if (uncalledAmount > 0) {
|
|
365
|
+
const player = newPlayers[winningSeat];
|
|
366
|
+
newPlayers[winningSeat] = {
|
|
367
|
+
...player,
|
|
368
|
+
stack: player.stack + uncalledAmount,
|
|
369
|
+
};
|
|
370
|
+
// Record the uncalled bet return
|
|
371
|
+
newActionHistory.push({
|
|
361
372
|
action: {
|
|
362
373
|
type: "UNCALLED_BET_RETURNED" /* ActionType.UNCALLED_BET_RETURNED */,
|
|
363
374
|
playerId: player.id,
|
|
364
|
-
amount:
|
|
375
|
+
amount: uncalledAmount,
|
|
365
376
|
timestamp: state.timestamp,
|
|
366
377
|
},
|
|
367
378
|
seat: winningSeat,
|
|
368
|
-
resultingPot:
|
|
369
|
-
resultingStack: player.stack +
|
|
379
|
+
resultingPot: getTotalPot(state) - uncalledAmount,
|
|
380
|
+
resultingStack: player.stack + uncalledAmount,
|
|
370
381
|
street: state.street,
|
|
371
|
-
};
|
|
372
|
-
newActionHistory.push(uncalledBetAction);
|
|
382
|
+
});
|
|
373
383
|
}
|
|
374
|
-
//
|
|
375
|
-
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
const existingIndex = winners.findIndex((w) => w.seat === winningSeat);
|
|
380
|
-
if (existingIndex >= 0) {
|
|
381
|
-
// Update existing winner's amount
|
|
382
|
-
winners[existingIndex] = {
|
|
383
|
-
...winners[existingIndex],
|
|
384
|
-
amount: winners[existingIndex].amount + actualWinnings,
|
|
385
|
-
};
|
|
384
|
+
// Step 2: Calculate contested pot (winner's called portion + all opponent bets)
|
|
385
|
+
let contestedPot = calledPortion;
|
|
386
|
+
for (const [seat, amount] of state.currentBets.entries()) {
|
|
387
|
+
if (seat !== winningSeat) {
|
|
388
|
+
contestedPot += amount;
|
|
386
389
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
390
|
+
}
|
|
391
|
+
// Step 3: Rake and award the contested portion only
|
|
392
|
+
if (contestedPot > 0) {
|
|
393
|
+
const { rake } = (0, rake_1.calculateRake)(state, contestedPot, totalRakeFromPots);
|
|
394
|
+
const totalRake = totalRakeFromPots + rake;
|
|
395
|
+
const winnings = contestedPot - rake;
|
|
396
|
+
const player = newPlayers[winningSeat];
|
|
397
|
+
newPlayers[winningSeat] = {
|
|
398
|
+
...player,
|
|
399
|
+
stack: player.stack + winnings,
|
|
400
|
+
};
|
|
401
|
+
// Update winners array
|
|
402
|
+
// Only count actual winnings (contested pot after rake, minus winner's own contribution)
|
|
403
|
+
const actualWinnings = winnings - calledPortion;
|
|
404
|
+
if (actualWinnings > 0) {
|
|
405
|
+
const existingIndex = winners.findIndex((w) => w.seat === winningSeat);
|
|
406
|
+
if (existingIndex >= 0) {
|
|
407
|
+
winners[existingIndex] = {
|
|
408
|
+
...winners[existingIndex],
|
|
409
|
+
amount: winners[existingIndex].amount + actualWinnings,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
winners.push({
|
|
414
|
+
seat: winningSeat,
|
|
415
|
+
amount: actualWinnings,
|
|
416
|
+
hand: null,
|
|
417
|
+
handRank: null,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
394
420
|
}
|
|
421
|
+
// Update total rake for the hand
|
|
422
|
+
totalRakeFromPots = totalRake;
|
|
395
423
|
}
|
|
396
424
|
}
|
|
397
425
|
// NOTE: We do NOT reset totalInvestedThisHand here because it's used by getInitialChips()
|
|
@@ -405,6 +433,6 @@ function awardPotToLastPlayer(state, winningSeat) {
|
|
|
405
433
|
winners,
|
|
406
434
|
actionTo: null,
|
|
407
435
|
actionHistory: newActionHistory,
|
|
408
|
-
rakeThisHand: state.rakeThisHand +
|
|
436
|
+
rakeThisHand: state.rakeThisHand + totalRakeFromPots,
|
|
409
437
|
};
|
|
410
438
|
}
|
package/dist/actions/dealing.js
CHANGED
|
@@ -21,17 +21,81 @@ function handleDeal(state, action) {
|
|
|
21
21
|
// Create and shuffle deck
|
|
22
22
|
const rng = state.config.randomProvider ?? Math.random;
|
|
23
23
|
const deck = (0, deck_1.shuffle)((0, deck_1.createDeck)(), rng);
|
|
24
|
+
// Create a copy of timeBanks to modify
|
|
25
|
+
const newTimeBanks = new Map(state.timeBanks);
|
|
26
|
+
// First, merge pendingAddOn into stack for all players
|
|
27
|
+
const newPlayers = state.players.map((player) => {
|
|
28
|
+
if (!player)
|
|
29
|
+
return null;
|
|
30
|
+
// Skip reserved players (they haven't confirmed yet)
|
|
31
|
+
if (player.status === "RESERVED" /* PlayerStatus.RESERVED */) {
|
|
32
|
+
// Check if reservation has expired
|
|
33
|
+
if (player.reservationExpiry && action.timestamp >= player.reservationExpiry) {
|
|
34
|
+
// Reservation expired, remove player
|
|
35
|
+
newTimeBanks.delete(player.seat);
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
// Keep reserved player as-is
|
|
39
|
+
return player;
|
|
40
|
+
}
|
|
41
|
+
// Merge pendingAddOn into stack
|
|
42
|
+
const newStack = player.stack + player.pendingAddOn;
|
|
43
|
+
return {
|
|
44
|
+
...player,
|
|
45
|
+
stack: newStack,
|
|
46
|
+
pendingAddOn: 0, // Clear pending add-on
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
// Get blind positions for this hand
|
|
50
|
+
const blindPositions = (0, blinds_1.getBlindPositions)({
|
|
51
|
+
...state,
|
|
52
|
+
buttonSeat: newButtonSeat,
|
|
53
|
+
players: newPlayers,
|
|
54
|
+
});
|
|
24
55
|
// Get players who will be dealt in
|
|
25
56
|
const playersToReceive = [];
|
|
26
|
-
for (let seat = 0; seat <
|
|
27
|
-
const player =
|
|
28
|
-
|
|
57
|
+
for (let seat = 0; seat < newPlayers.length; seat++) {
|
|
58
|
+
const player = newPlayers[seat];
|
|
59
|
+
// Basic eligibility checks
|
|
60
|
+
if (!player || player.stack <= 0 || player.status === "RESERVED" /* PlayerStatus.RESERVED */) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
// We check WAIT_FOR_BB *before* checking isSittingOut.
|
|
64
|
+
// This allows us to "unsit" a player if they hit the Big Blind.
|
|
65
|
+
let shouldPlay = true;
|
|
66
|
+
if (!isTournament && player.sitInOption === "WAIT_FOR_BB" /* SitInOption.WAIT_FOR_BB */) {
|
|
67
|
+
const isInBigBlind = blindPositions?.bigBlindSeat === seat;
|
|
68
|
+
if (isInBigBlind) {
|
|
69
|
+
// PLAYER RE-ENTRY: They are in the Big Blind. Force them active.
|
|
70
|
+
// We must update the player object in newPlayers to reflect they are back.
|
|
71
|
+
newPlayers[seat] = {
|
|
72
|
+
...player,
|
|
73
|
+
isSittingOut: false,
|
|
74
|
+
};
|
|
75
|
+
shouldPlay = true;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// Not in BB yet. Force them to sit out.
|
|
79
|
+
// Only update if not already sitting out to avoid object churn
|
|
80
|
+
if (!player.isSittingOut) {
|
|
81
|
+
newPlayers[seat] = {
|
|
82
|
+
...player,
|
|
83
|
+
isSittingOut: true,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
shouldPlay = false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else if (player.isSittingOut) {
|
|
90
|
+
// Standard sitting out check
|
|
91
|
+
shouldPlay = false;
|
|
92
|
+
}
|
|
93
|
+
if (shouldPlay) {
|
|
29
94
|
playersToReceive.push(seat);
|
|
30
95
|
}
|
|
31
96
|
}
|
|
32
97
|
// Deal 2 cards to each player
|
|
33
98
|
let remainingDeck = deck;
|
|
34
|
-
const newPlayers = [...state.players];
|
|
35
99
|
// Initialize hands for receiving players (active, not sitting out)
|
|
36
100
|
for (const seat of playersToReceive) {
|
|
37
101
|
newPlayers[seat] = {
|
|
@@ -76,14 +140,15 @@ function handleDeal(state, action) {
|
|
|
76
140
|
}
|
|
77
141
|
}
|
|
78
142
|
// Post blinds and antes
|
|
79
|
-
|
|
143
|
+
// Recalculate blind positions with the new button seat
|
|
144
|
+
const finalBlindPositions = (0, blinds_1.getBlindPositions)({
|
|
80
145
|
...state,
|
|
81
146
|
buttonSeat: newButtonSeat,
|
|
82
147
|
players: newPlayers,
|
|
83
148
|
});
|
|
84
149
|
const currentBets = new Map();
|
|
85
|
-
if (
|
|
86
|
-
const { smallBlindSeat, bigBlindSeat } =
|
|
150
|
+
if (finalBlindPositions) {
|
|
151
|
+
const { smallBlindSeat, bigBlindSeat } = finalBlindPositions;
|
|
87
152
|
// Post small blind
|
|
88
153
|
// In tournaments: sitting-out players MUST post to prevent "blinding off" exploit
|
|
89
154
|
// In cash games: sitting-out SB is treated as "Dead Small Blind" (no post)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GameState, SitAction, StandAction } from "@pokertools/types";
|
|
1
|
+
import { GameState, SitAction, StandAction, AddChipsAction, ReserveSeatAction } from "@pokertools/types";
|
|
2
2
|
/**
|
|
3
3
|
* Handle SIT action - add player to table
|
|
4
4
|
*/
|
|
@@ -7,3 +7,14 @@ export declare function handleSit(state: GameState, action: SitAction): GameStat
|
|
|
7
7
|
* Handle STAND action - remove player from table
|
|
8
8
|
*/
|
|
9
9
|
export declare function handleStand(state: GameState, action: StandAction): GameState;
|
|
10
|
+
/**
|
|
11
|
+
* Handle ADD_CHIPS action - add chips to player's pending stack
|
|
12
|
+
* Chips are held in pendingAddOn and will be merged into stack at start of next hand
|
|
13
|
+
*/
|
|
14
|
+
export declare function handleAddChips(state: GameState, action: AddChipsAction): GameState;
|
|
15
|
+
/**
|
|
16
|
+
* Handle RESERVE_SEAT action - reserve a seat for a player
|
|
17
|
+
* Marks the seat as RESERVED with an expiration timestamp
|
|
18
|
+
* API can use this to lock a seat while processing payment
|
|
19
|
+
*/
|
|
20
|
+
export declare function handleReserveSeat(state: GameState, action: ReserveSeatAction): GameState;
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.handleSit = handleSit;
|
|
4
4
|
exports.handleStand = handleStand;
|
|
5
|
+
exports.handleAddChips = handleAddChips;
|
|
6
|
+
exports.handleReserveSeat = handleReserveSeat;
|
|
5
7
|
const positioning_1 = require("../utils/positioning");
|
|
6
8
|
/**
|
|
7
9
|
* Handle SIT action - add player to table
|
|
@@ -19,6 +21,9 @@ function handleSit(state, action) {
|
|
|
19
21
|
totalInvestedThisHand: 0,
|
|
20
22
|
isSittingOut: false,
|
|
21
23
|
timeBank: state.config.timeBankSeconds ?? 30,
|
|
24
|
+
pendingAddOn: 0,
|
|
25
|
+
sitInOption: action.sitInOption ?? "IMMEDIATE" /* SitInOption.IMMEDIATE */,
|
|
26
|
+
reservationExpiry: null,
|
|
22
27
|
};
|
|
23
28
|
const newPlayers = [...state.players];
|
|
24
29
|
newPlayers[action.seat] = newPlayer;
|
|
@@ -56,3 +61,58 @@ function handleStand(state, action) {
|
|
|
56
61
|
timestamp: action.timestamp,
|
|
57
62
|
};
|
|
58
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Handle ADD_CHIPS action - add chips to player's pending stack
|
|
66
|
+
* Chips are held in pendingAddOn and will be merged into stack at start of next hand
|
|
67
|
+
*/
|
|
68
|
+
function handleAddChips(state, action) {
|
|
69
|
+
const result = (0, positioning_1.getPlayerById)(state, action.playerId);
|
|
70
|
+
if (!result) {
|
|
71
|
+
return state;
|
|
72
|
+
}
|
|
73
|
+
const { player, seat } = result;
|
|
74
|
+
const newPlayers = [...state.players];
|
|
75
|
+
newPlayers[seat] = {
|
|
76
|
+
...player,
|
|
77
|
+
pendingAddOn: player.pendingAddOn + action.amount,
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
...state,
|
|
81
|
+
players: newPlayers,
|
|
82
|
+
timestamp: action.timestamp,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Handle RESERVE_SEAT action - reserve a seat for a player
|
|
87
|
+
* Marks the seat as RESERVED with an expiration timestamp
|
|
88
|
+
* API can use this to lock a seat while processing payment
|
|
89
|
+
*/
|
|
90
|
+
function handleReserveSeat(state, action) {
|
|
91
|
+
// Check if seat is already occupied
|
|
92
|
+
if (state.players[action.seat] !== null) {
|
|
93
|
+
return state;
|
|
94
|
+
}
|
|
95
|
+
const reservedPlayer = {
|
|
96
|
+
id: action.playerId,
|
|
97
|
+
name: action.playerName,
|
|
98
|
+
seat: action.seat,
|
|
99
|
+
stack: 0,
|
|
100
|
+
hand: null,
|
|
101
|
+
shownCards: null,
|
|
102
|
+
status: "RESERVED" /* PlayerStatus.RESERVED */,
|
|
103
|
+
betThisStreet: 0,
|
|
104
|
+
totalInvestedThisHand: 0,
|
|
105
|
+
isSittingOut: false,
|
|
106
|
+
timeBank: state.config.timeBankSeconds ?? 30,
|
|
107
|
+
pendingAddOn: 0,
|
|
108
|
+
sitInOption: "IMMEDIATE" /* SitInOption.IMMEDIATE */,
|
|
109
|
+
reservationExpiry: action.expiryTimestamp,
|
|
110
|
+
};
|
|
111
|
+
const newPlayers = [...state.players];
|
|
112
|
+
newPlayers[action.seat] = reservedPlayer;
|
|
113
|
+
return {
|
|
114
|
+
...state,
|
|
115
|
+
players: newPlayers,
|
|
116
|
+
timestamp: action.timestamp,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
@@ -31,6 +31,12 @@ function validateAction(state, action) {
|
|
|
31
31
|
case "TIME_BANK" /* ActionType.TIME_BANK */:
|
|
32
32
|
validateTimeAction(state, action);
|
|
33
33
|
break;
|
|
34
|
+
case "ADD_CHIPS" /* ActionType.ADD_CHIPS */:
|
|
35
|
+
validateAddChipsAction(state, action);
|
|
36
|
+
break;
|
|
37
|
+
case "RESERVE_SEAT" /* ActionType.RESERVE_SEAT */:
|
|
38
|
+
validateReserveSeatAction(state, action);
|
|
39
|
+
break;
|
|
34
40
|
default:
|
|
35
41
|
// Other actions don't need validation
|
|
36
42
|
break;
|
|
@@ -145,8 +151,13 @@ function validateSitAction(state, action) {
|
|
|
145
151
|
if (action.seat < 0 || action.seat >= state.maxPlayers) {
|
|
146
152
|
throw new IllegalActionError_1.IllegalActionError(ErrorCodes_1.ErrorCodes.INVALID_SEAT, `Seat ${action.seat} is invalid (max: ${state.maxPlayers - 1})`, { seat: action.seat, maxPlayers: state.maxPlayers });
|
|
147
153
|
}
|
|
148
|
-
|
|
149
|
-
|
|
154
|
+
const existingPlayer = state.players[action.seat];
|
|
155
|
+
if (existingPlayer !== null) {
|
|
156
|
+
// Allow claiming the seat if it is RESERVED by THIS player
|
|
157
|
+
const isMyReservation = existingPlayer.status === "RESERVED" /* PlayerStatus.RESERVED */ && existingPlayer.id === action.playerId;
|
|
158
|
+
if (!isMyReservation) {
|
|
159
|
+
throw new IllegalActionError_1.IllegalActionError(ErrorCodes_1.ErrorCodes.SEAT_OCCUPIED, `Seat ${action.seat} is already occupied`, { seat: action.seat });
|
|
160
|
+
}
|
|
150
161
|
}
|
|
151
162
|
if (action.stack <= 0) {
|
|
152
163
|
throw new IllegalActionError_1.IllegalActionError(ErrorCodes_1.ErrorCodes.INVALID_STACK, `Stack must be positive, got ${action.stack}`, { stack: action.stack });
|
|
@@ -168,6 +179,20 @@ function validateTimeAction(state, action) {
|
|
|
168
179
|
throw new IllegalActionError_1.IllegalActionError(ErrorCodes_1.ErrorCodes.NOT_YOUR_TURN, `Player ${action.playerId} cannot use time action when it's not their turn`, { playerId: action.playerId, actionTo: state.actionTo });
|
|
169
180
|
}
|
|
170
181
|
}
|
|
182
|
+
function validateAddChipsAction(state, action) {
|
|
183
|
+
const result = (0, positioning_1.getPlayerById)(state, action.playerId);
|
|
184
|
+
if (!result) {
|
|
185
|
+
throw new IllegalActionError_1.IllegalActionError(ErrorCodes_1.ErrorCodes.PLAYER_NOT_FOUND, `Player ${action.playerId} not found`, { playerId: action.playerId });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function validateReserveSeatAction(state, action) {
|
|
189
|
+
if (action.seat < 0 || action.seat >= state.maxPlayers) {
|
|
190
|
+
throw new IllegalActionError_1.IllegalActionError(ErrorCodes_1.ErrorCodes.INVALID_SEAT, `Seat ${action.seat} is invalid (max: ${state.maxPlayers - 1})`, { seat: action.seat, maxPlayers: state.maxPlayers });
|
|
191
|
+
}
|
|
192
|
+
if (state.players[action.seat] !== null) {
|
|
193
|
+
throw new IllegalActionError_1.IllegalActionError(ErrorCodes_1.ErrorCodes.SEAT_OCCUPIED, `Seat ${action.seat} is already occupied`, { seat: action.seat });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
171
196
|
/**
|
|
172
197
|
* Get current highest bet this street
|
|
173
198
|
*/
|
|
@@ -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);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pokertools/engine",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Enterprise-grade Texas Hold'em poker engine",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -46,13 +46,5 @@
|
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@pokertools/evaluator": "*",
|
|
48
48
|
"@pokertools/types": "*"
|
|
49
|
-
},
|
|
50
|
-
"devDependencies": {
|
|
51
|
-
"@types/jest": "^30.0.0",
|
|
52
|
-
"@types/node": "^24.10.1",
|
|
53
|
-
"fast-check": "^3.15.0",
|
|
54
|
-
"jest": "^30.2.0",
|
|
55
|
-
"ts-jest": "^29.4.5",
|
|
56
|
-
"typescript": "^5.9.3"
|
|
57
49
|
}
|
|
58
50
|
}
|