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.
70 lines
2.4 KiB
TypeScript
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);
|
|
});
|
|
});
|