@pompidup/kingdomino-engine 3.5.1 → 3.6.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
@@ -157,6 +157,17 @@ type QuestTile = {
157
157
  /** Target terrain type (only for localTrade and kingdomBorders quests) */
158
158
  terrain?: Ground;
159
159
  };
160
+ /**
161
+ * A giant sent with `sendGiant` that the targeted opponent must now place on
162
+ * one of their own crowns (official rule: the opponent chooses the crown).
163
+ * While it is set, `nextAction` is `placeGiant` for a lord of the target.
164
+ */
165
+ type PendingGiant = {
166
+ /** Lord who sent the giant; their turn resumes once the giant is placed */
167
+ fromLordId: string;
168
+ /** Player who received the giant and must choose the crown */
169
+ targetPlayerId: string;
170
+ };
160
171
  /**
161
172
  * Complete Age of Giants-specific game state.
162
173
  * Stored as an optional field on Game and only present in AoG modes.
@@ -166,6 +177,8 @@ type AgeOfGiantsState = {
166
177
  giantPool: number;
167
178
  /** The 2 quest tiles drawn for this game */
168
179
  questTiles: QuestTile[];
180
+ /** Giant sent to an opponent, waiting for that opponent to choose a crown */
181
+ pendingGiant?: PendingGiant;
169
182
  };
170
183
 
171
184
  /**
@@ -322,8 +335,28 @@ type WarriorTile = {
322
335
  /** Combat power (1, 2, or 3) */
323
336
  power: number;
324
337
  };
325
- /** Discriminated union of all caveman tile types. */
338
+ /**
339
+ * Discriminant values of `CavemanTile["kind"]`.
340
+ */
341
+ declare const cavemanKinds: {
342
+ readonly hunterGatherer: "hunterGatherer";
343
+ readonly warrior: "warrior";
344
+ };
345
+ type CavemanKind = ObjectValues<typeof cavemanKinds>;
346
+ /** Discriminated union of all caveman tile types (discriminant: `kind`). */
326
347
  type CavemanTile = HunterGathererTile | WarriorTile;
348
+ /**
349
+ * Type guard to check if a caveman tile is a hunter-gatherer.
350
+ * @param caveman - The caveman tile to check
351
+ * @returns True if the tile is a `HunterGathererTile`
352
+ */
353
+ declare const isHunterGathererTile: (caveman: CavemanTile) => caveman is HunterGathererTile;
354
+ /**
355
+ * Type guard to check if a caveman tile is a warrior.
356
+ * @param caveman - The caveman tile to check
357
+ * @returns True if the tile is a `WarriorTile`
358
+ */
359
+ declare const isWarriorTile: (caveman: CavemanTile) => caveman is WarriorTile;
327
360
  /**
328
361
  * A caveman tile placed on a player's kingdom.
329
362
  */
