@pompidup/kingdomino-engine 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -84,6 +84,10 @@ type Kingdom = (Tile | EmptyTile)[][];
84
84
  * This allows for a 5x5 kingdom with the castle placed anywhere.
85
85
  */
86
86
  declare const GRIDSIZE = 9;
87
+ /**
88
+ * Maximum kingdom size (5x5 in standard rules, 7x7 for Mighty Duel).
89
+ */
90
+ declare const MAX_KINGDOM_SIZE = 5;
87
91
 
88
92
  /**
89
93
  * Represents a lord (player token) used for turn order and domino selection.
@@ -128,6 +132,10 @@ type Player = {
128
132
  name: string;
129
133
  /** The player's kingdom grid where dominoes are placed */
130
134
  kingdom: Kingdom;
135
+ /** If set, this player is controlled by a bot with the named strategy */
136
+ bot?: {
137
+ strategyName: string;
138
+ };
131
139
  };
132
140
  /**
133
141
  * Payload for creating a new player.
@@ -166,6 +174,8 @@ type BasicRules = {
166
174
  dominoesPerTurn: number;
167
175
  /** Maximum number of turns in the game */
168
176
  maxTurns: number;
177
+ /** Maximum kingdom grid size (5 for standard, 7 for Mighty Duel) */
178
+ maxKingdomSize: number;
169
179
  };
170
180
  /**
171
181
  * Optional extra rules that modify gameplay and scoring.
@@ -184,7 +194,7 @@ type ExtraRule = {
184
194
  * Complete rules configuration loaded from data source.
185
195
  */
