@idealic/poker-engine
Version:
Poker game engine and hand evaluator
261 lines (221 loc) • 7.69 kB
text/typescript
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);
}
}
});
});
});