@@ -454,6 +487,11 @@ type Player = {
454
487
  towers?: number;
455
488
  /** Knights placed on the kingdom (Queendomino) */
456
489
  knights?: Knight[];
490
+ /**
491
+ * Extra knights granted by building immediate bonuses (Queendomino).
492
+ * Each one raises the player's knight limit above the base maximum.
493
+ */
494
+ bonusKnights?: number;
457
495
  /** Buildings placed on the kingdom (Queendomino) */
458
496
  buildings?: PlacedBuilding[];
459
497
  /** Fire tokens placed on the kingdom (Origins) */
@@ -519,6 +557,12 @@ type QueenDominoState = {
519
557
  dragonAvailable: boolean;
520
558
  /** Whether the Dragon has been used this round (resets each round) */
521
559
  dragonUsedThisRound: boolean;
560
+ /**
561
+ * Kingdom positions of the two squares of the domino the current lord has
562
+ * just placed. Knights may only be placed on these squares. Empty when the
563
+ * lord discarded their domino.
564
+ */
565
+ lastPlacedPositions?: Position[];
522
566
  };
523
567
 
524
568
  /**
@@ -573,742 +617,1196 @@ type SelectedRules = {
573
617
  };
574
618
 
575
619
  /**
576
- * Represents the complete state of a Kingdomino game.
577
- * This is the main aggregate containing all game data.
578
- */
579
- type Game = {
580
- /** Unique identifier for the game */
581
- id: string;
582
- /** Remaining dominoes in the draw pile */
583
- dominoes: Domino[];
584
- /** Currently revealed dominoes available for picking */
585
- currentDominoes: RevealsDomino[];
586
- /** Players participating in the game with their kingdoms */
587
- players: Player[];
588
- /** Lords (player tokens) used for turn order and domino selection */
589
- lords: Lord[];
590
- /** Current turn number (1-based) */
591
- turn: number;
592
- /** Next required action or game step */
593
- nextAction: NextAction | NextStep;
594
- /** Active rules for this game */
595
- rules: SelectedRules;
596
- /** Game mode being played */
597
- mode: GameMode;
598
- /** Optional seed for deterministic shuffle (replays, sharing) */
599
- seed?: string;
600
- /** Queendomino-specific state (only present in QueenDomino mode) */
601
- queendomino?: QueenDominoState;
602
- /** Origins-specific state (only present in KingdominoOrigins modes) */
603
- origins?: OriginsState;
604
- /** Age of Giants-specific state (only present in AgeOfGiants modes) */
605
- ageOfGiants?: AgeOfGiantsState;
606
- };
607
- /**
608
- * Available game steps representing phases of game setup and completion.
620
+ * Error codes for domain-level errors.
621
+ * These codes can be used for i18n and programmatic error handling.
609
622
  */
610
- declare const gameSteps: {
611
- /** Waiting for players to be added */
612
- readonly addPlayers: "addPlayers";
613
- /** Optional rules selection phase */
614
- readonly options: "options";
615
- /** Ready to start the game */
616
- readonly start: "start";
617
- /** Game has ended, results available */
618
- readonly result: "result";
623
+ declare const ErrorCode: {
624
+ readonly INVALID_STEP: "INVALID_STEP";
625
+ readonly STEP_EXECUTION_FAILED: "STEP_EXECUTION_FAILED";
626
+ readonly ACTION_EXECUTION_FAILED: "ACTION_EXECUTION_FAILED";
627
+ readonly LORD_NOT_FOUND: "LORD_NOT_FOUND";
628
+ readonly PLAYER_NOT_FOUND: "PLAYER_NOT_FOUND";
629
+ readonly DOMINO_NOT_FOUND: "DOMINO_NOT_FOUND";
630
+ readonly MODE_NOT_FOUND: "MODE_NOT_FOUND";
631
+ readonly INVALID_PLAYER_COUNT: "INVALID_PLAYER_COUNT";
632
+ readonly INVALID_PLAYER_NAME: "INVALID_PLAYER_NAME";
633
+ readonly EXTRA_RULE_NOT_AVAILABLE: "EXTRA_RULE_NOT_AVAILABLE";
634
+ readonly INVALID_PLACEMENT: "INVALID_PLACEMENT";
635
+ readonly PLACEMENT_NOT_EMPTY: "PLACEMENT_NOT_EMPTY";
636
+ readonly PLACEMENT_NOT_ADJACENT: "PLACEMENT_NOT_ADJACENT";
637
+ readonly PLACEMENT_INVALID_TERRAIN: "PLACEMENT_INVALID_TERRAIN";
638
+ readonly PLACEMENT_OUT_OF_BOUNDS: "PLACEMENT_OUT_OF_BOUNDS";
639
+ readonly PLACEMENT_EXCEEDS_KINGDOM_SIZE: "PLACEMENT_EXCEEDS_KINGDOM_SIZE";
640
+ readonly NOT_YOUR_TURN: "NOT_YOUR_TURN";
641
+ readonly CANNOT_PICK: "CANNOT_PICK";
642
+ readonly CANNOT_PLACE: "CANNOT_PLACE";
643
+ readonly DOMINO_ALREADY_PICKED: "DOMINO_ALREADY_PICKED";
644
+ readonly UNDO_UNAVAILABLE: "UNDO_UNAVAILABLE";
645
+ readonly REDO_UNAVAILABLE: "REDO_UNAVAILABLE";
646
+ readonly CANNOT_PLACE_KNIGHT: "CANNOT_PLACE_KNIGHT";
647
+ readonly MAX_KNIGHTS_REACHED: "MAX_KNIGHTS_REACHED";
648
+ readonly KNIGHT_TERRITORY_OCCUPIED: "KNIGHT_TERRITORY_OCCUPIED";
649
+ readonly CANNOT_AFFORD_BUILDING: "CANNOT_AFFORD_BUILDING";
650
+ readonly NO_CONSTRUCTION_SQUARE: "NO_CONSTRUCTION_SQUARE";
651
+ readonly DRAGON_UNAVAILABLE: "DRAGON_UNAVAILABLE";
652
+ readonly QUEEN_HOLDER_CANNOT_USE_DRAGON: "QUEEN_HOLDER_CANNOT_USE_DRAGON";
653
+ readonly BUILDING_NOT_FOUND: "BUILDING_NOT_FOUND";
654
+ readonly INVALID_OPTIONAL_ACTION: "INVALID_OPTIONAL_ACTION";
655
+ readonly GIANT_POOL_EMPTY: "GIANT_POOL_EMPTY";
656
+ readonly GIANT_NOT_FOUND: "GIANT_NOT_FOUND";
657
+ readonly INVALID_GIANT_PLACEMENT: "INVALID_GIANT_PLACEMENT";
658
+ readonly NOT_AOG_MODE: "NOT_AOG_MODE";
659
+ readonly FIRE_TOKEN_POOL_EMPTY: "FIRE_TOKEN_POOL_EMPTY";
660
+ readonly FIRE_TOKEN_INVALID_POSITION: "FIRE_TOKEN_INVALID_POSITION";
661
+ readonly NO_PENDING_FIRE_TOKEN: "NO_PENDING_FIRE_TOKEN";
662
+ readonly CANNOT_RECRUIT_CAVEMAN: "CANNOT_RECRUIT_CAVEMAN";
663
+ readonly INSUFFICIENT_RESOURCES: "INSUFFICIENT_RESOURCES";
664
+ readonly INVALID_CAVEMAN_PLACEMENT: "INVALID_CAVEMAN_PLACEMENT";
665
+ readonly CAVEMAN_NOT_FOUND: "CAVEMAN_NOT_FOUND";
666
+ readonly NOT_TRIBE_MODE: "NOT_TRIBE_MODE";
667
+ readonly INVALID_PLAYER_ID: "INVALID_PLAYER_ID";
619
668
  };
620
- /** Union type of all possible game steps */
621
- type GameSteps = ObjectValues<typeof gameSteps>;
669
+ type ErrorCodeType = (typeof ErrorCode)[keyof typeof ErrorCode];
622
670
  /**
623
- * Represents a player action that needs to be performed.
624
- * Used during active gameplay.
671
+ * Structured domain error with code and context.
625
672
  */
626
- type NextAction = {
627
- /** Discriminator for action type */
628
- type: "action";
629
- /** ID of the lord who must perform the action */
630
- nextLord: string;
631
- /** The action the lord must perform */
632
- nextAction: PlayerActions;
633
- };
673
+ interface DomainError {
674
+ /** Error code for programmatic handling */
675
+ code: ErrorCodeType;
676
+ /** Human-readable error message */
677
+ message: string;
678
+ /** Optional context data for debugging */
679
+ context?: Record<string, unknown>;
680
+ }
634
681
  /**
635
- * Represents a game phase transition.
636
- * Used during setup and game end.
682
+ * Creates a domain error object.
637
683
  */
638
- type NextStep = {
639
- /** Discriminator for step type */
640
- type: "step";
641
- /** The current game step/phase */
642
- step: GameSteps;
643
- };
644
- /** Game state where a player action is required */
645
- type GameWithNextAction = Game & {
646
- nextAction: NextAction;
647
- };
648
- /** Game state where a phase transition is occurring */
649
- type GameWithNextStep = Game & {
650
- nextAction: NextStep;
651
- };
652
- /** Union of all possible game states */
653
- type GameState = GameWithNextAction | GameWithNextStep;
654
- /** Result wrapper for game state operations */
655
- type GameStateResult = Result<GameState>;
684
+ declare const createDomainError: (code: ErrorCodeType, message: string, context?: Record<string, unknown>) => DomainError;
656
685
  /**
657
- * Type guard to check if the game requires a player action.
658
- * @param game - The game state to check
659
- * @returns True if the game is waiting for a player action
686
+ * Base class for domain errors thrown by handlers.
660
687
  */
661
- declare const isGameWithNextAction: (game: GameWithNextAction | GameWithNextStep) => game is GameWithNextAction;
688
+ declare class DomainException extends Error {
689
+ readonly code: ErrorCodeType;
690
+ readonly context?: Record<string, unknown>;
691
+ constructor(error: DomainError);
692
+ }
662
693
  /**
663
- * Type guard to check if the game is in a phase transition.
664
- * @param game - The game state to check
665
- * @returns True if the game is transitioning between phases
694
+ * Error thrown when the game is in an invalid step for the requested action.
666
695
  */
667
- declare const isGameWithNextStep: (game: GameWithNextAction | GameWithNextStep) => game is GameWithNextStep;
696
+ declare class InvalidStepError extends DomainException {
697
+ constructor(message: string, context?: Record<string, unknown>);
698
+ }
668
699
  /**
669
- * Represents the score calculation for a kingdom.
700
+ * Error thrown when a step execution fails.
670
701
  */
671
- type Score = {
672
- /** Total points earned */
673
- points: number;
674
- /** Size of the largest contiguous property */
675
- maxPropertiesSize: number;
676
- /** Total number of crowns in the kingdom */
677
- totalCrowns: number;
678
- };
702
+ declare class StepExecutionError extends DomainException {
703
+ constructor(message: string, code?: ErrorCodeType, context?: Record<string, unknown>);
704
+ }
679
705
  /**
680
- * Score result for a specific player.
706
+ * Error thrown when an action execution fails.
681
707
  */
682
- type ScoreResult = {
683
- /** Player's unique identifier */
684
- playerId: string;
685
- /** Player's display name */
686
- playerName: string;
687
- /** Detailed score breakdown */
688
- details: Score;
689
- };
708
+ declare class ActionExecutionError extends DomainException {
709
+ constructor(message: string, code?: ErrorCodeType, context?: Record<string, unknown>);
710
+ }
690
711
  /**
691
- * Final result including ranking position.
712
+ * Error thrown when a required entity is not found.
692
713
  */
693
- type FinalResult = ScoreResult & {
694
- /** Player's final position (1 = winner) */
695
- position: number;
696
- };
697
- /** Game state with final results calculated */
698
- type GameWithResults = Game & {
699
- result: FinalResult[];
700
- };
701
-
702
- type AddExtraRulesCommand = {
703
- game: GameState;
704
- extraRules: string[];
705
- };
706
-
714
+ declare class NotFoundError extends DomainException {
715
+ constructor(message: string, code?: ErrorCodeType, context?: Record<string, unknown>);
716
+ }
707
717
  /**
708
- * Input for a single player: either a name string (human) or an object with bot config.
718
+ * Error thrown when validation fails.
709
719
  */
710
- type PlayerInput = string | {
711
- name: string;
712
- bot?: {
713
- strategyName: string;
714
- };
715
- };
716
- type AddPlayersCommand = {
717
- game: GameState;
718
- players: PlayerInput[];
719
- };
720
-
721
- type CalculateScoreCommand = {
722
- kingdom: Kingdom;
723
- };
724
-
720
+ declare class ValidationError extends DomainException {
721
+ constructor(message: string, code: ErrorCodeType, context?: Record<string, unknown>);
722
+ }
725
723
  /**
726
- * Severity level for a validation issue.
727
- * - "error": The state is corrupted and cannot be used safely.
728
- * - "warning": The state is suspicious but may still work.
724
+ * Error thrown when undo is requested but no previous state exists.
729
725
  */
730
- type ValidationSeverity = "error" | "warning";
726
+ declare class UndoError extends DomainException {
727
+ constructor(message?: string, context?: Record<string, unknown>);
728
+ }
731
729
  /**
732
- * Describes a single validation issue found in a game state.
730
+ * Error thrown when redo is requested but no future state exists.
733
731
  */
734
- type ValidationIssue = {
735
- /** JSON-path-like location of the issue (e.g. "players[0].kingdom") */
736
- path: string;
737
- /** Machine-readable code identifying the kind of issue */
738
- code: string;
739
- /** Human-readable description of the issue */
740
- message: string;
741
- /** Severity level */
742
- severity: ValidationSeverity;
743
- };
732
+ declare class RedoError extends DomainException {
733
+ constructor(message?: string, context?: Record<string, unknown>);
734
+ }
744
735
 
745
736
  /**
746
- * Command to check if a domino can be placed anywhere on a kingdom.
737
+ * Translation keys for internationalization support.
738
+ * Use these keys instead of hardcoded strings for translatable content.
747
739
  */
748
- type CanPlaceDominoCommand = {
749
- /** The kingdom grid to check */
750
- kingdom: Kingdom;
751
- /** The domino to check placement for */
752
- domino: Domino;
753
- /** Maximum kingdom size (default: 5, use 7 for Mighty Duel) */
754
- maxKingdomSize?: number;
755
- };
756
-
757
- type ChooseDominoCommand = {
758
- game: GameState;
759
- dominoPick: number;
760
- lordId: string;
761
- };
762
-
763
- type ConstructBuildingCommand = {
764
- game: GameWithNextAction;
765
- lordId: string;
766
- buildingId: number;
767
- position: Position;
768
- };
769
-
770
- type CreateGameCommand = {
771
- mode: string;
772
- seed?: string;
773
- };
774
-
775
- type DiscardDominoCommand = {
776
- game: GameState;
777
- lordId: string;
778
- };
779
-
780
- type GetDynastyResultCommand = {
781
- games: GameWithResults[];
782
- };
783
-
784
- type GetExtraRulesCommand = {
785
- mode: GameMode["name"];
786
- players: number;
787
- };
788
-
789
- type GetModesCommand = Record<string, never>;
790
-
791
- type GetResultCommand = {
792
- game: GameState;
793
- };
794
-
795
- /**
796
- * Command to get all valid placements for a domino on a kingdom.
797
- */
798
- type GetValidPlacementsCommand = {
799
- /** The kingdom grid to check placements on */
800
- kingdom: Kingdom;
801
- /** The domino to find placements for */
802
- domino: Domino;
803
- /** Maximum kingdom size (default: 5, use 7 for Mighty Duel) */
804
- maxKingdomSize?: number;
805
- };
806
-
807
- type PlaceDominoCommand = {
808
- game: GameState;
809
- lordId: string;
810
- position: Position;
811
- rotation: Rotation;
812
- };
813
-
814
- type PlaceFireTokenCommand = {
815
- game: GameWithNextAction;
816
- lordId: string;
817
- position: Position;
818
- };
819
-
820
- type PlaceGiantCommand = {
821
- game: GameWithNextAction;
822
- lordId: string;
823
- position: Position;
824
- };
825
-
826
- type PlaceKnightCommand = {
827
- game: GameWithNextAction;
828
- lordId: string;
829
- position: Position;
830
- };
831
-
832
- type RecruitCavemanCommand = {
833
- game: GameWithNextAction;
834
- lordId: string;
835
- /** ID of the caveman tile to recruit */
836
- cavemanId: number;
837
- /** Position on the kingdom to place the caveman */
838
- position: Position;
839
- /** Positions of resources to spend as payment */
840
- resourcePositions: Position[];
841
- };
842
-
843
- type SendGiantCommand = {
844
- game: GameWithNextAction;
845
- lordId: string;
846
- /** Index of the giant to send from the current player's giants array */
847
- giantIndex: number;
848
- /** ID of the opponent player to receive the giant */
849
- targetPlayerId: string;
850
- /** Position on the opponent's kingdom where the giant will cover a crown */
851
- targetCrownPosition: Position;
852
- };
853
-
854
- /**
855
- * Command to serialize a game state to JSON.
856
- */
857
- type SerializeGameCommand = {
858
- /** The game state to serialize */
859
- game: GameState;
740
+ declare const TranslationKey: {
741
+ readonly ERROR_INVALID_STEP: "error.invalidStep";
742
+ readonly ERROR_STEP_EXECUTION_FAILED: "error.stepExecutionFailed";
743
+ readonly ERROR_ACTION_EXECUTION_FAILED: "error.actionExecutionFailed";
744
+ readonly ERROR_LORD_NOT_FOUND: "error.lordNotFound";
745
+ readonly ERROR_PLAYER_NOT_FOUND: "error.playerNotFound";
746
+ readonly ERROR_DOMINO_NOT_FOUND: "error.dominoNotFound";
747
+ readonly ERROR_MODE_NOT_FOUND: "error.modeNotFound";
748
+ readonly ERROR_INVALID_PLACEMENT: "error.invalidPlacement";
749
+ readonly ERROR_PLACEMENT_NOT_EMPTY: "error.placementNotEmpty";
750
+ readonly ERROR_PLACEMENT_NOT_ADJACENT: "error.placementNotAdjacent";
751
+ readonly ERROR_PLACEMENT_INVALID_TERRAIN: "error.placementInvalidTerrain";
752
+ readonly ERROR_PLACEMENT_OUT_OF_BOUNDS: "error.placementOutOfBounds";
753
+ readonly ERROR_PLACEMENT_EXCEEDS_KINGDOM_SIZE: "error.placementExceedsKingdomSize";
754
+ readonly ERROR_NOT_YOUR_TURN: "error.notYourTurn";
755
+ readonly ERROR_CANNOT_PICK: "error.cannotPick";
756
+ readonly ERROR_CANNOT_PLACE: "error.cannotPlace";
757
+ readonly ERROR_DOMINO_ALREADY_PICKED: "error.dominoAlreadyPicked";
758
+ readonly ERROR_INVALID_PLAYER_COUNT: "error.invalidPlayerCount";
759
+ readonly ERROR_INVALID_PLAYER_NAME: "error.invalidPlayerName";
760
+ readonly ERROR_EXTRA_RULE_NOT_AVAILABLE: "error.extraRuleNotAvailable";
761
+ readonly ERROR_UNDO_UNAVAILABLE: "error.undoUnavailable";
762
+ readonly ERROR_REDO_UNAVAILABLE: "error.redoUnavailable";
763
+ readonly MODE_CLASSIC: "mode.classic";
764
+ readonly MODE_QUEENDOMINO: "mode.queenDomino";
765
+ readonly MODE_ORIGINS_DISCOVERY: "mode.originsDiscovery";
766
+ readonly MODE_ORIGINS_TOTEM: "mode.originsTotem";
767
+ readonly MODE_ORIGINS_TRIBE: "mode.originsTribe";
768
+ readonly MODE_AGE_OF_GIANTS: "mode.ageOfGiants";
769
+ readonly MODE_AGE_OF_GIANTS_QUEENDOMINO: "mode.ageOfGiantsQueenDomino";
770
+ readonly RULE_MIDDLE_KINGDOM: "rule.middleKingdom";
771
+ readonly RULE_HARMONY: "rule.harmony";
772
+ readonly RULE_MIGHTY_DUEL: "rule.mightyDuel";
773
+ readonly RULE_DYNASTY: "rule.dynasty";
774
+ readonly RULE_EMPIRE_OF_FIRE: "rule.empireOfFire";
775
+ readonly RULE_HOMO_HABILIS: "rule.homoHabilis";
776
+ readonly RULE_NEOLITHIC: "rule.neolithic";
777
+ readonly GROUND_CASTLE: "ground.castle";
778
+ readonly GROUND_WHEAT: "ground.wheat";
779
+ readonly GROUND_FOREST: "ground.forest";
780
+ readonly GROUND_SEA: "ground.sea";
781
+ readonly GROUND_PLAIN: "ground.plain";
782
+ readonly GROUND_SWAMP: "ground.swamp";
783
+ readonly GROUND_MINE: "ground.mine";
784
+ readonly GROUND_GRASSLAND: "ground.grassland";
785
+ readonly GROUND_LAKE: "ground.lake";
786
+ readonly GROUND_JUNGLE: "ground.jungle";
787
+ readonly GROUND_QUARRY: "ground.quarry";
788
+ readonly GROUND_DESERT: "ground.desert";
789
+ readonly GROUND_VOLCANO: "ground.volcano";
790
+ readonly RESOURCE_MAMMOTH: "resource.mammoth";
791
+ readonly RESOURCE_FISH: "resource.fish";
792
+ readonly RESOURCE_MUSHROOM: "resource.mushroom";
793
+ readonly RESOURCE_FLINT: "resource.flint";
794
+ readonly CAVEMAN_HUNTER_GATHERER: "caveman.hunterGatherer";
795
+ readonly CAVEMAN_WARRIOR: "caveman.warrior";
796
+ readonly CAVEMAN_MAMMOTH_HUNTER: "caveman.mammothHunter";
797
+ readonly CAVEMAN_FISHERMAN: "caveman.fisherman";
798
+ readonly CAVEMAN_MUSHROOM_PICKER: "caveman.mushroomPicker";
799
+ readonly CAVEMAN_FLINT_COLLECTOR: "caveman.flintCollector";
800
+ readonly CAVEMAN_TRAPPER: "caveman.trapper";
801
+ readonly CAVEMAN_GATHERER: "caveman.gatherer";
802
+ readonly CAVEMAN_FIRE_LADY: "caveman.fireLady";
803
+ readonly CAVEMAN_WARRIOR_SMALL: "caveman.warriorSmall";
804
+ readonly CAVEMAN_WARRIOR_AMAZON: "caveman.warriorAmazon";
805
+ readonly CAVEMAN_WARRIOR_OAFISH: "caveman.warriorOafish";
806
+ readonly PHASE_ADD_PLAYERS: "phase.addPlayers";
807
+ readonly PHASE_OPTIONS: "phase.options";
808
+ readonly PHASE_START: "phase.start";
809
+ readonly PHASE_RESULT: "phase.result";
810
+ readonly ACTION_PICK_DOMINO: "action.pickDomino";
811
+ readonly ACTION_PLACE_DOMINO: "action.placeDomino";
812
+ readonly ACTION_PASS: "action.pass";
813
+ readonly ACTION_PLACE_KNIGHT: "action.placeKnight";
814
+ readonly ACTION_CONSTRUCT_BUILDING: "action.constructBuilding";
815
+ readonly ACTION_USE_DRAGON: "action.useDragon";
816
+ readonly ACTION_SKIP_OPTIONAL_ACTION: "action.skipOptionalAction";
817
+ readonly ACTION_PLACE_GIANT: "action.placeGiant";
818
+ readonly ACTION_SEND_GIANT: "action.sendGiant";
819
+ readonly ACTION_PLACE_FIRE_TOKEN: "action.placeFireToken";
820
+ readonly ACTION_RECRUIT_CAVEMAN: "action.recruitCaveman";
821
+ readonly ERROR_CANNOT_PLACE_KNIGHT: "error.cannotPlaceKnight";
822
+ readonly ERROR_MAX_KNIGHTS_REACHED: "error.maxKnightsReached";
823
+ readonly ERROR_KNIGHT_TERRITORY_OCCUPIED: "error.knightTerritoryOccupied";
824
+ readonly ERROR_CANNOT_AFFORD_BUILDING: "error.cannotAffordBuilding";
825
+ readonly ERROR_NO_CONSTRUCTION_SQUARE: "error.noConstructionSquare";
826
+ readonly ERROR_DRAGON_UNAVAILABLE: "error.dragonUnavailable";
827
+ readonly ERROR_QUEEN_HOLDER_CANNOT_USE_DRAGON: "error.queenHolderCannotUseDragon";
828
+ readonly ERROR_BUILDING_NOT_FOUND: "error.buildingNotFound";
829
+ readonly ERROR_INVALID_OPTIONAL_ACTION: "error.invalidOptionalAction";
830
+ readonly ERROR_GIANT_POOL_EMPTY: "error.giantPoolEmpty";
831
+ readonly ERROR_GIANT_NOT_FOUND: "error.giantNotFound";
832
+ readonly ERROR_INVALID_GIANT_PLACEMENT: "error.invalidGiantPlacement";
833
+ readonly ERROR_NOT_AOG_MODE: "error.notAogMode";
834
+ readonly ERROR_FIRE_TOKEN_POOL_EMPTY: "error.fireTokenPoolEmpty";
835
+ readonly ERROR_FIRE_TOKEN_INVALID_POSITION: "error.fireTokenInvalidPosition";
836
+ readonly ERROR_NO_PENDING_FIRE_TOKEN: "error.noPendingFireToken";
837
+ readonly ERROR_CANNOT_RECRUIT_CAVEMAN: "error.cannotRecruitCaveman";
838
+ readonly ERROR_INSUFFICIENT_RESOURCES: "error.insufficientResources";
839
+ readonly ERROR_INVALID_CAVEMAN_PLACEMENT: "error.invalidCavemanPlacement";
840
+ readonly ERROR_CAVEMAN_NOT_FOUND: "error.cavemanNotFound";
841
+ readonly ERROR_NOT_TRIBE_MODE: "error.notTribeMode";
842
+ readonly ERROR_INVALID_PLAYER_ID: "error.invalidPlayerId";
843
+ readonly SCORE_QUEEN: "score.queen";
844
+ readonly SCORE_BUILDING: "score.building";
845
+ readonly SCORE_TOTEM_MAMMOTH: "score.totemMammoth";
846
+ readonly SCORE_TOTEM_FISH: "score.totemFish";
847
+ readonly SCORE_TOTEM_MUSHROOM: "score.totemMushroom";
848
+ readonly SCORE_TOTEM_FLINT: "score.totemFlint";
849
+ readonly SCORE_RESOURCES: "score.resources";
850
+ readonly SCORE_WARRIORS: "score.warriors";
851
+ readonly SCORE_FIRE: "score.fire";
852
+ readonly QUEST_LOCAL_TRADE: "quest.localTrade";
853
+ readonly QUEST_KINGDOM_BORDERS: "quest.kingdomBorders";
854
+ readonly QUEST_HARMONY: "quest.harmony";
855
+ readonly QUEST_MIDDLE_KINGDOM: "quest.middleKingdom";
856
+ readonly QUEST_LOST_CORNER: "quest.lostCorner";
857
+ readonly QUEST_MEGALOMANIA: "quest.megalomania";
858
+ readonly QUEST_AUSTERE_KING: "quest.austereKing";
860
859
  };
860
+ type TranslationKeyType = (typeof TranslationKey)[keyof typeof TranslationKey];
861
861
  /**
862
- * Command to deserialize a game state from JSON.
862
+ * Default English translations.
863
+ * Also available as `englishTranslations` / `translations.en`.
863
864
  */
864
- type DeserializeGameCommand = {
865
- /** The JSON string to deserialize */
866
- json: string;
867
- };
868
-
869
- type SkipOptionalActionCommand = {
870
- game: GameWithNextAction;
871
- lordId: string;
872
- };
873
-
874
- type StartGameCommand = {
875
- game: GameState;
876
- };
877
-
878
- type UseDragonCommand = {
879
- game: GameWithNextAction;
880
- lordId: string;
881
- buildingId: number;
882
- };
883
-
884
- type DynastyResult = {
885
- playerId: string;
886
- playerName: string;
887
- totalPoints: number;
888
- gamesPoints: number[];
889
- position: number;
890
- };
891
-
865
+ declare const defaultTranslations: Record<TranslationKeyType, string>;
866
+ /** English translations (alias of `defaultTranslations`). */
867
+ declare const englishTranslations: Record<TranslationKeyType, string>;
892
868
  /**
893
- * Represents a valid placement position with rotation.
869
+ * French translations.
894
870
  */
895
- type ValidPlacement = {
896
- position: Position;
897
- rotation: Rotation;
898
- };
899
-
871
+ declare const frenchTranslations: Record<TranslationKeyType, string>;
872
+ /** Locales shipped with the engine. */
873
+ declare const supportedLocales: readonly ["en", "fr"];
874
+ /** A locale shipped with the engine. */
875
+ type SupportedLocale = (typeof supportedLocales)[number];
900
876
  /**
901
- * The main interface for interacting with the Kingdomino game engine.
902
- *
903
- * This interface provides all methods needed to manage a complete game lifecycle,
904
- * from creation to scoring. Each method follows a command pattern and returns
905
- * an updated game state.
877
+ * Built-in translation dictionaries, indexed by locale.
878
+ * Every dictionary contains every `TranslationKey`.
906
879
  *
907
880
  * @example
908
881
  * ```typescript
909
- * import { createGameEngine, isGameWithNextAction } from '@pompidup/kingdomino-engine';
910
- *
911
- * const engine = createGameEngine({});
912
- * let game = engine.createGame({ mode: 'Classic' });
913
- * game = engine.addPlayers({ game, players: ['Alice', 'Bob'] });
914
- * game = engine.startGame({ game });
915
- *
916
- * while (isGameWithNextAction(game)) {
917
- * // Handle player actions...
918
- * }
919
- *
920
- * const results = engine.getResults({ game });
882
+ * const translator = createTranslator(localeTranslations.fr);
921
883
  * ```
922
884
  */
923
- type GameEngine = {
924
- /**
925
- * Retrieves all available game modes.
926
- * @param command - Empty command object
927
- * @returns Array of available game modes (e.g., 'Classic')
928
- */
929
- getModes: (command: GetModesCommand) => GameMode[];
930
- /**
931
- * Retrieves available extra rules for a specific mode and player count.
932
- * @param command - Contains mode name and number of players
933
- * @returns Array of applicable extra rules
934
- */
935
- getExtraRules: (command: GetExtraRulesCommand) => ExtraRule[];
936
- /**
937
- * Creates a new game instance with the specified mode.
938
- * This is the first step in setting up a game.
939
- * @param command - Contains the game mode name
940
- * @returns Game state with next step 'addPlayers'
941
- */
942
- createGame: (command: CreateGameCommand) => GameWithNextStep;
943
- /**
944
- * Adds players to the game and initializes their kingdoms.
945
- * Each player receives an empty kingdom with a castle at the center.
946
- * @param command - Contains game state and array of player names (1-4 players)
947
- * @returns Game state with next step 'options' or 'start'
948
- * @throws Error if player count is invalid or names are too short
949
- */
950
- addPlayers: (command: AddPlayersCommand) => GameWithNextStep;
951
- /**
952
- * Adds optional extra rules to modify gameplay.
953
- * @param command - Contains game state and array of extra rule names
954
- * @returns Game state with next step 'start'
955
- */
956
- addExtraRules: (command: AddExtraRulesCommand) => GameWithNextStep;
957
- /**
958
- * Starts the game, initializing lords, shuffling dominoes, and revealing the first set.
959
- * @param command - Contains the game state
960
- * @returns Game state with first player action required
961
- */
962
- startGame: (command: StartGameCommand) => GameWithNextAction;
963
- /**
964
- * Allows a lord to choose a domino from the currently revealed set.
965
- * The chosen domino is reserved for that lord to place later.
966
- * @param command - Contains game state, lord ID, and domino number to pick
967
- * @returns Updated game state with next action
968
- */
969
- chooseDomino: (command: ChooseDominoCommand) => GameWithNextAction;
970
- /**
971
- * Places a domino on the lord's kingdom at the specified position and rotation.
972
- * The domino must be adjacent to an existing tile and match terrain types.
973
- * @param command - Contains game state, lord ID, position {x, y}, and rotation (0, 90, 180, 270)
974
- * @returns Updated game state (may transition to 'result' step if game ends)
975
- * @throws Error if placement is invalid
976
- */
977
- placeDomino: (command: PlaceDominoCommand) => GameState;
978
- /**
979
- * Discards a domino when no valid placement exists.
980
- * The lord forfeits placing the domino this turn.
981
- * @param command - Contains game state and lord ID
982
- * @returns Updated game state with next action
983
- */
984
- discardDomino: (command: DiscardDominoCommand) => GameState;
985
- /**
986
- * Calculates and returns final results after the game ends.
987
- * Applies extra rule bonuses and ranks players.
988
- * @param command - Contains the final game state
989
- * @returns Game state with complete results including positions
990
- */
991
- getResults: (command: GetResultCommand) => GameWithResults;
992
- /**
993
- * Calculates the score for a single kingdom.
994
- * Useful for showing live scores during gameplay.
995
- * @param command - Contains the kingdom grid to score
996
- * @returns Score with points, largest property size, and total crowns
997
- */
998
- calculateScore: (command: CalculateScoreCommand) => Score;
999
- /**
1000
- * Finds all valid placements for a domino on a kingdom.
1001
- * Useful for AI players or showing available moves to human players.
1002
- * @param command - Contains kingdom and domino to check
1003
- * @returns Array of valid position/rotation combinations
1004
- */
1005
- getValidPlacements: (command: GetValidPlacementsCommand) => ValidPlacement[];
1006
- /**
1007
- * Checks if a domino can be placed anywhere on a kingdom.
1008
- * Quick check before deciding to discard.
1009
- * @param command - Contains kingdom and domino to check
1010
- * @returns True if at least one valid placement exists
1011
- */
1012
- canPlaceDomino: (command: CanPlaceDominoCommand) => boolean;
1013
- /**
1014
- * Serializes the game state to a JSON string for persistence.
1015
- * @param command - Contains the game state to serialize
1016
- * @returns JSON string representation of the game
1017
- */
1018
- serialize: (command: SerializeGameCommand) => string;
1019
- /**
1020
- * Deserializes a JSON string back to a game state.
1021
- * @param command - Contains the JSON string to parse
1022
- * @returns The restored game state
1023
- * @throws Error if the JSON is invalid or incompatible
1024
- */
1025
- deserialize: (command: DeserializeGameCommand) => GameState;
1026
- /**
1027
- * Calculates dynasty results from multiple completed games.
1028
- * Sum total points across games, highest total wins.
1029
- * @param command - Contains array of completed games with results
1030
- * @returns Ranked dynasty results per player
1031
- */
1032
- getDynastyResults: (command: GetDynastyResultCommand) => DynastyResult[];
1033
- /**
1034
- * Places a giant on a crown in the lord's kingdom (Age of Giants).
1035
- * Mandatory after placing a giant domino (A-F). The giant covers the crown for scoring.
1036
- * @param command - Contains game state, lord ID, and crown position
1037
- * @returns Updated game state with next action
1038
- */
1039
- placeGiant: (command: PlaceGiantCommand) => GameState;
1040
- /**
1041
- * Sends a giant from the lord's kingdom to an opponent's kingdom (Age of Giants).
1042
- * Optional after placing a footprint domino (49-54). The opponent chooses which crown is covered.
1043
- * @param command - Contains game state, lord ID, giant index, target player ID, and target crown position
1044
- * @returns Updated game state with next action
1045
- */
1046
- sendGiant: (command: SendGiantCommand) => GameState;
885
+ declare const localeTranslations: Record<SupportedLocale, Record<TranslationKeyType, string>>;
886
+ /**
887
+ * Maps every terrain type (`Ground`) to its translation key.
888
+ */
889
+ declare const groundToTranslationKey: Record<Ground, TranslationKeyType>;
890
+ /**
891
+ * Maps every player action (`PlayerActions`) to its translation key.
892
+ */
893
+ declare const playerActionToTranslationKey: Record<PlayerActions, TranslationKeyType>;
894
+ /**
895
+ * Maps every Origins resource type to its translation key.
896
+ */
897
+ declare const resourceToTranslationKey: Record<ResourceType, TranslationKeyType>;
898
+ /**
899
+ * Maps every caveman tile kind (`CavemanKind`) to its translation key.
900
+ */
901
+ declare const cavemanKindToTranslationKey: Record<CavemanKind, TranslationKeyType>;
902
+ /**
903
+ * Maps every hunter-gatherer type to its translation key.
904
+ */
905
+ declare const hunterGathererKindToTranslationKey: Record<HunterGathererKind, TranslationKeyType>;
906
+ /**
907
+ * Maps every warrior type to its translation key.
908
+ */
909
+ declare const warriorTypeToTranslationKey: Record<WarriorType, TranslationKeyType>;
910
+ /**
911
+ * Maps game mode names (`GameMode["name"]`, as returned by `getModes`)
912
+ * to their translation key.
913
+ */
914
+ declare const modeNameToTranslationKey: Readonly<Record<string, TranslationKeyType>>;
915
+ /**
916
+ * Maps extra rule names (`ExtraRule["name"]`, as returned by `getExtraRules`)
917
+ * to their translation key.
918
+ */
919
+ declare const extraRuleNameToTranslationKey: Readonly<Record<string, TranslationKeyType>>;
920
+ /**
921
+ * Maps ErrorCode values to their corresponding TranslationKey values.
922
+ * Used by handlers to translate error codes into localized messages.
923
+ */
924
+ declare const errorCodeToTranslationKey: Record<ErrorCodeType, TranslationKeyType>;
925
+ /**
926
+ * Translates an error code to a localized message.
927
+ * Convenience function combining errorCodeToTranslationKey lookup and translation.
928
+ */
929
+ declare const translateErrorCode: (translator: Translator, code: ErrorCodeType, params?: Record<string, string | number>) => string;
930
+ /**
931
+ * Translator interface for custom translations.
932
+ */
933
+ interface Translator {
1047
934
  /**
1048
- * Places a knight on the lord's kingdom at the specified position (Queendomino).
1049
- * @param command - Contains game state, lord ID, and position
1050
- * @returns Updated game state with next action
935
+ * Translate a key to the current language.
936
+ * @param key - The translation key
937
+ * @param params - Optional parameters for interpolation
938
+ * @returns The translated string
1051
939
  */
1052
- placeKnight: (command: PlaceKnightCommand) => GameState;
940
+ t(key: TranslationKeyType, params?: Record<string, string | number>): string;
941
+ }
942
+ /**
943
+ * Creates a translator with the provided translations.
944
+ * Falls back to default English translations for missing keys.
945
+ *
946
+ * @param translations - Custom translations to use
947
+ * @returns A translator instance
948
+ */
949
+ declare const createTranslator: (translations?: Partial<Record<TranslationKeyType, string>>) => Translator;
950
+ /**
951
+ * Default translator using English translations.
952
+ */
953
+ declare const defaultTranslator: Translator;
954
+ /**
955
+ * Maps every Age of Giants quest type to the translation key of its name
956
+ * (used as `label` of `quest` score bonuses).
957
+ */
958
+ declare const questTypeToTranslationKey: Record<QuestType, TranslationKeyType>;
959
+ /**
960
+ * Maps every Origins totem (by resource type) to the translation key of its
961
+ * name (used as `label` of `totem` score bonuses).
962
+ */
963
+ declare const totemToTranslationKey: Record<ResourceType, TranslationKeyType>;
964
+
965
+ /**
966
+ * One scored territory (a group of orthogonally connected squares of the
967
+ * same terrain) of a kingdom. The castle is never part of a territory.
968
+ */
969
+ type ScoreTerritory = {
970
+ /** Terrain shared by every square of the territory */
971
+ terrain: Ground;
972
+ /** Squares of the territory, sorted by row then column */
973
+ cells: Position[];
1053
974
  /**
1054
- * Constructs a building on the lord's kingdom (Queendomino).
1055
- * @param command - Contains game state, lord ID, building ID, and position
1056
- * @returns Updated game state with next action
975
+ * Crowns counted for scoring (Origins: fire symbols). Crowns covered by an
976
+ * Age of Giants giant are not counted.
1057
977
  */
1058
- constructBuilding: (command: ConstructBuildingCommand) => GameState;
978
+ crowns: number;
979
+ /** Crowns of the territory covered by giants (not counted in `crowns`) */
980
+ coveredCrowns: number;
981
+ /** Points scored by the territory: `cells.length * crowns` */
982
+ points: number;
983
+ };
984
+ /**
985
+ * Kinds of bonus that can appear in a score breakdown.
986
+ */
987
+ declare const scoreBonusTypes: {
988
+ /** "The middle Kingdom" optional rule: castle in the middle (+10) */
989
+ readonly middleKingdom: "middleKingdom";
990
+ /** "Empire of Fire" optional rule (Origins): hut in the middle (+10) */
991
+ readonly empireOfFire: "empireOfFire";
992
+ /** "Harmony" optional rule: complete kingdom, no discarded domino (+5) */
993
+ readonly harmony: "harmony";
994
+ /** "Homo Habilis" optional rule (Origins): complete territory (+5) */
995
+ readonly homoHabilis: "homoHabilis";
996
+ /** Queendomino: the Queen counts as an extra crown on the largest territory */
997
+ readonly queen: "queen";
998
+ /** Queendomino: end-game scoring of one constructed building */
999
+ readonly building: "building";
1000
+ /** Age of Giants: one quest tile */
1001
+ readonly quest: "quest";
1002
+ /** Origins Totem: one totem held by the player */
1003
+ readonly totem: "totem";
1004
+ /** Origins Totem: 1 point per wooden resource left in the kingdom */
1005
+ readonly resources: "resources";
1006
+ /** Origins Tribe: one hunter-gatherer */
1007
+ readonly hunterGatherer: "hunterGatherer";
1008
+ /** Origins Tribe: every warrior group */
1009
+ readonly warriors: "warriors";
1010
+ /** Origins: fire tokens placed in the kingdom's territories */
1011
+ readonly fire: "fire";
1012
+ };
1013
+ /** Union of every bonus kind */
1014
+ type ScoreBonusType = ObjectValues<typeof scoreBonusTypes>;
1015
+ /**
1016
+ * One bonus of a score breakdown.
1017
+ */
1018
+ type ScoreBonus = {
1019
+ /** Kind of bonus */
1020
+ type: ScoreBonusType;
1059
1021
  /**
1060
- * Uses the dragon to destroy a building on the builders board (Queendomino).
1061
- * @param command - Contains game state, lord ID, and building ID
1062
- * @returns Updated game state with next action
1022
+ * Translation key of the bonus name (see `TranslationKey`). Translate it
1023
+ * with `translator.t(bonus.label, bonus.labelParams)`.
1063
1024
  */
1064
- useDragon: (command: UseDragonCommand) => GameState;
1025
+ label: TranslationKeyType;
1026
+ /** Interpolation parameters of the label (e.g. the building name) */
1027
+ labelParams?: Record<string, string | number>;
1028
+ /** Points granted by the bonus (may be 0 when its condition is not met) */
1029
+ points: number;
1065
1030
  /**
1066
- * Places a fire token on the lord's kingdom after placing a volcano domino (Origins).
1067
- * @param command - Contains game state, lord ID, and target position
1068
- * @returns Updated game state with next action
1031
+ * `true` while the game is in progress: the bonus is evaluated on the
1032
+ * current state and may still change (or disappear) before the end of the
1033
+ * game. `false` once the game is over.
1069
1034
  */
1070
- placeFireToken: (command: PlaceFireTokenCommand) => GameState;
1035
+ provisional: boolean;
1036
+ /** Building id (`building` bonuses) */
1037
+ buildingId?: number;
1038
+ /** Quest tile id (`quest` bonuses) */
1039
+ questId?: number;
1040
+ /** Quest type (`quest` bonuses) */
1041
+ questType?: QuestType;
1042
+ /** Target terrain of the quest (Local Trade, Kingdom Borders) */
1043
+ terrain?: Ground;
1044
+ /** Resource of the totem (`totem` bonuses) */
1045
+ resource?: ResourceType;
1046
+ /** Caveman tile id (`hunterGatherer` bonuses) */
1047
+ cavemanId?: number;
1048
+ /** Kingdom position the bonus relates to (building, hunter-gatherer) */
1049
+ position?: Position;
1050
+ };
1051
+ /**
1052
+ * Detailed score of one kingdom.
1053
+ * Invariant: `total === territoryPoints + bonusPoints`, where
1054
+ * `territoryPoints` is the sum of the territory points and `bonusPoints`
1055
+ * the sum of the bonus points.
1056
+ */
1057
+ type ScoreBreakdown = {
1058
+ /** Every scored territory of the kingdom */
1059
+ territories: ScoreTerritory[];
1060
+ /** Every bonus applying to the player in the current game mode */
1061
+ bonuses: ScoreBonus[];
1062
+ /** Sum of the territory points */
1063
+ territoryPoints: number;
1064
+ /** Sum of the bonus points */
1065
+ bonusPoints: number;
1066
+ /** Total score: `territoryPoints + bonusPoints` */
1067
+ total: number;
1068
+ };
1069
+ /**
1070
+ * Mode-aware score of one player, as returned by `calculatePlayerScore`.
1071
+ */
1072
+ type PlayerScore = {
1073
+ /** Player's unique identifier */
1074
+ playerId: string;
1075
+ /** Player's display name */
1076
+ playerName: string;
1077
+ /** Total score (same as `breakdown.total`) */
1078
+ points: number;
1079
+ /** Size of the largest territory (tie-breaker) */
1080
+ maxPropertiesSize: number;
1081
+ /** Total crowns counted in the kingdom */
1082
+ totalCrowns: number;
1071
1083
  /**
1072
- * Recruits a caveman from the cave board and places it on the lord's kingdom (Origins).
1073
- * @param command - Contains game state, lord ID, caveman ID, position, and resource positions to spend
1074
- * @returns Updated game state with next action
1084
+ * `true` when the game is over: the score is the final score, identical
1085
+ * to the one returned by `getResults`. `false` during the game: the score
1086
+ * is a live estimate of the current state (end-of-game-only bonuses such
1087
+ * as Harmony are not included yet).
1075
1088
  */
1076
- recruitCaveman: (command: RecruitCavemanCommand) => GameState;
1089
+ final: boolean;
1090
+ /** Detailed score */
1091
+ breakdown: ScoreBreakdown;
1092
+ };
1093
+
1094
+ /**
1095
+ * Represents the complete state of a Kingdomino game.
1096
+ * This is the main aggregate containing all game data.
1097
+ */
1098
+ type Game = {
1099
+ /** Unique identifier for the game */
1100
+ id: string;
1101
+ /** Remaining dominoes in the draw pile */
1102
+ dominoes: Domino[];
1103
+ /** Currently revealed dominoes available for picking */
1104
+ currentDominoes: RevealsDomino[];
1105
+ /** Players participating in the game with their kingdoms */
1106
+ players: Player[];
1107
+ /** Lords (player tokens) used for turn order and domino selection */
1108
+ lords: Lord[];
1109
+ /** Current turn number (1-based) */
1110
+ turn: number;
1111
+ /** Next required action or game step */
1112
+ nextAction: NextAction | NextStep;
1113
+ /** Active rules for this game */
1114
+ rules: SelectedRules;
1115
+ /** Game mode being played */
1116
+ mode: GameMode;
1117
+ /** Optional seed for deterministic shuffle (replays, sharing) */
1118
+ seed?: string;
1119
+ /** Queendomino-specific state (only present in QueenDomino mode) */
1120
+ queendomino?: QueenDominoState;
1121
+ /** Origins-specific state (only present in KingdominoOrigins modes) */
1122
+ origins?: OriginsState;
1123
+ /** Age of Giants-specific state (only present in AgeOfGiants modes) */
1124
+ ageOfGiants?: AgeOfGiantsState;
1125
+ };
1126
+ /**
1127
+ * Available game steps representing phases of game setup and completion.
1128
+ */
1129
+ declare const gameSteps: {
1130
+ /** Waiting for players to be added */
1131
+ readonly addPlayers: "addPlayers";
1132
+ /** Optional rules selection phase */
1133
+ readonly options: "options";
1134
+ /** Ready to start the game */
1135
+ readonly start: "start";
1136
+ /** Game has ended, results available */
1137
+ readonly result: "result";
1138
+ };
1139
+ /** Union type of all possible game steps */
1140
+ type GameSteps = ObjectValues<typeof gameSteps>;
1141
+ /**
1142
+ * Represents a player action that needs to be performed.
1143
+ * Used during active gameplay.
1144
+ */
1145
+ type NextAction = {
1146
+ /** Discriminator for action type */
1147
+ type: "action";
1148
+ /** ID of the lord who must perform the action */
1149
+ nextLord: string;
1150
+ /** The action the lord must perform */
1151
+ nextAction: PlayerActions;
1152
+ };
1153
+ /**
1154
+ * Represents a game phase transition.
1155
+ * Used during setup and game end.
1156
+ */
1157
+ type NextStep = {
1158
+ /** Discriminator for step type */
1159
+ type: "step";
1160
+ /** The current game step/phase */
1161
+ step: GameSteps;
1162
+ };
1163
+ /** Game state where a player action is required */
1164
+ type GameWithNextAction = Game & {
1165
+ nextAction: NextAction;
1166
+ };
1167
+ /** Game state where a phase transition is occurring */
1168
+ type GameWithNextStep = Game & {
1169
+ nextAction: NextStep;
1170
+ };
1171
+ /** Union of all possible game states */
1172
+ type GameState = GameWithNextAction | GameWithNextStep;
1173
+ /** Result wrapper for game state operations */
1174
+ type GameStateResult = Result<GameState>;
1175
+ /**
1176
+ * Type guard to check if the game requires a player action.
1177
+ * @param game - The game state to check
1178
+ * @returns True if the game is waiting for a player action
1179
+ */
1180
+ declare const isGameWithNextAction: (game: GameWithNextAction | GameWithNextStep) => game is GameWithNextAction;
1181
+ /**
1182
+ * Type guard to check if the game is in a phase transition.
1183
+ * @param game - The game state to check
1184
+ * @returns True if the game is transitioning between phases
1185
+ */
1186
+ declare const isGameWithNextStep: (game: GameWithNextAction | GameWithNextStep) => game is GameWithNextStep;
1187
+ /**
1188
+ * Represents the score calculation for a kingdom.
1189
+ */
1190
+ type Score = {
1191
+ /** Total points earned */
1192
+ points: number;
1193
+ /** Size of the largest contiguous property */
1194
+ maxPropertiesSize: number;
1195
+ /** Total number of crowns in the kingdom */
1196
+ totalCrowns: number;
1197
+ };
1198
+ /**
1199
+ * Score result for a specific player.
1200
+ */
1201
+ type ScoreResult = {
1202
+ /** Player's unique identifier */
1203
+ playerId: string;
1204
+ /** Player's display name */
1205
+ playerName: string;
1206
+ /** Detailed score breakdown */
1207
+ details: Score;
1077
1208
  /**
1078
- * Skips the current optional action (Queendomino/Origins).
1079
- * @param command - Contains game state and lord ID
1080
- * @returns Updated game state with next action
1209
+ * Territory-by-territory and bonus-by-bonus score, computed by the same
1210
+ * function as `calculatePlayerScore` (`breakdown.total === details.points`).
1211
+ * Always set by `getResults`.
1081
1212
  */
1082
- skipOptionalAction: (command: SkipOptionalActionCommand) => GameState;
1213
+ breakdown?: ScoreBreakdown;
1083
1214
  };
1084
-
1085
1215
  /**
1086
- * Error codes for domain-level errors.
1087
- * These codes can be used for i18n and programmatic error handling.
1216
+ * Final result including ranking position.
1088
1217
  */
1089
- declare const ErrorCode: {
1090
- readonly INVALID_STEP: "INVALID_STEP";
1091
- readonly STEP_EXECUTION_FAILED: "STEP_EXECUTION_FAILED";
1092
- readonly ACTION_EXECUTION_FAILED: "ACTION_EXECUTION_FAILED";
1093
- readonly LORD_NOT_FOUND: "LORD_NOT_FOUND";
1094
- readonly PLAYER_NOT_FOUND: "PLAYER_NOT_FOUND";
1095
- readonly DOMINO_NOT_FOUND: "DOMINO_NOT_FOUND";
1096
- readonly MODE_NOT_FOUND: "MODE_NOT_FOUND";
1097
- readonly INVALID_PLAYER_COUNT: "INVALID_PLAYER_COUNT";
1098
- readonly INVALID_PLAYER_NAME: "INVALID_PLAYER_NAME";
1099
- readonly INVALID_PLACEMENT: "INVALID_PLACEMENT";
1100
- readonly PLACEMENT_NOT_EMPTY: "PLACEMENT_NOT_EMPTY";
1101
- readonly PLACEMENT_NOT_ADJACENT: "PLACEMENT_NOT_ADJACENT";
1102
- readonly PLACEMENT_INVALID_TERRAIN: "PLACEMENT_INVALID_TERRAIN";
1103
- readonly PLACEMENT_OUT_OF_BOUNDS: "PLACEMENT_OUT_OF_BOUNDS";
1104
- readonly PLACEMENT_EXCEEDS_KINGDOM_SIZE: "PLACEMENT_EXCEEDS_KINGDOM_SIZE";
1105
- readonly NOT_YOUR_TURN: "NOT_YOUR_TURN";
1106
- readonly CANNOT_PICK: "CANNOT_PICK";
1107
- readonly CANNOT_PLACE: "CANNOT_PLACE";
1108
- readonly DOMINO_ALREADY_PICKED: "DOMINO_ALREADY_PICKED";
1109
- readonly UNDO_UNAVAILABLE: "UNDO_UNAVAILABLE";
1110
- readonly REDO_UNAVAILABLE: "REDO_UNAVAILABLE";
1111
- readonly CANNOT_PLACE_KNIGHT: "CANNOT_PLACE_KNIGHT";
1112
- readonly MAX_KNIGHTS_REACHED: "MAX_KNIGHTS_REACHED";
1113
- readonly KNIGHT_TERRITORY_OCCUPIED: "KNIGHT_TERRITORY_OCCUPIED";
1114
- readonly CANNOT_AFFORD_BUILDING: "CANNOT_AFFORD_BUILDING";
1115
- readonly NO_CONSTRUCTION_SQUARE: "NO_CONSTRUCTION_SQUARE";
1116
- readonly DRAGON_UNAVAILABLE: "DRAGON_UNAVAILABLE";
1117
- readonly QUEEN_HOLDER_CANNOT_USE_DRAGON: "QUEEN_HOLDER_CANNOT_USE_DRAGON";
1118
- readonly BUILDING_NOT_FOUND: "BUILDING_NOT_FOUND";
1119
- readonly INVALID_OPTIONAL_ACTION: "INVALID_OPTIONAL_ACTION";
1120
- readonly GIANT_POOL_EMPTY: "GIANT_POOL_EMPTY";
1121
- readonly GIANT_NOT_FOUND: "GIANT_NOT_FOUND";
1122
- readonly INVALID_GIANT_PLACEMENT: "INVALID_GIANT_PLACEMENT";
1123
- readonly NOT_AOG_MODE: "NOT_AOG_MODE";
1124
- readonly FIRE_TOKEN_POOL_EMPTY: "FIRE_TOKEN_POOL_EMPTY";
1125
- readonly FIRE_TOKEN_INVALID_POSITION: "FIRE_TOKEN_INVALID_POSITION";
1126
- readonly NO_PENDING_FIRE_TOKEN: "NO_PENDING_FIRE_TOKEN";
1127
- readonly CANNOT_RECRUIT_CAVEMAN: "CANNOT_RECRUIT_CAVEMAN";
1128
- readonly INSUFFICIENT_RESOURCES: "INSUFFICIENT_RESOURCES";
1129
- readonly INVALID_CAVEMAN_PLACEMENT: "INVALID_CAVEMAN_PLACEMENT";
1130
- readonly CAVEMAN_NOT_FOUND: "CAVEMAN_NOT_FOUND";
1131
- readonly NOT_TRIBE_MODE: "NOT_TRIBE_MODE";
1218
+ type FinalResult = ScoreResult & {
1219
+ /** Player's final position (1 = winner) */
1220
+ position: number;
1221
+ };
1222
+ /** Game state with final results calculated */
1223
+ type GameWithResults = Game & {
1224
+ result: FinalResult[];
1132
1225
  };
1133
- type ErrorCodeType = (typeof ErrorCode)[keyof typeof ErrorCode];
1134
1226
  /**
1135
- * Structured domain error with code and context.
1227
+ * Type guard to check if final results have been computed for the game
1228
+ * (i.e. the state returned by `getResults`).
1229
+ * @param game - The game state to check
1230
+ * @returns True if the game carries a `result` array
1136
1231
  */
1137
- interface DomainError {
1138
- /** Error code for programmatic handling */
1139
- code: ErrorCodeType;
1140
- /** Human-readable error message */
1141
- message: string;
1142
- /** Optional context data for debugging */
1143
- context?: Record<string, unknown>;
1144
- }
1232
+ declare const isGameWithResults: (game: Game | GameWithResults) => game is GameWithResults;
1233
+
1234
+ type AddExtraRulesCommand = {
1235
+ game: GameState;
1236
+ extraRules: string[];
1237
+ };
1238
+
1145
1239
  /**
1146
- * Creates a domain error object.
1240
+ * Input for a single player: either a name string (human) or an object with
1241
+ * an optional stable `id` and an optional bot config.
1242
+ *
1243
+ * Pass a stable `id` (e.g. your own user/account id) when the same person plays
1244
+ * several games, such as a Dynasty: ids are otherwise regenerated by every
1245
+ * `addPlayers` call. Ids must be non-empty and unique within one call.
1147
1246
  */
1148
- declare const createDomainError: (code: ErrorCodeType, message: string, context?: Record<string, unknown>) => DomainError;
1247
+ type PlayerInput = string | {
1248
+ id?: string;
1249
+ name: string;
1250
+ bot?: {
1251
+ strategyName: string;
1252
+ };
1253
+ };
1254
+ type AddPlayersCommand = {
1255
+ game: GameState;
1256
+ players: PlayerInput[];
1257
+ };
1258
+
1259
+ type CalculatePlayerScoreCommand = {
1260
+ /**
1261
+ * Game state, at any stage: in progress (live score) or over (final
1262
+ * score, also accepts the state returned by `getResults`).
1263
+ */
1264
+ game: Game;
1265
+ /** Id of the player to score */
1266
+ playerId: string;
1267
+ };
1268
+
1269
+ type CalculateScoreCommand = {
1270
+ kingdom: Kingdom;
1271
+ };
1272
+
1149
1273
  /**
1150
- * Base class for domain errors thrown by handlers.
1274
+ * Severity level for a validation issue.
1275
+ * - "error": The state is corrupted and cannot be used safely.
1276
+ * - "warning": The state is suspicious but may still work.
1151
1277
  */
1152
- declare class DomainException extends Error {
1153
- readonly code: ErrorCodeType;
1154
- readonly context?: Record<string, unknown>;
1155
- constructor(error: DomainError);
1156
- }
1278
+ type ValidationSeverity = "error" | "warning";
1157
1279
  /**
1158
- * Error thrown when the game is in an invalid step for the requested action.
1280
+ * Describes a single validation issue found in a game state.
1159
1281
  */
1160
- declare class InvalidStepError extends DomainException {
1161
- constructor(message: string, context?: Record<string, unknown>);
1162
- }
1282
+ type ValidationIssue = {
1283
+ /** JSON-path-like location of the issue (e.g. "players[0].kingdom") */
1284
+ path: string;
1285
+ /** Machine-readable code identifying the kind of issue */
1286
+ code: string;
1287
+ /** Human-readable description of the issue */
1288
+ message: string;
1289
+ /** Severity level */
1290
+ severity: ValidationSeverity;
1291
+ };
1292
+
1163
1293
  /**
1164
- * Error thrown when a step execution fails.
1294
+ * Command to check if a domino can be placed anywhere on a kingdom.
1165
1295
  */
1166
- declare class StepExecutionError extends DomainException {
1167
- constructor(message: string, code?: ErrorCodeType, context?: Record<string, unknown>);
1168
- }
1296
+ type CanPlaceDominoCommand = {
1297
+ /** The kingdom grid to check */
1298
+ kingdom: Kingdom;
1299
+ /** The domino to check placement for */
1300
+ domino: Domino;
1301
+ /** Maximum kingdom size (default: 5, use 7 for Mighty Duel) */
1302
+ maxKingdomSize?: number;
1303
+ };
1304
+
1305
+ type ChooseDominoCommand = {
1306
+ game: GameState;
1307
+ dominoPick: number;
1308
+ lordId: string;
1309
+ };
1310
+
1311
+ type ConstructBuildingCommand = {
1312
+ game: GameWithNextAction;
1313
+ lordId: string;
1314
+ buildingId: number;
1315
+ position: Position;
1316
+ };
1317
+
1318
+ type CreateGameCommand = {
1319
+ mode: string;
1320
+ seed?: string;
1321
+ };
1322
+
1323
+ type DiscardDominoCommand = {
1324
+ game: GameState;
1325
+ lordId: string;
1326
+ };
1327
+
1169
1328
  /**
1170
- * Error thrown when an action execution fails.
1329
+ * How Dynasty results group a player across games.
1330
+ * - `"playerId"` (default): by `playerId`. Pass stable ids to `addPlayers`
1331
+ * (`players: [{ id, name }]`) so the same person keeps one id across games.
1332
+ * - `"playerName"`: by player name, for games whose players were added
1333
+ * without stable ids (ids are regenerated by every `addPlayers` call).
1171
1334
  */
1172
- declare class ActionExecutionError extends DomainException {
1173
- constructor(message: string, code?: ErrorCodeType, context?: Record<string, unknown>);
1174
- }
1335
+ type DynastyGroupBy = "playerId" | "playerName";
1336
+ type GetDynastyResultCommand = {
1337
+ games: GameWithResults[];
1338
+ /** Grouping key across games. Defaults to `"playerId"`. */
1339
+ groupBy?: DynastyGroupBy;
1340
+ };
1341
+
1342
+ type GetExtraRulesCommand = {
1343
+ mode: GameMode["name"];
1344
+ players: number;
1345
+ };
1346
+
1347
+ type GetModesCommand = Record<string, never>;
1348
+
1349
+ type GetResultCommand = {
1350
+ game: GameState;
1351
+ };
1352
+
1175
1353
  /**
1176
- * Error thrown when a required entity is not found.
1354
+ * Command to query the valid targets of the current extension action
1355
+ * (Queendomino, Origins, Age of Giants).
1356
+ *
1357
+ * Any game state is accepted: the query answers empty (`[]`, or `false` for
1358
+ * `canSkipOptionalAction`) when the action is not the one the game waits for,
1359
+ * when it does not exist in the game mode, or when `lordId` is not the lord
1360
+ * that must play.
1177
1361
  */
1178
- declare class NotFoundError extends DomainException {
1179
- constructor(message: string, code?: ErrorCodeType, context?: Record<string, unknown>);
1180
- }
1362
+ type GetValidActionTargetsCommand = {
1363
+ /** The current game state */
1364
+ game: GameState;
1365
+ /** Lord to check. Defaults to the lord that must play (`game.nextAction.nextLord`) */
1366
+ lordId?: string;
1367
+ };
1368
+ /** Command for `getValidFireTokenPositions` (Origins `placeFireToken`). */
1369
+ type GetValidFireTokenPositionsCommand = GetValidActionTargetsCommand;
1370
+ /** Command for `getValidKnightPositions` (Queendomino `placeKnight`). */
1371
+ type GetValidKnightPositionsCommand = GetValidActionTargetsCommand;
1372
+ /** Command for `getValidBuildingPositions` (Queendomino `constructBuilding`). */
1373
+ type GetValidBuildingPositionsCommand = GetValidActionTargetsCommand;
1374
+ /** Command for `getAffordableBuildings` (Queendomino `constructBuilding`). */
1375
+ type GetAffordableBuildingsCommand = GetValidActionTargetsCommand;
1376
+ /** Command for `getValidCavemanPlacements` (Origins Tribe `recruitCaveman`). */
1377
+ type GetValidCavemanPlacementsCommand = GetValidActionTargetsCommand;
1378
+ /** Command for `getValidGiantPositions` (Age of Giants `placeGiant`). */
1379
+ type GetValidGiantPositionsCommand = GetValidActionTargetsCommand;
1380
+ /** Command for `getValidGiantTargets` (Age of Giants `sendGiant`). */
1381
+ type GetValidGiantTargetsCommand = GetValidActionTargetsCommand;
1382
+ /** Command for `canSkipOptionalAction` (`skipOptionalAction`). */
1383
+ type CanSkipOptionalActionCommand = GetValidActionTargetsCommand;
1384
+
1181
1385
  /**
1182
- * Error thrown when validation fails.
1386
+ * Command to get all valid placements for a domino on a kingdom.
1183
1387
  */
1184
- declare class ValidationError extends DomainException {
1185
- constructor(message: string, code: ErrorCodeType, context?: Record<string, unknown>);
1186
- }
1388
+ type GetValidPlacementsCommand = {
1389
+ /** The kingdom grid to check placements on */
1390
+ kingdom: Kingdom;
1391
+ /** The domino to find placements for */
1392
+ domino: Domino;
1393
+ /** Maximum kingdom size (default: 5, use 7 for Mighty Duel) */
1394
+ maxKingdomSize?: number;
1395
+ };
1396
+
1397
+ type PlaceDominoCommand = {
1398
+ game: GameState;
1399
+ lordId: string;
1400
+ position: Position;
1401
+ rotation: Rotation;
1402
+ };
1403
+
1404
+ type PlaceFireTokenCommand = {
1405
+ game: GameWithNextAction;
1406
+ lordId: string;
1407
+ position: Position;
1408
+ };
1409
+
1410
+ type PlaceGiantCommand = {
1411
+ game: GameWithNextAction;
1412
+ lordId: string;
1413
+ position: Position;
1414
+ };
1415
+
1416
+ type PlaceKnightCommand = {
1417
+ game: GameWithNextAction;
1418
+ lordId: string;
1419
+ position: Position;
1420
+ };
1421
+
1422
+ type RecruitCavemanCommand = {
1423
+ game: GameWithNextAction;
1424
+ lordId: string;
1425
+ /** ID of the caveman tile to recruit */
1426
+ cavemanId: number;
1427
+ /** Position on the kingdom to place the caveman */
1428
+ position: Position;
1429
+ /** Positions of resources to spend as payment */
1430
+ resourcePositions: Position[];
1431
+ };
1432
+
1433
+ type SendGiantCommand = {
1434
+ game: GameWithNextAction;
1435
+ lordId: string;
1436
+ /** Index of the giant to send from the current player's giants array */
1437
+ giantIndex: number;
1438
+ /** ID of the opponent player to receive the giant */
1439
+ targetPlayerId: string;
1440
+ /**
1441
+ * Crown of the opponent's kingdom the giant will cover. Per the official
1442
+ * rules the targeted opponent chooses it: omit it and the engine places the
1443
+ * giant automatically when the opponent has a single free crown, or asks
1444
+ * the opponent with a `placeGiant` action otherwise. When provided, it must
1445
+ * be the crown chosen by the opponent.
1446
+ */
1447
+ targetCrownPosition?: Position;
1448
+ };
1449
+
1187
1450
  /**
1188
- * Error thrown when undo is requested but no previous state exists.
1451
+ * Command to serialize a game state to JSON.
1189
1452
  */
1190
- declare class UndoError extends DomainException {
1191
- constructor(message?: string, context?: Record<string, unknown>);
1192
- }
1453
+ type SerializeGameCommand = {
1454
+ /** The game state to serialize */
1455
+ game: GameState;
1456
+ };
1193
1457
  /**
1194
- * Error thrown when redo is requested but no future state exists.
1458
+ * Command to deserialize a game state from JSON.
1195
1459
  */
1196
- declare class RedoError extends DomainException {
1197
- constructor(message?: string, context?: Record<string, unknown>);
1198
- }
1460
+ type DeserializeGameCommand = {
1461
+ /** The JSON string to deserialize */
1462
+ json: string;
1463
+ };
1464
+
1465
+ type SkipOptionalActionCommand = {
1466
+ game: GameWithNextAction;
1467
+ lordId: string;
1468
+ };
1469
+
1470
+ type StartGameCommand = {
1471
+ game: GameState;
1472
+ };
1473
+
1474
+ type UseDragonCommand = {
1475
+ game: GameWithNextAction;
1476
+ lordId: string;
1477
+ buildingId: number;
1478
+ };
1479
+
1480
+ type DynastyResult = {
1481
+ /**
1482
+ * Player id. With `groupBy: "playerName"`, the id the player had in the
1483
+ * first game where that name appears.
1484
+ */
1485
+ playerId: string;
1486
+ playerName: string;
1487
+ totalPoints: number;
1488
+ gamesPoints: number[];
1489
+ position: number;
1490
+ };
1491
+
1492
+ type ShuffleMethod = <T>(array: T[]) => T[];
1199
1493
 
1200
1494
  /**
1201
- * Translation keys for internationalization support.
1202
- * Use these keys instead of hardcoded strings for translatable content.
1495
+ * Price of recruiting one caveman of the cave board.
1203
1496
  */
1204
- declare const TranslationKey: {
1205
- readonly ERROR_INVALID_STEP: "error.invalidStep";
1206
- readonly ERROR_STEP_EXECUTION_FAILED: "error.stepExecutionFailed";
1207
- readonly ERROR_ACTION_EXECUTION_FAILED: "error.actionExecutionFailed";
1208
- readonly ERROR_LORD_NOT_FOUND: "error.lordNotFound";
1209
- readonly ERROR_PLAYER_NOT_FOUND: "error.playerNotFound";
1210
- readonly ERROR_DOMINO_NOT_FOUND: "error.dominoNotFound";
1211
- readonly ERROR_MODE_NOT_FOUND: "error.modeNotFound";
1212
- readonly ERROR_INVALID_PLACEMENT: "error.invalidPlacement";
1213
- readonly ERROR_PLACEMENT_NOT_EMPTY: "error.placementNotEmpty";
1214
- readonly ERROR_PLACEMENT_NOT_ADJACENT: "error.placementNotAdjacent";
1215
- readonly ERROR_PLACEMENT_INVALID_TERRAIN: "error.placementInvalidTerrain";
1216
- readonly ERROR_PLACEMENT_OUT_OF_BOUNDS: "error.placementOutOfBounds";
1217
- readonly ERROR_PLACEMENT_EXCEEDS_KINGDOM_SIZE: "error.placementExceedsKingdomSize";
1218
- readonly ERROR_NOT_YOUR_TURN: "error.notYourTurn";
1219
- readonly ERROR_CANNOT_PICK: "error.cannotPick";
1220
- readonly ERROR_CANNOT_PLACE: "error.cannotPlace";
1221
- readonly ERROR_DOMINO_ALREADY_PICKED: "error.dominoAlreadyPicked";
1222
- readonly ERROR_INVALID_PLAYER_COUNT: "error.invalidPlayerCount";
1223
- readonly ERROR_INVALID_PLAYER_NAME: "error.invalidPlayerName";
1224
- readonly ERROR_UNDO_UNAVAILABLE: "error.undoUnavailable";
1225
- readonly ERROR_REDO_UNAVAILABLE: "error.redoUnavailable";
1226
- readonly MODE_CLASSIC: "mode.classic";
1227
- readonly RULE_MIDDLE_KINGDOM: "rule.middleKingdom";
1228
- readonly RULE_HARMONY: "rule.harmony";
1229
- readonly GROUND_CASTLE: "ground.castle";
1230
- readonly GROUND_WHEAT: "ground.wheat";
1231
- readonly GROUND_FOREST: "ground.forest";
1232
- readonly GROUND_SEA: "ground.sea";
1233
- readonly GROUND_PLAIN: "ground.plain";
1234
- readonly GROUND_SWAMP: "ground.swamp";
1235
- readonly GROUND_MINE: "ground.mine";
1236
- readonly PHASE_ADD_PLAYERS: "phase.addPlayers";
1237
- readonly PHASE_OPTIONS: "phase.options";
1238
- readonly PHASE_START: "phase.start";
1239
- readonly PHASE_RESULT: "phase.result";
1240
- readonly ACTION_PICK_DOMINO: "action.pickDomino";
1241
- readonly ACTION_PLACE_DOMINO: "action.placeDomino";
1242
- readonly ACTION_PASS: "action.pass";
1243
- readonly ERROR_CANNOT_PLACE_KNIGHT: "error.cannotPlaceKnight";
1244
- readonly ERROR_MAX_KNIGHTS_REACHED: "error.maxKnightsReached";
1245
- readonly ERROR_KNIGHT_TERRITORY_OCCUPIED: "error.knightTerritoryOccupied";
1246
- readonly ERROR_CANNOT_AFFORD_BUILDING: "error.cannotAffordBuilding";
1247
- readonly ERROR_NO_CONSTRUCTION_SQUARE: "error.noConstructionSquare";
1248
- readonly ERROR_DRAGON_UNAVAILABLE: "error.dragonUnavailable";
1249
- readonly ERROR_QUEEN_HOLDER_CANNOT_USE_DRAGON: "error.queenHolderCannotUseDragon";
1250
- readonly ERROR_BUILDING_NOT_FOUND: "error.buildingNotFound";
1251
- readonly ERROR_INVALID_OPTIONAL_ACTION: "error.invalidOptionalAction";
1252
- readonly ERROR_GIANT_POOL_EMPTY: "error.giantPoolEmpty";
1253
- readonly ERROR_GIANT_NOT_FOUND: "error.giantNotFound";
1254
- readonly ERROR_INVALID_GIANT_PLACEMENT: "error.invalidGiantPlacement";
1255
- readonly ERROR_NOT_AOG_MODE: "error.notAogMode";
1256
- readonly ERROR_FIRE_TOKEN_POOL_EMPTY: "error.fireTokenPoolEmpty";
1257
- readonly ERROR_FIRE_TOKEN_INVALID_POSITION: "error.fireTokenInvalidPosition";
1258
- readonly ERROR_NO_PENDING_FIRE_TOKEN: "error.noPendingFireToken";
1259
- readonly ERROR_CANNOT_RECRUIT_CAVEMAN: "error.cannotRecruitCaveman";
1260
- readonly ERROR_INSUFFICIENT_RESOURCES: "error.insufficientResources";
1261
- readonly ERROR_INVALID_CAVEMAN_PLACEMENT: "error.invalidCavemanPlacement";
1262
- readonly ERROR_CAVEMAN_NOT_FOUND: "error.cavemanNotFound";
1263
- readonly ERROR_NOT_TRIBE_MODE: "error.notTribeMode";
1497
+ type RecruitmentCost = {
1498
+ /** Where the caveman is taken from */
1499
+ source: "visible" | "drawPile";
1500
+ /** Number of resources to spend */
1501
+ resourceCount: number;
1502
+ /** Number of different resource types among the spent resources */
1503
+ differentTypes: number;
1264
1504
  };
1265
- type TranslationKeyType = (typeof TranslationKey)[keyof typeof TranslationKey];
1505
+
1266
1506
  /**
1267
- * Default English translations.
1507
+ * A caveman of the cave board that can be recruited now, with every valid way
1508
+ * to pay for it (Origins Tribe).
1268
1509
  */
1269
- declare const defaultTranslations: Record<TranslationKeyType, string>;
1510
+ type CavemanRecruitmentOption = {
1511
+ /** Caveman to pass as `cavemanId` to `recruitCaveman` */
1512
+ cavemanId: number;
1513
+ /** Visible caveman (2 different resources) or draw pile (4 different) */
1514
+ source: RecruitmentCost["source"];
1515
+ /** Every valid `resourcePositions` payment for this caveman */
1516
+ resourceCombinations: Position[][];
1517
+ };
1270
1518
  /**
1271
- * Maps ErrorCode values to their corresponding TranslationKey values.
1272
- * Used by handlers to translate error codes into localized messages.
1519
+ * Valid `recruitCaveman` arguments: any option, any of its resource
1520
+ * combinations and any position can be combined. Both lists are empty when
1521
+ * no recruitment is possible.
1273
1522
  */
1274
- declare const errorCodeToTranslationKey: Record<ErrorCodeType, TranslationKeyType>;
1523
+ type ValidCavemanPlacements = {
1524
+ /** Recruitable cavemen with their valid payments */
1525
+ options: CavemanRecruitmentOption[];
1526
+ /** Squares where the recruited caveman can be placed */
1527
+ positions: Position[];
1528
+ };
1275
1529
  /**
1276
- * Translates an error code to a localized message.
1277
- * Convenience function combining errorCodeToTranslationKey lookup and translation.
1530
+ * Valid `sendGiant` arguments: any giant index can be sent to any target
1531
+ * player. Both lists are empty when no giant can be sent. The crown covered is
1532
+ * chosen by the targeted opponent (see `getValidGiantPositions`).
1278
1533
  */
1279
- declare const translateErrorCode: (translator: Translator, code: ErrorCodeType, params?: Record<string, string | number>) => string;
1534
+ type ValidGiantTargets = {
1535
+ /** Indexes in the sender's `giants` array that can be sent */
1536
+ giantIndexes: number[];
1537
+ /** Opponents that can receive a giant */
1538
+ targetPlayerIds: string[];
1539
+ };
1540
+ /** Signature shared by the valid-target queries. */
1541
+ type ValidActionTargetsUseCase<T> = (game: GameState, lordId?: string) => T;
1542
+
1280
1543
  /**
1281
- * Translator interface for custom translations.
1544
+ * Represents a valid placement position with rotation.
1282
1545
  */
1283
- interface Translator {
1546
+ type ValidPlacement = {
1547
+ position: Position;
1548
+ rotation: Rotation;
1549
+ };
1550
+
1551
+ /**
1552
+ * The main interface for interacting with the Kingdomino game engine.
1553
+ *
1554
+ * This interface provides all methods needed to manage a complete game lifecycle,
1555
+ * from creation to scoring. Each method follows a command pattern and returns
1556
+ * an updated game state.
1557
+ *
1558
+ * @example
1559
+ * ```typescript
1560
+ * import { createGameEngine, isGameWithNextAction } from '@pompidup/kingdomino-engine';
1561
+ *
1562
+ * const engine = createGameEngine({});
1563
+ * let game = engine.createGame({ mode: 'Classic' });
1564
+ * game = engine.addPlayers({ game, players: ['Alice', 'Bob'] });
1565
+ * game = engine.startGame({ game });
1566
+ *
1567
+ * while (isGameWithNextAction(game)) {
1568
+ * // Handle player actions...
1569
+ * }
1570
+ *
1571
+ * const results = engine.getResults({ game });
1572
+ * ```
1573
+ */
1574
+ type GameEngine = {
1575
+ /**
1576
+ * Retrieves all available game modes.
1577
+ * @param command - Empty command object
1578
+ * @returns Array of available game modes (e.g., 'Classic')
1579
+ */
1580
+ getModes: (command: GetModesCommand) => GameMode[];
1581
+ /**
1582
+ * Retrieves available extra rules for a specific mode and player count.
1583
+ * @param command - Contains mode name and number of players
1584
+ * @returns Array of applicable extra rules
1585
+ */
1586
+ getExtraRules: (command: GetExtraRulesCommand) => ExtraRule[];
1587
+ /**
1588
+ * Creates a new game instance with the specified mode.
1589
+ * This is the first step in setting up a game.
1590
+ * @param command - Contains the game mode name
1591
+ * @returns Game state with next step 'addPlayers'
1592
+ */
1593
+ createGame: (command: CreateGameCommand) => GameWithNextStep;
1284
1594
  /**
1285
- * Translate a key to the current language.
1286
- * @param key - The translation key
1287
- * @param params - Optional parameters for interpolation
1288
- * @returns The translated string
1595
+ * Adds players to the game and initializes their kingdoms.
1596
+ * Each player receives an empty kingdom with a castle at the center.
1597
+ * @param command - Contains game state and array of player names (1-4 players)
1598
+ * @returns Game state with next step 'options' or 'start'
1599
+ * @throws Error if player count is invalid or names are too short
1289
1600
  */
1290
- t(key: TranslationKeyType, params?: Record<string, string | number>): string;
1291
- }
1292
- /**
1293
- * Creates a translator with the provided translations.
1294
- * Falls back to default English translations for missing keys.
1295
- *
1296
- * @param translations - Custom translations to use
1297
- * @returns A translator instance
1298
- */
1299
- declare const createTranslator: (translations?: Partial<Record<TranslationKeyType, string>>) => Translator;
1300
- /**
1301
- * Default translator using English translations.
1302
- */
1303
- declare const defaultTranslator: Translator;
1601
+ addPlayers: (command: AddPlayersCommand) => GameWithNextStep;
1602
+ /**
1603
+ * Adds optional extra rules to modify gameplay.
1604
+ * @param command - Contains game state and array of extra rule names
1605
+ * @returns Game state with next step 'start'
1606
+ */
1607
+ addExtraRules: (command: AddExtraRulesCommand) => GameWithNextStep;
1608
+ /**
1609
+ * Starts the game, initializing lords, shuffling dominoes, and revealing the first set.
1610
+ * @param command - Contains the game state
1611
+ * @returns Game state with first player action required
1612
+ */
1613
+ startGame: (command: StartGameCommand) => GameWithNextAction;
1614
+ /**
1615
+ * Allows a lord to choose a domino from the currently revealed set.
1616
+ * The chosen domino is reserved for that lord to place later.
1617
+ * @param command - Contains game state, lord ID, and domino number to pick
1618
+ * @returns Updated game state with next action
1619
+ */
1620
+ chooseDomino: (command: ChooseDominoCommand) => GameWithNextAction;
1621
+ /**
1622
+ * Places a domino on the lord's kingdom at the specified position and rotation.
1623
+ * The domino must be adjacent to an existing tile and match terrain types.
1624
+ * @param command - Contains game state, lord ID, position {x, y}, and rotation (0, 90, 180, 270)
1625
+ * @returns Updated game state (may transition to 'result' step if game ends)
1626
+ * @throws Error if placement is invalid
1627
+ */
1628
+ placeDomino: (command: PlaceDominoCommand) => GameState;
1629
+ /**
1630
+ * Discards a domino when no valid placement exists.
1631
+ * The lord forfeits placing the domino this turn.
1632
+ * @param command - Contains game state and lord ID
1633
+ * @returns Updated game state with next action
1634
+ */
1635
+ discardDomino: (command: DiscardDominoCommand) => GameState;
1636
+ /**
1637
+ * Calculates and returns final results after the game ends.
1638
+ * Applies extra rule bonuses and ranks players.
1639
+ * @param command - Contains the final game state
1640
+ * @returns Game state with complete results including positions
1641
+ */
1642
+ getResults: (command: GetResultCommand) => GameWithResults;
1643
+ /**
1644
+ * Calculates the score for a single kingdom.
1645
+ * Useful for showing live scores during gameplay.
1646
+ * @param command - Contains the kingdom grid to score
1647
+ * @returns Score with points, largest property size, and total crowns
1648
+ */
1649
+ calculateScore: (command: CalculateScoreCommand) => Score;
1650
+ /**
1651
+ * Finds all valid placements for a domino on a kingdom.
1652
+ * Useful for AI players or showing available moves to human players.
1653
+ * @param command - Contains kingdom and domino to check
1654
+ * @returns Array of valid position/rotation combinations
1655
+ */
1656
+ getValidPlacements: (command: GetValidPlacementsCommand) => ValidPlacement[];
1657
+ /**
1658
+ * Checks if a domino can be placed anywhere on a kingdom.
1659
+ * Quick check before deciding to discard.
1660
+ * @param command - Contains kingdom and domino to check
1661
+ * @returns True if at least one valid placement exists
1662
+ */
1663
+ canPlaceDomino: (command: CanPlaceDominoCommand) => boolean;
1664
+ /**
1665
+ * Serializes the game state to a JSON string for persistence.
1666
+ * @param command - Contains the game state to serialize
1667
+ * @returns JSON string representation of the game
1668
+ */
1669
+ serialize: (command: SerializeGameCommand) => string;
1670
+ /**
1671
+ * Deserializes a JSON string back to a game state.
1672
+ * @param command - Contains the JSON string to parse
1673
+ * @returns The restored game state
1674
+ * @throws Error if the JSON is invalid or incompatible
1675
+ */
1676
+ deserialize: (command: DeserializeGameCommand) => GameState;
1677
+ /**
1678
+ * Calculates dynasty results from multiple completed games.
1679
+ * Sum total points across games, highest total wins.
1680
+ * @param command - Contains array of completed games with results
1681
+ * @returns Ranked dynasty results per player
1682
+ */
1683
+ getDynastyResults: (command: GetDynastyResultCommand) => DynastyResult[];
1684
+ /**
1685
+ * Places a giant on a crown in the lord's kingdom (Age of Giants).
1686
+ * Mandatory after placing a giant domino (A-F). The giant covers the crown for scoring.
1687
+ * @param command - Contains game state, lord ID, and crown position
1688
+ * @returns Updated game state with next action
1689
+ */
1690
+ placeGiant: (command: PlaceGiantCommand) => GameState;
1691
+ /**
1692
+ * Sends a giant from the lord's kingdom to an opponent's kingdom (Age of Giants).
1693
+ * Optional after placing a footprint domino (49-54). The opponent chooses which crown is covered.
1694
+ * @param command - Contains game state, lord ID, giant index, target player ID, and target crown position
1695
+ * @returns Updated game state with next action
1696
+ */
1697
+ sendGiant: (command: SendGiantCommand) => GameState;
1698
+ /**
1699
+ * Places a knight on the lord's kingdom at the specified position (Queendomino).
1700
+ * @param command - Contains game state, lord ID, and position
1701
+ * @returns Updated game state with next action
1702
+ */
1703
+ placeKnight: (command: PlaceKnightCommand) => GameState;
1704
+ /**
1705
+ * Constructs a building on the lord's kingdom (Queendomino).
1706
+ * @param command - Contains game state, lord ID, building ID, and position
1707
+ * @returns Updated game state with next action
1708
+ */
1709
+ constructBuilding: (command: ConstructBuildingCommand) => GameState;
1710
+ /**
1711
+ * Uses the dragon to destroy a building on the builders board (Queendomino).
1712
+ * @param command - Contains game state, lord ID, and building ID
1713
+ * @returns Updated game state with next action
1714
+ */
1715
+ useDragon: (command: UseDragonCommand) => GameState;
1716
+ /**
1717
+ * Places a fire token on the lord's kingdom after placing a volcano domino (Origins).
1718
+ * @param command - Contains game state, lord ID, and target position
1719
+ * @returns Updated game state with next action
1720
+ */
1721
+ placeFireToken: (command: PlaceFireTokenCommand) => GameState;
1722
+ /**
1723
+ * Recruits a caveman from the cave board and places it on the lord's kingdom (Origins).
1724
+ * @param command - Contains game state, lord ID, caveman ID, position, and resource positions to spend
1725
+ * @returns Updated game state with next action
1726
+ */
1727
+ recruitCaveman: (command: RecruitCavemanCommand) => GameState;
1728
+ /**
1729
+ * Skips the current optional action (Queendomino/Origins).
1730
+ * @param command - Contains game state and lord ID
1731
+ * @returns Updated game state with next action
1732
+ */
1733
+ skipOptionalAction: (command: SkipOptionalActionCommand) => GameState;
1734
+ /**
1735
+ * Squares where `placeFireToken` accepts the pending fire token (Origins).
1736
+ * @param command - Contains game state and optional lord ID
1737
+ * @returns Valid positions, or [] when no fire token is pending for the lord
1738
+ */
1739
+ getValidFireTokenPositions: (command: GetValidFireTokenPositionsCommand) => Position[];
1740
+ /**
1741
+ * Squares where `placeKnight` accepts a knight: squares of the domino just
1742
+ * placed, within the knight limit, one knight per territory (Queendomino).
1743
+ * @param command - Contains game state and optional lord ID
1744
+ * @returns Valid positions, or [] when placeKnight is not the current action
1745
+ */
1746
+ getValidKnightPositions: (command: GetValidKnightPositionsCommand) => Position[];
1747
+ /**
1748
+ * Construction squares where `constructBuilding` succeeds with any building
1749
+ * of `getAffordableBuildings` (Queendomino).
1750
+ * @param command - Contains game state and optional lord ID
1751
+ * @returns Valid positions, or [] when no construction is possible
1752
+ */
1753
+ getValidBuildingPositions: (command: GetValidBuildingPositionsCommand) => Position[];
1754
+ /**
1755
+ * Buildings of the Builders Board the lord can pay for, buildable on any
1756
+ * square of `getValidBuildingPositions` (Queendomino).
1757
+ * @param command - Contains game state and optional lord ID
1758
+ * @returns Buildings to pass to constructBuilding, or [] when none applies
1759
+ */
1760
+ getAffordableBuildings: (command: GetAffordableBuildingsCommand) => BuildingTile[];
1761
+ /**
1762
+ * Valid `recruitCaveman` arguments: recruitable cavemen with every valid
1763
+ * resource payment, and the squares for the caveman (Origins Tribe).
1764
+ * @param command - Contains game state and optional lord ID
1765
+ * @returns Options and positions (any combination is valid), both empty when none applies
1766
+ */
1767
+ getValidCavemanPlacements: (command: GetValidCavemanPlacementsCommand) => ValidCavemanPlacements;
1768
+ /**
1769
+ * Crowns where `placeGiant` accepts the giant (Age of Giants), including a
1770
+ * giant sent by an opponent: the targeted player's own crowns.
1771
+ * @param command - Contains game state and optional lord ID
1772
+ * @returns Valid crown positions, or [] when placeGiant is not the current action
1773
+ */
1774
+ getValidGiantPositions: (command: GetValidGiantPositionsCommand) => Position[];
1775
+ /**
1776
+ * Valid `sendGiant` arguments: the lord's giants that can be sent and the
1777
+ * opponents that can receive one (Age of Giants). The opponent chooses the crown.
1778
+ * @param command - Contains game state and optional lord ID
1779
+ * @returns Giant indexes and target player IDs (any pair is valid), both empty when none applies
1780
+ */
1781
+ getValidGiantTargets: (command: GetValidGiantTargetsCommand) => ValidGiantTargets;
1782
+ /**
1783
+ * Whether `skipOptionalAction` can skip the current action for the lord
1784
+ * (false for placeGiant, which is mandatory).
1785
+ * @param command - Contains game state and optional lord ID
1786
+ * @returns True if the current action can be skipped
1787
+ */
1788
+ canSkipOptionalAction: (command: CanSkipOptionalActionCommand) => boolean;
1789
+ /**
1790
+ * Calculates the mode-aware score of one player, with its breakdown
1791
+ * (territories and every bonus of the game mode: Queen, buildings, fire
1792
+ * tokens, resources, totems, cavemen, quests, optional rules...).
1793
+ *
1794
+ * During the game the score is live (`final: false`): it reflects the
1795
+ * current state and end-of-game-only bonuses (Harmony, Homo Habilis,
1796
+ * Harmony quest) are not included yet. Once the game is over it is final
1797
+ * and identical to the score returned by `getResults`.
1798
+ * @param command - Contains the game state (any stage) and the player id
1799
+ * @returns The player's score with its breakdown
1800
+ * @throws NotFoundError if the player is not in the game
1801
+ */
1802
+ calculatePlayerScore: (command: CalculatePlayerScoreCommand) => PlayerScore;
1803
+ };
1304
1804
 
1305
1805
  type Logger = Readonly<{
1306
1806
  info: (message: string) => void;
1307
1807
  error: (message: string) => void;
1308
1808
  }>;
1309
1809
 
1310
- type ShuffleMethod = <T>(array: T[]) => T[];
1311
-
1312
1810
  type UuidMethod = () => string;
1313
1811
 
1314
1812
  /**
@@ -1439,7 +1937,7 @@ type GameEventCallbacks = {
1439
1937
  game: GameState;
1440
1938
  turn: number;
1441
1939
  }) => void;
1442
- /** Called when the game reaches the result phase */
1940
+ /** Called once when the game reaches the result phase, whatever the action that ended it */
1443
1941
  onGameEnd?: (event: {
1444
1942
  game: GameState;
1445
1943
  }) => void;
@@ -1482,14 +1980,31 @@ type GameEventCallbacks = {
1482
1980
  lordId: string;
1483
1981
  targetPlayerId: string;
1484
1982
  }) => void;
1983
+ /** Called after extra rules are added to the game (setup) */
1984
+ onExtraRulesAdded?: (event: {
1985
+ game: GameWithNextStep;
1986
+ extraRules: string[];
1987
+ }) => void;
1988
+ /** Called after a lord skips an optional action (QueenDomino, Origins, Age of Giants) */
1989
+ onOptionalActionSkipped?: (event: {
1990
+ game: GameState;
1991
+ lordId: string;
1992
+ /** The optional action that was skipped (e.g. `"placeKnight"`, `"sendGiant"`) */
1993
+ skippedAction: PlayerActions;
1994
+ }) => void;
1485
1995
  };
1486
1996
  /**
1487
1997
  * Wraps a GameEngine to emit event callbacks after each state-changing method.
1488
1998
  * Read-only methods (getModes, calculateScore, etc.) are passed through unchanged.
1489
1999
  *
1490
- * Derived events:
1491
- * - `onTurnEnd` + `onTurnStart`: emitted when `chooseDomino` causes a turn increment
1492
- * - `onGameEnd`: emitted when `placeDomino` or `discardDomino` transitions to the "result" step
2000
+ * Derived events are detected centrally, by comparing the game state before
2001
+ * and after every gameplay method (whatever the method that caused it):
2002
+ * - `onTurnEnd` + `onTurnStart`: emitted when the turn number changes
2003
+ * - `onGameEnd`: emitted exactly once, when the game transitions to the
2004
+ * "result" step (placeDomino, discardDomino, skipOptionalAction,
2005
+ * placeFireToken, recruitCaveman, useDragon, placeGiant, sendGiant…)
2006
+ *
2007
+ * The action-specific event is always emitted before the derived ones.
1493
2008
  *
1494
2009
  * @param engine - The base game engine to wrap
1495
2010
  * @param callbacks - Optional event callbacks
@@ -1561,6 +2076,88 @@ type RuleRepository = {
1561
2076
  getAllExtra: () => ExtraRule[];
1562
2077
  };
1563
2078
 
2079
+ /**
2080
+ * Mode-aware evaluation and extension-action decisions of the built-in bots
2081
+ * (Queendomino, Kingdomino Origins, Age of Giants).
2082
+ *
2083
+ * Every decision follows the same recipe, without re-implementing any rule:
2084
+ * 1. enumerate the valid targets of the action (`getValidActionTargets`, whose
2085
+ * targets are guaranteed to be accepted by the action), plus "skip" when
2086
+ * `canSkipOptionalAction` allows it;
2087
+ * 2. simulate each target with the pure use case of the action;
2088
+ * 3. evaluate the resulting state with the mode-aware live score
2089
+ * (`computePlayerScore`) of the deciding player, plus a small value for the
2090
+ * assets the score does not count yet (coins, knights left, Tribe
2091
+ * resources), optionally minus the score of the best opponent;
2092
+ * 4. keep the best target (the first one on ties, "skip" coming first).
2093
+ */
2094
+
2095
+ /**
2096
+ * Common part of the context given to a bot strategy for an extension action.
2097
+ */
2098
+ type ExtensionContext = {
2099
+ game: GameWithNextAction;
2100
+ /** Lord whose action it is (`game.nextAction.nextLord`) */
2101
+ lordId: string;
2102
+ /**
2103
+ * Player who decides: the owner of the lord. For a giant sent by an
2104
+ * opponent (`ageOfGiants.pendingGiant`), the targeted player.
2105
+ */
2106
+ playerId: string;
2107
+ /** Whether returning `null` (skipping the action) is allowed */
2108
+ canSkip: boolean;
2109
+ };
2110
+ /** `placeKnight` (Queendomino): squares where a knight can go. */
2111
+ type KnightContext = ExtensionContext & {
2112
+ validPositions: Position[];
2113
+ };
2114
+ /** `constructBuilding` (Queendomino): any building on any square. */
2115
+ type BuildingContext = ExtensionContext & {
2116
+ validPositions: Position[];
2117
+ affordableBuildings: BuildingTile[];
2118
+ };
2119
+ /** A building to construct and its construction square. */
2120
+ type BuildingChoice = {
2121
+ buildingId: number;
2122
+ position: Position;
2123
+ };
2124
+ /** `useDragon` (Queendomino): buildings of the board the dragon can burn. */
2125
+ type DragonContext = ExtensionContext & {
2126
+ targetBuildings: BuildingTile[];
2127
+ };
2128
+ /** `placeFireToken` (Origins): squares where the pending token can go. */
2129
+ type FireTokenContext = ExtensionContext & {
2130
+ validPositions: Position[];
2131
+ };
2132
+ /** `recruitCaveman` (Origins Tribe): recruitable cavemen and squares. */
2133
+ type CavemanContext = ExtensionContext & {
2134
+ recruitment: ValidCavemanPlacements;
2135
+ };
2136
+ /** A caveman to recruit, where to place it and how to pay for it. */
2137
+ type CavemanChoice = {
2138
+ cavemanId: number;
2139
+ position: Position;
2140
+ resourcePositions: Position[];
2141
+ };
2142
+ /**
2143
+ * `placeGiant` (Age of Giants, mandatory): crowns of the deciding player's
2144
+ * kingdom the giant can cover.
2145
+ */
2146
+ type GiantPositionContext = ExtensionContext & {
2147
+ validPositions: Position[];
2148
+ /** True when the giant was sent by an opponent (`pendingGiant`) */
2149
+ sentByOpponent: boolean;
2150
+ };
2151
+ /** `sendGiant` (Age of Giants): giants that can be sent and their targets. */
2152
+ type SendGiantContext = ExtensionContext & {
2153
+ targets: ValidGiantTargets;
2154
+ };
2155
+ /** A giant to send and the opponent receiving it. */
2156
+ type SendGiantChoice = {
2157
+ giantIndex: number;
2158
+ targetPlayerId: string;
2159
+ };
2160
+
1564
2161
  /**
1565
2162
  * Context provided to a bot strategy when choosing a domino to pick.
1566
2163
  */
@@ -1580,38 +2177,72 @@ type PlaceContext = {
1580
2177
  validPlacements: ValidPlacement[];
1581
2178
  };
1582
2179
  /**
1583
- * A bot strategy that decides which domino to pick and where to place it.
1584
- * Strategies are stateless — all information comes from the context.
2180
+ * A bot strategy that decides which domino to pick and where to place it,
2181
+ * and optionally how to play the extension actions (Queendomino, Origins,
2182
+ * Age of Giants). Strategies are stateless — all information comes from the
2183
+ * context.
2184
+ *
2185
+ * Every extension method is optional: when a strategy does not define one,
2186
+ * `playBotTurn` uses the choice of `greedyStrategy` (best immediate
2187
+ * mode-aware score). An extension method is only called when the action has
2188
+ * at least one valid target, and every target of its context is accepted by
2189
+ * the action. Returning `null` skips the action (for the mandatory
2190
+ * `placeGiant`, `null` falls back to the greedy choice).
1585
2191
  */
1586
2192
  type BotStrategy = {
1587
2193
  chooseDomino: (context: PickContext) => number;
1588
2194
  choosePlacement: (context: PlaceContext) => ValidPlacement | null;
1589
- };
1590
- /**
1591
- * Bot strategy that makes random choices.
1592
- * Picks a random available domino and places it at a random valid position.
2195
+ /** `placeKnight` (Queendomino): a square, or null to skip */
2196
+ chooseKnightPosition?: (context: KnightContext) => Position | null;
2197
+ /** `constructBuilding` (Queendomino): a building and square, or null */
2198
+ chooseBuilding?: (context: BuildingContext) => BuildingChoice | null;
2199
+ /** `useDragon` (Queendomino): id of the building to burn, or null */
2200
+ chooseDragonTarget?: (context: DragonContext) => number | null;
2201
+ /** `placeFireToken` (Origins): a square, or null to skip */
2202
+ chooseFireTokenPosition?: (context: FireTokenContext) => Position | null;
2203
+ /** `recruitCaveman` (Origins Tribe): a recruitment, or null to skip */
2204
+ chooseCavemanRecruitment?: (context: CavemanContext) => CavemanChoice | null;
2205
+ /** `placeGiant` (Age of Giants, mandatory): the crown to cover */
2206
+ chooseGiantPosition?: (context: GiantPositionContext) => Position | null;
2207
+ /** `sendGiant` (Age of Giants): a giant and its target, or null to skip */
2208
+ chooseGiantToSend?: (context: SendGiantContext) => SendGiantChoice | null;
2209
+ };
2210
+ /** The extension-action methods of a strategy. */
2211
+ type BotExtensionChoices = Required<Omit<BotStrategy, "chooseDomino" | "choosePlacement">>;
2212
+ /**
2213
+ * Bot strategy that makes random choices, seeded by the game seed and the
2214
+ * decision point (the same game replayed with the same seed gets the same
2215
+ * decisions). Picks a random available domino, places it at a random valid
2216
+ * position, and plays every extension action on a random valid target, or
2217
+ * skips it ("skip" being one more option when the action is optional).
1593
2218
  */
1594
2219
  declare const randomStrategy: BotStrategy;
1595
2220
  /**
1596
- * Bot strategy that maximizes the immediate score.
1597
- * Evaluates every valid placement and picks the one yielding the highest score.
1598
- * When picking a domino, simulates the best placement for each option.
2221
+ * Bot strategy that maximizes the immediate mode-aware score.
2222
+ * Evaluates every valid placement and picks the one yielding the highest
2223
+ * score (territories plus every bonus of the mode). When picking a domino,
2224
+ * simulates the best placement for each option. Extension actions: the
2225
+ * target with the best own score (taxes, buildings, fire tokens, cavemen,
2226
+ * least costly crown for a giant), or skip when nothing improves it.
1599
2227
  */
1600
- declare const greedyStrategy: BotStrategy;
2228
+ declare const greedyStrategy: BotStrategy & BotExtensionChoices;
1601
2229
  /**
1602
2230
  * Bot strategy with 1-turn lookahead.
1603
- * When making decisions, considers not just the immediate score but also
1604
- * the best possible score achievable in the next turn by peeking at
1605
- * the upcoming domino batch from the draw pile.
2231
+ * When making decisions, considers not just the immediate mode-aware score
2232
+ * but also the best possible score achievable in the next turn by peeking at
2233
+ * the upcoming domino batch from the draw pile. Extension actions: the target
2234
+ * with the best own score minus half the best opponent's score (e.g. sending
2235
+ * a giant to the leader, taking a Totem majority from them).
1606
2236
  */
1607
- declare const advancedStrategy: BotStrategy;
2237
+ declare const advancedStrategy: BotStrategy & BotExtensionChoices;
1608
2238
  /**
1609
2239
  * Bot strategy that searches the game tree with beam-search pruning.
1610
- * Explores all remaining turns, but limits the number of placements
1611
- * evaluated per domino to the top candidates (by greedy score).
1612
- * Since kingdoms are independent, this is a solo optimization (no adversarial search needed).
2240
+ * Explores the next turns, but limits the number of placements evaluated per
2241
+ * domino to the top candidates (by greedy mode-aware score). Since kingdoms
2242
+ * are independent, the domino search is a solo optimization. Extension
2243
+ * actions: the target that maximizes the lead over the best opponent.
1613
2244
  */
1614
- declare const expertStrategy: BotStrategy;
2245
+ declare const expertStrategy: BotStrategy & BotExtensionChoices;
1615
2246
  /**
1616
2247
  * Executes a single bot turn using the given strategy and engine.
1617
2248
  * Reads the current game state to determine the required action,
@@ -1653,28 +2284,52 @@ declare const getStrategy: (name: string, custom?: Record<string, BotStrategy>)
1653
2284
  */
1654
2285
  declare const getStrategyNames: () => string[];
1655
2286
 
2287
+ type CalculatePlayerScoreUseCase = (game: Game, playerId: string) => Result<PlayerScore>;
2288
+
1656
2289
  type ConstructBuildingUseCase = (game: GameWithNextAction, lordId: string, buildingId: number, position: Position) => GameStateResult;
1657
2290
 
2291
+ /**
2292
+ * Current action log format version.
2293
+ *
2294
+ * - `1` (or no `version` field): legacy logs. `createGame` may omit the seed,
2295
+ * player ids and lord ids are not recorded, so a replay regenerates them.
2296
+ * - `2`: the effective seed, the game id, the player ids and the lord ids are
2297
+ * recorded, so a replay reproduces a strictly identical game state.
2298
+ *
2299
+ * Version 1 logs remain replayable.
2300
+ */
2301
+ declare const ACTION_LOG_VERSION = 2;
1658
2302
  /**
1659
2303
  * The types of state-changing actions that can be recorded.
1660
2304
  */
1661
- type ActionType = "createGame" | "addPlayers" | "addExtraRules" | "startGame" | "chooseDomino" | "placeDomino" | "discardDomino" | "placeGiant" | "sendGiant";
2305
+ type ActionType = "createGame" | "addPlayers" | "addExtraRules" | "startGame" | "chooseDomino" | "placeDomino" | "discardDomino" | "placeGiant" | "sendGiant" | "placeKnight" | "constructBuilding" | "useDragon" | "placeFireToken" | "recruitCaveman" | "skipOptionalAction";
1662
2306
  /**
1663
2307
  * Payload types for each action, containing only the command parameters
1664
- * (excluding the game state, which is too large to store).
2308
+ * (excluding the game state, which is too large to store), plus the ids the
2309
+ * engine generated so that a replay reproduces them.
1665
2310
  */
1666
2311
  type ActionPayloadMap = {
1667
2312
  createGame: {
1668
2313
  mode: string;
2314
+ /** Effective seed of the game (generated by the engine when the caller gave none) */
1669
2315
  seed?: string;
2316
+ /** Id the engine generated for the game (restored on replay) */
2317
+ gameId?: string;
1670
2318
  };
2319
+ /**
2320
+ * Players as `{ id, name, bot? }` objects carrying the ids the engine
2321
+ * assigned (legacy logs may contain plain names).
2322
+ */
1671
2323
  addPlayers: {
1672
2324
  players: PlayerInput[];
1673
2325
  };
1674
2326
  addExtraRules: {
1675
2327
  extraRules: string[];
1676
2328
  };
1677
- startGame: Record<string, never>;
2329
+ startGame: {
2330
+ /** Ids of the lords, in `game.lords` order (restored on replay) */
2331
+ lordIds?: string[];
2332
+ };
1678
2333
  chooseDomino: {
1679
2334
  lordId: string;
1680
2335
  dominoPick: number;
@@ -1687,6 +2342,10 @@ type ActionPayloadMap = {
1687
2342
  discardDomino: {
1688
2343
  lordId: string;
1689
2344
  };
2345
+ /**
2346
+ * `lordId` is the lord that played the action: the current lord, or a lord
2347
+ * of the targeted player when it places a giant sent by an opponent.
2348
+ */
1690
2349
  placeGiant: {
1691
2350
  lordId: string;
1692
2351
  position: Position;
@@ -1695,7 +2354,33 @@ type ActionPayloadMap = {
1695
2354
  lordId: string;
1696
2355
  giantIndex: number;
1697
2356
  targetPlayerId: string;
1698
- targetCrownPosition: Position;
2357
+ targetCrownPosition?: Position;
2358
+ };
2359
+ placeKnight: {
2360
+ lordId: string;
2361
+ position: Position;
2362
+ };
2363
+ constructBuilding: {
2364
+ lordId: string;
2365
+ buildingId: number;
2366
+ position: Position;
2367
+ };
2368
+ useDragon: {
2369
+ lordId: string;
2370
+ buildingId: number;
2371
+ };
2372
+ placeFireToken: {
2373
+ lordId: string;
2374
+ position: Position;
2375
+ };
2376
+ recruitCaveman: {
2377
+ lordId: string;
2378
+ cavemanId: number;
2379
+ position: Position;
2380
+ resourcePositions: Position[];
2381
+ };
2382
+ skipOptionalAction: {
2383
+ lordId: string;
1699
2384
  };
1700
2385
  };
1701
2386
  /**
@@ -1716,6 +2401,8 @@ type ActionEntry<T extends ActionType = ActionType> = {
1716
2401
  /**
1717
2402
  * An immutable log of all actions performed during a game.
1718
2403
  * Can be used to replay a game or analyze player decisions.
2404
+ * It is plain JSON: persist it with `JSON.stringify` and read it back with
2405
+ * `JSON.parse`.
1719
2406
  *
1720
2407
  * @example
1721
2408
  * ```typescript
@@ -1728,6 +2415,8 @@ type ActionEntry<T extends ActionType = ActionType> = {
1728
2415
  * ```
1729
2416
  */
1730
2417
  type GameActionLog = {
2418
+ /** Format version (see `ACTION_LOG_VERSION`); absent in legacy logs (version 1) */
2419
+ version?: number;
1731
2420
  /** The game ID this log belongs to */
1732
2421
  gameId: string;
1733
2422
  /** Immutable array of recorded actions */
@@ -1774,12 +2463,18 @@ declare const wrapWithActionLog: (engine: GameEngine) => {
1774
2463
  };
1775
2464
  /**
1776
2465
  * Replays all actions from a log on a fresh engine to reconstruct the final game state.
1777
- * The engine should use the same configuration (especially shuffleMethod) as the original game
1778
- * to guarantee deterministic results when a seed is present.
1779
2466
  *
1780
- * @param engine - A game engine instance (should match original config)
2467
+ * Version 2 logs (see `ACTION_LOG_VERSION`) record the effective seed and every
2468
+ * generated id (game, players, lords), so the replayed state is strictly
2469
+ * identical to the original one, whatever the replay engine's `uuidMethod`
2470
+ * and `shuffleMethod`. Legacy logs (version 1) are still replayed; their ids
2471
+ * are regenerated, and when they lack a seed the replay engine should use the
2472
+ * same configuration (especially shuffleMethod) as the original game.
2473
+ *
2474
+ * @param engine - A game engine instance
1781
2475
  * @param log - The action log to replay
1782
2476
  * @returns The final game state after replaying all actions
2477
+ * @throws Error if the log is empty, does not start with createGame, or has an unsupported version
1783
2478
  */
1784
2479
  declare const replayActions: (engine: GameEngine, log: GameActionLog) => GameState;
1785
2480
 
@@ -1870,7 +2565,7 @@ type PlaceKnightUseCase = (game: GameWithNextAction, lordId: string, position: P
1870
2565
 
1871
2566
  type RecruitCavemanUseCase = (game: GameWithNextAction, lordId: string, cavemanId: number, position: Position, resourcePositions: Position[]) => GameStateResult;
1872
2567
 
1873
- type SendGiantUseCase = (game: GameWithNextAction, lordId: string, giantIndex: number, targetPlayerId: string, targetCrownPosition: Position) => GameStateResult;
2568
+ type SendGiantUseCase = (game: GameWithNextAction, lordId: string, giantIndex: number, targetPlayerId: string, targetCrownPosition?: Position) => GameStateResult;
1874
2569
 
1875
2570
  /**
1876
2571
  * Serializes a game state to a JSON string.
@@ -1918,6 +2613,11 @@ declare const createSavePoint: (game: GameState) => GameSavePoint;
1918
2613
  declare const restoreFromSavePoint: (savePoint: GameSavePoint) => Result<GameState>;
1919
2614
 
1920
2615
  type SkipOptionalActionUseCase = (game: GameWithNextAction, lordId: string) => GameStateResult;
2616
+ /**
2617
+ * Whether `action` is an optional action that `skipOptionalAction` accepts.
2618
+ * `placeDomino`, `pickDomino`, `pass` and `placeGiant` cannot be skipped.
2619
+ */
2620
+ declare const isSkippableAction: (action: PlayerActions) => boolean;
1921
2621
 
1922
2622
  type UseDragonUseCase = (game: GameWithNextAction, lordId: string, buildingId: number) => GameStateResult;
1923
2623
 
@@ -1946,4 +2646,4 @@ type UseDragonUseCase = (game: GameWithNextAction, lordId: string, buildingId: n
1946
2646
  */
1947
2647
  declare const createGameEngine: (config: Partial<EngineConfig>) => GameEngine;
1948
2648
 
1949
- export { type ActionEntry, ActionExecutionError, type ActionPayloadMap, type ActionType, type AddExtraRulesCommand, type AddPlayersCommand, type AgeOfGiantsState, type BasicRules, type BotStrategy, type BuildersBoard, type BuildingSlot, type BuildingTile, type BuildingsRepository, type CalculateScoreCommand, type CanPlaceDominoCommand, type CaveBoard, type CavemanTile, type CavemenRepository, type ChooseDominoCommand, type ConstructBuildingCommand, type ConstructBuildingUseCase, 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 EndGameScoring, type EndGameScoringType, type EngineConfig, ErrorCode, type ErrorCodeType, type ExtraRule, type FinalResult, type FireTokenPool, 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, type HunterGathererKind, type HunterGathererTile, type ImmediateBonus, type ImmediateBonusType, InvalidStepError, type Kingdom, type Knight, type Logger, type Lord, MAX_KINGDOM_SIZE, type ModeRepository, type NextAction, type NextStep, NotFoundError, type ObjectValues, type OriginsState, type OriginsSubMode, type PendingFireToken, type PickContext, type PlaceContext, type PlaceDominoCommand, type PlaceFireTokenCommand, type PlaceFireTokenUseCase, type PlaceGiantCommand, type PlaceGiantUseCase, type PlaceKnightCommand, type PlaceKnightUseCase, type PlacedBuilding, type PlacedCaveman, type PlacedFireToken, type PlacedGiant, type Player, type PlayerActions, type PlayerInput, type PlayerPayload, type Players, type PlayersPayload, type Position, type QueenDominoState, type QuestTile, type QuestTilesRepository, type QuestType, type RecruitCavemanCommand, type RecruitCavemanUseCase, RedoError, type Resource, type ResourceType, type RevealsDomino, type Rotation, type RuleRepository, type Rules, type Score, type ScoreResult, type SelectedRules, type SendGiantCommand, type SendGiantUseCase, type SerializeGameCommand, type ShuffleMethod, type SkipOptionalActionCommand, type SkipOptionalActionUseCase, type StartGameCommand, StepExecutionError, type StrategyName, type Tile, type TotemBonus, type TotemState, TranslationKey, type TranslationKeyType, type Translator, UndoError, type UseDragonCommand, type UseDragonUseCase, type UuidMethod, type ValidPlacement, ValidationError, type ValidationIssue, type ValidationSeverity, WARRIOR_POWER, type WarriorTile, type WarriorType, advancedStrategy, appendAction, canRedo, canUndo, clearHistory, createActionLog, createDomainError, createGameEngine, createGameHistory, createSavePoint, createTranslator, defaultTranslations, defaultTranslator, deserializeGame, errorCodeToTranslationKey, expertStrategy, gameSteps, getActions, getActionsByTurn, getActionsByType, getHistorySize, getStrategy, getStrategyNames, greedyStrategy, hunterGathererKinds, isBotTurn, isGameWithNextAction, isGameWithNextStep, originsSubModes, playBotTurn, playBotTurns, playerActions, pushState, questTypes, randomStrategy, redo, replayActions, resourceTypes, restoreFromSavePoint, serializeGame, summarizeGameState, translateErrorCode, undo, validateGameState, warriorTypes, wrapWithActionLog, wrapWithDebug, wrapWithEvents };
2649
+ export { ACTION_LOG_VERSION, type ActionEntry, ActionExecutionError, type ActionPayloadMap, type ActionType, type AddExtraRulesCommand, type AddPlayersCommand, type AgeOfGiantsState, type BasicRules, type BotExtensionChoices, type BotStrategy, type BuildersBoard, type BuildingChoice, type BuildingContext, type BuildingSlot, type BuildingTile, type BuildingsRepository, type CalculatePlayerScoreCommand, type CalculatePlayerScoreUseCase, type CalculateScoreCommand, type CanPlaceDominoCommand, type CanSkipOptionalActionCommand, type CaveBoard, type CavemanChoice, type CavemanContext, type CavemanKind, type CavemanRecruitmentOption, type CavemanTile, type CavemenRepository, type ChooseDominoCommand, type ConstructBuildingCommand, type ConstructBuildingUseCase, type CreateGameCommand, type Crown, type DebugLogEntry, type DebugLogLevel, type DebugLogger, type DebugOptions, type DeserializeGameCommand, type DiscardDominoCommand, type DomainError, DomainException, type Domino, type DominoesRepository, type DragonContext, type DynastyGroupBy, type DynastyResult, type EmptyGround, type EmptyTile, type EndGameScoring, type EndGameScoringType, type EngineConfig, ErrorCode, type ErrorCodeType, type ExtensionContext, type ExtraRule, type FinalResult, type FireTokenContext, type FireTokenPool, 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 GetAffordableBuildingsCommand, type GetDynastyResultCommand, type GetExtraRulesCommand, type GetModesCommand, type GetResultCommand, type GetValidActionTargetsCommand, type GetValidBuildingPositionsCommand, type GetValidCavemanPlacementsCommand, type GetValidFireTokenPositionsCommand, type GetValidGiantPositionsCommand, type GetValidGiantTargetsCommand, type GetValidKnightPositionsCommand, type GetValidPlacementsCommand, type GiantPositionContext, type Ground, type HunterGathererKind, type HunterGathererTile, type ImmediateBonus, type ImmediateBonusType, InvalidStepError, type Kingdom, type Knight, type KnightContext, type Logger, type Lord, MAX_KINGDOM_SIZE, type ModeRepository, type NextAction, type NextStep, NotFoundError, type ObjectValues, type OriginsState, type OriginsSubMode, type PendingFireToken, type PendingGiant, type PickContext, type PlaceContext, type PlaceDominoCommand, type PlaceFireTokenCommand, type PlaceFireTokenUseCase, type PlaceGiantCommand, type PlaceGiantUseCase, type PlaceKnightCommand, type PlaceKnightUseCase, type PlacedBuilding, type PlacedCaveman, type PlacedFireToken, type PlacedGiant, type Player, type PlayerActions, type PlayerInput, type PlayerPayload, type PlayerScore, type Players, type PlayersPayload, type Position, type QueenDominoState, type QuestTile, type QuestTilesRepository, type QuestType, type RecruitCavemanCommand, type RecruitCavemanUseCase, RedoError, type Resource, type ResourceType, type RevealsDomino, type Rotation, type RuleRepository, type Rules, type Score, type ScoreBonus, type ScoreBonusType, type ScoreBreakdown, type ScoreResult, type ScoreTerritory, type SelectedRules, type SendGiantChoice, type SendGiantCommand, type SendGiantContext, type SendGiantUseCase, type SerializeGameCommand, type ShuffleMethod, type SkipOptionalActionCommand, type SkipOptionalActionUseCase, type StartGameCommand, StepExecutionError, type StrategyName, type SupportedLocale, type Tile, type TotemBonus, type TotemState, TranslationKey, type TranslationKeyType, type Translator, UndoError, type UseDragonCommand, type UseDragonUseCase, type UuidMethod, type ValidActionTargetsUseCase, type ValidCavemanPlacements, type ValidGiantTargets, type ValidPlacement, ValidationError, type ValidationIssue, type ValidationSeverity, WARRIOR_POWER, type WarriorTile, type WarriorType, advancedStrategy, appendAction, canRedo, canUndo, cavemanKindToTranslationKey, cavemanKinds, clearHistory, createActionLog, createDomainError, createGameEngine, createGameHistory, createSavePoint, createTranslator, defaultTranslations, defaultTranslator, deserializeGame, englishTranslations, errorCodeToTranslationKey, expertStrategy, extraRuleNameToTranslationKey, frenchTranslations, gameSteps, getActions, getActionsByTurn, getActionsByType, getHistorySize, getStrategy, getStrategyNames, greedyStrategy, groundToTranslationKey, hunterGathererKindToTranslationKey, hunterGathererKinds, isBotTurn, isGameWithNextAction, isGameWithNextStep, isGameWithResults, isHunterGathererTile, isSkippableAction, isWarriorTile, localeTranslations, modeNameToTranslationKey, originsSubModes, playBotTurn, playBotTurns, playerActionToTranslationKey, playerActions, pushState, questTypeToTranslationKey, questTypes, randomStrategy, redo, replayActions, resourceToTranslationKey, resourceTypes, restoreFromSavePoint, scoreBonusTypes, serializeGame, summarizeGameState, supportedLocales, totemToTranslationKey, translateErrorCode, undo, validateGameState, warriorTypeToTranslationKey, warriorTypes, wrapWithActionLog, wrapWithDebug, wrapWithEvents };