/** * Model-agnostic decision core shared by every game built on this library. * * Nothing here knows about dominoes, trains, or any specific rule set: it only * knows how to turn a set of candidate actions into one chosen action, given a * skill profile and a way to score actions. The domino-specific player * (`createAiPlayer`) and downstream variants (e.g. Warp12) are thin adapters * over {@link createPolicyPlayer}. */ /** Pseudo-random source in [0, 1). Inject a seeded one for deterministic play. */ export type Rng = () => number; /** * The dials that define "skill". The same engine spans beginner→advanced purely * by changing which heuristics are active, their weights, and how sharply (or * randomly) the policy commits to the highest-scoring action. */ export interface SkillProfile { readonly id: string; /** Softmax temperature over candidate scores. 0 = argmax; higher = noisier. */ readonly temperature: number; /** Probability of ignoring the policy and picking a uniformly random action. */ readonly blunderRate: number; /** Plies of simulation (0 = greedy). Reserved; greedy-only in this release. */ readonly lookaheadDepth: number; readonly weights: Readonly>; readonly enabled: ReadonlySet; } /** * A single, pure rule-of-thumb over actions of type `TAction`, given a turn * context of type `TCtx`. Higher score = more attractive; return 0 when the * heuristic doesn't apply so it stays weight-neutral. */ export interface GenericHeuristic { readonly id: string; score(action: TAction, ctx: TCtx): number; } /** Weighted sum of the enabled heuristics for one action. */ export declare function scoreWithHeuristics(action: TAction, ctx: TCtx, byId: ReadonlyMap>, skill: SkillProfile): number; /** Index of the max score, breaking ties uniformly at random. */ export declare function argmaxIndex(scores: readonly number[], rng: Rng): number; /** Sample an index proportional to exp(score / temperature). */ export declare function softmaxIndex(scores: readonly number[], temperature: number, rng: Rng): number; /** Temperature-controlled choice: argmax at 0, softmax sampling above it. */ export declare function chooseActionIndex(scores: readonly number[], skill: SkillProfile, rng: Rng): number; export interface PolicyPlayerConfig { skill: SkillProfile; heuristics: ReadonlyArray>; generateCandidates: (obs: TObs) => TAction[]; buildContext: (obs: TObs, candidates: readonly TAction[]) => TCtx; /** Returned when the generator yields no candidates at all. */ fallback: (obs: TObs) => TAction; rng?: Rng; } export interface PolicyPlayer { decide(obs: TObs): TAction; } /** * The reusable decision engine. Per turn: * * observation → candidates → (blunder?) → weighted heuristics → policy → action * * Context is built once per decision and shared across heuristics. A single * candidate short-circuits scoring; an empty set returns `fallback`. */ export declare function createPolicyPlayer(config: PolicyPlayerConfig): PolicyPlayer;