@idealic/poker-engine
Version:
Poker game engine and hand evaluator
1,080 lines (879 loc) • 113 kB
text/typescript
import { beforeEach, describe, expect, it } from 'vitest';
import { Hand } from '../../../Hand';
import type { NoLimitHand } from '../../../types';
import { BASE_HAND } from './fixtures/baseHand';
describe('next hand logic', () => {
let completedHand: NoLimitHand;
beforeEach(() => {
// Deep copy of BASE_HAND and mark as completed
completedHand = JSON.parse(JSON.stringify(BASE_HAND)) as NoLimitHand;
// Add fields to make it a completed hand
completedHand.finishingStacks = [980, 1010, 1010]; // Alice lost 20, Bob/Charlie split pot
completedHand.winnings = [0, 30, 30];
completedHand.rake = 0;
completedHand.totalPot = 60;
// Add sit-in/out fields
completedHand._inactive = completedHand._inactive || [0, 0, 0];
completedHand._intents = completedHand._intents || [0, 0, 0];
completedHand._deadBlinds = completedHand._deadBlinds || [0, 0, 0];
completedHand.seatCount = 6;
});
describe('Order of operations verification', () => {
it('should execute removal BEFORE rotation', () => {
// Scenario: Verify removal happens first, then rotation
// Input: Player to remove at index 1, verify rotation on filtered data
// Expected: Arrays filtered first, then rotated
completedHand._intents = [0, 3, 0]; // Bob wants to leave
completedHand.blindsOrStraddles = [0, 10, 20];
const nextHand = Hand.next(completedHand);
// Bob removed first, then blinds rotated
expect(nextHand.players).toEqual(['Alice', 'Charlie']);
// Rotation happens on [0, 10, 20] -> [10, 20]
expect(nextHand.blindsOrStraddles).toEqual([10, 20]);
});
it('should execute removal BEFORE dead blind calculation', () => {
// Scenario: Dead blinds calculated on already-filtered players
// Input: Remove player, then calculate dead blinds for remaining
// Expected: Dead blind positions based on filtered array
completedHand._intents = [2, 3, 0]; // Alice paused, Bob leaving, Charlie active
completedHand._inactive = [1, 0, 0];
completedHand.blindsOrStraddles = [0, 10, 20];
completedHand._deadBlinds = [0, 0, 0];
const nextHand = Hand.next(completedHand);
// Bob removed, Alice and Charlie remain
expect(nextHand.players).toEqual(['Alice', 'Charlie']);
// Dead blinds calculated on filtered positions
expect(nextHand._deadBlinds?.length).toBe(2);
});
it('should filter ALL arrays before ANY other operation', () => {
// Scenario: All player-related arrays filtered in sync
// Input: Remove player at index 1 from 3-player game
// Expected: All arrays have length 2 before rotation/calculation
completedHand._intents = [0, 3, 0]; // Bob leaving
completedHand.seats = [1, 3, 5];
completedHand._venueIds = ['id1', 'id2', 'id3'];
const nextHand = Hand.next(completedHand);
// All arrays filtered to length 2
expect(nextHand.players).toHaveLength(2);
expect(nextHand.startingStacks).toHaveLength(2);
expect(nextHand.blindsOrStraddles).toHaveLength(2);
expect(nextHand.antes).toHaveLength(2);
expect(nextHand._intents).toHaveLength(2);
expect(nextHand._inactive).toHaveLength(2);
expect(nextHand._deadBlinds).toHaveLength(2);
expect(nextHand.seats).toHaveLength(2);
expect(nextHand._venueIds).toHaveLength(2);
});
it('should base blind positions on filtered players', () => {
// Scenario: After removal, blind positions recalculated
// Input: Remove middle player, verify blind assignments
// Expected: Correct SB/BB positions on remaining players
completedHand._intents = [0, 3, 0]; // Bob (SB) leaving
completedHand.blindsOrStraddles = [0, 10, 20]; // Alice UTG, Bob SB, Charlie BB
const nextHand = Hand.next(completedHand);
// After Bob removed and rotation
expect(nextHand.players).toEqual(['Alice', 'Charlie']);
// Blinds rotate on 2-player: [0, 10, 20] -> [10, 20]
expect(nextHand.blindsOrStraddles).toEqual([10, 20]);
});
});
describe('Player removal (Step 1 - happens first)', () => {
describe('removing players who want to leave', () => {
it('should remove player with leave intent (_intents: 3)', () => {
// Scenario: Bob wants to leave the table
// Input: _intents: [0, 3, 0]
// Expected: Bob removed from all arrays in nextHand
completedHand._intents = [0, 3, 0];
const nextHand = Hand.next(completedHand);
expect(nextHand.players).toEqual(['Alice', 'Charlie']);
expect(nextHand.startingStacks).toEqual([980, 1010]);
expect(nextHand._intents).toEqual([0, 0]);
expect(nextHand._inactive).toEqual([0, 0]);
expect(nextHand._deadBlinds).toEqual([0, 0]);
});
it('should remove multiple players with leave intent', () => {
// Scenario: Alice and Charlie both leaving
// Input: _intents: [3, 0, 3]
// Expected: Only Bob remains in nextHand
completedHand._intents = [3, 0, 3];
const nextHand = Hand.next(completedHand);
expect(nextHand.players).toEqual(['Bob']);
expect(nextHand.startingStacks).toEqual([1010]);
expect(nextHand._intents).toEqual([0]);
expect(nextHand._inactive).toEqual([0]);
expect(nextHand._deadBlinds).toEqual([0]);
});
it('should handle all players leaving - return error or empty table', () => {
// Scenario: Everyone wants to leave
// Input: _intents: [3, 3, 3]
// Expected: Empty table - all player arrays empty
completedHand._intents = [3, 3, 3];
const nextHand = Hand.next(completedHand);
expect(nextHand.players).toEqual([]);
expect(nextHand.startingStacks).toEqual([]);
expect(nextHand.blindsOrStraddles).toEqual([]);
expect(nextHand.antes).toEqual([]);
expect(nextHand._intents).toEqual([]);
expect(nextHand._inactive).toEqual([]);
expect(nextHand._deadBlinds).toEqual([]);
});
it('should handle single remaining player after others leave', () => {
// Scenario: Two players leave, one remains
// Input: _intents: [3, 3, 0]
// Expected: Single player remains (heads-up not possible)
completedHand._intents = [3, 3, 0];
const nextHand = Hand.next(completedHand);
expect(nextHand.players).toEqual(['Charlie']);
expect(nextHand.startingStacks).toEqual([1010]);
expect(nextHand._intents).toEqual([0]);
});
});
describe('removing players with zero chips', () => {
it('should remove player with zero finishing stack', () => {
// Scenario: Alice busted out
// Input: finishingStacks: [0, 1050, 950]
// Expected: Alice removed from nextHand
completedHand.finishingStacks = [0, 1050, 950];
const nextHand = Hand.next(completedHand);
expect(nextHand.players).toEqual(['Bob', 'Charlie']);
expect(nextHand.startingStacks).toEqual([1050, 950]);
});
it('should remove player with negative finishing stack', () => {
// Scenario: Player went negative (shouldn't happen but handle)
// Input: finishingStacks: [-5, 1050, 955]
// Expected: Alice removed from nextHand
completedHand.finishingStacks = [-5, 1050, 955];
const nextHand = Hand.next(completedHand);
expect(nextHand.players).toEqual(['Bob', 'Charlie']);
expect(nextHand.startingStacks).toEqual([1050, 955]);
});
it('should remove multiple busted players', () => {
// Scenario: Two players busted in all-in
// Input: finishingStacks: [0, 2000, 0]
// Expected: Only Bob in nextHand
completedHand.finishingStacks = [0, 2000, 0];
const nextHand = Hand.next(completedHand);
expect(nextHand.players).toEqual(['Bob']);
expect(nextHand.startingStacks).toEqual([2000]);
});
});
describe('removing players who cannot afford blinds', () => {
it('should remove player who cannot afford upcoming BB', () => {
// Scenario: Charlie next BB but only has 15 chips (BB is 20)
// Input: finishingStacks: [1000, 1000, 15], next blinds would be [0, 10, 20]
// Expected: Charlie removed from nextHand
completedHand.finishingStacks = [1000, 1000, 15];
completedHand.blindsOrStraddles = [10, 20, 0]; // Alice (SB), Bob (BB), Charlie (BTN)
// After rotation: [0, 10, 20] - Alice (BTN), Bob (SB), Charlie (BB)
const nextHand = Hand.next(completedHand);
expect(nextHand.players).toEqual(['Alice', 'Bob']);
expect(nextHand.startingStacks).toEqual([1000, 1000]);
});
it('should remove player who cannot afford upcoming SB', () => {
// Scenario: Bob next SB but has 5 chips (SB is 10)
// Input: finishingStacks: [1000, 5, 1000], next blinds would be [10, 20, 0]
// Expected: Bob removed from nextHand
completedHand.finishingStacks = [1000, 5, 1000];
completedHand.blindsOrStraddles = [20, 0, 10]; // After rotation, Bob would be SB
const nextHand = Hand.next(completedHand);
expect(nextHand.players).toEqual(['Alice', 'Charlie']);
expect(nextHand.startingStacks).toEqual([1000, 1000]);
});
it('should remove player who cannot afford antes', () => {
// Scenario: Player has 1 chip but ante is 2
// Input: finishingStacks: [1000, 1000, 1], antes: [2, 2, 2]
// Expected: Charlie removed from nextHand
completedHand.finishingStacks = [1000, 1000, 1];
completedHand.antes = [2, 2, 2];
const nextHand = Hand.next(completedHand);
expect(nextHand.players).toEqual(['Alice', 'Bob']);
expect(nextHand.startingStacks).toEqual([1000, 1000]);
});
it('should keep player who can exactly afford blind', () => {
// Scenario: Player has exactly BB amount
// Input: finishingStacks: [1000, 1000, 20], next BB is 20
// Expected: Charlie remains (can go all-in)
completedHand.finishingStacks = [1000, 1000, 20];
completedHand.blindsOrStraddles = [20, 0, 10]; // Alice (BB), Bob (BTN), Charlie (SB)
const nextHand = Hand.next(completedHand);
expect(nextHand.players).toEqual(['Alice', 'Bob', 'Charlie']);
expect(nextHand.startingStacks).toEqual([1000, 1000, 20]);
});
});
describe('Player removal with complete chip requirements', () => {
it('should remove player who can afford blind but NOT ante', () => {
// Scenario: Player has exactly enough for blind, but needs ante too
// Input: finishingStacks: [10, 200, 300], blinds: [0,1,10], antes: [2,2,2]
// Expected: Alice removed (has 10, needs 10+2=12)
completedHand.finishingStacks = [10, 200, 300];
completedHand.blindsOrStraddles = [0, 1, 10];
completedHand.antes = [2, 2, 2];
const nextHand = Hand.next(completedHand);
// Alice needs blind(10) + ante(2) = 12 total
expect(nextHand.players).toEqual(['Bob', 'Charlie']);
expect(nextHand.startingStacks).toEqual([200, 300]);
});
it('should keep player who has exactly blind+ante', () => {
// SCENARIO: Player has precisely the required amount
// INPUT: finishingStacks: [22, 200, 300], blinds: [0,10,20], antes: [2,2,2]
// EXPECTED: Alice kept (has 22, needs BB=20 + ante=2 = 22 after rotation)
completedHand.finishingStacks = [22, 200, 300];
completedHand.blindsOrStraddles = [0, 10, 20]; // minBet=20 → SB=10, BB=20
completedHand.antes = [2, 2, 2];
const nextHand = Hand.next(completedHand);
// Alice has exactly 22, needs 22 (BB+ante), stays in game
expect(nextHand.players).toContain('Alice');
expect(nextHand.startingStacks[0]).toBe(22);
});
it('should remove inactive player who cannot afford blind+ante+deadBlinds', () => {
// SCENARIO: Returning player needs to pay accumulated dead blinds
// INPUT: finishingStacks: [35, 200, 300], _deadBlinds: [15, 0, 0], BB=20, ante=2
// EXPECTED: Alice removed (has 35, needs BB=20 + ante=2 + dead=15 = 37)
completedHand.finishingStacks = [35, 200, 300];
completedHand.blindsOrStraddles = [0, 10, 20]; // minBet=20 → SB=10, BB=20
completedHand.antes = [2, 2, 2];
completedHand._inactive = [1, 0, 0];
completedHand._intents = [0, 0, 0]; // Alice wants to return
completedHand._deadBlinds = [15, 0, 0];
const nextHand = Hand.next(completedHand);
// Alice needs BB(20) + ante(2) + dead(15) = 37, has only 35
expect(nextHand.players).toEqual(['Bob', 'Charlie']);
});
it('should handle player with intent=1 at BB position with insufficient chips', () => {
// Scenario: Wait-for-BB player arrives at BB but cannot afford it
// Input: finishingStacks: [8, 200, 300], at BB position, needs 10
// Expected: Alice removed (has 8, needs 10 for BB)
completedHand.finishingStacks = [8, 200, 300];
completedHand.blindsOrStraddles = [0, 10, 20]; // After rotation: [20,0,10]
completedHand._inactive = [1, 0, 0];
completedHand._intents = [1, 0, 0]; // Alice waiting for BB
const nextHand = Hand.next(completedHand);
// Alice would be at BB(20) but only has 8 chips
expect(nextHand.players).not.toContain('Alice');
expect(nextHand.players).toEqual(['Bob', 'Charlie']);
});
it('should keep player with intent=1 NOT at BB with low chips', () => {
// Scenario: Wait-for-BB player not yet at BB, insufficient chips
// Input: finishingStacks: [5, 200, 300], NOT at BB position
// Expected: Alice kept (waiting, no chip requirement yet)
completedHand.finishingStacks = [5, 200, 300];
completedHand.blindsOrStraddles = [0, 20, 10]; // Alice=0 (inactive), Bob=BB, Charlie=SB. After rotation: [10,0,20]
completedHand._inactive = [1, 0, 0];
completedHand._intents = [1, 0, 0]; // Alice waiting for BB
const nextHand = Hand.next(completedHand);
// Alice not at BB, stays inactive with low chips
expect(nextHand.players).toContain('Alice');
expect(nextHand._inactive![0]).toBe(1); // Still inactive
});
it('should handle paused player with exact blind amount but not ante', () => {
// Scenario: Paused player (intent=2) needs blind+ante
// Input: finishingStacks: [10, 200, 300], blind: 10, ante: 1, intent: 2
// Expected: Alice removed (has 10, needs 10+1=11)
completedHand.finishingStacks = [10, 200, 300];
completedHand.blindsOrStraddles = [0, 1, 10];
completedHand.antes = [1, 1, 1];
completedHand._inactive = [1, 0, 0];
completedHand._intents = [2, 0, 0]; // Alice paused
const nextHand = Hand.next(completedHand);
// Paused player needs blind(10) + ante(1) = 11, has only 10
expect(nextHand.players).toEqual(['Bob', 'Charlie']);
});
});
describe('removal with dead blinds', () => {
it('should not charge dead blinds to leaving player', () => {
// Scenario: Player leaving with accumulated debt
// Input: _intents: [3, 0, 0], _deadBlinds: [30, 0, 0]
// Expected: Alice removed, debt not collected
completedHand._intents = [3, 0, 0];
completedHand._deadBlinds = [30, 0, 0];
const nextHand = Hand.next(completedHand);
expect(nextHand.players).toEqual(['Bob', 'Charlie']);
// Alice's stack not reduced by dead blinds
expect(nextHand.startingStacks).toEqual([1010, 1010]);
});
it('should remove player who cannot afford dead blinds plus blinds', () => {
// Scenario: Returning player can't cover debt + blind
// Input: finishingStacks: [25, 1000, 1000], _deadBlinds: [20, 0, 0], upcoming BB
// Expected: Alice removed (can't pay 20 debt + 20 BB)
completedHand.finishingStacks = [25, 1000, 1000];
completedHand._deadBlinds = [20, 0, 0];
completedHand._inactive = [1, 0, 0];
completedHand._intents = [0, 0, 0]; // Alice wants to return
completedHand.blindsOrStraddles = [0, 10, 20]; // Alice (BTN), Bob (SB), Charlie (BB)
// After rotation: [20, 0, 10] - Alice (BB), Bob (BTN), Charlie (SB)
const nextHand = Hand.next(completedHand);
expect(nextHand.players).toEqual(['Bob', 'Charlie']);
});
});
describe('auto-removal scenarios', () => {
it('should auto-remove player who cannot afford dead blinds + blinds', () => {
// Scenario: Insufficient chips for return to play
// Input: finishingStack: 15, _deadBlinds: 10, upcoming BB: 20
// Expected: Auto-set _intents: 3 and remove player
completedHand.finishingStacks = [15, 1000, 1000]; // Alice has only 15
completedHand._inactive = [1, 0, 0];
completedHand._intents = [0, 0, 0]; // Alice wants to return
completedHand._deadBlinds = [10, 0, 0]; // Owes 10
completedHand.blindsOrStraddles = [0, 10, 20]; // Alice (BTN), Bob (SB), Charlie (BB)
// After rotation: [20, 0, 10] - Alice (BB), Bob (BTN), Charlie (SB)
const nextHand = Hand.next(completedHand);
// Alice auto-removed (15 < 10 debt + 20 BB)
expect(nextHand.players).toEqual(['Bob', 'Charlie']);
expect(nextHand.startingStacks).toEqual([1000, 1000]);
});
it('should auto-remove inactive player with insufficient chips', () => {
// Scenario: Paused player runs out of money
// Input: _inactive: 1, finishingStack: 5, needs 10 for SB
// Expected: Auto-marked for removal with _intents: 3
completedHand.finishingStacks = [1000, 5, 1000]; // Bob has only 5
completedHand._inactive = [0, 1, 0]; // Bob is inactive
completedHand._intents = [0, 2, 0]; // Bob is paused
completedHand.blindsOrStraddles = [20, 0, 10]; // Alice (BB), Bob (BTN), Charlie (SB)
// After rotation: [10, 20, 0] - Alice (SB), Bob (BB), Charlie (BTN)
const nextHand = Hand.next(completedHand);
// Bob auto-removed (5 < 10 SB)
expect(nextHand.players).toEqual(['Alice', 'Charlie']);
expect(nextHand.startingStacks).toEqual([1000, 1000]);
});
it('should handle multiple auto-removals in one operation', () => {
// Scenario: Multiple players insufficient funds
// Input: 2 players can't afford blinds/debts
// Expected: Player with insufficient funds removed
completedHand.finishingStacks = [5, 8, 2000]; // Alice low with dead blinds
completedHand._inactive = [1, 0, 0];
completedHand._intents = [0, 0, 0];
completedHand._deadBlinds = [10, 0, 0]; // Alice owes more than she has (5 < 10)
completedHand.blindsOrStraddles = [0, 20, 10]; // Alice=0 (inactive), Bob=BB, Charlie=SB
// After rotation: [10, 0, 20] - Alice at SB (but inactive, still 0), Bob at BTN, Charlie at BB
const nextHand = Hand.next(completedHand);
// Alice removed (can't afford dead blinds), Bob stays (at BTN, doesn't need blinds)
expect(nextHand.players).toEqual(['Bob', 'Charlie']);
expect(nextHand.startingStacks).toEqual([8, 2000]);
});
});
});
describe('Dead blind calculations', () => {
describe('dead blind formula verification', () => {
it('should calculate SB as exactly 0.5 * BB in chips', () => {
// Scenario: Verify exact formula for SB
// Input: BB = 20, player will miss SB in next hand
// Expected: _deadBlinds += 10 (0.5 * 20)
completedHand._inactive = [0, 0, 1]; // Charlie inactive
completedHand._intents = [0, 0, 2]; // Charlie paused
completedHand.blindsOrStraddles = [20, 10, 0]; // Alice=BB, Bob=SB, Charlie=0 (inactive). Charlie was at theoretical BB position.
completedHand._deadBlinds = [0, 0, 0];
const nextHand = Hand.next(completedHand);
// After rotation: Charlie at theoretical SB position, will miss SB(10)
// Should add 0.5 * 20 = 10
expect(nextHand._deadBlinds![2]).toBe(10);
});
it('should calculate BB as exactly 1.0 * BB in chips', () => {
// Scenario: Verify exact formula for BB
// Input: BB = 20, player will miss BB in next hand
// Expected: _deadBlinds += 20 (1.0 * 20)
completedHand._inactive = [1, 0, 0]; // Alice inactive
completedHand._intents = [2, 0, 0]; // Alice paused
completedHand.blindsOrStraddles = [0, 10, 20]; // Alice at UTG, after rotation will be at BB
completedHand._deadBlinds = [0, 0, 0];
const nextHand = Hand.next(completedHand);
// After rotation: [20, 0, 10] - Alice will miss BB(20)
// Should add 1.0 * 20 = 20
expect(nextHand._deadBlinds![0]).toBe(20);
});
it('should use absolute chip values not coefficients', () => {
// SCENARIO: Dead blinds in chips, not multipliers
// INPUT: minBet=100 → BB=100, SB=50, will miss SB in next hand
// EXPECTED: _deadBlinds += 50 chips (not 0.5)
completedHand.minBet = 100; // BB=100, SB=50
completedHand.blindsOrStraddles = [100, 50, 0]; // Alice=BB, Bob=SB, Charlie=BTN (inactive)
completedHand._inactive = [0, 0, 1]; // Charlie inactive
completedHand._intents = [0, 0, 2];
completedHand._deadBlinds = [0, 0, 0];
const nextHand = Hand.next(completedHand);
// After rotation: Charlie will miss SB(50)
// Should add 50 chips (0.5 * 100), not 0.5
expect(nextHand._deadBlinds![2]).toBe(50);
expect(typeof nextHand._deadBlinds![2]).toBe('number');
});
it('should calculate based on NEXT hand positions', () => {
// Scenario: Use positions after rotation
// Input: Player was BTN in completed hand, will be SB in next
// Expected: Calculate based on next position after rotation
completedHand.blindsOrStraddles = [10, 0, 20]; // Alice=SB (shifted from Bob), Bob=0 (inactive), Charlie=BB
completedHand._inactive = [0, 1, 0];
completedHand._intents = [0, 2, 0];
completedHand._deadBlinds = [0, 0, 0];
const nextHand = Hand.next(completedHand);
// After rotation: [20, 10, 0] - Bob would be at SB position (index 1)
// Bob as inactive at SB position accumulates dead blinds = 0.5*BB = 10
expect(nextHand._deadBlinds![1]).toBe(10);
// With skip-inactive, actual blinds shift: Alice=BB, Charlie=SB
expect(nextHand.blindsOrStraddles).toEqual([20, 0, 10]);
});
it('should cap at exactly 1.5 * BB in chips', () => {
// Scenario: Maximum cap verification
// Input: BB = 20, accumulate past 1.5
// Expected: Caps at 30 chips (1.5 * 20)
completedHand.blindsOrStraddles = [20, 0, 10]; // Alice at BB
completedHand._inactive = [1, 0, 0]; // Alice inactive
completedHand._intents = [2, 0, 0];
completedHand._deadBlinds = [30, 0, 0]; // Already at max (1.5 * 20)
const nextHand = Hand.next(completedHand);
// Should remain capped at 30, not increase
expect(nextHand._deadBlinds![0]).toBe(30);
});
});
describe('accumulating dead blinds for inactive players', () => {
it('should add 0.5BB for missed SB position', () => {
// Scenario: Inactive player will be in SB position after rotation
// Input: _inactive: [0, 0, 1], blindsOrStraddles: [20, 10, 0]
// Expected: _deadBlinds increases by 10 (0.5 * BB of 20)
completedHand._inactive = [0, 0, 1]; // Charlie is inactive
completedHand._intents = [0, 0, 2]; // Charlie paused
completedHand.blindsOrStraddles = [20, 10, 0]; // Alice=BB (shifted), Bob=SB, Charlie=0 (inactive, at theoretical BB)
completedHand._deadBlinds = [0, 0, 0];
const nextHand = Hand.next(completedHand);
// After rotation: [20, 0, 10] - Charlie will be at SB
// Charlie's dead blinds should increase by 10 (0.5 * 20)
expect(nextHand._deadBlinds).toEqual([0, 0, 10]);
});
it('should add 1BB for missed BB position', () => {
// Scenario: Inactive player will be in BB position after rotation
// Input: _inactive: [1, 0, 0], blindsOrStraddles: [0, 10, 20]
// Expected: _deadBlinds increases by 20 (1 * BB of 20)
completedHand._inactive = [1, 0, 0]; // Alice is inactive
completedHand._intents = [2, 0, 0]; // Alice paused
completedHand.blindsOrStraddles = [0, 10, 20]; // Alice at UTG
completedHand._deadBlinds = [0, 0, 0];
const nextHand = Hand.next(completedHand);
// After rotation: [20, 0, 10] - Alice will be at BB
// Alice's dead blinds should increase by 20 (1.0 * 20)
expect(nextHand._deadBlinds).toEqual([20, 0, 0]);
});
it('should not accumulate for non-blind position', () => {
// Scenario: Inactive player will be in non-blind position after rotation
// Input: _inactive: [0, 1, 0], blindsOrStraddles: [20, 0, 10]
// Expected: _deadBlinds unchanged (Bob at BTN after rotation)
completedHand._inactive = [0, 1, 0]; // Bob is inactive
completedHand._intents = [0, 2, 0]; // Bob paused
completedHand.blindsOrStraddles = [20, 0, 10]; // Alice=BB, Bob=0 (inactive), Charlie=SB
completedHand._deadBlinds = [0, 0, 0];
const nextHand = Hand.next(completedHand);
// After rotation: [10, 20, 0] - Bob at theoretical BB position
// But with skip-inactive, actual blinds shift: Alice=SB(10), Charlie=BB(20)
// Bob as inactive at BB position still accumulates dead blinds = 1.0*BB = 20
expect(nextHand._deadBlinds).toEqual([0, 20, 0]);
});
it('should not accumulate for any non-blind positions', () => {
// Scenario: Inactive players check future positions after rotation
// Input: 6 players, P1,P2,P3 inactive
// Expected: Only those who WILL BE on blind accumulate
const sixPlayerHand = {
...completedHand,
players: ['P1', 'P2', 'P3', 'P4', 'P5', 'P6'],
finishingStacks: [1000, 1000, 1000, 1000, 1000, 1000],
blindsOrStraddles: [0, 0, 0, 0, 10, 20], // P5=SB, P6=BB
antes: [0, 0, 0, 0, 0, 0],
_inactive: [1, 1, 1, 0, 0, 0], // P1,P2,P3 inactive
_intents: [2, 2, 2, 0, 0, 0],
_deadBlinds: [0, 0, 0, 0, 0, 0],
};
const nextHand = Hand.next(sixPlayerHand);
// After rotation: [20, 0, 0, 0, 0, 10] - P1 at BB, P2-P5 not on blind, P6 at SB
expect(nextHand._deadBlinds![0]).toBe(20); // P1 will miss BB
expect(nextHand._deadBlinds![1]).toBe(0); // P2 not in blind position
expect(nextHand._deadBlinds![2]).toBe(0); // P3 not in blind position
});
it('should accumulate for button if will be on blind next', () => {
// Scenario: Inactive player on button will be BB after rotation
// Input: _inactive: [1, 0, 0], blindsOrStraddles: [0, 10, 20]
// Expected: _deadBlinds accumulate for future BB position
completedHand.blindsOrStraddles = [0, 10, 20]; // Alice=BTN, Bob=SB, Charlie=BB
completedHand._inactive = [1, 0, 0]; // Alice (button) inactive
completedHand._intents = [2, 0, 0];
completedHand._deadBlinds = [0, 0, 0];
const nextHand = Hand.next(completedHand);
// After rotation: [20, 0, 10] - Alice will be at BB
expect(nextHand._deadBlinds![0]).toBe(20); // Alice will miss BB
});
it('should cap dead blinds at 1.5BB maximum', () => {
// Scenario: Player already at max debt
// Input: _deadBlinds: [30, 0, 0] (1.5 * 20), missed another BB
// Expected: _deadBlinds remains at 30
completedHand._inactive = [1, 0, 0]; // Alice is inactive
completedHand._intents = [2, 0, 0]; // Alice paused
completedHand.blindsOrStraddles = [0, 20, 10]; // Alice=0 (inactive), Bob=BB, Charlie=SB
completedHand._deadBlinds = [30, 0, 0]; // Already at max (1.5 * 20)
const nextHand = Hand.next(completedHand);
// Should remain capped at 30
expect(nextHand._deadBlinds).toEqual([30, 0, 0]);
});
it('should accumulate correctly over multiple hands', () => {
// Scenario: Track accumulation pattern with shift-to-active logic
// Input: Player inactive through multiple rotations
// Expected: Accumulate based on THEORETICAL position after rotation
// Note: With shift-to-active, blinds shift to active players,
// which changes the actual blind array but dead blinds use theoretical positions
// First hand: Charlie at BB, theoretical position after rotation is SB
completedHand.blindsOrStraddles = [20, 10, 0]; // Alice=BB, Bob=SB, Charlie=0 (inactive)
completedHand._inactive = [0, 0, 1]; // Charlie inactive
completedHand._intents = [0, 0, 2]; // Charlie paused
completedHand._deadBlinds = [0, 0, 0];
let hand1 = Hand.next(completedHand);
// Theoretical rotation: [20, 0, 10] - Charlie at SB position
// Dead blinds: Charlie misses SB = 0.5 * 20 = 10
// Actual blindsOrStraddles with shift: [10, 20, 0] (Alice SB, Bob BB, Charlie 0)
expect(hand1._deadBlinds![2]).toBe(10);
// Second hand: hand1.blindsOrStraddles = [10, 20, 0]
hand1.finishingStacks = hand1.startingStacks;
hand1._inactive = [0, 0, 1]; // Charlie still inactive
let hand2 = Hand.next(hand1);
// Theoretical rotation of [10, 20, 0]: [0, 10, 20] - Charlie at BB position!
// Dead blinds: Charlie misses BB = 1.0 * 20 = 20
// Total: 10 + 20 = 30 (capped at 1.5 * 20 = 30)
expect(hand2._deadBlinds![2]).toBe(30);
// Third hand: hand2.blindsOrStraddles = [10, 20, 0] (shifted)
hand2.finishingStacks = hand2.startingStacks;
hand2._inactive = [0, 0, 1]; // Charlie still inactive
let hand3 = Hand.next(hand2);
// Dead blinds already at cap (30), no further accumulation
expect(hand3._deadBlinds![2]).toBe(30);
});
it('should handle different blind amounts', () => {
// SCENARIO: 50/100 blinds instead of 10/20
// INPUT: minBet=100 → BB=100, SB=50, player will be on SB after rotation
// EXPECTED: _deadBlinds increases by 50 (0.5 * 100)
completedHand.minBet = 100; // BB=100, SB=50
completedHand.blindsOrStraddles = [100, 50, 0]; // Alice=BB, Bob=SB, Charlie=BTN (inactive)
completedHand._inactive = [0, 0, 1]; // Charlie inactive
completedHand._intents = [0, 0, 2]; // Charlie paused
completedHand._deadBlinds = [0, 0, 0];
const nextHand = Hand.next(completedHand);
// After rotation: Charlie will miss SB(50)
// Should add 50 (0.5 * 100)
expect(nextHand._deadBlinds![2]).toBe(50);
});
});
describe('Dead blind accumulation across multiple hands', () => {
it('should accumulate 0.5×BB when inactive player misses SB', () => {
// Scenario: Track dead blind accumulation over 2 hands with shift-to-active logic
// Input: Hand1: Charlie inactive, will be at SB in theoretical rotation
// Expected: Charlie accumulates dead blinds based on theoretical positions
// === HAND 1 COMPLETION ===
completedHand.blindsOrStraddles = [20, 10, 0]; // Alice=BB, Bob=SB, Charlie=0 (inactive)
completedHand._inactive = [0, 0, 1]; // Charlie inactive
completedHand._intents = [0, 0, 2]; // Charlie paused
completedHand._deadBlinds = [0, 0, 0]; // No prior debt
const hand2 = Hand.next(completedHand);
// Theoretical rotation: [20, 10, 0] -> [0, 20, 10] - Charlie would be at SB
// Dead blinds: Charlie misses SB = 0.5 * 20 = 10
// Actual blindsOrStraddles with shift: [20, 10, 0] (Alice BB, Bob SB)
expect(hand2._deadBlinds![2]).toBe(10);
// === HAND 2 COMPLETION ===
const hand2Completed = {
...hand2,
finishingStacks: hand2.startingStacks,
};
const hand3 = Hand.next(hand2Completed);
// hand2.blindsOrStraddles = [10, 20, 0]
// Theoretical rotation: [0, 10, 20] - Charlie would be at BB!
// Dead blinds: Charlie misses BB = 1.0 * 20 = 20
// Total: 10 + 20 = 30 (capped at 1.5 * 20 = 30)
expect(hand3._deadBlinds![2]).toBe(30);
});
it('should accumulate 1.0×BB when inactive player misses BB', () => {
// Scenario: Track dead blind accumulation for missed BB position
// Input: Hand1: Alice at UTG inactive, will be at BB in next; Hand2: check accumulation
// Expected: Alice accumulates 20 chips (1.0×20) dead blind
// === HAND 1 COMPLETION ===
completedHand.blindsOrStraddles = [0, 10, 20]; // Alice at UTG
completedHand._inactive = [1, 0, 0]; // Alice inactive
completedHand._intents = [2, 0, 0]; // Alice paused
completedHand._deadBlinds = [0, 0, 0];
const hand2 = Hand.next(completedHand);
// After rotation: [20, 0, 10] - Alice will miss BB(20)
// Adds 1.0×20 = 20
expect(hand2._deadBlinds![0]).toBe(20);
});
it('should accumulate correctly over 3 hands: various positions', () => {
// Scenario: Player accumulates dead blinds based on NEXT hand positions
// Input: Track through 3 hands with different positions
// Expected: Accumulate only when will be on blind in next hand
// === HAND 1: Alice at UTG, will be BB after rotation ===
completedHand.blindsOrStraddles = [0, 10, 20]; // Alice UTG, Bob SB, Charlie BB
completedHand._inactive = [1, 0, 0]; // Alice inactive
completedHand._intents = [2, 0, 0]; // Alice paused
completedHand._deadBlinds = [0, 0, 0];
const hand2 = Hand.next(completedHand);
// After rotation: [20, 0, 10] - Alice at BB
expect(hand2._deadBlinds![0]).toBe(20); // Alice will miss BB = +1.0×20 = 20
// === HAND 2: Alice at BB, will be SB after rotation ===
hand2.finishingStacks = hand2.startingStacks;
hand2._inactive = [1, 0, 0]; // Alice still inactive
// hand2.blindsOrStraddles is already [20, 0, 10] from rotation
const hand3 = Hand.next(hand2);
// After rotation: [10, 20, 0] - Alice at SB
expect(hand3._deadBlinds![0]).toBe(30); // 20 + 0.5×20 = 30
// === HAND 3: Alice at SB, will be UTG after rotation (no blind) ===
hand3.finishingStacks = hand3.startingStacks;
hand3._inactive = [1, 0, 0]; // Alice still inactive
// hand3.blindsOrStraddles is already [10, 20, 0] from rotation
const hand4 = Hand.next(hand3);
// After rotation: [0, 10, 20] - Alice at UTG (no blind)
expect(hand4._deadBlinds![0]).toBe(30); // No change - not on blind next hand
});
it('should track separate accumulation for multiple inactive players', () => {
// Scenario: Two players inactive, check their NEXT positions
// Input: Alice at UTG, Bob at SB, both inactive
// Expected: Based on next positions after rotation
completedHand.blindsOrStraddles = [0, 10, 20]; // Alice UTG, Bob SB, Charlie BB
completedHand._inactive = [1, 1, 0]; // Alice and Bob inactive
completedHand._intents = [2, 2, 0]; // Both paused
completedHand._deadBlinds = [0, 0, 0];
const nextHand = Hand.next(completedHand);
// After rotation: [20, 0, 10] - Alice at BB, Bob at UTG, Charlie at SB
expect(nextHand._deadBlinds![0]).toBe(20); // Alice: will miss BB = 1.0×20
expect(nextHand._deadBlinds![1]).toBe(0); // Bob: not on blind
expect(nextHand._deadBlinds![2]).toBe(0); // Charlie: active
});
it('should NOT accumulate when player returns and becomes active', () => {
// Scenario: Previously inactive player returns, stops accumulating
// Input: Alice was inactive with debt, now intent=0 (returning)
// Expected: Dead blinds preserved for Game() to charge, stack unchanged
completedHand.finishingStacks = [100, 200, 300];
completedHand.blindsOrStraddles = [10, 20, 0]; // Alice at SB
completedHand._inactive = [1, 0, 0]; // Alice was inactive
completedHand._intents = [0, 0, 0]; // Alice returns
completedHand._deadBlinds = [10, 0, 0]; // Has debt
const nextHand = Hand.next(completedHand);
// Alice returns, debt preserved for Game() to charge
expect(nextHand._inactive![0]).toBe(0); // Now active
expect(nextHand._deadBlinds![0]).toBe(10); // Debt preserved for Game()
expect(nextHand.startingStacks[0]).toBe(100); // Stack unchanged
});
});
describe('dead blind payment scenarios', () => {
it('should preserve dead blinds when player returns early (Game() will charge)', () => {
// Scenario: Player stops pause, debt preserved for Game() to charge
// Input: _intents: [0, 0, 0], _deadBlinds: [20, 0, 0], finishingStacks: [1000, 1000, 1000]
// Expected: startingStacks: [1000, 1000, 1000], _deadBlinds: [20, 0, 0]
completedHand.finishingStacks = [1000, 1000, 1000];
completedHand._inactive = [1, 0, 0]; // Alice was inactive
completedHand._intents = [0, 0, 0]; // Alice wants to return
completedHand._deadBlinds = [20, 0, 0]; // Alice owes 20
const nextHand = Hand.next(completedHand);
expect(nextHand.startingStacks).toEqual([1000, 1000, 1000]); // Stack unchanged
expect(nextHand._deadBlinds).toEqual([20, 0, 0]); // Debt preserved for Game()
expect(nextHand._inactive).toEqual([0, 0, 0]);
});
it('should clear dead blinds when reaching BB position', () => {
// Scenario: Player waited for BB, no payment
// Input: _intents: [1, 0, 0], at BB position, _deadBlinds: [20, 0, 0]
// Expected: _deadBlinds: [0, 0, 0], stack unchanged
completedHand.finishingStacks = [1000, 1000, 1000];
completedHand._inactive = [1, 0, 0]; // Alice was inactive
completedHand._intents = [1, 0, 0]; // Alice waiting for BB
completedHand._deadBlinds = [20, 0, 0]; // Alice has debt
completedHand.blindsOrStraddles = [0, 10, 20]; // Alice (BTN), Bob (SB), Charlie (BB)
// After rotation: [20, 0, 10] - Alice (BB), Bob (BTN), Charlie (SB)
const nextHand = Hand.next(completedHand);
// Check rotated positions - Alice should now be at BB
expect(nextHand.blindsOrStraddles).toEqual([20, 0, 10]);
expect(nextHand.startingStacks).toEqual([1000, 1000, 1000]); // No deduction
expect(nextHand._deadBlinds).toEqual([0, 0, 0]); // Debt cleared
expect(nextHand._inactive).toEqual([0, 0, 0]); // Activated
expect(nextHand._intents).toEqual([0, 0, 0]); // Intent reset
});
it('should handle partial payment if insufficient chips', () => {
// Scenario: Player has 10 chips, owes 30
// Input: finishingStacks: [10, 1000, 1000], _deadBlinds: [30, 0, 0]
// Expected: Player removed (can't afford)
completedHand.finishingStacks = [10, 1000, 1000];
completedHand._inactive = [1, 0, 0];
completedHand._intents = [0, 0, 0]; // Alice wants to return
completedHand._deadBlinds = [30, 0, 0];
const nextHand = Hand.next(completedHand);
// Alice removed - can't afford dead blinds
expect(nextHand.players).toEqual(['Bob', 'Charlie']);
expect(nextHand.startingStacks).toEqual([1000, 1000]);
});
});
});
describe('Player activation states', () => {
describe('state transition coverage', () => {
it('should transition active player with pause intent to inactive', () => {
// Scenario: Active player requested pause
// Input: _inactive: 0, _intents: 1 (or 2)
// Expected: nextHand has _inactive: 1
completedHand._inactive = [0, 0, 0]; // All active
completedHand._intents = [1, 0, 0]; // Alice wants to pause
completedHand.blindsOrStraddles = [10, 20, 0]; // Alice=SB, Bob=BB, Charlie=BTN
// After rotation: [0, 10, 20] - Alice=BTN (not BB), so becomes inactive
const nextHand = Hand.next(completedHand);
expect(nextHand._inactive).toEqual([1, 0, 0]); // Alice now inactive
expect(nextHand._intents).toEqual([1, 0, 0]); // Intent preserved
});
it('should transition from _intents: 1 to inactive', () => {
// Scenario: Wait-for-BB intent takes effect
// Input: _inactive: 0, _intents: 1
// Expected: _inactive: 1, _intents: 1 (preserved)
completedHand._inactive = [0, 0, 0];
completedHand._intents = [1, 0, 0]; // Alice wait-for-BB
completedHand.blindsOrStraddles = [10, 20, 0]; // Alice=SB, not BB
// After rotation: [0, 10, 20] - Alice=BTN (not BB), so becomes inactive
const nextHand = Hand.next(completedHand);
expect(nextHand._inactive).toEqual([1, 0, 0]);
expect(nextHand._intents).toEqual([1, 0, 0]); // Preserved until BB
});
it('should transition from _intents: 2 to inactive', () => {
// Scenario: Simple pause intent takes effect
// Input: _inactive: 0, _intents: 2
// Expected: _inactive: 1, _intents: 2 (preserved)
completedHand._inactive = [0, 0, 0];
completedHand._intents = [2, 0, 0]; // Alice simple pause
const nextHand = Hand.next(completedHand);
expect(nextHand._inactive).toEqual([1, 0, 0]);
expect(nextHand._intents).toEqual([2, 0, 0]); // Preserved
});
it('should not transition if already inactive', () => {
// Scenario: Already paused player
// Input: _inactive: 1, _intents: 1
// Expected: Remains _inactive: 1
completedHand._inactive = [1, 0, 0]; // Already inactive
completedHand._intents = [1, 0, 0];
completedHand.blindsOrStraddles = [10, 20, 0]; // Alice=SB
// After rotation: [0, 10, 20] - Alice=BTN (not BB)
const nextHand = Hand.next(completedHand);
expect(nextHand._inactive).toEqual([1, 0, 0]); // Stays inactive
expect(nextHand._intents).toEqual([1, 0, 0]);
});
it('should handle transition during hand completion', () => {
// Scenario: Intent changed during hand
// Input: Changed from 0 to 1 during play
// Expected: Becomes inactive in next hand
completedHand._inactive = [0, 0, 0]; // Was active during hand
completedHand._intents = [1, 0, 0]; // Changed intent during hand
completedHand.blindsOrStraddles = [10, 20, 0]; // Alice=SB
// After rotation: [0, 10, 20] - Alice=BTN (not BB)
const nextHand = Hand.next(completedHand);
expect(nextHand._inactive).toEqual([1, 0, 0]); // Now inactive
expect(nextHand._intents).toEqual([1, 0, 0]);
});
});
describe('new players joining next hand', () => {
it('should activate player waiting to join', () => {
// Scenario: Player joined mid-hand, now active
// Input: _inactive: [0, 0, 1], _intents: [0, 0, 0]
// Expected: _inactive: [0, 0, 0] in nextHand
completedHand._inactive = [0, 0, 1]; // Charlie waiting to join
completedHand._intents = [0, 0, 0]; // Charlie wants to play
const nextHand = Hand.next(completedHand);
expect(nextHand._inactive).toEqual([0, 0, 0]);
expect(nextHand.players).toEqual(['Alice', 'Bob', 'Charlie']);
});
it('should not activate if insufficient chips', () => {
// Scenario: New player doesn't have enough for blinds
// Input: _inactive: [0, 0, 1], finishingStacks: [1000, 1000, 0]
// Expected: Player removed instead of activated
completedHand._inactive = [0, 0, 1];
completedHand._intents = [0, 0, 0];
completedHand.finishingStacks = [1000, 1000, 0];
const nextHand = Hand.next(completedHand);
expect(nextHand.players).toEqual(['Alice', 'Bob']);
expect(nextHand.startingStacks).toEqual([1000, 1000]);
});
});
describe('paused players returning', () => {
it('should activate player returning at BB without payment', () => {
// Scenario: Waited for BB position
// Input: _intents: [1, 0, 0], now at BB, _deadBlinds: [20, 0, 0]
// Expected: _inactive: [0, 0, 0], _deadBlinds: [0, 0, 0]
completedHand._inactive = [1, 0, 0];
completedHand._intents = [1, 0, 0]; // Alice waiting for BB
completedHand._deadBlinds = [20, 0, 0];
completedHand.blindsOrStraddles = [0, 10, 20]; // Next rotation puts Alice at BB (right rotation)
const nextHand = Hand.next(completedHand);
expect(nextHand._inactive).toEqual([0, 0, 0]);
expect(nextHand._deadBlinds).toEqual([0, 0, 0]);
});
it('should activate player returning early with debt preserved', () => {
// Scenario: Stops pause, debt preserved for Game() to charge
// Input: _intents: [0, 0, 0], _inactive: [1, 0, 0], _deadBlinds: [10, 0, 0]
// Expected: _inactive: [0, 0, 0], stack unchanged, debt preserved
completedHand.finishingStacks = [1000, 1000, 1000];
completedHand._inactive = [1, 0, 0];
completedHand._intents = [0, 0, 0]; // Alice wants to return
completedHand._deadBlinds = [10, 0, 0];
const nextHand = Hand.next(completedHand);
expect(nextHand._inactive).toEqual([0, 0, 0]);
expect(nextHand.startingStacks).toEqual([1000, 1000, 1000]); // Stack unchanged
expect(nextHand._deadBlinds).toEqual([10, 0, 0]); // Debt preserved for Game()
});
it('should not activate if intent still paused', () => {
// Scenario: Still on pause
// Input: _intents: [2, 0, 0], _inactive: [1, 0, 0]
// Expected: _inactive: [1, 0, 0] unchanged
completedHand._inactive = [1, 0, 0];
completedHand._intents = [2, 0, 0]; // Alice still paused
const nextHand = Hand.next(completedHand);
expect(nextHand._inactive).toEqual([1, 0, 0]);
});
});
describe('intent state transitions', () => {
it('should handle pause request taking effect', () => {
// Scenario: Was active with pause intent, now inactive
// Input: _inactive: [0, 0, 0], _intents: [1, 0, 0]
// Expected: _inactive: [1, 0, 0] in nextHand
completedHand._inactive = [0, 0, 0];
completedHand._intents = [1, 0, 0]; // Alice wants to pause
completedHand.blindsOrStraddles = [10, 20, 0]; // Alice=SB
// After rotation: [0, 10, 20] - Alice=BTN (not BB)
const nextHand = Hand.next(completedHand);
expect(nextHand._inactive).toEqual([1, 0, 0]);
expect(nextHand._intents).toEqual([1, 0, 0]); // Intent preserved
});
it('should reset intents when returning at BB', () => {
// Scenario: Reached BB position
// Input: _intents: [1, 0, 0] at BB
// Expected: _intents: [0, 0, 0] in nextHand
completedHand._inactive = [1, 0, 0];
completedHand._intents = [1, 0, 0];
completedHand.blindsOrStraddles = [0, 10, 20]; // Next rotation puts Alice at BB (right rotation)
const nextHand = Hand.next(completedHand);
expect(nextHand._intents).toEqual([0, 0, 0]);
expect(nextHand._inactive).toEqual([0, 0, 0]);
});
it('should preserve pause intent if not at BB', () => {
// Scenario: Still waiting for BB
// Input: _intents: [1, 0, 0] not at BB
// Expected: _intents: [1, 0, 0] unchanged
completedHand._inactive = [1, 0, 0];
completedHand._intents = [1, 0, 0];
completedHand.blindsOrStraddles = [10, 20, 0]; // Alice at SB, not BB
// After rotation: [0, 10, 20] - Alice=BTN (still not BB)
const nextHand = Hand.next(completedHand);
expect(nextHand._intents).toEqual([1, 0, 0]); // Preserved
expect(nextHand._inactive).toEqual([1, 0, 0]); // Still inactive
});
});
describe('State transitions based on intents', () => {
it('should activate player with intent=1 when reaching BB position', () => {
// Scenario: Player waiting for BB reaches BB position after rotation
// Input: _intents=[1,0,0], _inactive=[1,0,0], blinds=[0,10,20]
// Expected: Player becomes active and intent is cleared
completedHand._intents = [1, 0, 0]; // Alice waiting for BB
completedHand._inactive = [1, 0, 0]; // Alice inactive
completedHand.blindsOrStraddles = [0, 10, 20]; // Alice at button
const nextHand = Hand.next(completedHand);
// After rotation: [20,0,10], Alice at BB position
expect(nextHand._intents).toEqual([0, 0, 0]); // Intent cleared per spec
expect(nextHand._inactive).toEqual([0, 0, 0]); // Alice becomes active at BB
expect(nextHand.blindsOrStraddles).toEqual([20, 0, 10]);
});
it('should keep player with intent=1 inactive when NOT at BB position', () => {
// Scenario: Player waiting for BB, but not at BB position yet
// Input: After rotation, player with intent=1 is NOT at BB
// Expected: Player remains inactive with intent preserved
completedHand._intents = [0, 1, 0]; // Bob waiting for BB
completedHand._inactive = [0, 1, 0]; // Bob inactive
completedHand.blindsOrStraddles = [10, 0, 20]; //