@dreamboard-games/sdk 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +96 -0
- package/README.md +12 -0
- package/dist/HandView-ncJIVLhN.d.ts +193 -0
- package/dist/ResourceCounter-CTREyF73.d.ts +102 -0
- package/dist/ThemeProvider-fy0_QzgO.d.ts +99 -0
- package/dist/bundle-TIZcw8LB.d.ts +281 -0
- package/dist/cards-Sl3b40Mv.d.ts +13 -0
- package/dist/chunk-7YAHLYBR.js +481 -0
- package/dist/chunk-7YAHLYBR.js.map +1 -0
- package/dist/chunk-FDNZTDD6.js +8085 -0
- package/dist/chunk-FDNZTDD6.js.map +1 -0
- package/dist/chunk-GKKBPPSW.js +598 -0
- package/dist/chunk-GKKBPPSW.js.map +1 -0
- package/dist/chunk-I46YJSOD.js +1 -0
- package/dist/chunk-I46YJSOD.js.map +1 -0
- package/dist/chunk-KAELH4KC.js +104 -0
- package/dist/chunk-KAELH4KC.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/chunk-T3ZKNUZ7.js +1 -0
- package/dist/chunk-T3ZKNUZ7.js.map +1 -0
- package/dist/chunk-T52J5RMF.js +1 -0
- package/dist/chunk-T52J5RMF.js.map +1 -0
- package/dist/chunk-TDSWKVZ4.js +5401 -0
- package/dist/chunk-TDSWKVZ4.js.map +1 -0
- package/dist/chunk-U5C6BONG.js +34 -0
- package/dist/chunk-U5C6BONG.js.map +1 -0
- package/dist/chunk-VDXOF4FW.js +69 -0
- package/dist/chunk-VDXOF4FW.js.map +1 -0
- package/dist/chunk-VFTAA4WO.js +115 -0
- package/dist/chunk-VFTAA4WO.js.map +1 -0
- package/dist/chunk-WN74KVNY.js +17 -0
- package/dist/chunk-WN74KVNY.js.map +1 -0
- package/dist/chunk-WYPQ3GG5.js +10990 -0
- package/dist/chunk-WYPQ3GG5.js.map +1 -0
- package/dist/components-D5ZRE2Hl.d.ts +1451 -0
- package/dist/generated/runtime/primitives.d.ts +12 -0
- package/dist/generated/runtime/primitives.js +180 -0
- package/dist/generated/runtime/primitives.js.map +1 -0
- package/dist/generated/runtime-api.d.ts +3 -0
- package/dist/generated/runtime-api.js +2 -0
- package/dist/generated/runtime-api.js.map +1 -0
- package/dist/generated/runtime.d.ts +14 -0
- package/dist/generated/runtime.js +18 -0
- package/dist/generated/runtime.js.map +1 -0
- package/dist/generated/workspace-contract.d.ts +14 -0
- package/dist/generated/workspace-contract.js +14 -0
- package/dist/generated/workspace-contract.js.map +1 -0
- package/dist/hex-board-view-D_07hO6O.d.ts +933 -0
- package/dist/hex-color-MhOyuY-o.d.ts +8 -0
- package/dist/index-BwqPQtBu.d.ts +1433 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/infrastructure/reducer-bundle-abi.d.ts +1083 -0
- package/dist/infrastructure/reducer-bundle-abi.js +14 -0
- package/dist/infrastructure/reducer-bundle-abi.js.map +1 -0
- package/dist/infrastructure/workspace-codegen.d.ts +53 -0
- package/dist/infrastructure/workspace-codegen.js +44 -0
- package/dist/infrastructure/workspace-codegen.js.map +1 -0
- package/dist/manifest-contract-BNHVGFtU.d.ts +9 -0
- package/dist/package-set.d.ts +13 -0
- package/dist/package-set.js +12 -0
- package/dist/package-set.js.map +1 -0
- package/dist/primitive-props-DpKs-GCr.d.ts +11 -0
- package/dist/reducer.d.ts +3786 -0
- package/dist/reducer.js +8131 -0
- package/dist/reducer.js.map +1 -0
- package/dist/runtime/primitives.d.ts +226 -0
- package/dist/runtime/primitives.js +180 -0
- package/dist/runtime/primitives.js.map +1 -0
- package/dist/runtime/types/runtime-api.d.ts +1 -0
- package/dist/runtime/types/runtime-api.js +2 -0
- package/dist/runtime/types/runtime-api.js.map +1 -0
- package/dist/runtime/workspace-contract.d.ts +172 -0
- package/dist/runtime/workspace-contract.js +14 -0
- package/dist/runtime/workspace-contract.js.map +1 -0
- package/dist/runtime-api-3dshj6kK.d.ts +101 -0
- package/dist/runtime-api-DWxvTr-O.d.ts +379 -0
- package/dist/runtime.d.ts +58 -0
- package/dist/runtime.js +13 -0
- package/dist/runtime.js.map +1 -0
- package/dist/slots-1GPGihk8.d.ts +8 -0
- package/dist/testing.d.ts +149 -0
- package/dist/testing.js +513 -0
- package/dist/testing.js.map +1 -0
- package/dist/types.d.ts +496 -0
- package/dist/types.js +28 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/components.d.ts +16 -0
- package/dist/ui/components.js +192 -0
- package/dist/ui/components.js.map +1 -0
- package/dist/ui/defaults.d.ts +19 -0
- package/dist/ui/defaults.js +104 -0
- package/dist/ui/defaults.js.map +1 -0
- package/dist/ui/plugin-styles.css +250 -0
- package/dist/ui/types/player-state.d.ts +365 -0
- package/dist/ui/types/player-state.js +1 -0
- package/dist/ui/types/player-state.js.map +1 -0
- package/dist/ui-contract-iQfTtUSL.d.ts +1161 -0
- package/dist/ui.d.ts +320 -0
- package/dist/ui.js +253 -0
- package/dist/ui.js.map +1 -0
- package/package.json +199 -0
- package/src/generated/reducer-contract/builders.ts +90 -0
- package/src/generated/reducer-contract/version.ts +9 -0
- package/src/generated/reducer-contract/wire.ts +100 -0
- package/src/generated/reducer-contract/zod.ts +101 -0
- package/src/generated/runtime/primitives.ts +2 -0
- package/src/generated/runtime-api.ts +5 -0
- package/src/generated/runtime.ts +35 -0
- package/src/generated/workspace-contract.ts +2 -0
- package/src/index.ts +7 -0
- package/src/infrastructure/reducer-bundle-abi.ts +8 -0
- package/src/infrastructure/reducer-contract/bundle.ts +37 -0
- package/src/infrastructure/workspace-codegen/hex-geometry.ts +69 -0
- package/src/infrastructure/workspace-codegen/index.ts +64 -0
- package/src/infrastructure/workspace-codegen/manifest-contract.ts +6632 -0
- package/src/infrastructure/workspace-codegen/manifest-validation.ts +795 -0
- package/src/infrastructure/workspace-codegen/ownership.ts +131 -0
- package/src/infrastructure/workspace-codegen/preset-card-sets.ts +169 -0
- package/src/infrastructure/workspace-codegen/seeds.ts +1705 -0
- package/src/infrastructure/workspace-codegen.ts +1 -0
- package/src/package-set.ts +19 -0
- package/src/reducer/authoring/contract.ts +157 -0
- package/src/reducer/authoring/effect.ts +224 -0
- package/src/reducer/authoring/game.ts +23 -0
- package/src/reducer/authoring/interaction.ts +98 -0
- package/src/reducer/authoring/phase.ts +300 -0
- package/src/reducer/authoring/types.ts +70 -0
- package/src/reducer/authoring/validation.ts +382 -0
- package/src/reducer/authoring/view-stage.ts +68 -0
- package/src/reducer/authoring.ts +29 -0
- package/src/reducer/bundle/ingress-bundle.ts +491 -0
- package/src/reducer/bundle/trusted/engine-instruction-resolver.ts +254 -0
- package/src/reducer/bundle/trusted/flow-instruction-resolver.ts +73 -0
- package/src/reducer/bundle/trusted/instruction-runner.ts +414 -0
- package/src/reducer/bundle/trusted/interaction-authorization.ts +137 -0
- package/src/reducer/bundle/trusted/interaction-collectors.ts +859 -0
- package/src/reducer/bundle/trusted/interaction-decision.ts +747 -0
- package/src/reducer/bundle/trusted/interaction-resolver.ts +95 -0
- package/src/reducer/bundle/trusted/interaction-types.ts +171 -0
- package/src/reducer/bundle/trusted/lifecycle-runner.ts +427 -0
- package/src/reducer/bundle/trusted/projection-builder.ts +356 -0
- package/src/reducer/bundle/trusted/projection-context.ts +39 -0
- package/src/reducer/bundle/trusted/rng-sampler.ts +150 -0
- package/src/reducer/bundle/trusted/runtime-registry.ts +120 -0
- package/src/reducer/bundle/trusted/runtime-scope.ts +336 -0
- package/src/reducer/bundle/trusted/simultaneous-player.ts +97 -0
- package/src/reducer/bundle/trusted/stage-resolver.ts +87 -0
- package/src/reducer/bundle/trusted/static-projection.ts +116 -0
- package/src/reducer/bundle/trusted/trusted-runtime-args.ts +97 -0
- package/src/reducer/bundle/trusted/trusted-runtime-result.ts +39 -0
- package/src/reducer/bundle/trusted/trusted-setup-profiles.ts +43 -0
- package/src/reducer/bundle/trusted/trusted-state-codec.ts +48 -0
- package/src/reducer/bundle/trusted-bundle.ts +97 -0
- package/src/reducer/bundle/types.ts +171 -0
- package/src/reducer/bundle.ts +2 -0
- package/src/reducer/client-param-schemas.ts +57 -0
- package/src/reducer/compose.ts +34 -0
- package/src/reducer/core/runtime-input.ts +30 -0
- package/src/reducer/core/runtime-instruction.ts +59 -0
- package/src/reducer/core/types.ts +62 -0
- package/src/reducer/definition-index.ts +277 -0
- package/src/reducer/derived.ts +106 -0
- package/src/reducer/effects.ts +92 -0
- package/src/reducer/engine/runtime-instruction-engine.ts +155 -0
- package/src/reducer/ingress/decode-runtime-input.ts +7 -0
- package/src/reducer/ingress/decode-session-state.ts +9 -0
- package/src/reducer/ingress/encode-session-state.ts +6 -0
- package/src/reducer/ingress/input-codec.ts +18 -0
- package/src/reducer/ingress/phase-schemas.ts +62 -0
- package/src/reducer/ingress/raw-types.ts +107 -0
- package/src/reducer/ingress/runtime-codec.ts +14 -0
- package/src/reducer/ingress/runtime-payload.ts +13 -0
- package/src/reducer/ingress/session-codec.ts +392 -0
- package/src/reducer/ingress/types.ts +6 -0
- package/src/reducer/inputs/boardInput.ts +217 -0
- package/src/reducer/inputs/boardTarget.ts +190 -0
- package/src/reducer/inputs/cardInput.ts +86 -0
- package/src/reducer/inputs/cardTarget.ts +101 -0
- package/src/reducer/inputs/choiceTarget.ts +104 -0
- package/src/reducer/inputs/defineInputs.ts +71 -0
- package/src/reducer/inputs/formInput.ts +809 -0
- package/src/reducer/inputs/many.ts +120 -0
- package/src/reducer/inputs/promptInput.ts +87 -0
- package/src/reducer/inputs/rngInput.ts +58 -0
- package/src/reducer/inputs/targetRule.ts +123 -0
- package/src/reducer/inputs.ts +41 -0
- package/src/reducer/model/definition.ts +1072 -0
- package/src/reducer/model/extract.ts +745 -0
- package/src/reducer/model/manifest.ts +570 -0
- package/src/reducer/model/queries.ts +641 -0
- package/src/reducer/model/runtime.ts +264 -0
- package/src/reducer/model/spec.ts +1386 -0
- package/src/reducer/model/table.ts +260 -0
- package/src/reducer/model.ts +7 -0
- package/src/reducer/ops.ts +1034 -0
- package/src/reducer/parse-utils.ts +28 -0
- package/src/reducer/per-player.ts +422 -0
- package/src/reducer/rng.ts +69 -0
- package/src/reducer/schema-helpers.ts +185 -0
- package/src/reducer/setup-bootstrap-helpers.ts +171 -0
- package/src/reducer/setup-bootstrap.ts +481 -0
- package/src/reducer/table-ops.ts +2671 -0
- package/src/reducer/table-queries.ts +372 -0
- package/src/reducer/transaction.ts +120 -0
- package/src/reducer.ts +314 -0
- package/src/runtime/primitives.ts +1 -0
- package/src/runtime/types/runtime-api.ts +1 -0
- package/src/runtime/workspace-contract.ts +32 -0
- package/src/runtime-internal/components/InteractionForm.tsx +1309 -0
- package/src/runtime-internal/components/PluginRuntime.tsx +103 -0
- package/src/runtime-internal/components/board/target-layer.ts +70 -0
- package/src/runtime-internal/context/ClientParamSchemaContext.tsx +44 -0
- package/src/runtime-internal/context/InteractionDraftContext.tsx +279 -0
- package/src/runtime-internal/context/PluginSessionContext.tsx +47 -0
- package/src/runtime-internal/context/PluginStateContext.tsx +262 -0
- package/src/runtime-internal/context/RuntimeContext.tsx +96 -0
- package/src/runtime-internal/defaults/components.tsx +409 -0
- package/src/runtime-internal/defaults/index.ts +11 -0
- package/src/runtime-internal/errors/ValidationError.ts +29 -0
- package/src/runtime-internal/hooks/useActivePlayers.ts +33 -0
- package/src/runtime-internal/hooks/useBoardInteractions.ts +665 -0
- package/src/runtime-internal/hooks/useGameSelector.ts +105 -0
- package/src/runtime-internal/hooks/useGameView.ts +9 -0
- package/src/runtime-internal/hooks/useInteractionByKey.ts +354 -0
- package/src/runtime-internal/hooks/useInteractionHandle.ts +438 -0
- package/src/runtime-internal/hooks/useIsMyTurn.ts +20 -0
- package/src/runtime-internal/hooks/useLobby.ts +76 -0
- package/src/runtime-internal/hooks/useMe.ts +48 -0
- package/src/runtime-internal/hooks/usePlayerInfo.ts +28 -0
- package/src/runtime-internal/hooks/usePlayerTurnOrder.ts +23 -0
- package/src/runtime-internal/hooks/usePluginRuntime.ts +147 -0
- package/src/runtime-internal/hooks/useSeatInbox.ts +61 -0
- package/src/runtime-internal/hooks/useSimultaneousPhase.ts +10 -0
- package/src/runtime-internal/index.ts +42 -0
- package/src/runtime-internal/internal.ts +43 -0
- package/src/runtime-internal/plugin-styles.css +250 -0
- package/src/runtime-internal/primitives/board.tsx +459 -0
- package/src/runtime-internal/primitives/dialog-lifecycle.ts +58 -0
- package/src/runtime-internal/primitives/dice.tsx +79 -0
- package/src/runtime-internal/primitives/game-ui-provider.tsx +35 -0
- package/src/runtime-internal/primitives/game.tsx +387 -0
- package/src/runtime-internal/primitives/hand-intent-adapter.ts +147 -0
- package/src/runtime-internal/primitives/hand-surface.tsx +594 -0
- package/src/runtime-internal/primitives/index.ts +196 -0
- package/src/runtime-internal/primitives/interaction-form-binding.tsx +56 -0
- package/src/runtime-internal/primitives/interaction-submit.ts +90 -0
- package/src/runtime-internal/primitives/interaction.tsx +987 -0
- package/src/runtime-internal/primitives/phase.tsx +43 -0
- package/src/runtime-internal/primitives/player-roster.tsx +302 -0
- package/src/runtime-internal/primitives/primitive-props.tsx +101 -0
- package/src/runtime-internal/primitives/prompt.tsx +255 -0
- package/src/runtime-internal/primitives/ui.tsx +60 -0
- package/src/runtime-internal/primitives/zone.tsx +791 -0
- package/src/runtime-internal/reducer.ts +30 -0
- package/src/runtime-internal/runtime/createPluginRuntimeAPI.ts +605 -0
- package/src/runtime-internal/types/plugin-state.ts +508 -0
- package/src/runtime-internal/types/reducer-state.ts +24 -0
- package/src/runtime-internal/types/runtime-api.ts +114 -0
- package/src/runtime-internal/ui-contract.ts +519 -0
- package/src/runtime-internal/utils/card-intent-adapter.ts +546 -0
- package/src/runtime-internal/utils/interaction-inputs.ts +492 -0
- package/src/runtime-internal/utils/interaction-labels.ts +23 -0
- package/src/runtime-internal/utils/interaction-router.ts +273 -0
- package/src/runtime-internal/utils/interaction-status.ts +74 -0
- package/src/runtime-internal/workspace-contract.ts +1170 -0
- package/src/runtime.ts +34 -0
- package/src/testing/create-expect-api.ts +352 -0
- package/src/testing/create-test-runtime.ts +381 -0
- package/src/testing/definitions.ts +127 -0
- package/src/testing/index.ts +3 -0
- package/src/testing.ts +1 -0
- package/src/type-stubs/manifest-contract.d.ts +42 -0
- package/src/type-stubs/manifest-contract.js +72 -0
- package/src/type-stubs/ui-contract.d.ts +5 -0
- package/src/type-stubs/ui-contract.js +1 -0
- package/src/types/authoring-card-properties.type-test.ts +266 -0
- package/src/types/authoring.ts +1282 -0
- package/src/types/cards.ts +19 -0
- package/src/types/contracts.ts +1550 -0
- package/src/types/generated-helpers.ts +35 -0
- package/src/types/index.ts +147 -0
- package/src/types/slots.ts +11 -0
- package/src/types.ts +1 -0
- package/src/ui/components/ActionButton.tsx +97 -0
- package/src/ui/components/ActionPanel.tsx +315 -0
- package/src/ui/components/Card.tsx +378 -0
- package/src/ui/components/CardDragSurface.tsx +1076 -0
- package/src/ui/components/ChromeSuppressionContext.tsx +70 -0
- package/src/ui/components/CostDisplay.tsx +145 -0
- package/src/ui/components/DiceRoller.tsx +581 -0
- package/src/ui/components/Drawer.tsx +180 -0
- package/src/ui/components/ErrorBoundary.tsx +275 -0
- package/src/ui/components/GameEndDisplay.tsx +398 -0
- package/src/ui/components/GameSkeleton.tsx +260 -0
- package/src/ui/components/Hand.tsx +468 -0
- package/src/ui/components/HandDock.tsx +299 -0
- package/src/ui/components/HandView.tsx +441 -0
- package/src/ui/components/MobileHandTray.tsx +381 -0
- package/src/ui/components/MoreActions.tsx +143 -0
- package/src/ui/components/PhaseIndicator.tsx +341 -0
- package/src/ui/components/PlayArea.tsx +146 -0
- package/src/ui/components/PrimaryActionButton.tsx +336 -0
- package/src/ui/components/PrimaryButton.tsx +45 -0
- package/src/ui/components/ResourceCounter.tsx +270 -0
- package/src/ui/components/StagingZone.tsx +134 -0
- package/src/ui/components/ThemedButton.tsx +113 -0
- package/src/ui/components/Toast.tsx +264 -0
- package/src/ui/components/board/HexGrid.tsx +1294 -0
- package/src/ui/components/board/NetworkGraph.tsx +476 -0
- package/src/ui/components/board/SlotSystem.tsx +388 -0
- package/src/ui/components/board/SquareGrid.tsx +1165 -0
- package/src/ui/components/board/TrackBoard.tsx +496 -0
- package/src/ui/components/board/ZoneMap.tsx +448 -0
- package/src/ui/components/board/hex-board-view.ts +123 -0
- package/src/ui/components/board/index.ts +142 -0
- package/src/ui/components/board/interaction-accessibility.ts +21 -0
- package/src/ui/components/board/target-layer.ts +66 -0
- package/src/ui/components/card-render-content.type-test.ts +27 -0
- package/src/ui/components/hand-layout-math.ts +163 -0
- package/src/ui/components/hand-pointer-engine.ts +413 -0
- package/src/ui/components/index.ts +245 -0
- package/src/ui/components.ts +1 -0
- package/src/ui/defaults/components.tsx +106 -0
- package/src/ui/defaults/index.ts +8 -0
- package/src/ui/defaults.ts +1 -0
- package/src/ui/errors/ValidationError.ts +29 -0
- package/src/ui/helpers/cards.ts +19 -0
- package/src/ui/helpers/track-board.ts +211 -0
- package/src/ui/hooks/useBoardTopology.ts +316 -0
- package/src/ui/hooks/useCards.ts +10 -0
- package/src/ui/hooks/useHandCardPointer.ts +381 -0
- package/src/ui/hooks/useHandLayout.ts +378 -0
- package/src/ui/hooks/useHandPresentation.ts +121 -0
- package/src/ui/hooks/useHexBoard.ts +74 -0
- package/src/ui/hooks/useHexGrid.ts +185 -0
- package/src/ui/hooks/useIsMobile.ts +35 -0
- package/src/ui/hooks/usePanZoom.ts +278 -0
- package/src/ui/hooks/useSquareBoard.ts +124 -0
- package/src/ui/hooks/useSquareGrid.ts +328 -0
- package/src/ui/index.ts +98 -0
- package/src/ui/internal/ui/alert.tsx +51 -0
- package/src/ui/internal/ui/button.tsx +58 -0
- package/src/ui/internal/ui/dialog.tsx +134 -0
- package/src/ui/internal/ui/input.tsx +21 -0
- package/src/ui/internal/ui/label.tsx +21 -0
- package/src/ui/internal/ui/select.tsx +129 -0
- package/src/ui/internal/ui/tooltip.tsx +54 -0
- package/src/ui/internal/ui/utils.ts +5 -0
- package/src/ui/plugin-styles.css +250 -0
- package/src/ui/primitives/dialog-lifecycle.ts +58 -0
- package/src/ui/primitives/dice.tsx +79 -0
- package/src/ui/primitives/primitive-props.tsx +101 -0
- package/src/ui/theme/ThemeProvider.tsx +252 -0
- package/src/ui/theme/board.ts +61 -0
- package/src/ui/theme/css-vars.ts +105 -0
- package/src/ui/theme/derive.ts +240 -0
- package/src/ui/theme/index.ts +61 -0
- package/src/ui/theme/presets/arcade.ts +261 -0
- package/src/ui/theme/presets/studio.ts +261 -0
- package/src/ui/theme/presets/tabletop.ts +266 -0
- package/src/ui/theme/tokens.ts +392 -0
- package/src/ui/types/hex-color.ts +20 -0
- package/src/ui/types/player-state.ts +463 -0
- package/src/ui/types/tiled-board.ts +785 -0
- package/src/ui/types/visual-state.ts +137 -0
- package/src/ui.ts +1 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { RuntimeInstructionForState } from "./runtime-instruction";
|
|
2
|
+
import type { TrustedRuntimeInput } from "./runtime-input";
|
|
3
|
+
|
|
4
|
+
export type DispatchTraceEntry<
|
|
5
|
+
State,
|
|
6
|
+
PlayerId extends string = string,
|
|
7
|
+
Input extends TrustedRuntimeInput<PlayerId> = TrustedRuntimeInput<PlayerId>,
|
|
8
|
+
> =
|
|
9
|
+
| {
|
|
10
|
+
type: "acceptedClientInput";
|
|
11
|
+
input: Input;
|
|
12
|
+
}
|
|
13
|
+
| {
|
|
14
|
+
type: "appliedInstruction";
|
|
15
|
+
instruction: RuntimeInstructionForState<State>;
|
|
16
|
+
}
|
|
17
|
+
| {
|
|
18
|
+
type: "rngConsumption";
|
|
19
|
+
operation: string;
|
|
20
|
+
traceEntry: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type TrustedReducerDispatchResult<State, PlayerId extends string> =
|
|
24
|
+
| {
|
|
25
|
+
type: "reject";
|
|
26
|
+
errorCode: string;
|
|
27
|
+
message?: string;
|
|
28
|
+
}
|
|
29
|
+
| {
|
|
30
|
+
type: "accept";
|
|
31
|
+
state: State;
|
|
32
|
+
trace: DispatchTraceEntry<State, PlayerId>[];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type TrustedInstructionResolutionResult<
|
|
36
|
+
State,
|
|
37
|
+
PlayerId extends string,
|
|
38
|
+
Input extends TrustedRuntimeInput<PlayerId> = TrustedRuntimeInput<PlayerId>,
|
|
39
|
+
> = {
|
|
40
|
+
state: State;
|
|
41
|
+
queuedInputs: Input[];
|
|
42
|
+
queuedInstructions: RuntimeInstructionForState<State>[];
|
|
43
|
+
trace: DispatchTraceEntry<State, PlayerId, Input>[];
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type TrustedReducerEngine<State, PlayerId extends string> = {
|
|
47
|
+
dispatch: (
|
|
48
|
+
state: State,
|
|
49
|
+
input: TrustedRuntimeInput<PlayerId>,
|
|
50
|
+
) => TrustedReducerDispatchResult<State, PlayerId>;
|
|
51
|
+
drainInstructions: (
|
|
52
|
+
state: State,
|
|
53
|
+
instructions: RuntimeInstructionForState<State>[],
|
|
54
|
+
) => State;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type {
|
|
58
|
+
DecodedReducerInput,
|
|
59
|
+
TrustedContinuationInput,
|
|
60
|
+
TrustedInteractionInput,
|
|
61
|
+
TrustedRuntimeInput,
|
|
62
|
+
} from "./runtime-input";
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { cardInput } from "./inputs/cardInput";
|
|
2
|
+
import { cardTarget } from "./inputs/cardTarget";
|
|
3
|
+
import type {
|
|
4
|
+
AnyCardActionSpec,
|
|
5
|
+
AnyContinuationCallable,
|
|
6
|
+
AnyInteractionSpec,
|
|
7
|
+
BaseGameStateOfContract,
|
|
8
|
+
EffectMap,
|
|
9
|
+
InputCollector,
|
|
10
|
+
InteractionMap,
|
|
11
|
+
ManifestContractOf,
|
|
12
|
+
PhaseDefinition,
|
|
13
|
+
PhaseMapOf,
|
|
14
|
+
PhaseNamesOfDefinition,
|
|
15
|
+
ReducerGameContractLike,
|
|
16
|
+
ReducerGameDefinition,
|
|
17
|
+
SchemaLike,
|
|
18
|
+
StageMap,
|
|
19
|
+
StageSpec,
|
|
20
|
+
ViewMapOf,
|
|
21
|
+
PhaseZoneList,
|
|
22
|
+
PlayerZoneIdOfManifest,
|
|
23
|
+
} from "./model";
|
|
24
|
+
|
|
25
|
+
export type ReducerIndexedPhase<Contract extends ReducerGameContractLike> =
|
|
26
|
+
PhaseDefinition<
|
|
27
|
+
SchemaLike<object>,
|
|
28
|
+
BaseGameStateOfContract<Contract>,
|
|
29
|
+
ManifestContractOf<Contract>,
|
|
30
|
+
Record<string, InputCollector>,
|
|
31
|
+
EffectMap<BaseGameStateOfContract<Contract>, ManifestContractOf<Contract>>,
|
|
32
|
+
InteractionMap<
|
|
33
|
+
BaseGameStateOfContract<Contract>,
|
|
34
|
+
ManifestContractOf<Contract>
|
|
35
|
+
>,
|
|
36
|
+
StageMap<BaseGameStateOfContract<Contract>, ManifestContractOf<Contract>>,
|
|
37
|
+
PhaseZoneList<ManifestContractOf<Contract>>
|
|
38
|
+
>;
|
|
39
|
+
|
|
40
|
+
export type ReducerIndexedInteractionEntry<
|
|
41
|
+
Contract extends ReducerGameContractLike,
|
|
42
|
+
> = readonly [
|
|
43
|
+
string,
|
|
44
|
+
AnyInteractionSpec<
|
|
45
|
+
BaseGameStateOfContract<Contract>,
|
|
46
|
+
ManifestContractOf<Contract>
|
|
47
|
+
>,
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
export type ReducerIndexedStageEntry<Contract extends ReducerGameContractLike> =
|
|
51
|
+
readonly [
|
|
52
|
+
string,
|
|
53
|
+
StageSpec<BaseGameStateOfContract<Contract>, ManifestContractOf<Contract>>,
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
export type ReducerIndexedZoneEntry<Contract extends ReducerGameContractLike> =
|
|
57
|
+
PlayerZoneIdOfManifest<ManifestContractOf<Contract>>;
|
|
58
|
+
|
|
59
|
+
export type ReducerIndexedCardActionEntry<
|
|
60
|
+
Contract extends ReducerGameContractLike,
|
|
61
|
+
> = readonly [
|
|
62
|
+
string,
|
|
63
|
+
AnyCardActionSpec<
|
|
64
|
+
BaseGameStateOfContract<Contract>,
|
|
65
|
+
ManifestContractOf<Contract>
|
|
66
|
+
>,
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
export type ReducerIndexedEffectEntry<
|
|
70
|
+
Contract extends ReducerGameContractLike,
|
|
71
|
+
> = readonly [
|
|
72
|
+
string,
|
|
73
|
+
{
|
|
74
|
+
readonly id: string;
|
|
75
|
+
readonly type: string;
|
|
76
|
+
readonly continuation?: AnyContinuationCallable<
|
|
77
|
+
BaseGameStateOfContract<Contract>
|
|
78
|
+
> & {
|
|
79
|
+
readonly id?: string;
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
export interface ReducerDefinitionPhaseIndex<
|
|
85
|
+
Contract extends ReducerGameContractLike,
|
|
86
|
+
Definitions extends PhaseMapOf<Contract>,
|
|
87
|
+
Views extends ViewMapOf<Contract>,
|
|
88
|
+
> {
|
|
89
|
+
readonly phaseName: PhaseNamesOfDefinition<
|
|
90
|
+
ReducerGameDefinition<Contract, Definitions, Views>
|
|
91
|
+
>;
|
|
92
|
+
readonly phase: ReducerIndexedPhase<Contract>;
|
|
93
|
+
readonly interactions: ReadonlyArray<
|
|
94
|
+
ReducerIndexedInteractionEntry<Contract>
|
|
95
|
+
>;
|
|
96
|
+
readonly stages: ReadonlyArray<ReducerIndexedStageEntry<Contract>>;
|
|
97
|
+
readonly zones: ReadonlyArray<ReducerIndexedZoneEntry<Contract>>;
|
|
98
|
+
readonly cardActions: ReadonlyArray<ReducerIndexedCardActionEntry<Contract>>;
|
|
99
|
+
readonly effects: ReadonlyArray<ReducerIndexedEffectEntry<Contract>>;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface ReducerDefinitionIndex<
|
|
103
|
+
Contract extends ReducerGameContractLike,
|
|
104
|
+
Definitions extends PhaseMapOf<Contract>,
|
|
105
|
+
Views extends ViewMapOf<Contract>,
|
|
106
|
+
> {
|
|
107
|
+
readonly phaseEntries: ReadonlyArray<
|
|
108
|
+
readonly [
|
|
109
|
+
PhaseNamesOfDefinition<
|
|
110
|
+
ReducerGameDefinition<Contract, Definitions, Views>
|
|
111
|
+
>,
|
|
112
|
+
ReducerIndexedPhase<Contract>,
|
|
113
|
+
]
|
|
114
|
+
>;
|
|
115
|
+
readonly phasesByName: ReadonlyMap<
|
|
116
|
+
PhaseNamesOfDefinition<ReducerGameDefinition<Contract, Definitions, Views>>,
|
|
117
|
+
ReducerDefinitionPhaseIndex<Contract, Definitions, Views>
|
|
118
|
+
>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
type RawEffectEntry<Contract extends ReducerGameContractLike> = readonly [
|
|
122
|
+
string,
|
|
123
|
+
{
|
|
124
|
+
id?: string;
|
|
125
|
+
type?: string;
|
|
126
|
+
__continuation?: AnyContinuationCallable<
|
|
127
|
+
BaseGameStateOfContract<Contract>
|
|
128
|
+
> & {
|
|
129
|
+
id?: string;
|
|
130
|
+
};
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
function phaseEntriesOf<
|
|
135
|
+
Contract extends ReducerGameContractLike,
|
|
136
|
+
Definitions extends PhaseMapOf<Contract>,
|
|
137
|
+
Views extends ViewMapOf<Contract>,
|
|
138
|
+
>(
|
|
139
|
+
definition: ReducerGameDefinition<Contract, Definitions, Views>,
|
|
140
|
+
): Array<
|
|
141
|
+
readonly [
|
|
142
|
+
PhaseNamesOfDefinition<ReducerGameDefinition<Contract, Definitions, Views>>,
|
|
143
|
+
ReducerIndexedPhase<Contract>,
|
|
144
|
+
]
|
|
145
|
+
> {
|
|
146
|
+
type PhaseName = PhaseNamesOfDefinition<
|
|
147
|
+
ReducerGameDefinition<Contract, Definitions, Views>
|
|
148
|
+
>;
|
|
149
|
+
return Object.entries(definition.phases) as unknown as Array<
|
|
150
|
+
readonly [PhaseName, ReducerIndexedPhase<Contract>]
|
|
151
|
+
>;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function interactionEntriesOf<Contract extends ReducerGameContractLike>(
|
|
155
|
+
phase: ReducerIndexedPhase<Contract>,
|
|
156
|
+
): Array<ReducerIndexedInteractionEntry<Contract>> {
|
|
157
|
+
return Object.entries(
|
|
158
|
+
(phase as { interactions?: Record<string, unknown> }).interactions ?? {},
|
|
159
|
+
) as Array<ReducerIndexedInteractionEntry<Contract>>;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function simultaneousSubmitEntriesOf<Contract extends ReducerGameContractLike>(
|
|
163
|
+
phase: ReducerIndexedPhase<Contract>,
|
|
164
|
+
): Array<ReducerIndexedInteractionEntry<Contract>> {
|
|
165
|
+
const submit = (phase as { submit?: unknown }).submit;
|
|
166
|
+
if (!submit) return [];
|
|
167
|
+
return [
|
|
168
|
+
["submit", submit] as unknown as ReducerIndexedInteractionEntry<Contract>,
|
|
169
|
+
];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function cardActionEntriesOf<Contract extends ReducerGameContractLike>(
|
|
173
|
+
phase: ReducerIndexedPhase<Contract>,
|
|
174
|
+
): Array<ReducerIndexedCardActionEntry<Contract>> {
|
|
175
|
+
return Object.entries(
|
|
176
|
+
(phase as { cardActions?: Record<string, unknown> }).cardActions ?? {},
|
|
177
|
+
) as Array<ReducerIndexedCardActionEntry<Contract>>;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function stageEntriesOf<Contract extends ReducerGameContractLike>(
|
|
181
|
+
phase: ReducerIndexedPhase<Contract>,
|
|
182
|
+
): Array<ReducerIndexedStageEntry<Contract>> {
|
|
183
|
+
return Object.entries(
|
|
184
|
+
(phase as { stages?: Record<string, unknown> }).stages ?? {},
|
|
185
|
+
) as Array<ReducerIndexedStageEntry<Contract>>;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function zoneEntriesOf<Contract extends ReducerGameContractLike>(
|
|
189
|
+
phase: ReducerIndexedPhase<Contract>,
|
|
190
|
+
): Array<ReducerIndexedZoneEntry<Contract>> {
|
|
191
|
+
return Array.from(
|
|
192
|
+
(phase as { zones?: readonly unknown[] }).zones ?? [],
|
|
193
|
+
) as Array<ReducerIndexedZoneEntry<Contract>>;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function effectEntriesOf<Contract extends ReducerGameContractLike>(
|
|
197
|
+
phase: ReducerIndexedPhase<Contract>,
|
|
198
|
+
): Array<RawEffectEntry<Contract>> {
|
|
199
|
+
return Object.entries(
|
|
200
|
+
(phase as { effects?: Record<string, unknown> }).effects ?? {},
|
|
201
|
+
) as Array<RawEffectEntry<Contract>>;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function collectReducerDefinitionIndex<
|
|
205
|
+
Contract extends ReducerGameContractLike,
|
|
206
|
+
Definitions extends PhaseMapOf<Contract>,
|
|
207
|
+
Views extends ViewMapOf<Contract>,
|
|
208
|
+
>(
|
|
209
|
+
definition: ReducerGameDefinition<Contract, Definitions, Views>,
|
|
210
|
+
): ReducerDefinitionIndex<Contract, Definitions, Views> {
|
|
211
|
+
type State = BaseGameStateOfContract<Contract>;
|
|
212
|
+
type Manifest = ManifestContractOf<Contract>;
|
|
213
|
+
type PhaseName = PhaseNamesOfDefinition<
|
|
214
|
+
ReducerGameDefinition<Contract, Definitions, Views>
|
|
215
|
+
>;
|
|
216
|
+
|
|
217
|
+
const phaseEntries = phaseEntriesOf(definition);
|
|
218
|
+
const phasesByName = new Map<
|
|
219
|
+
PhaseName,
|
|
220
|
+
ReducerDefinitionPhaseIndex<Contract, Definitions, Views>
|
|
221
|
+
>();
|
|
222
|
+
|
|
223
|
+
for (const [phaseName, phase] of phaseEntries) {
|
|
224
|
+
const interactionEntries = [
|
|
225
|
+
...interactionEntriesOf(phase),
|
|
226
|
+
...simultaneousSubmitEntriesOf(phase),
|
|
227
|
+
];
|
|
228
|
+
const cardActionEntries: Array<ReducerIndexedInteractionEntry<Contract>> =
|
|
229
|
+
cardActionEntriesOf(phase).map(([cardActionId, cardAction]) => [
|
|
230
|
+
cardActionId,
|
|
231
|
+
{
|
|
232
|
+
...cardAction,
|
|
233
|
+
__steps: cardAction.__steps,
|
|
234
|
+
inputs: {
|
|
235
|
+
cardId: cardInput<State>({
|
|
236
|
+
target: cardTarget
|
|
237
|
+
.zones<State>([cardAction.playFrom])
|
|
238
|
+
.where({
|
|
239
|
+
id: "card-type",
|
|
240
|
+
errorCode: "WRONG_CARD_TYPE",
|
|
241
|
+
message: `Card must be ${cardAction.cardType}.`,
|
|
242
|
+
test: ({ q, targetId }) =>
|
|
243
|
+
q.card.get(targetId).cardType === cardAction.cardType,
|
|
244
|
+
})
|
|
245
|
+
.build(),
|
|
246
|
+
}),
|
|
247
|
+
...(cardAction.inputs ?? {}),
|
|
248
|
+
},
|
|
249
|
+
} satisfies AnyInteractionSpec<State, Manifest>,
|
|
250
|
+
]);
|
|
251
|
+
const effects: Array<ReducerIndexedEffectEntry<Contract>> = effectEntriesOf(
|
|
252
|
+
phase,
|
|
253
|
+
).map(([effectKey, effectValue]) => [
|
|
254
|
+
effectKey,
|
|
255
|
+
{
|
|
256
|
+
id: effectValue.id ?? effectKey,
|
|
257
|
+
type: effectValue.type ?? "rollDie",
|
|
258
|
+
continuation: effectValue.__continuation,
|
|
259
|
+
},
|
|
260
|
+
]);
|
|
261
|
+
|
|
262
|
+
phasesByName.set(phaseName, {
|
|
263
|
+
phaseName,
|
|
264
|
+
phase,
|
|
265
|
+
interactions: [...interactionEntries, ...cardActionEntries],
|
|
266
|
+
stages: stageEntriesOf(phase),
|
|
267
|
+
zones: zoneEntriesOf(phase),
|
|
268
|
+
cardActions: cardActionEntriesOf(phase),
|
|
269
|
+
effects,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
phaseEntries,
|
|
275
|
+
phasesByName,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { RuntimeTableRecord } from "./model/table";
|
|
2
|
+
import type { TableQueriesOfState } from "./model/queries";
|
|
3
|
+
import type { BaseGameStateOfContract } from "./model/definition";
|
|
4
|
+
import { createStateQueries } from "./table-queries";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A freestanding, memoized projection of immutable game state.
|
|
8
|
+
*
|
|
9
|
+
* Use `defineDerived` for values that are pure functions of component
|
|
10
|
+
* locations, zone contents, resources, or other state fields - and are
|
|
11
|
+
* consumed from multiple reducer or view call sites, or whose compute cost
|
|
12
|
+
* is non-trivial (graph walks, aggregate reductions).
|
|
13
|
+
*
|
|
14
|
+
* Do NOT cache derived values in `publicState`. State fields should be
|
|
15
|
+
* inputs to derivations, not mirrors of them.
|
|
16
|
+
*/
|
|
17
|
+
export type DerivedDefinition<Contract, Value> = {
|
|
18
|
+
/** Optional label for debugging. Does not affect caching. */
|
|
19
|
+
readonly name?: string;
|
|
20
|
+
readonly compute: (ctx: {
|
|
21
|
+
readonly state: BaseGameStateOfContract<Contract>;
|
|
22
|
+
readonly q: TableQueriesOfState<BaseGameStateOfContract<Contract>>;
|
|
23
|
+
readonly derived: DerivedResolver;
|
|
24
|
+
}) => Value;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Resolves a `DerivedDefinition` to its value. Callers do not need to know
|
|
29
|
+
* about memoization - the resolver handles caching internally.
|
|
30
|
+
*/
|
|
31
|
+
export type DerivedResolver = <Value>(
|
|
32
|
+
def: DerivedDefinition<never, Value>,
|
|
33
|
+
) => Value;
|
|
34
|
+
|
|
35
|
+
type AnyDerivedDefinition = DerivedDefinition<unknown, unknown>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Author-facing factory for creating a derived value tied to a game
|
|
39
|
+
* contract. Usage:
|
|
40
|
+
*
|
|
41
|
+
* ```ts
|
|
42
|
+
* export const longestRoad = defineDerived<GameContract>()({
|
|
43
|
+
* name: "longestRoad",
|
|
44
|
+
* compute: ({ q, derived }) => computeLongestRoad(q),
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export function defineDerived<Contract>() {
|
|
49
|
+
return <Value>(def: {
|
|
50
|
+
name?: string;
|
|
51
|
+
compute: (ctx: {
|
|
52
|
+
state: BaseGameStateOfContract<Contract>;
|
|
53
|
+
q: TableQueriesOfState<BaseGameStateOfContract<Contract>>;
|
|
54
|
+
derived: DerivedResolver;
|
|
55
|
+
}) => Value;
|
|
56
|
+
}): DerivedDefinition<Contract, Value> => def;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Creates a resolver scoped to a single state snapshot. Each engine tick
|
|
61
|
+
* and each view projection call creates its own resolver; cache lifetime
|
|
62
|
+
* ends with the call. Cache key is the `DerivedDefinition` identity (the
|
|
63
|
+
* object reference).
|
|
64
|
+
*
|
|
65
|
+
* Re-entry on an in-flight definition throws a readable error.
|
|
66
|
+
*/
|
|
67
|
+
export function createDerivedResolver<
|
|
68
|
+
State extends { table: RuntimeTableRecord },
|
|
69
|
+
>(
|
|
70
|
+
state: State,
|
|
71
|
+
options: { q?: TableQueriesOfState<State> } = {},
|
|
72
|
+
): DerivedResolver {
|
|
73
|
+
const cache = new Map<AnyDerivedDefinition, unknown>();
|
|
74
|
+
const inflight = new Set<AnyDerivedDefinition>();
|
|
75
|
+
const q = options.q ?? createStateQueries(state);
|
|
76
|
+
|
|
77
|
+
const resolver: DerivedResolver = <Value>(
|
|
78
|
+
def: DerivedDefinition<never, Value>,
|
|
79
|
+
): Value => {
|
|
80
|
+
const typedDef = def as unknown as AnyDerivedDefinition;
|
|
81
|
+
if (cache.has(typedDef)) {
|
|
82
|
+
return cache.get(typedDef) as Value;
|
|
83
|
+
}
|
|
84
|
+
if (inflight.has(typedDef)) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`Cyclic derived: '${def.name ?? "<anonymous>"}' depends on itself.`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
inflight.add(typedDef);
|
|
90
|
+
try {
|
|
91
|
+
const value = (
|
|
92
|
+
def.compute as unknown as (ctx: {
|
|
93
|
+
state: unknown;
|
|
94
|
+
q: unknown;
|
|
95
|
+
derived: DerivedResolver;
|
|
96
|
+
}) => Value
|
|
97
|
+
)({ state, q, derived: resolver });
|
|
98
|
+
cache.set(typedDef, value);
|
|
99
|
+
return value;
|
|
100
|
+
} finally {
|
|
101
|
+
inflight.delete(typedDef);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return resolver;
|
|
106
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AnyContinuationToken,
|
|
3
|
+
EffectSpecLike,
|
|
4
|
+
ReducerFx,
|
|
5
|
+
RuntimeTableRecord,
|
|
6
|
+
} from "./model";
|
|
7
|
+
|
|
8
|
+
type FxState = {
|
|
9
|
+
table: RuntimeTableRecord;
|
|
10
|
+
flow: { currentPhase: string };
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type InternalEffectDefinition = EffectSpecLike;
|
|
14
|
+
|
|
15
|
+
function buildResume(
|
|
16
|
+
effect: InternalEffectDefinition,
|
|
17
|
+
context: unknown,
|
|
18
|
+
): AnyContinuationToken | undefined {
|
|
19
|
+
if (!effect.__continuation) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
return (effect.__continuation as unknown as (data: unknown) => unknown)(
|
|
23
|
+
context ?? {},
|
|
24
|
+
) as AnyContinuationToken;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function invokeEffect(
|
|
28
|
+
effect: InternalEffectDefinition,
|
|
29
|
+
options: Record<string, unknown>,
|
|
30
|
+
) {
|
|
31
|
+
const continuation = buildResume(effect, options.context);
|
|
32
|
+
if (effect.type === "rollDie") {
|
|
33
|
+
return continuation === undefined
|
|
34
|
+
? {
|
|
35
|
+
kind: "engine.rollDie" as const,
|
|
36
|
+
dieId: options.dieId as string,
|
|
37
|
+
}
|
|
38
|
+
: {
|
|
39
|
+
kind: "engine.rollDie" as const,
|
|
40
|
+
dieId: options.dieId as string,
|
|
41
|
+
continuation,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (effect.type === "shuffleSharedZone") {
|
|
45
|
+
return continuation === undefined
|
|
46
|
+
? {
|
|
47
|
+
kind: "engine.shuffleSharedZone" as const,
|
|
48
|
+
zoneId: options.zoneId as string,
|
|
49
|
+
}
|
|
50
|
+
: {
|
|
51
|
+
kind: "engine.shuffleSharedZone" as const,
|
|
52
|
+
zoneId: options.zoneId as string,
|
|
53
|
+
continuation,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (effect.type === "shufflePlayerZone") {
|
|
57
|
+
return continuation === undefined
|
|
58
|
+
? {
|
|
59
|
+
kind: "engine.shufflePlayerZone" as const,
|
|
60
|
+
zoneId: options.zoneId as string,
|
|
61
|
+
playerId: options.playerId as string,
|
|
62
|
+
}
|
|
63
|
+
: {
|
|
64
|
+
kind: "engine.shufflePlayerZone" as const,
|
|
65
|
+
zoneId: options.zoneId as string,
|
|
66
|
+
playerId: options.playerId as string,
|
|
67
|
+
continuation,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
throw new Error(
|
|
71
|
+
`fx.effect: unsupported effect type '${(effect as InternalEffectDefinition).type}'.`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function createReducerFx<State extends FxState>(
|
|
76
|
+
_state: State,
|
|
77
|
+
): ReducerFx<State> {
|
|
78
|
+
const fx: ReducerFx<State> = {
|
|
79
|
+
transition(to) {
|
|
80
|
+
return { kind: "flow.transition", to } as ReturnType<
|
|
81
|
+
ReducerFx<State>["transition"]
|
|
82
|
+
>;
|
|
83
|
+
},
|
|
84
|
+
effect(effect, options) {
|
|
85
|
+
return invokeEffect(
|
|
86
|
+
effect as unknown as InternalEffectDefinition,
|
|
87
|
+
options as Record<string, unknown>,
|
|
88
|
+
) as ReturnType<ReducerFx<State>["effect"]>;
|
|
89
|
+
},
|
|
90
|
+
} as ReducerFx<State>;
|
|
91
|
+
return fx;
|
|
92
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DispatchTraceEntry,
|
|
3
|
+
TrustedReducerDispatchResult,
|
|
4
|
+
TrustedRuntimeInput,
|
|
5
|
+
} from "../core/types";
|
|
6
|
+
import type { RuntimeInstructionForState } from "../core/runtime-instruction";
|
|
7
|
+
|
|
8
|
+
export function createRuntimeInstructionEngine<
|
|
9
|
+
State,
|
|
10
|
+
PlayerId extends string,
|
|
11
|
+
Input extends TrustedRuntimeInput<PlayerId>,
|
|
12
|
+
>({
|
|
13
|
+
reduce,
|
|
14
|
+
resolveInstruction,
|
|
15
|
+
afterInput,
|
|
16
|
+
}: {
|
|
17
|
+
reduce: (
|
|
18
|
+
state: State,
|
|
19
|
+
input: Input,
|
|
20
|
+
) =>
|
|
21
|
+
| { type: "reject"; errorCode: string; message?: string }
|
|
22
|
+
| {
|
|
23
|
+
type: "accept";
|
|
24
|
+
state: State;
|
|
25
|
+
instructions?: RuntimeInstructionForState<State>[];
|
|
26
|
+
};
|
|
27
|
+
resolveInstruction: (
|
|
28
|
+
state: State,
|
|
29
|
+
instruction: RuntimeInstructionForState<State>,
|
|
30
|
+
) => {
|
|
31
|
+
state: State;
|
|
32
|
+
queuedInputs: Input[];
|
|
33
|
+
queuedInstructions: RuntimeInstructionForState<State>[];
|
|
34
|
+
trace: DispatchTraceEntry<State, PlayerId, Input>[];
|
|
35
|
+
};
|
|
36
|
+
afterInput?: (
|
|
37
|
+
state: State,
|
|
38
|
+
input: Input,
|
|
39
|
+
) => {
|
|
40
|
+
state: State;
|
|
41
|
+
trace: DispatchTraceEntry<State, PlayerId, Input>[];
|
|
42
|
+
};
|
|
43
|
+
}) {
|
|
44
|
+
function drainInstructions(
|
|
45
|
+
state: State,
|
|
46
|
+
instructionsToDrain: RuntimeInstructionForState<State>[],
|
|
47
|
+
): State {
|
|
48
|
+
let workingState = state;
|
|
49
|
+
const instructionQueue = [...instructionsToDrain];
|
|
50
|
+
const systemQueue: Input[] = [];
|
|
51
|
+
|
|
52
|
+
while (instructionQueue.length > 0 || systemQueue.length > 0) {
|
|
53
|
+
while (instructionQueue.length > 0) {
|
|
54
|
+
const instruction = instructionQueue.shift();
|
|
55
|
+
if (instruction === undefined) break;
|
|
56
|
+
const resolved = resolveInstruction(workingState, instruction);
|
|
57
|
+
workingState = resolved.state;
|
|
58
|
+
if (resolved.queuedInstructions.length > 0) {
|
|
59
|
+
instructionQueue.unshift(...resolved.queuedInstructions);
|
|
60
|
+
}
|
|
61
|
+
systemQueue.push(...resolved.queuedInputs);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
while (systemQueue.length > 0) {
|
|
65
|
+
const input = systemQueue.shift();
|
|
66
|
+
if (input === undefined) break;
|
|
67
|
+
const result = reduce(workingState, input);
|
|
68
|
+
if (result.type === "reject") {
|
|
69
|
+
throw new Error(
|
|
70
|
+
result.message ??
|
|
71
|
+
`Reducer rejected internal input '${input.kind}'.`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
workingState = result.state;
|
|
75
|
+
instructionQueue.push(...(result.instructions ?? []));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return workingState;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function dispatch(
|
|
83
|
+
state: State,
|
|
84
|
+
input: Input,
|
|
85
|
+
): TrustedReducerDispatchResult<State, PlayerId> {
|
|
86
|
+
let workingState = state;
|
|
87
|
+
const pendingInputs: Input[] = [input];
|
|
88
|
+
const trace: DispatchTraceEntry<State, PlayerId, Input>[] = [
|
|
89
|
+
{
|
|
90
|
+
type: "acceptedClientInput",
|
|
91
|
+
input,
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
while (pendingInputs.length > 0) {
|
|
96
|
+
const pendingInput = pendingInputs.shift();
|
|
97
|
+
if (pendingInput === undefined) break;
|
|
98
|
+
const result = reduce(workingState, pendingInput);
|
|
99
|
+
if (result.type === "reject") {
|
|
100
|
+
if (pendingInput === input) {
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
throw new Error(
|
|
104
|
+
result.message ??
|
|
105
|
+
`Reducer rejected internal input '${pendingInput.kind}'.`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
workingState = result.state;
|
|
110
|
+
|
|
111
|
+
if (afterInput) {
|
|
112
|
+
const afterInputResult = afterInput(workingState, pendingInput);
|
|
113
|
+
workingState = afterInputResult.state;
|
|
114
|
+
trace.push(...afterInputResult.trace);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const instructionQueue = [...(result.instructions ?? [])];
|
|
118
|
+
const continuationQueue: Input[] = [];
|
|
119
|
+
while (instructionQueue.length > 0 || continuationQueue.length > 0) {
|
|
120
|
+
while (instructionQueue.length > 0) {
|
|
121
|
+
const instruction = instructionQueue.shift();
|
|
122
|
+
if (instruction === undefined) break;
|
|
123
|
+
trace.push({
|
|
124
|
+
type: "appliedInstruction",
|
|
125
|
+
instruction,
|
|
126
|
+
});
|
|
127
|
+
const resolved = resolveInstruction(workingState, instruction);
|
|
128
|
+
workingState = resolved.state;
|
|
129
|
+
trace.push(...resolved.trace);
|
|
130
|
+
if (resolved.queuedInstructions.length > 0) {
|
|
131
|
+
instructionQueue.unshift(...resolved.queuedInstructions);
|
|
132
|
+
}
|
|
133
|
+
continuationQueue.push(...resolved.queuedInputs);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
while (continuationQueue.length > 0) {
|
|
137
|
+
const queued = continuationQueue.shift();
|
|
138
|
+
if (queued === undefined) break;
|
|
139
|
+
pendingInputs.push(queued);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
type: "accept",
|
|
146
|
+
state: workingState,
|
|
147
|
+
trace,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
dispatch,
|
|
153
|
+
drainInstructions,
|
|
154
|
+
};
|
|
155
|
+
}
|