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