186
196
  type Rules = {
187
- /** Basic rules indexed by player count (2-4) */
197
+ /** Basic rules indexed by player count (1-4) */
188
198
  basic: Record<number, BasicRules>;
189
199
  /** Available extra rules */
190
200
  extraRules: ExtraRule[];
@@ -232,6 +242,8 @@ type Game = {
232
242
  rules: SelectedRules;
233
243
  /** Game mode being played */
234
244
  mode: GameMode;
245
+ /** Optional seed for deterministic shuffle (replays, sharing) */
246
+ seed?: string;
235
247
  };
236
248
  /**
237
249
  * Available game steps representing phases of game setup and completion.
@@ -328,6 +340,26 @@ type GameWithResults = Game & {
328
340
  result: FinalResult[];
329
341
  };
330
342
 
343
+ /**
344
+ * Severity level for a validation issue.
345
+ * - "error": The state is corrupted and cannot be used safely.
346
+ * - "warning": The state is suspicious but may still work.
347
+ */
348
+ type ValidationSeverity = "error" | "warning";
349
+ /**
350
+ * Describes a single validation issue found in a game state.
351
+ */
352
+ type ValidationIssue = {
353
+ /** JSON-path-like location of the issue (e.g. "players[0].kingdom") */
354
+ path: string;
355
+ /** Machine-readable code identifying the kind of issue */
356
+ code: string;
357
+ /** Human-readable description of the issue */
358
+ message: string;
359
+ /** Severity level */
360
+ severity: ValidationSeverity;
361
+ };
362
+
331
363
  type GetModesCommand = Record<string, never>;
332
364
 
333
365
  type GetExtraRulesCommand = {
@@ -337,11 +369,21 @@ type GetExtraRulesCommand = {
337
369
 
338
370
  type CreateGameCommand = {
339
371
  mode: string;
372
+ seed?: string;
340
373
  };
341
374
 
375
+ /**
376
+ * Input for a single player: either a name string (human) or an object with bot config.
377
+ */
378
+ type PlayerInput = string | {
379
+ name: string;
380
+ bot?: {
381
+ strategyName: string;
382
+ };
383
+ };
342
384
  type AddPlayersCommand = {
343
385
  game: GameState;
344
- players: string[];
386
+ players: PlayerInput[];
345
387
  };
346
388
 
347
389
  type AddExtraRulesCommand = {
@@ -387,6 +429,8 @@ type GetValidPlacementsCommand = {
387
429
  kingdom: Kingdom;
388
430
  /** The domino to find placements for */
389
431
  domino: Domino;
432
+ /** Maximum kingdom size (default: 5, use 7 for Mighty Duel) */
433
+ maxKingdomSize?: number;
390
434
  };
391
435
 
392
436
  /**
@@ -397,6 +441,8 @@ type CanPlaceDominoCommand = {
397
441
  kingdom: Kingdom;
398
442
  /** The domino to check placement for */
399
443
  domino: Domino;
444
+ /** Maximum kingdom size (default: 5, use 7 for Mighty Duel) */
445
+ maxKingdomSize?: number;
400
446
  };
401
447
 
402
448
  /**
@@ -414,6 +460,10 @@ type DeserializeGameCommand = {
414
460
  json: string;
415
461
  };
416
462
 
463
+ type GetDynastyResultCommand = {
464
+ games: GameWithResults[];
465
+ };
466
+
417
467
  /**
418
468
  * Represents a valid placement position with rotation.
419
469
  */
@@ -422,6 +472,14 @@ type ValidPlacement = {
422
472
  rotation: Rotation;
423
473
  };
424
474
 
475
+ type DynastyResult = {
476
+ playerId: string;
477
+ playerName: string;
478
+ totalPoints: number;
479
+ gamesPoints: number[];
480
+ position: number;
481
+ };
482
+
425
483
  /**
426
484
  * The main interface for interacting with the Kingdomino game engine.
427
485
  *
@@ -468,7 +526,7 @@ type GameEngine = {
468
526
  /**
469
527
  * Adds players to the game and initializes their kingdoms.
470
528
  * Each player receives an empty kingdom with a castle at the center.
471
- * @param command - Contains game state and array of player names (2-4 players)
529
+ * @param command - Contains game state and array of player names (1-4 players)
472
530
  * @returns Game state with next step 'options' or 'start'
473
531
  * @throws Error if player count is invalid or names are too short
474
532
  */
@@ -548,29 +606,13 @@ type GameEngine = {
548
606
  * @throws Error if the JSON is invalid or incompatible
549
607
  */
550
608
  deserialize: (command: DeserializeGameCommand) => GameState;
551
- };
552
-
553
- type ShuffleMethod = <T>(array: T[]) => T[];
554
-
555
- type UuidMethod = () => string;
556
-
557
- type Logger = Readonly<{
558
- info: (message: string) => void;
559
- error: (message: string) => void;
560
- }>;
561
-
562
- /**
563
- * Configuration options for the game engine.
564
- */
565
- type EngineConfig = {
566
- /** Custom UUID generation method. Defaults to crypto.randomUUID() */
567
- uuidMethod?: UuidMethod;
568
- /** Custom shuffle method. Defaults to Fisher-Yates algorithm */
569
- shuffleMethod?: ShuffleMethod;
570
- /** Enable console logging. Defaults to false */
571
- logging?: boolean;
572
- /** Custom logger instance. When provided, takes precedence over 'logging' option */
573
- logger?: Logger;
609
+ /**
610
+ * Calculates dynasty results from multiple completed games.
611
+ * Sum total points across games, highest total wins.
612
+ * @param command - Contains array of completed games with results
613
+ * @returns Ranked dynasty results per player
614
+ */
615
+ getDynastyResults: (command: GetDynastyResultCommand) => DynastyResult[];
574
616
  };
575
617
 
576
618
  /**
@@ -592,10 +634,13 @@ declare const ErrorCode: {
592
634
  readonly PLACEMENT_NOT_ADJACENT: "PLACEMENT_NOT_ADJACENT";
593
635
  readonly PLACEMENT_INVALID_TERRAIN: "PLACEMENT_INVALID_TERRAIN";
594
636
  readonly PLACEMENT_OUT_OF_BOUNDS: "PLACEMENT_OUT_OF_BOUNDS";
637
+ readonly PLACEMENT_EXCEEDS_KINGDOM_SIZE: "PLACEMENT_EXCEEDS_KINGDOM_SIZE";
595
638
  readonly NOT_YOUR_TURN: "NOT_YOUR_TURN";
596
639
  readonly CANNOT_PICK: "CANNOT_PICK";
597
640
  readonly CANNOT_PLACE: "CANNOT_PLACE";
598
641
  readonly DOMINO_ALREADY_PICKED: "DOMINO_ALREADY_PICKED";
642
+ readonly UNDO_UNAVAILABLE: "UNDO_UNAVAILABLE";
643
+ readonly REDO_UNAVAILABLE: "REDO_UNAVAILABLE";
599
644
  };
600
645
  type ErrorCodeType = (typeof ErrorCode)[keyof typeof ErrorCode];
601
646
  /**
@@ -631,7 +676,7 @@ declare class InvalidStepError extends DomainException {
631
676
  * Error thrown when a step execution fails.
632
677
  */
633
678
  declare class StepExecutionError extends DomainException {
634
- constructor(message: string, context?: Record<string, unknown>);
679
+ constructor(message: string, code?: ErrorCodeType, context?: Record<string, unknown>);
635
680
  }
636
681
  /**
637
682
  * Error thrown when an action execution fails.
@@ -651,143 +696,18 @@ declare class NotFoundError extends DomainException {
651
696
  declare class ValidationError extends DomainException {
652
697
  constructor(message: string, code: ErrorCodeType, context?: Record<string, unknown>);
653
698
  }
654
-
655
- /**
656
- * All possible game event types.
657
- */
658
- declare const GameEventType: {
659
- readonly GAME_CREATED: "game:created";
660
- readonly PLAYERS_ADDED: "game:playersAdded";
661
- readonly GAME_STARTED: "game:started";
662
- readonly DOMINO_PICKED: "domino:picked";
663
- readonly DOMINO_PLACED: "domino:placed";
664
- readonly DOMINO_DISCARDED: "domino:discarded";
665
- readonly TURN_CHANGED: "turn:changed";
666
- readonly GAME_ENDED: "game:ended";
667
- };
668
- type GameEventTypeValue = (typeof GameEventType)[keyof typeof GameEventType];
669
- /**
670
- * Base event structure with common properties.
671
- */
672
- interface BaseEvent {
673
- /** Event type identifier */
674
- type: GameEventTypeValue;
675
- /** Timestamp when the event occurred */
676
- timestamp: string;
677
- /** Game ID this event belongs to */
678
- gameId: string;
679
- }
680
- /**
681
- * Event emitted when a new game is created.
682
- */
683
- interface GameCreatedEvent extends BaseEvent {
684
- type: typeof GameEventType.GAME_CREATED;
685
- mode: string;
686
- }
687
699
  /**
688
- * Event emitted when players are added to the game.
700
+ * Error thrown when undo is requested but no previous state exists.
689
701
  */
690
- interface PlayersAddedEvent extends BaseEvent {
691
- type: typeof GameEventType.PLAYERS_ADDED;
692
- playerNames: string[];
693
- playerCount: number;
702
+ declare class UndoError extends DomainException {
703
+ constructor(message?: string, context?: Record<string, unknown>);
694
704
  }
695
705
  /**
696
- * Event emitted when the game starts.
706
+ * Error thrown when redo is requested but no future state exists.
697
707
  */
698
- interface GameStartedEvent extends BaseEvent {
699
- type: typeof GameEventType.GAME_STARTED;
700
- firstLordId: string;
701
- }
702
- /**
703
- * Event emitted when a lord picks a domino.
704
- */
705
- interface DominoPickedEvent extends BaseEvent {
706
- type: typeof GameEventType.DOMINO_PICKED;
707
- lordId: string;
708
- playerId: string;
709
- domino: Domino;
710
- }
711
- /**
712
- * Event emitted when a lord places a domino.
713
- */
714
- interface DominoPlacedEvent extends BaseEvent {
715
- type: typeof GameEventType.DOMINO_PLACED;
716
- lordId: string;
717
- playerId: string;
718
- domino: Domino;
719
- position: Position;
720
- rotation: Rotation;
708
+ declare class RedoError extends DomainException {
709
+ constructor(message?: string, context?: Record<string, unknown>);
721
710
  }
722
- /**
723
- * Event emitted when a lord discards a domino.
724
- */
725
- interface DominoDiscardedEvent extends BaseEvent {
726
- type: typeof GameEventType.DOMINO_DISCARDED;
727
- lordId: string;
728
- playerId: string;
729
- domino: Domino;
730
- }
731
- /**
732
- * Event emitted when the turn changes.
733
- */
734
- interface TurnChangedEvent extends BaseEvent {
735
- type: typeof GameEventType.TURN_CHANGED;
736
- previousTurn: number;
737
- newTurn: number;
738
- nextLordId: string;
739
- }
740
- /**
741
- * Event emitted when the game ends.
742
- */
743
- interface GameEndedEvent extends BaseEvent {
744
- type: typeof GameEventType.GAME_ENDED;
745
- results: FinalResult[];
746
- winner: {
747
- playerId: string;
748
- playerName: string;
749
- score: number;
750
- };
751
- }
752
- /**
753
- * Union type of all game events.
754
- */
755
- type GameEvent = GameCreatedEvent | PlayersAddedEvent | GameStartedEvent | DominoPickedEvent | DominoPlacedEvent | DominoDiscardedEvent | TurnChangedEvent | GameEndedEvent;
756
- /**
757
- * Event listener function type.
758
- */
759
- type GameEventListener<T extends GameEvent = GameEvent> = (event: T) => void;
760
- /**
761
- * Event emitter for game events.
762
- * Allows subscribing to specific event types or all events.
763
- */
764
- declare class GameEventEmitter {
765
- private listeners;
766
- /**
767
- * Subscribe to a specific event type.
768
- *
769
- * @param eventType - The event type to listen for, or "*" for all events
770
- * @param listener - The callback function to invoke
771
- * @returns Unsubscribe function
772
- */
773
- on<T extends GameEvent>(eventType: T["type"] | "*", listener: GameEventListener<T>): () => void;
774
- /**
775
- * Emit an event to all registered listeners.
776
- *
777
- * @param event - The event to emit
778
- */
779
- emit(event: GameEvent): void;
780
- /**
781
- * Remove all listeners for a specific event type or all listeners.
782
- *
783
- * @param eventType - Optional event type to clear, clears all if not provided
784
- */
785
- clear(eventType?: GameEventTypeValue | "*"): void;
786
- }
787
- /**
788
- * Creates an event with common properties filled in.
789
- */
790
- declare const createEvent: <T extends GameEvent>(gameId: string, type: T["type"], data: Omit<T, "type" | "timestamp" | "gameId">) => T;
791
711
 
792
712
  /**
793
713
  * Translation keys for internationalization support.
@@ -795,20 +715,26 @@ declare const createEvent: <T extends GameEvent>(gameId: string, type: T["type"]
795
715
  */
796
716
  declare const TranslationKey: {
797
717
  readonly ERROR_INVALID_STEP: "error.invalidStep";
718
+ readonly ERROR_STEP_EXECUTION_FAILED: "error.stepExecutionFailed";
719
+ readonly ERROR_ACTION_EXECUTION_FAILED: "error.actionExecutionFailed";
798
720
  readonly ERROR_LORD_NOT_FOUND: "error.lordNotFound";
799
721
  readonly ERROR_PLAYER_NOT_FOUND: "error.playerNotFound";
800
722
  readonly ERROR_DOMINO_NOT_FOUND: "error.dominoNotFound";
723
+ readonly ERROR_MODE_NOT_FOUND: "error.modeNotFound";
801
724
  readonly ERROR_INVALID_PLACEMENT: "error.invalidPlacement";
802
725
  readonly ERROR_PLACEMENT_NOT_EMPTY: "error.placementNotEmpty";
803
726
  readonly ERROR_PLACEMENT_NOT_ADJACENT: "error.placementNotAdjacent";
804
727
  readonly ERROR_PLACEMENT_INVALID_TERRAIN: "error.placementInvalidTerrain";
805
728
  readonly ERROR_PLACEMENT_OUT_OF_BOUNDS: "error.placementOutOfBounds";
729
+ readonly ERROR_PLACEMENT_EXCEEDS_KINGDOM_SIZE: "error.placementExceedsKingdomSize";
806
730
  readonly ERROR_NOT_YOUR_TURN: "error.notYourTurn";
807
731
  readonly ERROR_CANNOT_PICK: "error.cannotPick";
808
732
  readonly ERROR_CANNOT_PLACE: "error.cannotPlace";
809
733
  readonly ERROR_DOMINO_ALREADY_PICKED: "error.dominoAlreadyPicked";
810
734
  readonly ERROR_INVALID_PLAYER_COUNT: "error.invalidPlayerCount";
811
735
  readonly ERROR_INVALID_PLAYER_NAME: "error.invalidPlayerName";
736
+ readonly ERROR_UNDO_UNAVAILABLE: "error.undoUnavailable";
737
+ readonly ERROR_REDO_UNAVAILABLE: "error.redoUnavailable";
812
738
  readonly MODE_CLASSIC: "mode.classic";
813
739
  readonly RULE_MIDDLE_KINGDOM: "rule.middleKingdom";
814
740
  readonly RULE_HARMONY: "rule.harmony";
@@ -832,6 +758,16 @@ type TranslationKeyType = (typeof TranslationKey)[keyof typeof TranslationKey];
832
758
  * Default English translations.
833
759
  */
834
760
  declare const defaultTranslations: Record<TranslationKeyType, string>;
761
+ /**
762
+ * Maps ErrorCode values to their corresponding TranslationKey values.
763
+ * Used by handlers to translate error codes into localized messages.
764
+ */
765
+ declare const errorCodeToTranslationKey: Record<ErrorCodeType, TranslationKeyType>;
766
+ /**
767
+ * Translates an error code to a localized message.
768
+ * Convenience function combining errorCodeToTranslationKey lookup and translation.
769
+ */
770
+ declare const translateErrorCode: (translator: Translator, code: ErrorCodeType, params?: Record<string, string | number>) => string;
835
771
  /**
836
772
  * Translator interface for custom translations.
837
773
  */
@@ -857,6 +793,503 @@ declare const createTranslator: (translations?: Partial<Record<TranslationKeyTyp
857
793
  */
858
794
  declare const defaultTranslator: Translator;
859
795
 
796
+ type Logger = Readonly<{
797
+ info: (message: string) => void;
798
+ error: (message: string) => void;
799
+ }>;
800
+
801
+ type ShuffleMethod = <T>(array: T[]) => T[];
802
+
803
+ type UuidMethod = () => string;
804
+
805
+ /**
806
+ * Debug log verbosity level.
807
+ * - `minimal`: method name + phase only
808
+ * - `standard`: + game state summary + key params
809
+ * - `verbose`: + full game state JSON dump
810
+ */
811
+ type DebugLogLevel = "minimal" | "standard" | "verbose";
812
+ /**
813
+ * Summary of the current game state, extracted for quick inspection.
814
+ */
815
+ type GameStateSummary = {
816
+ gameId: string;
817
+ turn: number;
818
+ nextAction: string;
819
+ playersCount: number;
820
+ dominoesRemaining: number;
821
+ currentDominoesCount: number;
822
+ lordsCount: number;
823
+ };
824
+ /**
825
+ * A single debug log entry emitted by the debug wrapper.
826
+ */
827
+ type DebugLogEntry = {
828
+ timestamp: string;
829
+ method: string;
830
+ phase: "before" | "after" | "error";
831
+ summary: GameStateSummary | null;
832
+ params?: Record<string, unknown>;
833
+ error?: string;
834
+ durationMs?: number;
835
+ fullState?: GameState;
836
+ };
837
+ /**
838
+ * Custom debug logger interface.
839
+ * Consumers can provide their own to redirect debug output.
840
+ */
841
+ type DebugLogger = {
842
+ log: (entry: DebugLogEntry) => void;
843
+ };
844
+ /**
845
+ * Configuration for the debug wrapper.
846
+ */
847
+ type DebugOptions = {
848
+ /** Custom debug logger. Defaults to console-based logger. */
849
+ logger?: DebugLogger;
850
+ /** Verbosity level. Defaults to "standard". */
851
+ level?: DebugLogLevel;
852
+ };
853
+ /**
854
+ * Extracts a concise summary from a game state object.
855
+ */
856
+ declare const summarizeGameState: (game: GameState) => GameStateSummary;
857
+ /**
858
+ * Wraps a GameEngine to emit detailed debug log entries before and after each method call.
859
+ * Useful for UI developers to inspect engine behavior at every step.
860
+ *
861
+ * @param engine - The base game engine to wrap
862
+ * @param options - Debug configuration (logger, verbosity level)
863
+ * @returns A new GameEngine that emits debug logs
864
+ *
865
+ * @example
866
+ * ```typescript
867
+ * const debugEngine = wrapWithDebug(engine, { level: "verbose" });
868
+ * let game = debugEngine.createGame({ mode: "Classic" });
869
+ * // Console: [KDomino:Debug] ... BEFORE createGame
870
+ * // Console: [KDomino:Debug] ... AFTER createGame | turn=0 ...
871
+ * ```
872
+ */
873
+ declare const wrapWithDebug: (engine: GameEngine, options?: DebugOptions) => GameEngine;
874
+
875
+ /**
876
+ * Optional callbacks for game events.
877
+ * Consumers provide these to react to state changes (UI updates, animations, logging).
878
+ * All callbacks are optional — only the ones you provide will be called.
879
+ *
880
+ * @example
881
+ * ```typescript
882
+ * const engine = createGameEngine({
883
+ * events: {
884
+ * onDominoPlaced: ({ game, lordId }) => {
885
+ * console.log(`Lord ${lordId} placed a domino`);
886
+ * animatePlacement(game);
887
+ * },
888
+ * onTurnEnd: ({ turn }) => console.log(`Turn ${turn} ended`),
889
+ * onGameEnd: () => showResultsScreen(),
890
+ * },
891
+ * });
892
+ * ```
893
+ */
894
+ type GameEventCallbacks = {
895
+ /** Called after a new game is created */
896
+ onGameCreated?: (event: {
897
+ game: GameWithNextStep;
898
+ }) => void;
899
+ /** Called after players are added to the game */
900
+ onPlayersAdded?: (event: {
901
+ game: GameWithNextStep;
902
+ }) => void;
903
+ /** Called after the game starts (first dominoes revealed) */
904
+ onGameStarted?: (event: {
905
+ game: GameWithNextAction;
906
+ }) => void;
907
+ /** Called after a lord picks a domino */
908
+ onDominoPicked?: (event: {
909
+ game: GameState;
910
+ lordId: string;
911
+ dominoNumber: number;
912
+ }) => void;
913
+ /** Called after a lord places a domino on their kingdom */
914
+ onDominoPlaced?: (event: {
915
+ game: GameState;
916
+ lordId: string;
917
+ }) => void;
918
+ /** Called after a lord discards a domino (no valid placement) */
919
+ onDominoDiscarded?: (event: {
920
+ game: GameState;
921
+ lordId: string;
922
+ }) => void;
923
+ /** Called when a new turn begins */
924
+ onTurnStart?: (event: {
925
+ game: GameState;
926
+ turn: number;
927
+ }) => void;
928
+ /** Called when a turn ends (all lords have played) */
929
+ onTurnEnd?: (event: {
930
+ game: GameState;
931
+ turn: number;
932
+ }) => void;
933
+ /** Called when the game reaches the result phase */
934
+ onGameEnd?: (event: {
935
+ game: GameState;
936
+ }) => void;
937
+ };
938
+ /**
939
+ * Wraps a GameEngine to emit event callbacks after each state-changing method.
940
+ * Read-only methods (getModes, calculateScore, etc.) are passed through unchanged.
941
+ *
942
+ * Derived events:
943
+ * - `onTurnEnd` + `onTurnStart`: emitted when `chooseDomino` causes a turn increment
944
+ * - `onGameEnd`: emitted when `placeDomino` or `discardDomino` transitions to the "result" step
945
+ *
946
+ * @param engine - The base game engine to wrap
947
+ * @param callbacks - Optional event callbacks
948
+ * @returns A new GameEngine that emits events
949
+ */
950
+ declare const wrapWithEvents: (engine: GameEngine, callbacks: GameEventCallbacks) => GameEngine;
951
+
952
+ /**
953
+ * Configuration options for the game engine.
954
+ */
955
+ type EngineConfig = {
956
+ /** Custom UUID generation method. Defaults to crypto.randomUUID() */
957
+ uuidMethod?: UuidMethod;
958
+ /** Custom shuffle method. Defaults to Fisher-Yates algorithm */
959
+ shuffleMethod?: ShuffleMethod;
960
+ /** Enable console logging. Defaults to false */
961
+ logging?: boolean;
962
+ /** Custom logger instance. When provided, takes precedence over 'logging' option */
963
+ logger?: Logger;
964
+ /** Optional event callbacks for UI/animation integration */
965
+ events?: GameEventCallbacks;
966
+ /** Custom translator for i18n error messages. Defaults to English */
967
+ translator?: Translator;
968
+ /** Enable debug mode with detailed logging. Pass `true` for defaults or a `DebugOptions` object. */
969
+ debug?: boolean | DebugOptions;
970
+ };
971
+
972
+ /**
973
+ * Validates the integrity of a game state.
974
+ *
975
+ * Use this function after deserializing or restoring a persisted game state
976
+ * to detect corruptions before resuming gameplay.
977
+ *
978
+ * @param game - The game state to validate
979
+ * @returns An array of validation issues. Empty array means the state is valid.
980
+ *
981
+ * @example
982
+ * ```typescript
983
+ * const issues = validateGameState(game);
984
+ * if (issues.length > 0) {
985
+ * console.error("Game state is corrupted:", issues);
986
+ * }
987
+ * ```
988
+ */
989
+ declare const validateGameState: (game: Game) => ValidationIssue[];
990
+
991
+ type DominoesRepository = {
992
+ getForMode: (mode: GameMode) => Domino[] | [];
993
+ };
994
+
995
+ type ModeRepository = {
996
+ getAvailables: () => GameMode[];
997
+ };
998
+
999
+ type RuleRepository = {
1000
+ getAll: () => Rules;
1001
+ getAllExtra: () => ExtraRule[];
1002
+ };
1003
+
1004
+ /**
1005
+ * Context provided to a bot strategy when choosing a domino to pick.
1006
+ */
1007
+ type PickContext = {
1008
+ game: GameWithNextAction;
1009
+ lordId: string;
1010
+ availableDominoes: RevealsDomino[];
1011
+ };
1012
+ /**
1013
+ * Context provided to a bot strategy when choosing where to place a domino.
1014
+ */
1015
+ type PlaceContext = {
1016
+ game: GameWithNextAction;
1017
+ lordId: string;
1018
+ domino: Domino;
1019
+ kingdom: Kingdom;
1020
+ validPlacements: ValidPlacement[];
1021
+ };
1022
+ /**
1023
+ * A bot strategy that decides which domino to pick and where to place it.
1024
+ * Strategies are stateless — all information comes from the context.
1025
+ */
1026
+ type BotStrategy = {
1027
+ chooseDomino: (context: PickContext) => number;
1028
+ choosePlacement: (context: PlaceContext) => ValidPlacement | null;
1029
+ };
1030
+ /**
1031
+ * Bot strategy that makes random choices.
1032
+ * Picks a random available domino and places it at a random valid position.
1033
+ */
1034
+ declare const randomStrategy: BotStrategy;
1035
+ /**
1036
+ * Bot strategy that maximizes the immediate score.
1037
+ * Evaluates every valid placement and picks the one yielding the highest score.
1038
+ * When picking a domino, simulates the best placement for each option.
1039
+ */
1040
+ declare const greedyStrategy: BotStrategy;
1041
+ /**
1042
+ * Bot strategy with 1-turn lookahead.
1043
+ * When making decisions, considers not just the immediate score but also
1044
+ * the best possible score achievable in the next turn by peeking at
1045
+ * the upcoming domino batch from the draw pile.
1046
+ */
1047
+ declare const advancedStrategy: BotStrategy;
1048
+ /**
1049
+ * Bot strategy that searches the game tree with beam-search pruning.
1050
+ * Explores all remaining turns, but limits the number of placements
1051
+ * evaluated per domino to the top candidates (by greedy score).
1052
+ * Since kingdoms are independent, this is a solo optimization (no adversarial search needed).
1053
+ */
1054
+ declare const expertStrategy: BotStrategy;
1055
+ /**
1056
+ * Executes a single bot turn using the given strategy and engine.
1057
+ * Reads the current game state to determine the required action,
1058
+ * asks the strategy for a decision, and executes it via the engine.
1059
+ *
1060
+ * @param engine - The game engine instance
1061
+ * @param game - Current game state (must require a player action)
1062
+ * @param strategy - The bot strategy to use for decisions
1063
+ * @returns Updated game state after the bot's action
1064
+ */
1065
+ declare const playBotTurn: (engine: GameEngine, game: GameWithNextAction, strategy: BotStrategy) => GameState;
1066
+ /**
1067
+ * Checks if the current turn belongs to a bot player.
1068
+ */
1069
+ declare const isBotTurn: (game: GameWithNextAction) => boolean;
1070
+ /**
1071
+ * Plays all consecutive bot turns until a human turn or game end is reached.
1072
+ * Resolves each bot's strategy by name from the registry (or custom map).
1073
+ * Falls back to randomStrategy if the strategy name is not found.
1074
+ *
1075
+ * @param engine - The game engine instance
1076
+ * @param game - Current game state
1077
+ * @param customStrategies - Optional custom strategy map (name → BotStrategy)
1078
+ * @returns Updated game state paused at the next human turn or game end
1079
+ */
1080
+ declare const playBotTurns: (engine: GameEngine, game: GameState, customStrategies?: Record<string, BotStrategy>) => GameState;
1081
+
1082
+ /**
1083
+ * Built-in strategy names.
1084
+ */
1085
+ type StrategyName = "random" | "greedy" | "advanced" | "expert";
1086
+ /**
1087
+ * Resolves a strategy by name. Checks custom strategies first, then defaults.
1088
+ * Returns undefined if the name is not found.
1089
+ */
1090
+ declare const getStrategy: (name: string, custom?: Record<string, BotStrategy>) => BotStrategy | undefined;
1091
+ /**
1092
+ * Returns the list of built-in strategy names.
1093
+ */
1094
+ declare const getStrategyNames: () => string[];
1095
+
1096
+ /**
1097
+ * The types of state-changing actions that can be recorded.
1098
+ */
1099
+ type ActionType = "createGame" | "addPlayers" | "addExtraRules" | "startGame" | "chooseDomino" | "placeDomino" | "discardDomino";
1100
+ /**
1101
+ * Payload types for each action, containing only the command parameters
1102
+ * (excluding the game state, which is too large to store).
1103
+ */
1104
+ type ActionPayloadMap = {
1105
+ createGame: {
1106
+ mode: string;
1107
+ seed?: string;
1108
+ };
1109
+ addPlayers: {
1110
+ players: PlayerInput[];
1111
+ };
1112
+ addExtraRules: {
1113
+ extraRules: string[];
1114
+ };
1115
+ startGame: Record<string, never>;
1116
+ chooseDomino: {
1117
+ lordId: string;
1118
+ dominoPick: number;
1119
+ };
1120
+ placeDomino: {
1121
+ lordId: string;
1122
+ position: Position;
1123
+ rotation: Rotation;
1124
+ };
1125
+ discardDomino: {
1126
+ lordId: string;
1127
+ };
1128
+ };
1129
+ /**
1130
+ * A single recorded action in the game log.
1131
+ */
1132
+ type ActionEntry<T extends ActionType = ActionType> = {
1133
+ /** Auto-incrementing index (0-based) */
1134
+ id: number;
1135
+ /** Timestamp when the action was recorded (Date.now()) */
1136
+ timestamp: number;
1137
+ /** The type of action performed */
1138
+ action: T;
1139
+ /** Command parameters (excludes game state) */
1140
+ payload: ActionPayloadMap[T];
1141
+ /** Turn number after the action was applied */
1142
+ turn: number;
1143
+ };
1144
+ /**
1145
+ * An immutable log of all actions performed during a game.
1146
+ * Can be used to replay a game or analyze player decisions.
1147
+ *
1148
+ * @example
1149
+ * ```typescript
1150
+ * const { engine, getLog } = wrapWithActionLog(baseEngine);
1151
+ * let game = engine.createGame({ mode: 'Classic' });
1152
+ * game = engine.addPlayers({ game, players: ['Alice', 'Bob'] });
1153
+ * // ... play the game ...
1154
+ * const log = getLog();
1155
+ * console.log(log.entries); // all actions recorded
1156
+ * ```
1157
+ */
1158
+ type GameActionLog = {
1159
+ /** The game ID this log belongs to */
1160
+ gameId: string;
1161
+ /** Immutable array of recorded actions */
1162
+ entries: readonly ActionEntry[];
1163
+ };
1164
+ /**
1165
+ * Creates an empty action log for a game.
1166
+ */
1167
+ declare const createActionLog: (gameId: string) => GameActionLog;
1168
+ /**
1169
+ * Appends an action to the log (immutable — returns a new log).
1170
+ */
1171
+ declare const appendAction: (log: GameActionLog, entry: Omit<ActionEntry, "id" | "timestamp">) => GameActionLog;
1172
+ /**
1173
+ * Returns all entries in the log.
1174
+ */
1175
+ declare const getActions: (log: GameActionLog) => readonly ActionEntry[];
1176
+ /**
1177
+ * Returns entries filtered by action type.
1178
+ */
1179
+ declare const getActionsByType: (log: GameActionLog, type: ActionType) => readonly ActionEntry[];
1180
+ /**
1181
+ * Returns entries filtered by turn number.
1182
+ */
1183
+ declare const getActionsByTurn: (log: GameActionLog, turn: number) => readonly ActionEntry[];
1184
+ /**
1185
+ * Wraps a GameEngine to automatically record every state-changing action.
1186
+ * Read-only methods are passed through unchanged.
1187
+ *
1188
+ * @param engine - The base game engine to wrap
1189
+ * @returns An object with the wrapped engine and a getLog() accessor
1190
+ *
1191
+ * @example
1192
+ * ```typescript
1193
+ * const { engine: logged, getLog } = wrapWithActionLog(baseEngine);
1194
+ * let game = logged.createGame({ mode: 'Classic' });
1195
+ * // ... play ...
1196
+ * const log = getLog(); // retrieve the full action log
1197
+ * ```
1198
+ */
1199
+ declare const wrapWithActionLog: (engine: GameEngine) => {
1200
+ engine: GameEngine;
1201
+ getLog: () => GameActionLog;
1202
+ };
1203
+ /**
1204
+ * Replays all actions from a log on a fresh engine to reconstruct the final game state.
1205
+ * The engine should use the same configuration (especially shuffleMethod) as the original game
1206
+ * to guarantee deterministic results when a seed is present.
1207
+ *
1208
+ * @param engine - A game engine instance (should match original config)
1209
+ * @param log - The action log to replay
1210
+ * @returns The final game state after replaying all actions
1211
+ */
1212
+ declare const replayActions: (engine: GameEngine, log: GameActionLog) => GameState;
1213
+
1214
+ /**
1215
+ * Manages a history of game states for undo/redo support.
1216
+ * Uses a snapshot-based approach: each state is stored as a full GameState.
1217
+ *
1218
+ * @example
1219
+ * ```typescript
1220
+ * let history = createGameHistory(initialGame);
1221
+ * const newGame = engine.chooseDomino({ game: history.current, ... });
1222
+ * history = pushState(history, newGame);
1223
+ * history = undo(history); // back to initialGame
1224
+ * history = redo(history); // forward to newGame
1225
+ * ```
1226
+ */
1227
+ type GameHistory = {
1228
+ /** The current game state */
1229
+ current: GameState;
1230
+ /** Stack of previous states (most recent last) */
1231
+ past: GameState[];
1232
+ /** Stack of undone states (most recent last) */
1233
+ future: GameState[];
1234
+ };
1235
+ /**
1236
+ * Creates a new game history initialized with a game state.
1237
+ *
1238
+ * @param initialState - The starting game state
1239
+ * @returns A new GameHistory with empty past and future
1240
+ */
1241
+ declare const createGameHistory: (initialState: GameState) => GameHistory;
1242
+ /**
1243
+ * Records a new game state in the history.
1244
+ * The current state is pushed to the past stack, and the future stack is cleared
1245
+ * (branching from a previous undo point discards the old future).
1246
+ *
1247
+ * @param history - The current history
1248
+ * @param newState - The new game state to record
1249
+ * @returns Updated history with the new state as current
1250
+ */
1251
+ declare const pushState: (history: GameHistory, newState: GameState) => GameHistory;
1252
+ /**
1253
+ * Reverts to the previous game state.
1254
+ * The current state is moved to the future stack for potential redo.
1255
+ *
1256
+ * @param history - The current history
1257
+ * @returns Updated history with the previous state as current
1258
+ * @throws {UndoError} If there is no previous state to revert to
1259
+ */
1260
+ declare const undo: (history: GameHistory) => GameHistory;
1261
+ /**
1262
+ * Re-applies a previously undone game state.
1263
+ * The current state is moved to the past stack.
1264
+ *
1265
+ * @param history - The current history
1266
+ * @returns Updated history with the next undone state as current
1267
+ * @throws {RedoError} If there is no undone state to re-apply
1268
+ */
1269
+ declare const redo: (history: GameHistory) => GameHistory;
1270
+ /**
1271
+ * Checks whether an undo operation is available.
1272
+ */
1273
+ declare const canUndo: (history: GameHistory) => boolean;
1274
+ /**
1275
+ * Checks whether a redo operation is available.
1276
+ */
1277
+ declare const canRedo: (history: GameHistory) => boolean;
1278
+ /**
1279
+ * Clears the undo/redo history, keeping only the current state.
1280
+ *
1281
+ * @param history - The current history
1282
+ * @returns A new history with empty past and future
1283
+ */
1284
+ declare const clearHistory: (history: GameHistory) => GameHistory;
1285
+ /**
1286
+ * Returns the number of states in the past and future stacks.
1287
+ */
1288
+ declare const getHistorySize: (history: GameHistory) => {
1289
+ past: number;
1290
+ future: number;
1291
+ };
1292
+
860
1293
  /**
861
1294
  * Serializes a game state to a JSON string.
862
1295
  * The serialized string includes version information for future compatibility.
@@ -916,12 +1349,15 @@ declare const restoreFromSavePoint: (savePoint: GameSavePoint) => Result<GameSta
916
1349
  * // With logging
917
1350
  * const engine = createGameEngine({ logging: true });
918
1351
  *
919
- * // With custom shuffle for testing
1352
+ * // With event callbacks
920
1353
  * const engine = createGameEngine({
921
- * shuffleMethod: (array) => array, // No shuffle
1354
+ * events: {
1355
+ * onDominoPlaced: ({ lordId }) => console.log(`${lordId} placed`),
1356
+ * onGameEnd: () => console.log('Game over!'),
1357
+ * },
922
1358
  * });
923
1359
  * ```
924
1360
  */
925
1361
  declare const createGameEngine: (config: Partial<EngineConfig>) => GameEngine;
926
1362
 
927
- export { ActionExecutionError, type AddExtraRulesCommand, type AddPlayersCommand, type BasicRules, type CalculateScoreCommand, type CanPlaceDominoCommand, type ChooseDominoCommand, type CreateGameCommand, type Crown, type DeserializeGameCommand, type DiscardDominoCommand, type DomainError, DomainException, type Domino, type DominoDiscardedEvent, type DominoPickedEvent, type DominoPlacedEvent, type EmptyGround, type EmptyTile, type EngineConfig, ErrorCode, type ErrorCodeType, type ExtraRule, type FinalResult, GRIDSIZE, GROUND, type Game, type GameCreatedEvent, type GameEndedEvent, type GameEngine, type GameEvent, GameEventEmitter, type GameEventListener, GameEventType, type GameEventTypeValue, type GameMode, type GameSavePoint, type GameStartedEvent, type GameState, type GameStateResult, type GameSteps, type GameWithNextAction, type GameWithNextStep, type GameWithResults, type GetExtraRulesCommand, type GetModesCommand, type GetResultCommand, type GetValidPlacementsCommand, type Ground, InvalidStepError, type Kingdom, type Lord, type NextAction, type NextStep, NotFoundError, type ObjectValues, type PlaceDominoCommand, type Player, type PlayerActions, type PlayerPayload, type Players, type PlayersAddedEvent, type PlayersPayload, type Position, type RevealsDomino, type Rotation, type Rules, type Score, type ScoreResult, type SelectedRules, type SerializeGameCommand, type ShuffleMethod, type StartGameCommand, StepExecutionError, type Tile, TranslationKey, type TranslationKeyType, type Translator, type TurnChangedEvent, type UuidMethod, type ValidPlacement, ValidationError, createDomainError, createEvent, createGameEngine, createSavePoint, createTranslator, defaultTranslations, defaultTranslator, deserializeGame, gameSteps, isGameWithNextAction, isGameWithNextStep, playerActions, restoreFromSavePoint, serializeGame };
1363
+ export { type ActionEntry, ActionExecutionError, type ActionPayloadMap, type ActionType, type AddExtraRulesCommand, type AddPlayersCommand, type BasicRules, type BotStrategy, type CalculateScoreCommand, type CanPlaceDominoCommand, type ChooseDominoCommand, type CreateGameCommand, type Crown, type DebugLogEntry, type DebugLogLevel, type DebugLogger, type DebugOptions, type DeserializeGameCommand, type DiscardDominoCommand, type DomainError, DomainException, type Domino, type DominoesRepository, type DynastyResult, type EmptyGround, type EmptyTile, type EngineConfig, ErrorCode, type ErrorCodeType, type ExtraRule, type FinalResult, GRIDSIZE, GROUND, type Game, type GameActionLog, type GameEngine, type GameEventCallbacks, type GameHistory, type GameMode, type GameSavePoint, type GameState, type GameStateResult, type GameStateSummary, type GameSteps, type GameWithNextAction, type GameWithNextStep, type GameWithResults, type GetDynastyResultCommand, type GetExtraRulesCommand, type GetModesCommand, type GetResultCommand, type GetValidPlacementsCommand, type Ground, InvalidStepError, type Kingdom, type Logger, type Lord, MAX_KINGDOM_SIZE, type ModeRepository, type NextAction, type NextStep, NotFoundError, type ObjectValues, type PickContext, type PlaceContext, type PlaceDominoCommand, type Player, type PlayerActions, type PlayerInput, type PlayerPayload, type Players, type PlayersPayload, type Position, RedoError, type RevealsDomino, type Rotation, type RuleRepository, type Rules, type Score, type ScoreResult, type SelectedRules, type SerializeGameCommand, type ShuffleMethod, type StartGameCommand, StepExecutionError, type StrategyName, type Tile, TranslationKey, type TranslationKeyType, type Translator, UndoError, type UuidMethod, type ValidPlacement, ValidationError, type ValidationIssue, type ValidationSeverity, advancedStrategy, appendAction, canRedo, canUndo, clearHistory, createActionLog, createDomainError, createGameEngine, createGameHistory, createSavePoint, createTranslator, defaultTranslations, defaultTranslator, deserializeGame, errorCodeToTranslationKey, expertStrategy, gameSteps, getActions, getActionsByTurn, getActionsByType, getHistorySize, getStrategy, getStrategyNames, greedyStrategy, isBotTurn, isGameWithNextAction, isGameWithNextStep, playBotTurn, playBotTurns, playerActions, pushState, randomStrategy, redo, replayActions, restoreFromSavePoint, serializeGame, summarizeGameState, translateErrorCode, undo, validateGameState, wrapWithActionLog, wrapWithDebug, wrapWithEvents };