PokeR/src/lib/game/observation-tracker.test.ts
Veit F. 422fa5b3ab feat: implement bot intelligence system with 8 archetypes and coaching
Add personality-driven bots with 8 archetypes (Nit, TAG, LAG, Maniac, Calling Station, Loose Fish, Old Man, Monster TAG) across 5 skill levels.

Includes:

- Three-layer decision pipeline (base strategy → personality filter → skill noise)

- Decision timer system with archetype-specific timeout defaults

- Observation tracking engine (VPIP, PFR, Fold-to-CBet, WTSD, bet sizing, timing tells)

- Player classification engine with weighted scoring and confidence scaling

- Table setup UI with visual seat editor and quick presets

- Info display system with 4 visibility levels

- Teaching coach with post-hand analysis and real-time suggestions

Archives bot-intelligence change and syncs all 8 delta specs to main specs.
2026-05-17 22:41:09 +02:00

70 lines
2.4 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { updateObservationsAfterHand, updateBetSizing, updateTimingStats } from '$lib/game/observation-tracker';
import { createInitialState } from '$lib/game/state';
import { createEmptyObservationStats } from '$lib/types/observation-stats';
describe('Observation Tracker', () => {
it('tracks VPIP correctly when player raises', () => {
const state = createInitialState(4, 1000);
state.observationData['player-1']!.handsPlayed = 10;
state.observationData['player-1']!.volPotEntries = 3;
// Simulate player-1 raising (voluntarily entering pot)
state.actionHistory.push({
playerId: 'player-1',
type: 'raise',
amount: 20,
timestamp: Date.now()
});
const newState = updateObservationsAfterHand(state);
expect(newState.observationData['player-1']!.volPotEntries).toBe(4);
expect(newState.observationData['player-1']!.handsPlayed).toBe(11);
});
it('tracks PFR correctly when player raises pre-flop', () => {
const state = createInitialState(4, 1000);
state.observationData['player-1']!.handsPlayed = 10;
state.observationData['player-1']!.preFlopRaises = 2;
state.actionHistory.push({
playerId: 'player-1',
type: 'raise',
amount: 30,
timestamp: Date.now()
});
const newState = updateObservationsAfterHand(state);
expect(newState.observationData['player-1']!.preFlopRaises).toBe(3);
});
it('does not count check as VPIP entry', () => {
const state = createInitialState(4, 1000);
state.observationData['player-1']!.handsPlayed = 10;
state.observationData['player-1']!.volPotEntries = 5;
state.actionHistory.push({
playerId: 'player-1',
type: 'check',
timestamp: Date.now()
});
const newState = updateObservationsAfterHand(state);
expect(newState.observationData['player-1']!.volPotEntries).toBe(5);
});
it('updates bet sizing profile', () => {
const stats = createEmptyObservationStats();
updateBetSizing(stats, 'flop', 30, 100);
expect(stats.betSizingFlop.averagePercent).toBeGreaterThan(0);
expect(stats.betSizingFlop.pattern).toBe('small');
});
it('updates timing stats', () => {
const stats = createEmptyObservationStats();
updateTimingStats(stats, 'call', 2.5);
expect(stats.timing.avgCallTime).toBe(2.5);
expect(stats.timing.avgDecisionTime).toBeGreaterThan(0);
});
});