@dreamboard-games/workspace-codegen 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/manifest-contract.js +38 -14
- package/dist/seeds.js +307 -260
- package/package.json +2 -2
- package/src/authoring-benchmark.test.ts +146 -29
- package/src/manifest-contract.test.ts +1 -1
- package/src/manifest-contract.ts +59 -14
- package/src/seeds.ts +311 -260
package/src/seeds.ts
CHANGED
|
@@ -14,9 +14,9 @@ import {
|
|
|
14
14
|
createClientParamSchemasByPhase,
|
|
15
15
|
} from "@dreamboard/app-sdk/reducer";
|
|
16
16
|
import type {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
ClientParamsOfInteractionOfDefinition,
|
|
18
|
+
DefaultedClientParamKeysOfInteractionOfDefinition,
|
|
19
|
+
InteractionIdOfDefinition,
|
|
20
20
|
InteractionIdOfDefinitionPhase,
|
|
21
21
|
PhaseNamesOfDefinition,
|
|
22
22
|
StageNamesOfDefinitionPhase,
|
|
@@ -24,35 +24,38 @@ import type {
|
|
|
24
24
|
ViewOfDefinition,
|
|
25
25
|
} from "@dreamboard/app-sdk/reducer";
|
|
26
26
|
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
} from "@dreamboard/ui-sdk";
|
|
49
|
-
import { createElement,
|
|
50
|
-
import {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
27
|
+
createDreamboardUI,
|
|
28
|
+
createResourceCounter,
|
|
29
|
+
type BoardHexGridProps as BoardHexGridPropsGeneric,
|
|
30
|
+
type BoardHexViewProps as BoardHexViewPropsGeneric,
|
|
31
|
+
type BoardSpaceIdOf,
|
|
32
|
+
type ClientParamSchemaMap,
|
|
33
|
+
type InteractionDescriptor,
|
|
34
|
+
type InteractionHandle,
|
|
35
|
+
type InteractionRouteMap as InteractionRouteMapGeneric,
|
|
36
|
+
type InteractionSwitchProps as InteractionSwitchPropsGeneric,
|
|
37
|
+
type InteractionSwitchRenderState,
|
|
38
|
+
type DreamboardUI,
|
|
39
|
+
type ResourceCounterComponents,
|
|
40
|
+
type ResourceDisplayConfig,
|
|
41
|
+
type TypedGame,
|
|
42
|
+
type UIContract,
|
|
43
|
+
type UIRootProps,
|
|
44
|
+
type ZoneCardAtProps,
|
|
45
|
+
type ZoneCardRenderItem,
|
|
46
|
+
type ZoneListProps,
|
|
47
|
+
type ZonePileCardsProps,
|
|
48
|
+
} from "@dreamboard/ui-sdk";
|
|
49
|
+
import { createElement, type ReactElement, type ReactNode } from "react";
|
|
50
|
+
import {
|
|
51
|
+
literals,
|
|
52
|
+
staticBoards,
|
|
53
|
+
type CardId,
|
|
54
|
+
type CardProperties,
|
|
55
|
+
type CardType,
|
|
56
|
+
type EdgeId,
|
|
55
57
|
type PlayerId,
|
|
58
|
+
type ResourceId,
|
|
56
59
|
type SpaceId,
|
|
57
60
|
type VertexId,
|
|
58
61
|
type ZoneId as ManifestZoneId,
|
|
@@ -151,8 +154,7 @@ export type InteractionDescriptorFor<Key extends InteractionKey> =
|
|
|
151
154
|
|
|
152
155
|
/**
|
|
153
156
|
* Params shape for a phase-qualified interaction key. Drives strong typing
|
|
154
|
-
* for
|
|
155
|
-
* \`submit\`, and \`setInput\`.
|
|
157
|
+
* for component-first interaction state, forms, and submits.
|
|
156
158
|
*/
|
|
157
159
|
export type InteractionParamsOf<Key extends InteractionKey> =
|
|
158
160
|
InteractionParams<PhaseOfInteractionKey<Key>, IdOfInteractionKey<Key>>;
|
|
@@ -175,6 +177,50 @@ type InteractionHandleDefaultedKeys<Key extends InteractionKey> = Extract<
|
|
|
175
177
|
keyof InteractionParamsShape<Key> & string
|
|
176
178
|
>;
|
|
177
179
|
|
|
180
|
+
type RequiredInteractionInputKeysOf<Key extends InteractionKey> = Exclude<
|
|
181
|
+
InteractionInputKeysOf<Key>,
|
|
182
|
+
InteractionHandleDefaultedKeys<Key>
|
|
183
|
+
>;
|
|
184
|
+
|
|
185
|
+
export type RequiredInteractionInputKey<Key extends InteractionKey> =
|
|
186
|
+
RequiredInteractionInputKeysOf<Key>;
|
|
187
|
+
|
|
188
|
+
export type ZeroInputInteractionKey = {
|
|
189
|
+
[K in InteractionKey]: InteractionInputKeysOf<K> extends never
|
|
190
|
+
? K
|
|
191
|
+
: never;
|
|
192
|
+
}[InteractionKey];
|
|
193
|
+
|
|
194
|
+
export type InputInteractionKey = Exclude<
|
|
195
|
+
InteractionKey,
|
|
196
|
+
ZeroInputInteractionKey
|
|
197
|
+
>;
|
|
198
|
+
|
|
199
|
+
export type InteractionRouteRenderState<Key extends InteractionKey> =
|
|
200
|
+
Omit<InteractionSwitchRenderState<Key>, "descriptor"> & {
|
|
201
|
+
descriptor: InteractionDescriptorFor<Key>;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
export type InteractionRouteMap = {
|
|
205
|
+
[K in InteractionKey]: (state: InteractionRouteRenderState<K>) => ReactNode;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
type ExactInteractionRoutes<Routes extends InteractionRouteMap> = Routes &
|
|
209
|
+
Record<Exclude<keyof Routes, keyof InteractionRouteMap>, never>;
|
|
210
|
+
|
|
211
|
+
export function defineInteractionRoutes<const Routes extends InteractionRouteMap>(
|
|
212
|
+
routes: ExactInteractionRoutes<Routes>,
|
|
213
|
+
): InteractionRouteMap {
|
|
214
|
+
return routes;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
type InteractionSwitchProps = Omit<
|
|
218
|
+
InteractionSwitchPropsGeneric<InteractionKey>,
|
|
219
|
+
"routes"
|
|
220
|
+
> & {
|
|
221
|
+
routes: InteractionRouteMap;
|
|
222
|
+
};
|
|
223
|
+
|
|
178
224
|
type InteractionInputKeyOf<Key extends InteractionKey> =
|
|
179
225
|
Key extends InteractionKey ? InteractionInputKeysOf<Key> : never;
|
|
180
226
|
|
|
@@ -206,6 +252,10 @@ type UIPromptOptionRegistry = {
|
|
|
206
252
|
[K in PromptOptionValue]: { value: K };
|
|
207
253
|
};
|
|
208
254
|
|
|
255
|
+
type UIPlayerRegistry = {
|
|
256
|
+
[K in PlayerId & string]: { player: K };
|
|
257
|
+
};
|
|
258
|
+
|
|
209
259
|
type UIZoneRegistry = {
|
|
210
260
|
[K in ZoneId & string]: { zone: K };
|
|
211
261
|
};
|
|
@@ -227,6 +277,7 @@ export const uiContract = {
|
|
|
227
277
|
inputs: {} as UIInputRegistry,
|
|
228
278
|
prompts: {} as UIPromptRegistry,
|
|
229
279
|
promptOptions: {} as UIPromptOptionRegistry,
|
|
280
|
+
players: {} as UIPlayerRegistry,
|
|
230
281
|
zones: {} as UIZoneRegistry,
|
|
231
282
|
cards: {} as UICardRegistry,
|
|
232
283
|
phases: {} as UIPhaseRegistry,
|
|
@@ -240,142 +291,6 @@ declare module "@dreamboard/ui-sdk" {
|
|
|
240
291
|
}
|
|
241
292
|
}
|
|
242
293
|
|
|
243
|
-
type WorkspaceUI = DreamboardUI<typeof uiContract>;
|
|
244
|
-
|
|
245
|
-
export const UI: WorkspaceUI = createDreamboardUI(uiContract);
|
|
246
|
-
export const Interaction = UI.Interaction;
|
|
247
|
-
export const Prompt = UI.Prompt;
|
|
248
|
-
export const PromptInbox = UI.PromptInbox;
|
|
249
|
-
export const PromptDialogHost = UI.PromptDialogHost;
|
|
250
|
-
export const PlayerRoster: WorkspaceUI["PlayerRoster"] = UI.PlayerRoster;
|
|
251
|
-
export const Phase = UI.Phase;
|
|
252
|
-
export const Zone = UI.Zone;
|
|
253
|
-
export const Board = UI.Board;
|
|
254
|
-
|
|
255
|
-
export type InteractionFieldRenderers<Key extends InteractionKey> =
|
|
256
|
-
InteractionFieldRenderMap<InteractionParamsShape<Key>>;
|
|
257
|
-
|
|
258
|
-
export type InteractionFormProps<Key extends InteractionKey> = Omit<
|
|
259
|
-
InteractionFormPropsGeneric<
|
|
260
|
-
InteractionParamsShape<Key>,
|
|
261
|
-
InteractionHandleDefaultedKeys<Key>
|
|
262
|
-
>,
|
|
263
|
-
"descriptor" | "handle"
|
|
264
|
-
> & {
|
|
265
|
-
descriptor: InteractionDescriptorFor<Key>;
|
|
266
|
-
handle: InteractionHandle<
|
|
267
|
-
InteractionParamsShape<Key>,
|
|
268
|
-
InteractionHandleDefaultedKeys<Key>
|
|
269
|
-
>;
|
|
270
|
-
renderFields?: InteractionFieldRenderers<Key>;
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
export type InteractionFieldProps<
|
|
274
|
-
Key extends InteractionKey,
|
|
275
|
-
InputKey extends keyof InteractionParamsShape<Key> & string,
|
|
276
|
-
> = Omit<
|
|
277
|
-
InteractionFieldPropsGeneric<InteractionParamsShape<Key>, InputKey>,
|
|
278
|
-
"descriptor" | "handle"
|
|
279
|
-
> & {
|
|
280
|
-
descriptor: InteractionDescriptorFor<Key>;
|
|
281
|
-
handle: InteractionHandle<
|
|
282
|
-
InteractionParamsShape<Key>,
|
|
283
|
-
InteractionHandleDefaultedKeys<Key>
|
|
284
|
-
>;
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
export const clientParamSchemasByPhase = createClientParamSchemasByPhase(
|
|
288
|
-
game,
|
|
289
|
-
) as ClientParamSchemaMap;
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Workspace-typed wrapper over \`@dreamboard/ui-sdk\`'s generic
|
|
293
|
-
* \`useInteractionByKey\`. The \`key\` argument is constrained to the
|
|
294
|
-
* generated {@link InteractionKey} union (typos are compile errors), and
|
|
295
|
-
* the returned {@link InteractionHandle} is parameterised on
|
|
296
|
-
* {@link InteractionParamsOf} so \`handle.draft\`, \`handle.submit\`, and
|
|
297
|
-
* \`handle.setInput\` are statically typed against the interaction's
|
|
298
|
-
* declared inputs.
|
|
299
|
-
*
|
|
300
|
-
* \`\`\`ts
|
|
301
|
-
* const handle = useInteractionByKey("play.placeThingCard");
|
|
302
|
-
* if (!handle) return null;
|
|
303
|
-
* handle.setInput("cardId", card.id); // inferred as ThingsDeckCardId
|
|
304
|
-
* await handle.submit(); // params typed from the reducer contract
|
|
305
|
-
* \`\`\`
|
|
306
|
-
*/
|
|
307
|
-
export function useInteractionByKey<Key extends InteractionKey>(
|
|
308
|
-
key: Key | null | undefined,
|
|
309
|
-
): InteractionHandle<
|
|
310
|
-
InteractionParamsShape<Key>,
|
|
311
|
-
InteractionHandleDefaultedKeys<Key>
|
|
312
|
-
> | null {
|
|
313
|
-
return useInteractionByKeyGeneric<
|
|
314
|
-
Key,
|
|
315
|
-
InteractionParamsShape<Key>,
|
|
316
|
-
InteractionHandleDefaultedKeys<Key>
|
|
317
|
-
>(key);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
export function InteractionForm<Key extends InteractionKey>(
|
|
321
|
-
props: InteractionFormProps<Key>,
|
|
322
|
-
): ReactElement {
|
|
323
|
-
return createElement(
|
|
324
|
-
InteractionFormGeneric<
|
|
325
|
-
InteractionParamsShape<Key>,
|
|
326
|
-
InteractionHandleDefaultedKeys<Key>
|
|
327
|
-
>,
|
|
328
|
-
props,
|
|
329
|
-
);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
export function InteractionField<
|
|
333
|
-
Key extends InteractionKey,
|
|
334
|
-
InputKey extends keyof InteractionParamsShape<Key> & string,
|
|
335
|
-
>(props: InteractionFieldProps<Key, InputKey>): ReactElement | null {
|
|
336
|
-
return createElement(
|
|
337
|
-
InteractionFieldGeneric<InteractionParamsShape<Key>, InputKey>,
|
|
338
|
-
props,
|
|
339
|
-
);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
/**
|
|
343
|
-
* Workspace-typed wrapper over \`@dreamboard/ui-sdk\`'s
|
|
344
|
-
* \`useBoardInteractions\`. Returns a {@link BoardInteractionsContext}
|
|
345
|
-
* narrowed to this workspace's board-interaction union for exhaustive
|
|
346
|
-
* downstream logic.
|
|
347
|
-
*
|
|
348
|
-
* \`\`\`tsx
|
|
349
|
-
* const board = useBoardInteractions();
|
|
350
|
-
* <HexGrid interactiveVertices={board.targetLayers.vertex()} />
|
|
351
|
-
* \`\`\`
|
|
352
|
-
*
|
|
353
|
-
* Reach for this whenever a screen keeps more than one board target
|
|
354
|
-
* interaction live simultaneously and dispatch is target-driven instead of
|
|
355
|
-
* armed-then-clicked. Ambiguous unarmed overlaps should be resolved in the UI
|
|
356
|
-
* by arming an explicit interaction/input route.
|
|
357
|
-
*/
|
|
358
|
-
export function useBoardInteractions(
|
|
359
|
-
options?: BoardInteractionsOptions,
|
|
360
|
-
): BoardInteractionsContext<BoardInteractions> {
|
|
361
|
-
return useBoardInteractionsGeneric<BoardInteractions>(options);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/** Workspace-typed active-player hook. */
|
|
365
|
-
export function useActivePlayers(): readonly PlayerId[] {
|
|
366
|
-
return useActivePlayersGeneric() as readonly PlayerId[];
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/** Workspace-typed reducer-native player view hook. */
|
|
370
|
-
export function useGameView(): GameView {
|
|
371
|
-
return useGameViewGeneric() as GameView;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
/** Workspace-typed player turn order hook. */
|
|
375
|
-
export function usePlayerTurnOrder(): readonly PlayerId[] {
|
|
376
|
-
return usePlayerTurnOrderGeneric() as readonly PlayerId[];
|
|
377
|
-
}
|
|
378
|
-
|
|
379
294
|
// -------------------------------------------------------------------------
|
|
380
295
|
// Typed hex-board view adapter
|
|
381
296
|
// -------------------------------------------------------------------------
|
|
@@ -394,50 +309,177 @@ export type HexBoardSpaceId<Id extends HexBoardId> = BoardSpaceIdOf<
|
|
|
394
309
|
HexBoardTopology<Id>
|
|
395
310
|
>;
|
|
396
311
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
>
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
312
|
+
export type HexBoardViewProps<
|
|
313
|
+
Id extends HexBoardId,
|
|
314
|
+
TSpaceView extends { id: HexBoardSpaceId<Id> },
|
|
315
|
+
> = Omit<BoardHexViewPropsGeneric<HexBoardTopology<Id>, TSpaceView>, "board"> & {
|
|
316
|
+
board: Id;
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
export type HexBoardGridProps<
|
|
320
|
+
Id extends HexBoardId,
|
|
321
|
+
TSpaceView extends { id: HexBoardSpaceId<Id> },
|
|
322
|
+
> = Omit<
|
|
323
|
+
BoardHexGridPropsGeneric<HexBoardTopology<Id>, TSpaceView>,
|
|
324
|
+
"board" | "interactions"
|
|
325
|
+
> & {
|
|
326
|
+
board: Id;
|
|
327
|
+
interactions?:
|
|
328
|
+
| "auto"
|
|
329
|
+
| false
|
|
330
|
+
| {
|
|
331
|
+
edge?: readonly InteractionKey[];
|
|
332
|
+
vertex?: readonly InteractionKey[];
|
|
333
|
+
space?: readonly InteractionKey[];
|
|
334
|
+
};
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
type WorkspaceBoard = Omit<DreamboardUI<typeof uiContract>["Board"], "HexView" | "HexGrid"> & {
|
|
338
|
+
HexView<
|
|
339
|
+
const Id extends HexBoardId,
|
|
340
|
+
const TSpaceView extends { id: HexBoardSpaceId<Id> },
|
|
341
|
+
>(props: HexBoardViewProps<Id, TSpaceView>): ReactElement;
|
|
342
|
+
HexGrid<
|
|
343
|
+
const Id extends HexBoardId,
|
|
344
|
+
const TSpaceView extends { id: HexBoardSpaceId<Id> },
|
|
345
|
+
>(props: HexBoardGridProps<Id, TSpaceView>): ReactElement;
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
type WorkspaceCardProperties = CardProperties extends Record<string, unknown>
|
|
349
|
+
? CardProperties
|
|
350
|
+
: Record<string, unknown>;
|
|
351
|
+
type WorkspaceZoneId = [ZoneId] extends [never] ? string : ZoneId;
|
|
352
|
+
type WorkspaceCardId = [CardId] extends [never] ? string : CardId;
|
|
353
|
+
type WorkspaceCardType = [CardType] extends [never] ? string : CardType;
|
|
354
|
+
|
|
355
|
+
// ZoneCardRenderItem is a discriminated union (hidden: true | false).
|
|
356
|
+
// A bare Omit<..., "zone"> would collapse the union to its common keys and
|
|
357
|
+
// strip cardType / properties from the hydrated branch, defeating the
|
|
358
|
+
// discriminated-union narrowing the SDK contract promises. Distribute Omit
|
|
359
|
+
// across the union so each branch keeps its own shape.
|
|
360
|
+
type WorkspaceZoneCard = ZoneCardRenderItem<
|
|
361
|
+
WorkspaceCardId & string,
|
|
362
|
+
WorkspaceCardType & string,
|
|
363
|
+
WorkspaceCardProperties
|
|
364
|
+
> extends infer Item
|
|
365
|
+
? Item extends { zone: string }
|
|
366
|
+
? Omit<Item, "zone"> & { zone: WorkspaceZoneId }
|
|
367
|
+
: never
|
|
368
|
+
: never;
|
|
369
|
+
|
|
370
|
+
type WorkspaceZone = Omit<
|
|
371
|
+
DreamboardUI<typeof uiContract>["Zone"],
|
|
372
|
+
"CardAt" | "TopCard" | "List" | "PileCards"
|
|
373
|
+
> & {
|
|
374
|
+
CardAt(
|
|
375
|
+
props: Omit<ZoneCardAtProps<WorkspaceZoneId>, "zone" | "children"> & {
|
|
376
|
+
zone?: WorkspaceZoneId;
|
|
377
|
+
children?: ReactNode | ((card: WorkspaceZoneCard) => ReactNode);
|
|
378
|
+
},
|
|
379
|
+
): ReactElement | null;
|
|
380
|
+
TopCard(
|
|
381
|
+
props: Omit<
|
|
382
|
+
ZoneCardAtProps<WorkspaceZoneId>,
|
|
383
|
+
"zone" | "index" | "children"
|
|
384
|
+
> & {
|
|
385
|
+
zone?: WorkspaceZoneId;
|
|
386
|
+
children?: ReactNode | ((card: WorkspaceZoneCard) => ReactNode);
|
|
387
|
+
},
|
|
388
|
+
): ReactElement | null;
|
|
389
|
+
List(
|
|
390
|
+
props: Omit<ZoneListProps, "children"> & {
|
|
391
|
+
children?: ReactNode | ((card: WorkspaceZoneCard) => ReactNode);
|
|
392
|
+
},
|
|
393
|
+
): ReactElement;
|
|
394
|
+
PileCards(
|
|
395
|
+
props: Omit<ZonePileCardsProps, "renderCard"> & {
|
|
396
|
+
renderCard: (card: WorkspaceZoneCard) => ReactNode;
|
|
397
|
+
},
|
|
398
|
+
): ReactElement | null;
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
type WorkspaceUI = Omit<
|
|
402
|
+
DreamboardUI<typeof uiContract>,
|
|
403
|
+
"Root" | "Game" | "Interaction" | "Board" | "Zone"
|
|
404
|
+
> & {
|
|
405
|
+
Root(props: UIRootProps): ReactElement;
|
|
406
|
+
readonly Game: TypedGame<typeof uiContract, GameView, PlayerId, PhaseName>;
|
|
407
|
+
readonly Interaction: Omit<
|
|
408
|
+
DreamboardUI<typeof uiContract>["Interaction"],
|
|
409
|
+
"Switch"
|
|
410
|
+
> & {
|
|
411
|
+
Switch(props: InteractionSwitchProps): ReactElement;
|
|
412
|
+
};
|
|
413
|
+
readonly Board: WorkspaceBoard;
|
|
414
|
+
readonly Zone: WorkspaceZone;
|
|
415
|
+
readonly ResourceCounter: ResourceCounterComponents<ResourceId>;
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
const baseUI = createDreamboardUI(uiContract);
|
|
419
|
+
|
|
420
|
+
const resourcePresentationById = literals.resourcePresentationById as Partial<
|
|
421
|
+
Record<string, { label?: string; icon?: string }>
|
|
422
|
+
>;
|
|
423
|
+
|
|
424
|
+
const resourceDisplayConfig = literals.resourceIds.map((resource) => {
|
|
425
|
+
const resourceId = resource as ResourceId;
|
|
426
|
+
const presentation = resourcePresentationById[resource as string];
|
|
427
|
+
return {
|
|
428
|
+
type: resourceId,
|
|
429
|
+
label: presentation?.label ?? resource,
|
|
430
|
+
icon: presentation?.icon ?? resource,
|
|
431
|
+
};
|
|
432
|
+
}) satisfies readonly ResourceDisplayConfig<ResourceId>[];
|
|
433
|
+
|
|
434
|
+
const resourceCounter = createResourceCounter<ResourceId>(resourceDisplayConfig);
|
|
435
|
+
|
|
436
|
+
export const Board: WorkspaceUI["Board"] = {
|
|
437
|
+
...baseUI.Board,
|
|
438
|
+
HexView<
|
|
439
|
+
const Id extends HexBoardId,
|
|
440
|
+
const TSpaceView extends { id: HexBoardSpaceId<Id> },
|
|
441
|
+
>({ board: boardId, ...props }: HexBoardViewProps<Id, TSpaceView>) {
|
|
442
|
+
return createElement(baseUI.Board.HexView<HexBoardTopology<Id>, TSpaceView>, {
|
|
443
|
+
...props,
|
|
444
|
+
board: hexStaticBoards[boardId],
|
|
445
|
+
});
|
|
446
|
+
},
|
|
447
|
+
HexGrid<
|
|
448
|
+
const Id extends HexBoardId,
|
|
449
|
+
const TSpaceView extends { id: HexBoardSpaceId<Id> },
|
|
450
|
+
>({ board: boardId, ...props }: HexBoardGridProps<Id, TSpaceView>) {
|
|
451
|
+
return createElement(baseUI.Board.HexGrid<HexBoardTopology<Id>, TSpaceView>, {
|
|
452
|
+
...props,
|
|
453
|
+
board: hexStaticBoards[boardId],
|
|
454
|
+
});
|
|
455
|
+
},
|
|
456
|
+
} as WorkspaceUI["Board"];
|
|
457
|
+
|
|
458
|
+
export const UI: WorkspaceUI = {
|
|
459
|
+
...baseUI,
|
|
460
|
+
Board,
|
|
461
|
+
ResourceCounter: resourceCounter,
|
|
462
|
+
};
|
|
463
|
+
export const Game: WorkspaceUI["Game"] = UI.Game;
|
|
464
|
+
export const Interaction = UI.Interaction;
|
|
465
|
+
export const Prompt = UI.Prompt;
|
|
466
|
+
export const PromptInbox = UI.PromptInbox;
|
|
467
|
+
export const PlayerRoster: WorkspaceUI["PlayerRoster"] = UI.PlayerRoster;
|
|
468
|
+
export const Dice: WorkspaceUI["Dice"] = UI.Dice;
|
|
469
|
+
export const Phase = UI.Phase;
|
|
470
|
+
export const Zone = UI.Zone;
|
|
471
|
+
export const ResourceCounter: WorkspaceUI["ResourceCounter"] = UI.ResourceCounter;
|
|
472
|
+
|
|
473
|
+
export const clientParamSchemasByPhase = createClientParamSchemasByPhase(
|
|
474
|
+
game,
|
|
475
|
+
) as ClientParamSchemaMap;
|
|
433
476
|
`;
|
|
434
477
|
}
|
|
435
478
|
|
|
436
479
|
function generateReducerSupportSeed(): string {
|
|
437
480
|
return `import {
|
|
438
|
-
|
|
481
|
+
createReducerEdit,
|
|
439
482
|
createStateQueries,
|
|
440
|
-
pipe,
|
|
441
483
|
} from "@dreamboard/app-sdk/reducer";
|
|
442
484
|
import type { GameState } from "./game-contract";
|
|
443
485
|
|
|
@@ -456,16 +498,12 @@ import type { GameState } from "./game-contract";
|
|
|
456
498
|
*
|
|
457
499
|
* Recommended authoring pattern inside a phase reducer:
|
|
458
500
|
*
|
|
459
|
-
* const
|
|
460
|
-
*
|
|
461
|
-
*
|
|
462
|
-
* );
|
|
463
|
-
* return accept(next);
|
|
501
|
+
* const tx = edit(state);
|
|
502
|
+
* tx.setActivePlayers([q.players.order()[0]]);
|
|
503
|
+
* return accept(tx.state);
|
|
464
504
|
*/
|
|
465
505
|
|
|
466
|
-
export const
|
|
467
|
-
|
|
468
|
-
export { pipe };
|
|
506
|
+
export const edit = createReducerEdit<GameState>();
|
|
469
507
|
|
|
470
508
|
export function stateQueries(state: GameState) {
|
|
471
509
|
return createStateQueries(state);
|
|
@@ -549,7 +587,7 @@ function generateReducerGameContractSeed(): string {
|
|
|
549
587
|
import { ids, manifestContract } from "../shared/manifest-contract";
|
|
550
588
|
import { defineGameContract, type GameStateOf } from "@dreamboard/app-sdk/reducer";
|
|
551
589
|
|
|
552
|
-
// Contract files should stay focused on schemas,
|
|
590
|
+
// Contract files should stay focused on schemas, phases, and exported
|
|
553
591
|
// state types. Put reducers in app/phases/* and pure computations in
|
|
554
592
|
// app/derived.ts or app/rules/*.
|
|
555
593
|
const publicStateSchema = z.object({
|
|
@@ -561,15 +599,14 @@ const hiddenStateSchema = z.object({});
|
|
|
561
599
|
|
|
562
600
|
export const gameContract = defineGameContract({
|
|
563
601
|
manifest: manifestContract,
|
|
564
|
-
// Keep this list in sync with the keys of the \`phases\` record in ./phases.
|
|
565
|
-
// Declaring phase names here narrows \`fx.transition(...)\`, \`phase.dispatch\`,
|
|
566
|
-
// and the flow state's \`currentPhase\` to a literal union.
|
|
567
|
-
phaseNames: ["setup"] as const,
|
|
568
602
|
state: {
|
|
569
603
|
public: publicStateSchema,
|
|
570
604
|
private: privateStateSchema,
|
|
571
605
|
hidden: hiddenStateSchema,
|
|
572
606
|
},
|
|
607
|
+
phases: {
|
|
608
|
+
setup: z.object({}),
|
|
609
|
+
},
|
|
573
610
|
});
|
|
574
611
|
|
|
575
612
|
export type GameContract = typeof gameContract;
|
|
@@ -683,8 +720,10 @@ function generateAppFrameworkTsConfig(): string {
|
|
|
683
720
|
moduleResolution: "bundler",
|
|
684
721
|
strict: true,
|
|
685
722
|
esModuleInterop: true,
|
|
686
|
-
skipLibCheck:
|
|
723
|
+
skipLibCheck: false,
|
|
724
|
+
types: [],
|
|
687
725
|
declaration: false,
|
|
726
|
+
rootDir: "..",
|
|
688
727
|
outDir: "./dist",
|
|
689
728
|
paths: {
|
|
690
729
|
"@dreamboard/manifest-contract": ["../shared/manifest-contract.ts"],
|
|
@@ -706,49 +745,57 @@ function generateAppFrameworkTsConfig(): string {
|
|
|
706
745
|
}
|
|
707
746
|
|
|
708
747
|
function generateUiFrameworkTsConfig(): string {
|
|
709
|
-
return
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
]
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
"./**/*.tsx",
|
|
733
|
-
"../shared/**/*.ts"
|
|
734
|
-
],
|
|
735
|
-
"exclude": [
|
|
736
|
-
"node_modules",
|
|
737
|
-
"dist"
|
|
738
|
-
]
|
|
739
|
-
}
|
|
740
|
-
`;
|
|
748
|
+
return `${JSON.stringify(
|
|
749
|
+
{
|
|
750
|
+
compilerOptions: {
|
|
751
|
+
target: "ES2020",
|
|
752
|
+
module: "ESNext",
|
|
753
|
+
moduleResolution: "bundler",
|
|
754
|
+
jsx: "react-jsx",
|
|
755
|
+
strict: true,
|
|
756
|
+
esModuleInterop: true,
|
|
757
|
+
skipLibCheck: false,
|
|
758
|
+
types: [],
|
|
759
|
+
paths: {
|
|
760
|
+
"@dreamboard/manifest-contract": ["../shared/manifest-contract.ts"],
|
|
761
|
+
"@dreamboard/ui-contract": ["../shared/generated/ui-contract.ts"],
|
|
762
|
+
"@shared/*": ["../shared/*"],
|
|
763
|
+
},
|
|
764
|
+
},
|
|
765
|
+
include: ["./**/*.ts", "./**/*.tsx", "../shared/**/*.ts"],
|
|
766
|
+
exclude: ["node_modules", "dist"],
|
|
767
|
+
},
|
|
768
|
+
null,
|
|
769
|
+
2,
|
|
770
|
+
)}\n`;
|
|
741
771
|
}
|
|
742
772
|
|
|
743
773
|
function generateReducerUiAppContent(): string {
|
|
744
|
-
return `import { Phase, PromptInbox } from "@dreamboard/ui-contract";
|
|
774
|
+
return `import { Game, Phase, Prompt, PromptInbox } from "@dreamboard/ui-contract";
|
|
745
775
|
|
|
746
776
|
function SetupPhase() {
|
|
747
777
|
return (
|
|
748
778
|
<main>
|
|
749
779
|
<PromptInbox.Root>
|
|
750
780
|
<PromptInbox.Empty>No available prompts.</PromptInbox.Empty>
|
|
751
|
-
<PromptInbox.Items
|
|
781
|
+
<PromptInbox.Items>
|
|
782
|
+
{(prompt) => (
|
|
783
|
+
<Prompt.Root
|
|
784
|
+
key={prompt.interactionKey}
|
|
785
|
+
interaction={prompt.interactionKey}
|
|
786
|
+
>
|
|
787
|
+
<Prompt.Title />
|
|
788
|
+
<Prompt.Message />
|
|
789
|
+
<Prompt.Options>
|
|
790
|
+
{(option) => (
|
|
791
|
+
<Prompt.Option value={option.id}>
|
|
792
|
+
{option.label ?? option.id}
|
|
793
|
+
</Prompt.Option>
|
|
794
|
+
)}
|
|
795
|
+
</Prompt.Options>
|
|
796
|
+
</Prompt.Root>
|
|
797
|
+
)}
|
|
798
|
+
</PromptInbox.Items>
|
|
752
799
|
</PromptInbox.Root>
|
|
753
800
|
</main>
|
|
754
801
|
);
|
|
@@ -756,11 +803,15 @@ function SetupPhase() {
|
|
|
756
803
|
|
|
757
804
|
export default function App() {
|
|
758
805
|
return (
|
|
759
|
-
<
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
806
|
+
<Game.Root>
|
|
807
|
+
{() => (
|
|
808
|
+
<Phase.Switch
|
|
809
|
+
routes={{
|
|
810
|
+
setup: () => <SetupPhase />,
|
|
811
|
+
}}
|
|
812
|
+
/>
|
|
813
|
+
)}
|
|
814
|
+
</Game.Root>
|
|
764
815
|
);
|
|
765
816
|
}
|
|
766
817
|
`;
|