UNPKG

@idealic/poker-engine

Version:

Poker game engine and hand evaluator

261 lines (221 loc) 7.69 kB
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import * as Poker from '../../../index'; import { BASE_HAND, COMPLETED_HAND, SHOWDOWN_HAND } from './fixtures/baseGame'; describe('Game API - State Analysis', () => { describe('isShowdown', () => { it('should return true when multiple players remain after final betting', () => { const game = Poker.Game(SHOWDOWN_HAND); expect(game.isShowdown).toBe(true); }); it('should return false during betting rounds', () => { const game = Poker.Game(BASE_HAND); expect(game.isShowdown).toBe(false); }); it('should return false with single player remaining', () => { const hand: Poker.Hand = { ...BASE_HAND, actions: [ 'd dh p1 AsKs', 'd dh p2 7h2d', 'd dh p3 QhQc', 'd dh p4 JdTd', 'p3 f', 'p4 f', 'p1 cbr 60', 'p2 f', // Bob folds, only Alice remains ], }; const game = Poker.Game(hand); expect(game.isShowdown).toBe(false); expect(game.isComplete).toBe(true); expect(game.players[0].winnings).toEqual(40); expect(game.players[1].winnings).toEqual(0); expect(game.players[2].winnings).toEqual(0); expect(game.players[3].winnings).toEqual(0); }); it('should handle all-in scenarios correctly', () => { const hand: Poker.Hand = { ...BASE_HAND, startingStacks: [100, 100, 800, 1200], actions: [ 'd dh p1 AsKs', 'd dh p2 7h2d', 'd dh p3 QhQc', 'd dh p4 JdTd', 'p3 f', // Charlie folds 'p4 f', // David folds 'p1 cbr 100', // Alice all-in 'p2 cc 100', // Bob calls all-in 'd db AhKhQd', // Flop 'd db Td', // Turn 'd db 9s', // River ], }; const game = Poker.Game(hand); // Should be in showdown after river with both all-in expect(game.isShowdown).toBe(true); }); }); describe('getElapsedTime', () => { beforeEach(() => { vi.useFakeTimers(); }); afterEach(() => { vi.useRealTimers(); }); it('should return elapsed time since last action', () => { const now = Date.now(); const hand: Poker.Hand = { ...BASE_HAND, actions: [ 'd dh p1 AsKs', 'd dh p2 7h2d', 'd dh p3 QhQc', 'd dh p4 JdTd', 'p3 f', 'p4 f', `p1 f #${now}`, // Action with current timestamp ], }; const game = Poker.Game(hand); // Should track elapsed time expect(Poker.Game.getElapsedTime(game)).toBeGreaterThanOrEqual(0); // After 5 seconds vi.advanceTimersByTime(5000); const elapsed = Poker.Game.getElapsedTime(game); expect(elapsed).toBeGreaterThanOrEqual(5000); }); it('should return 0 when no timestamp available', () => { const hand: Poker.Hand = { ...BASE_HAND, actions: [ 'd dh p1 AsKs', 'd dh p2 7h2d', 'd dh p3 QhQc', 'd dh p4 JdTd', 'p3 f', 'p4 f', 'p1 f', ], // No timestamp }; const game = Poker.Game(hand); // Without lastTimestamp, should return 0 expect(Poker.Game.getElapsedTime(game)).toBe(0); }); it("should use game's lastTimestamp property", () => { const game = Poker.Game(BASE_HAND); // Game should track lastTimestamp internally if (game.lastTimestamp) { const elapsed = Poker.Game.getElapsedTime(game); expect(elapsed).toBeGreaterThanOrEqual(0); } else { expect(Poker.Game.getElapsedTime(game)).toBe(0); } }); }); describe('finish', () => { it('should extract finishing data from completed game', () => { const game = Poker.Game(COMPLETED_HAND); const result = Poker.Game.finish(game, COMPLETED_HAND); expect(result.finishingStacks).toBeDefined(); expect(result.winnings).toBeDefined(); expect(result.totalPot).toBeDefined(); }); it('should return unchanged hand if game not complete', () => { const game = Poker.Game(BASE_HAND); const result = Poker.Game.finish(game, BASE_HAND); // Should return same hand object expect(result).toBe(BASE_HAND); expect(result.finishingStacks).toBeUndefined(); }); it('should calculate winnings correctly', () => { const game = Poker.Game(COMPLETED_HAND); const result = Poker.Game.finish(game, COMPLETED_HAND); if (result.winnings) { // Winnings should be non-negative result.winnings.forEach(w => { expect(w).toBeGreaterThanOrEqual(0); }); // At least one player should have winnings expect(result.winnings.some(w => w > 0)).toBe(true); } }); it('should preserve chip continuity', () => { const game = Poker.Game(COMPLETED_HAND); const result = Poker.Game.finish(game, COMPLETED_HAND); if (result.finishingStacks) { // Total chips should be conserved (minus rake) const startingTotal = COMPLETED_HAND.startingStacks.reduce( (a: number, b: number) => a + b, 0 ); const finishingTotal = result.finishingStacks.reduce((a, b) => a + b, 0); const rake = result.rake || 0; expect(finishingTotal).toBeLessThanOrEqual(startingTotal); expect(finishingTotal + rake).toBe(startingTotal); } }); it('should handle split pots', () => { const splitPotHand: Poker.Hand = { ...COMPLETED_HAND, actions: [ ...SHOWDOWN_HAND.actions.slice(0, -2), 'p3 sm AsAc', // Charlie shows aces 'p4 sm AdAh', // David shows aces too - split pot ], }; const game = Poker.Game(splitPotHand); expect(game.isShowdown).toBe(true); const result = Poker.Game.finish(game, splitPotHand); if (result.winnings) { // Both Charlie and David should have winnings expect(result.winnings[2]).toEqual(175); // Charlie wins expect(result.winnings[3]).toEqual(175); // David wins } }); it('should include rake information', () => { const handWithRake: Poker.Hand = { ...COMPLETED_HAND, rakePercentage: 0.05, }; const game = Poker.Game(handWithRake); const result = Poker.Game.finish(game, handWithRake); if (game.isComplete && result.rake !== undefined) { expect(result.rake).toBeGreaterThanOrEqual(0); expect(result.rakePercentage).toBe(0.05); } }); it('should extract total pot', () => { const game = Poker.Game(COMPLETED_HAND); const result = Poker.Game.finish(game, COMPLETED_HAND); if (result.totalPot) { expect(result.totalPot).toBeGreaterThan(0); } }); it('should handle games with all players folding except one', () => { const hand: Poker.Hand = { ...BASE_HAND, actions: [ 'd dh p1 AsKs', 'd dh p2 7h2d', 'd dh p3 QhQc', 'd dh p4 JdTd', 'p3 cbr 60', 'p4 f', 'p1 f', 'p2 f', // Everyone folds to Charlie ], }; const game = Poker.Game(hand); const result = Poker.Game.finish(game, hand); if (game.isComplete) { expect(result.finishingStacks).toBeDefined(); expect(result.winnings).toBeDefined(); // Charlie (index 2) should win the pot if (result.winnings) { expect(result.winnings[2]).toBeGreaterThan(0); } } }); }); });