@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/README.md +1 -1
- package/dist/index.d.ts +1376 -676
- package/dist/index.js +4020 -2365
- package/dist/index.js.map +1 -1
- package/dist/index.universal.js +4020 -2365
- package/dist/wasm-bridge.js +3751 -2172
- package/package.json +1 -9
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
|
-
/**
|
|
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
|
-
*
|
|
577
|
-
*
|
|
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
|
|
611
|
-
|
|
612
|
-
readonly
|
|
613
|
-
|
|
614
|
-
readonly
|
|
615
|
-
|
|
616
|
-
readonly
|
|
617
|
-
|
|
618
|
-
readonly
|
|
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
|
-
|
|
621
|
-
type GameSteps = ObjectValues<typeof gameSteps>;
|
|
669
|
+
type ErrorCodeType = (typeof ErrorCode)[keyof typeof ErrorCode];
|
|
622
670
|
/**
|
|
623
|
-
*
|
|
624
|
-
* Used during active gameplay.
|
|
671
|
+
* Structured domain error with code and context.
|
|
625
672
|
*/
|
|
626
|
-
|
|
627
|
-
/**
|
|
628
|
-
|
|
629
|
-
/**
|
|
630
|
-
|
|
631
|
-
/**
|
|
632
|
-
|
|
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
|
-
*
|
|
636
|
-
* Used during setup and game end.
|
|
682
|
+
* Creates a domain error object.
|
|
637
683
|
*/
|
|
638
|
-
|
|
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
|
-
*
|
|
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
|
|
688
|
+
declare class DomainException extends Error {
|
|
689
|
+
readonly code: ErrorCodeType;
|
|
690
|
+
readonly context?: Record<string, unknown>;
|
|
691
|
+
constructor(error: DomainError);
|
|
692
|
+
}
|
|
662
693
|
/**
|
|
663
|
-
*
|
|
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
|
|
696
|
+
declare class InvalidStepError extends DomainException {
|
|
697
|
+
constructor(message: string, context?: Record<string, unknown>);
|
|
698
|
+
}
|
|
668
699
|
/**
|
|
669
|
-
*
|
|
700
|
+
* Error thrown when a step execution fails.
|
|
670
701
|
*/
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
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
|
-
*
|
|
706
|
+
* Error thrown when an action execution fails.
|
|
681
707
|
*/
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
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
|
-
*
|
|
712
|
+
* Error thrown when a required entity is not found.
|
|
692
713
|
*/
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
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
|
-
*
|
|
718
|
+
* Error thrown when validation fails.
|
|
709
719
|
*/
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
726
|
+
declare class UndoError extends DomainException {
|
|
727
|
+
constructor(message?: string, context?: Record<string, unknown>);
|
|
728
|
+
}
|
|
731
729
|
/**
|
|
732
|
-
*
|
|
730
|
+
* Error thrown when redo is requested but no future state exists.
|
|
733
731
|
*/
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
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
|
-
*
|
|
737
|
+
* Translation keys for internationalization support.
|
|
738
|
+
* Use these keys instead of hardcoded strings for translatable content.
|
|
747
739
|
*/
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
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
|
-
*
|
|
862
|
+
* Default English translations.
|
|
863
|
+
* Also available as `englishTranslations` / `translations.en`.
|
|
863
864
|
*/
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
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
|
-
*
|
|
869
|
+
* French translations.
|
|
894
870
|
*/
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
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
|
-
*
|
|
1049
|
-
* @param
|
|
1050
|
-
* @
|
|
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
|
-
|
|
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
|
-
*
|
|
1055
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
1061
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
1067
|
-
*
|
|
1068
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
1073
|
-
*
|
|
1074
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
1079
|
-
*
|
|
1080
|
-
*
|
|
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
|
-
|
|
1213
|
+
breakdown?: ScoreBreakdown;
|
|
1083
1214
|
};
|
|
1084
|
-
|
|
1085
1215
|
/**
|
|
1086
|
-
*
|
|
1087
|
-
* These codes can be used for i18n and programmatic error handling.
|
|
1216
|
+
* Final result including ranking position.
|
|
1088
1217
|
*/
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
1153
|
-
readonly code: ErrorCodeType;
|
|
1154
|
-
readonly context?: Record<string, unknown>;
|
|
1155
|
-
constructor(error: DomainError);
|
|
1156
|
-
}
|
|
1278
|
+
type ValidationSeverity = "error" | "warning";
|
|
1157
1279
|
/**
|
|
1158
|
-
*
|
|
1280
|
+
* Describes a single validation issue found in a game state.
|
|
1159
1281
|
*/
|
|
1160
|
-
|
|
1161
|
-
|
|
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
|
-
*
|
|
1294
|
+
* Command to check if a domino can be placed anywhere on a kingdom.
|
|
1165
1295
|
*/
|
|
1166
|
-
|
|
1167
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
1173
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
1179
|
-
|
|
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
|
-
*
|
|
1386
|
+
* Command to get all valid placements for a domino on a kingdom.
|
|
1183
1387
|
*/
|
|
1184
|
-
|
|
1185
|
-
|
|
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
|
-
*
|
|
1451
|
+
* Command to serialize a game state to JSON.
|
|
1189
1452
|
*/
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1453
|
+
type SerializeGameCommand = {
|
|
1454
|
+
/** The game state to serialize */
|
|
1455
|
+
game: GameState;
|
|
1456
|
+
};
|
|
1193
1457
|
/**
|
|
1194
|
-
*
|
|
1458
|
+
* Command to deserialize a game state from JSON.
|
|
1195
1459
|
*/
|
|
1196
|
-
|
|
1197
|
-
|
|
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
|
-
*
|
|
1202
|
-
* Use these keys instead of hardcoded strings for translatable content.
|
|
1495
|
+
* Price of recruiting one caveman of the cave board.
|
|
1203
1496
|
*/
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
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
|
-
|
|
1505
|
+
|
|
1266
1506
|
/**
|
|
1267
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
1272
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
1277
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
1544
|
+
* Represents a valid placement position with rotation.
|
|
1282
1545
|
*/
|
|
1283
|
-
|
|
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
|
-
*
|
|
1286
|
-
*
|
|
1287
|
-
* @param
|
|
1288
|
-
* @returns
|
|
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
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
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
|
-
*
|
|
1492
|
-
* - `
|
|
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
|
-
*
|
|
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
|
-
|
|
1592
|
-
|
|
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
|
|
1598
|
-
*
|
|
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
|
|
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
|
|
1611
|
-
*
|
|
1612
|
-
*
|
|
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:
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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 };
|