@dreamboard-games/workspace-codegen 0.1.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 +89 -0
- package/NOTICE +1 -0
- package/dist/hex-geometry.d.ts +2 -0
- package/dist/hex-geometry.js +49 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +22 -0
- package/dist/manifest-contract.d.ts +14 -0
- package/dist/manifest-contract.js +4897 -0
- package/dist/manifest-validation.d.ts +6 -0
- package/dist/manifest-validation.js +506 -0
- package/dist/ownership.d.ts +31 -0
- package/dist/ownership.js +86 -0
- package/dist/preset-card-sets.d.ts +5 -0
- package/dist/preset-card-sets.js +135 -0
- package/dist/seeds.d.ts +6 -0
- package/dist/seeds.js +766 -0
- package/ownership.json +51 -0
- package/package.json +46 -0
- package/src/__fixtures__/sdk-types/invalid-card-properties-extra-key.ts +62 -0
- package/src/__fixtures__/sdk-types/invalid-card-properties-missing-required.ts +60 -0
- package/src/__fixtures__/sdk-types/invalid-card-properties-wrong-enum.ts +61 -0
- package/src/__fixtures__/sdk-types/invalid-card-properties-wrong-nested.ts +61 -0
- package/src/__fixtures__/sdk-types/invalid-card-properties-wrong-scalar.ts +61 -0
- package/src/__fixtures__/sdk-types/invalid-card-visibility.ts +40 -0
- package/src/__fixtures__/sdk-types/invalid-container-card-set-manifest.ts +62 -0
- package/src/__fixtures__/sdk-types/invalid-die-fields-wrong-array-item.ts +43 -0
- package/src/__fixtures__/sdk-types/invalid-die-fields-wrong-player-id.ts +43 -0
- package/src/__fixtures__/sdk-types/invalid-die-fields-wrong-resource-id.ts +43 -0
- package/src/__fixtures__/sdk-types/invalid-die-home-per-player-zone-no-owner.ts +35 -0
- package/src/__fixtures__/sdk-types/invalid-die-seed-type-id.ts +31 -0
- package/src/__fixtures__/sdk-types/invalid-die-visibility.ts +28 -0
- package/src/__fixtures__/sdk-types/invalid-generic-board-edge-id.ts +38 -0
- package/src/__fixtures__/sdk-types/invalid-generic-board-nested-field.ts +45 -0
- package/src/__fixtures__/sdk-types/invalid-hex-edge-field-edge-id.ts +47 -0
- package/src/__fixtures__/sdk-types/invalid-hex-vertex-field-vertex-id.ts +47 -0
- package/src/__fixtures__/sdk-types/invalid-manifest.ts +143 -0
- package/src/__fixtures__/sdk-types/invalid-piece-fields-extra-key.ts +62 -0
- package/src/__fixtures__/sdk-types/invalid-piece-fields-wrong-card-id.ts +61 -0
- package/src/__fixtures__/sdk-types/invalid-piece-fields-wrong-enum.ts +61 -0
- package/src/__fixtures__/sdk-types/invalid-piece-fields-wrong-scalar.ts +61 -0
- package/src/__fixtures__/sdk-types/invalid-piece-fields-wrong-zone-id.ts +61 -0
- package/src/__fixtures__/sdk-types/invalid-piece-home-per-player-container-no-owner.ts +48 -0
- package/src/__fixtures__/sdk-types/invalid-piece-home-per-player-edge-no-owner.ts +47 -0
- package/src/__fixtures__/sdk-types/invalid-piece-home-per-player-space-no-owner.ts +42 -0
- package/src/__fixtures__/sdk-types/invalid-piece-home-per-player-vertex-no-owner.ts +49 -0
- package/src/__fixtures__/sdk-types/invalid-piece-seed-type-id.ts +30 -0
- package/src/__fixtures__/sdk-types/invalid-piece-visibility.ts +28 -0
- package/src/__fixtures__/sdk-types/invalid-slot-host-manifest.ts +47 -0
- package/src/__fixtures__/sdk-types/invalid-slot-id-manifest.ts +49 -0
- package/src/__fixtures__/sdk-types/invalid-square-board-edge-id.ts +47 -0
- package/src/__fixtures__/sdk-types/invalid-square-board-space-id.ts +47 -0
- package/src/__fixtures__/sdk-types/invalid-square-board-vertex-id.ts +49 -0
- package/src/__fixtures__/sdk-types/invalid-square-container-field-space-id.ts +59 -0
- package/src/__fixtures__/sdk-types/invalid-square-container-host-space-id.ts +49 -0
- package/src/__fixtures__/sdk-types/invalid-square-relation-field-scalar.ts +48 -0
- package/src/__fixtures__/sdk-types/invalid-square-relation-space-id.ts +48 -0
- package/src/__fixtures__/sdk-types/invalid-square-space-fields-enum.ts +44 -0
- package/src/__fixtures__/sdk-types/invalid-square-space-fields-extra-key.ts +45 -0
- package/src/__fixtures__/sdk-types/valid-die-type-omits-sides.ts +29 -0
- package/src/__fixtures__/sdk-types/valid-manifest-omits-board-templates.ts +19 -0
- package/src/__fixtures__/sdk-types/valid-manifest.ts +612 -0
- package/src/__fixtures__/sdk-types/valid-player-scoped-seed-homes.ts +59 -0
- package/src/authoring-benchmark.test.ts +362 -0
- package/src/hex-geometry.ts +69 -0
- package/src/index.ts +64 -0
- package/src/manifest-contract.test.ts +1764 -0
- package/src/manifest-contract.ts +6581 -0
- package/src/manifest-validation.test.ts +393 -0
- package/src/manifest-validation.ts +795 -0
- package/src/ownership.ts +127 -0
- package/src/preset-card-sets.ts +169 -0
- package/src/sdk-types-authoring.test.ts +361 -0
- package/src/seeds.ts +800 -0
|
@@ -0,0 +1,4897 @@
|
|
|
1
|
+
import { resolveHexVertexGeometryKey } from "./hex-geometry.js";
|
|
2
|
+
import { addStandardDecksIfNeeded, materializeCardSet, } from "./preset-card-sets.js";
|
|
3
|
+
function isCardPropertySchemaVariants(schema) {
|
|
4
|
+
return Boolean(schema && "variants" in schema);
|
|
5
|
+
}
|
|
6
|
+
function mergeSharedCardProperties(schema, cardType) {
|
|
7
|
+
const variant = schema.variants[cardType];
|
|
8
|
+
if (!variant) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
properties: {
|
|
13
|
+
...(schema.shared ?? {}),
|
|
14
|
+
...variant.properties,
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function hasPropertySchemaDefault(property) {
|
|
19
|
+
return Boolean(property && "default" in property);
|
|
20
|
+
}
|
|
21
|
+
function quote(value) {
|
|
22
|
+
return JSON.stringify(value);
|
|
23
|
+
}
|
|
24
|
+
function toPascalCase(input) {
|
|
25
|
+
return input
|
|
26
|
+
.split(/[_-]/g)
|
|
27
|
+
.filter(Boolean)
|
|
28
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
29
|
+
.join("");
|
|
30
|
+
}
|
|
31
|
+
function toHandleKey(input) {
|
|
32
|
+
if (!/[_-]/.test(input)) {
|
|
33
|
+
return input.charAt(0).toLowerCase() + input.slice(1);
|
|
34
|
+
}
|
|
35
|
+
const [first = "", ...rest] = input.split(/[_-]/g).filter(Boolean);
|
|
36
|
+
return [
|
|
37
|
+
first.toLowerCase(),
|
|
38
|
+
...rest.map((part) => {
|
|
39
|
+
const lower = part.toLowerCase();
|
|
40
|
+
return lower.charAt(0).toUpperCase() + lower.slice(1);
|
|
41
|
+
}),
|
|
42
|
+
].join("");
|
|
43
|
+
}
|
|
44
|
+
function renderBlocks(blocks) {
|
|
45
|
+
return blocks.filter((block) => Boolean(block)).join("\n\n");
|
|
46
|
+
}
|
|
47
|
+
function dedupeSorted(values) {
|
|
48
|
+
return Array.from(new Set(values)).sort();
|
|
49
|
+
}
|
|
50
|
+
function renderConstArray(values) {
|
|
51
|
+
return `[${values.map((value) => quote(value)).join(", ")}] as const`;
|
|
52
|
+
}
|
|
53
|
+
function renderStringUnion(values, fallback = "never") {
|
|
54
|
+
return values.length > 0
|
|
55
|
+
? values.map((value) => quote(value)).join(" | ")
|
|
56
|
+
: fallback;
|
|
57
|
+
}
|
|
58
|
+
function renderStringRecord(entries) {
|
|
59
|
+
const lines = Array.from(entries)
|
|
60
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
61
|
+
.map(([key, value]) => ` ${quote(key)}: ${quote(value)},`);
|
|
62
|
+
return `{\n${lines.join("\n")}\n} as const`;
|
|
63
|
+
}
|
|
64
|
+
function renderReadonlyArrayRecord(entries) {
|
|
65
|
+
const lines = Array.from(entries)
|
|
66
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
67
|
+
.map(([key, values]) => ` ${quote(key)}: [${values.map((value) => quote(value)).join(", ")}] as const,`);
|
|
68
|
+
return `{\n${lines.join("\n")}\n} as const`;
|
|
69
|
+
}
|
|
70
|
+
function renderJsonConst(value) {
|
|
71
|
+
return `${JSON.stringify(value, null, 2)} as const`;
|
|
72
|
+
}
|
|
73
|
+
const GENERATED_ID_FAMILIES = [
|
|
74
|
+
{
|
|
75
|
+
literalKey: "playerIds",
|
|
76
|
+
typeName: "PlayerId",
|
|
77
|
+
guardName: "PlayerId",
|
|
78
|
+
description: "player id",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
literalKey: "boardLayouts",
|
|
82
|
+
typeName: "BoardLayout",
|
|
83
|
+
guardName: "BoardLayout",
|
|
84
|
+
description: "board layout",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
literalKey: "setupOptionIds",
|
|
88
|
+
typeName: "SetupOptionId",
|
|
89
|
+
guardName: "SetupOptionId",
|
|
90
|
+
description: "setup option id",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
literalKey: "setupProfileIds",
|
|
94
|
+
typeName: "SetupProfileId",
|
|
95
|
+
guardName: "SetupProfileId",
|
|
96
|
+
description: "setup profile id",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
literalKey: "cardSetIds",
|
|
100
|
+
typeName: "CardSetId",
|
|
101
|
+
guardName: "CardSetId",
|
|
102
|
+
description: "card set id",
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
literalKey: "cardTypes",
|
|
106
|
+
typeName: "CardType",
|
|
107
|
+
guardName: "CardType",
|
|
108
|
+
description: "card type",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
literalKey: "cardIds",
|
|
112
|
+
typeName: "CardId",
|
|
113
|
+
guardName: "CardId",
|
|
114
|
+
description: "card id",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
literalKey: "deckIds",
|
|
118
|
+
typeName: "DeckId",
|
|
119
|
+
guardName: "DeckId",
|
|
120
|
+
description: "deck id",
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
literalKey: "handIds",
|
|
124
|
+
typeName: "HandId",
|
|
125
|
+
guardName: "HandId",
|
|
126
|
+
description: "hand id",
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
literalKey: "sharedZoneIds",
|
|
130
|
+
typeName: "SharedZoneId",
|
|
131
|
+
guardName: "SharedZoneId",
|
|
132
|
+
description: "shared zone id",
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
literalKey: "playerZoneIds",
|
|
136
|
+
typeName: "PlayerZoneId",
|
|
137
|
+
guardName: "PlayerZoneId",
|
|
138
|
+
description: "player zone id",
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
literalKey: "zoneIds",
|
|
142
|
+
typeName: "ZoneId",
|
|
143
|
+
guardName: "ZoneId",
|
|
144
|
+
description: "zone id",
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
literalKey: "resourceIds",
|
|
148
|
+
typeName: "ResourceId",
|
|
149
|
+
guardName: "ResourceId",
|
|
150
|
+
description: "resource id",
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
literalKey: "pieceTypeIds",
|
|
154
|
+
typeName: "PieceTypeId",
|
|
155
|
+
guardName: "PieceTypeId",
|
|
156
|
+
description: "piece type id",
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
literalKey: "pieceIds",
|
|
160
|
+
typeName: "PieceId",
|
|
161
|
+
guardName: "PieceId",
|
|
162
|
+
description: "piece id",
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
literalKey: "dieTypeIds",
|
|
166
|
+
typeName: "DieTypeId",
|
|
167
|
+
guardName: "DieTypeId",
|
|
168
|
+
description: "die type id",
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
literalKey: "dieIds",
|
|
172
|
+
typeName: "DieId",
|
|
173
|
+
guardName: "DieId",
|
|
174
|
+
description: "die id",
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
literalKey: "boardTypeIds",
|
|
178
|
+
typeName: "BoardTypeId",
|
|
179
|
+
guardName: "BoardTypeId",
|
|
180
|
+
description: "board type id",
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
literalKey: "boardBaseIds",
|
|
184
|
+
typeName: "BoardBaseId",
|
|
185
|
+
guardName: "BoardBaseId",
|
|
186
|
+
description: "board base id",
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
literalKey: "boardIds",
|
|
190
|
+
typeName: "BoardId",
|
|
191
|
+
guardName: "BoardId",
|
|
192
|
+
description: "board id",
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
literalKey: "boardContainerIds",
|
|
196
|
+
typeName: "BoardContainerId",
|
|
197
|
+
guardName: "BoardContainerId",
|
|
198
|
+
description: "board container id",
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
literalKey: "relationTypeIds",
|
|
202
|
+
typeName: "RelationTypeId",
|
|
203
|
+
guardName: "RelationTypeId",
|
|
204
|
+
description: "relation type id",
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
literalKey: "edgeIds",
|
|
208
|
+
typeName: "EdgeId",
|
|
209
|
+
guardName: "EdgeId",
|
|
210
|
+
description: "edge id",
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
literalKey: "edgeTypeIds",
|
|
214
|
+
typeName: "EdgeTypeId",
|
|
215
|
+
guardName: "EdgeTypeId",
|
|
216
|
+
description: "edge type id",
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
literalKey: "vertexIds",
|
|
220
|
+
typeName: "VertexId",
|
|
221
|
+
guardName: "VertexId",
|
|
222
|
+
description: "vertex id",
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
literalKey: "vertexTypeIds",
|
|
226
|
+
typeName: "VertexTypeId",
|
|
227
|
+
guardName: "VertexTypeId",
|
|
228
|
+
description: "vertex type id",
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
literalKey: "spaceIds",
|
|
232
|
+
typeName: "SpaceId",
|
|
233
|
+
guardName: "SpaceId",
|
|
234
|
+
description: "space id",
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
literalKey: "spaceTypeIds",
|
|
238
|
+
typeName: "SpaceTypeId",
|
|
239
|
+
guardName: "SpaceTypeId",
|
|
240
|
+
description: "space type id",
|
|
241
|
+
},
|
|
242
|
+
];
|
|
243
|
+
// `playerIds` are excluded from the generated `records`/`idGuards` surfaces:
|
|
244
|
+
// `PlayerId` is now an opaque brand whose runtime roster comes from the active
|
|
245
|
+
// session, not `manifest.players.maxPlayers`. Authors must use
|
|
246
|
+
// `perPlayer(runtimeIds, init)` and `asPlayerId` from `@dreamboard/app-sdk/reducer`
|
|
247
|
+
// instead of a `Record<PlayerId, T>` keyed on the max-players set.
|
|
248
|
+
const GENERATED_ID_FAMILIES_WITHOUT_PLAYERS = GENERATED_ID_FAMILIES.filter((family) => family.literalKey !== "playerIds");
|
|
249
|
+
function renderGeneratedRecordsHelpers() {
|
|
250
|
+
return `export const records = {
|
|
251
|
+
${GENERATED_ID_FAMILIES_WITHOUT_PLAYERS.map(({ literalKey, typeName }) => ` ${literalKey}<Value>(
|
|
252
|
+
initial: Value | ((${literalKey.slice(0, -1)}: ${typeName}) => Value),
|
|
253
|
+
): Record<${typeName}, Value> {
|
|
254
|
+
return buildTypedRecord(literals.${literalKey}, initial);
|
|
255
|
+
},`).join("\n")}
|
|
256
|
+
} as const;`;
|
|
257
|
+
}
|
|
258
|
+
function renderGeneratedIdGuards() {
|
|
259
|
+
return `export const idGuards = {
|
|
260
|
+
${GENERATED_ID_FAMILIES_WITHOUT_PLAYERS.map(({ literalKey, typeName, guardName, description, }) => ` is${guardName}(value: string): value is ${typeName} {
|
|
261
|
+
return isTypedId(literals.${literalKey}, value);
|
|
262
|
+
},
|
|
263
|
+
expect${guardName}(value: string): ${typeName} {
|
|
264
|
+
return expectTypedId(literals.${literalKey}, value, ${quote(description)});
|
|
265
|
+
},`).join("\n")}
|
|
266
|
+
} as const;`;
|
|
267
|
+
}
|
|
268
|
+
function renderGeneratedIdHandles(analysis) {
|
|
269
|
+
return `export const cardTypes = ${renderHandleObject("CardType", analysis.cardTypes)};
|
|
270
|
+
|
|
271
|
+
export const zones = ${renderHandleObject("ZoneId", analysis.zoneIds)};`;
|
|
272
|
+
}
|
|
273
|
+
function renderHandleObject(typeName, values) {
|
|
274
|
+
const lines = Array.from(values)
|
|
275
|
+
.sort((left, right) => left.localeCompare(right))
|
|
276
|
+
.map((value) => ` ${quote(toHandleKey(value))}: ${quote(value)},`);
|
|
277
|
+
return `{\n${lines.join("\n")}\n} as const satisfies Record<string, ${typeName}>`;
|
|
278
|
+
}
|
|
279
|
+
function sortedObject(entries) {
|
|
280
|
+
return Object.fromEntries(Array.from(entries).sort(([left], [right]) => left.localeCompare(right)));
|
|
281
|
+
}
|
|
282
|
+
function renderCardInstanceIds(card) {
|
|
283
|
+
if (card.count > 1) {
|
|
284
|
+
return Array.from({ length: card.count }, (_, index) => `${card.type}-${index + 1}`);
|
|
285
|
+
}
|
|
286
|
+
return [card.type];
|
|
287
|
+
}
|
|
288
|
+
function expandSeedIds(seeds) {
|
|
289
|
+
const expanded = [];
|
|
290
|
+
for (const seed of seeds) {
|
|
291
|
+
const count = seed.count ?? 1;
|
|
292
|
+
const baseId = seed.id ?? seed.typeId;
|
|
293
|
+
if (count <= 1) {
|
|
294
|
+
expanded.push(baseId);
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
for (let index = 1; index <= count; index += 1) {
|
|
298
|
+
expanded.push(`${baseId}-${index}`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return expanded;
|
|
302
|
+
}
|
|
303
|
+
function isHexBoardTemplateSpec(boardTemplate) {
|
|
304
|
+
return boardTemplate.layout === "hex";
|
|
305
|
+
}
|
|
306
|
+
function isSquareBoardTemplateSpec(boardTemplate) {
|
|
307
|
+
return boardTemplate.layout === "square";
|
|
308
|
+
}
|
|
309
|
+
function isGenericBoardTemplateSpec(boardTemplate) {
|
|
310
|
+
return boardTemplate.layout === "generic";
|
|
311
|
+
}
|
|
312
|
+
function isHexBoardSpec(board) {
|
|
313
|
+
return board.layout === "hex";
|
|
314
|
+
}
|
|
315
|
+
function isSquareBoardSpec(board) {
|
|
316
|
+
return board.layout === "square";
|
|
317
|
+
}
|
|
318
|
+
const HEX_SIDES = ["e", "ne", "nw", "w", "sw", "se"];
|
|
319
|
+
const HEX_CORNERS = ["ne-e", "e-se", "se-sw", "sw-w", "w-nw", "nw-ne"];
|
|
320
|
+
const HEX_SIDE_OFFSETS = {
|
|
321
|
+
e: [1, 0],
|
|
322
|
+
ne: [1, -1],
|
|
323
|
+
nw: [0, -1],
|
|
324
|
+
w: [-1, 0],
|
|
325
|
+
sw: [-1, 1],
|
|
326
|
+
se: [0, 1],
|
|
327
|
+
};
|
|
328
|
+
const HEX_CORNER_OFFSETS = {
|
|
329
|
+
"ne-e": [2, -1, -1],
|
|
330
|
+
"e-se": [1, -2, 1],
|
|
331
|
+
"se-sw": [-1, -1, 2],
|
|
332
|
+
"sw-w": [-2, 1, 1],
|
|
333
|
+
"w-nw": [-1, 2, -1],
|
|
334
|
+
"nw-ne": [1, 1, -2],
|
|
335
|
+
};
|
|
336
|
+
const HEX_SIDE_CORNERS = {
|
|
337
|
+
e: ["ne-e", "e-se"],
|
|
338
|
+
ne: ["nw-ne", "ne-e"],
|
|
339
|
+
nw: ["w-nw", "nw-ne"],
|
|
340
|
+
w: ["sw-w", "w-nw"],
|
|
341
|
+
sw: ["se-sw", "sw-w"],
|
|
342
|
+
se: ["e-se", "se-sw"],
|
|
343
|
+
};
|
|
344
|
+
const HEX_CORNER_SIDES = {
|
|
345
|
+
"ne-e": ["ne", "e"],
|
|
346
|
+
"e-se": ["e", "se"],
|
|
347
|
+
"se-sw": ["se", "sw"],
|
|
348
|
+
"sw-w": ["sw", "w"],
|
|
349
|
+
"w-nw": ["w", "nw"],
|
|
350
|
+
"nw-ne": ["nw", "ne"],
|
|
351
|
+
};
|
|
352
|
+
function cubeFromAxial(space) {
|
|
353
|
+
const x = space.q;
|
|
354
|
+
const z = space.r;
|
|
355
|
+
return [x, -x - z, z];
|
|
356
|
+
}
|
|
357
|
+
function cornerGeometryKey(space, corner) {
|
|
358
|
+
const [x, y, z] = cubeFromAxial(space);
|
|
359
|
+
const [dx, dy, dz] = HEX_CORNER_OFFSETS[corner];
|
|
360
|
+
return `${3 * x + dx},${3 * y + dy},${3 * z + dz}`;
|
|
361
|
+
}
|
|
362
|
+
function edgeGeometryKey(space, side) {
|
|
363
|
+
const [leftCorner, rightCorner] = HEX_SIDE_CORNERS[side];
|
|
364
|
+
return [
|
|
365
|
+
cornerGeometryKey(space, leftCorner),
|
|
366
|
+
cornerGeometryKey(space, rightCorner),
|
|
367
|
+
]
|
|
368
|
+
.sort((left, right) => left.localeCompare(right))
|
|
369
|
+
.join("::");
|
|
370
|
+
}
|
|
371
|
+
function edgeIdFromGeometryKey(key) {
|
|
372
|
+
return `hex-edge:${key}`;
|
|
373
|
+
}
|
|
374
|
+
function vertexIdFromGeometryKey(key) {
|
|
375
|
+
return `hex-vertex:${key}`;
|
|
376
|
+
}
|
|
377
|
+
const SQUARE_SIDES = ["north", "east", "south", "west"];
|
|
378
|
+
const SQUARE_CORNERS = ["nw", "ne", "se", "sw"];
|
|
379
|
+
function squareEdgeIdFromGeometryKey(key) {
|
|
380
|
+
return `square-edge:${key}`;
|
|
381
|
+
}
|
|
382
|
+
function squareVertexIdFromGeometryKey(key) {
|
|
383
|
+
return `square-vertex:${key}`;
|
|
384
|
+
}
|
|
385
|
+
function squareCornerGeometryKey(space, corner) {
|
|
386
|
+
switch (corner) {
|
|
387
|
+
case "nw":
|
|
388
|
+
return `${space.col},${space.row}`;
|
|
389
|
+
case "ne":
|
|
390
|
+
return `${space.col + 1},${space.row}`;
|
|
391
|
+
case "se":
|
|
392
|
+
return `${space.col + 1},${space.row + 1}`;
|
|
393
|
+
case "sw":
|
|
394
|
+
return `${space.col},${space.row + 1}`;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
function squareEdgeGeometryKey(space, side) {
|
|
398
|
+
const endpoints = side === "north"
|
|
399
|
+
? [`${space.col},${space.row}`, `${space.col + 1},${space.row}`]
|
|
400
|
+
: side === "east"
|
|
401
|
+
? [`${space.col + 1},${space.row}`, `${space.col + 1},${space.row + 1}`]
|
|
402
|
+
: side === "south"
|
|
403
|
+
? [
|
|
404
|
+
`${space.col},${space.row + 1}`,
|
|
405
|
+
`${space.col + 1},${space.row + 1}`,
|
|
406
|
+
]
|
|
407
|
+
: [`${space.col},${space.row}`, `${space.col},${space.row + 1}`];
|
|
408
|
+
return endpoints.sort((left, right) => left.localeCompare(right)).join("::");
|
|
409
|
+
}
|
|
410
|
+
function geometryKeyFromSquareEdgeRef(ref, spacesById) {
|
|
411
|
+
const resolvedSpaces = [...ref.spaces]
|
|
412
|
+
.sort((a, b) => a.localeCompare(b))
|
|
413
|
+
.map((spaceId) => {
|
|
414
|
+
const space = spacesById.get(spaceId);
|
|
415
|
+
if (!space) {
|
|
416
|
+
throw new Error(`Square edge ref references unknown space '${spaceId}'.`);
|
|
417
|
+
}
|
|
418
|
+
return space;
|
|
419
|
+
});
|
|
420
|
+
const keyCounts = new Map();
|
|
421
|
+
for (const space of resolvedSpaces) {
|
|
422
|
+
for (const side of SQUARE_SIDES) {
|
|
423
|
+
const key = squareEdgeGeometryKey(space, side);
|
|
424
|
+
keyCounts.set(key, (keyCounts.get(key) ?? 0) + 1);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
const candidates = [...keyCounts.entries()]
|
|
428
|
+
.filter(([, count]) => count === resolvedSpaces.length)
|
|
429
|
+
.map(([key]) => key)
|
|
430
|
+
.sort((left, right) => left.localeCompare(right));
|
|
431
|
+
if (candidates.length !== 1) {
|
|
432
|
+
throw new Error(`Square edge ref spaces '${ref.spaces.join(", ")}' do not resolve to exactly one shared edge.`);
|
|
433
|
+
}
|
|
434
|
+
const [only] = candidates;
|
|
435
|
+
if (only === undefined) {
|
|
436
|
+
throw new Error("unreachable: candidates.length === 1 but first is undefined");
|
|
437
|
+
}
|
|
438
|
+
return only;
|
|
439
|
+
}
|
|
440
|
+
function geometryKeyFromSquareVertexRef(ref, spacesById) {
|
|
441
|
+
const resolvedSpaces = [...ref.spaces]
|
|
442
|
+
.sort((a, b) => a.localeCompare(b))
|
|
443
|
+
.map((spaceId) => {
|
|
444
|
+
const space = spacesById.get(spaceId);
|
|
445
|
+
if (!space) {
|
|
446
|
+
throw new Error(`Square vertex ref references unknown space '${spaceId}'.`);
|
|
447
|
+
}
|
|
448
|
+
return space;
|
|
449
|
+
});
|
|
450
|
+
const keyCounts = new Map();
|
|
451
|
+
for (const space of resolvedSpaces) {
|
|
452
|
+
for (const corner of SQUARE_CORNERS) {
|
|
453
|
+
const key = squareCornerGeometryKey(space, corner);
|
|
454
|
+
keyCounts.set(key, (keyCounts.get(key) ?? 0) + 1);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const candidates = [...keyCounts.entries()]
|
|
458
|
+
.filter(([, count]) => count === resolvedSpaces.length)
|
|
459
|
+
.map(([key]) => key)
|
|
460
|
+
.sort((left, right) => left.localeCompare(right));
|
|
461
|
+
if (candidates.length !== 1) {
|
|
462
|
+
throw new Error(`Square vertex ref spaces '${ref.spaces.join(", ")}' do not resolve to exactly one shared vertex.`);
|
|
463
|
+
}
|
|
464
|
+
const [only] = candidates;
|
|
465
|
+
if (only === undefined) {
|
|
466
|
+
throw new Error("unreachable: candidates.length === 1 but first is undefined");
|
|
467
|
+
}
|
|
468
|
+
return only;
|
|
469
|
+
}
|
|
470
|
+
function geometryKeyFromEdgeRef(ref, spacesById) {
|
|
471
|
+
const [leftId, rightId] = [...ref.spaces].sort((a, b) => a.localeCompare(b));
|
|
472
|
+
const leftSpace = spacesById.get(leftId);
|
|
473
|
+
const rightSpace = spacesById.get(rightId);
|
|
474
|
+
if (!leftSpace || !rightSpace) {
|
|
475
|
+
throw new Error(`Hex edge ref references unknown spaces: ${ref.spaces.join(", ")}.`);
|
|
476
|
+
}
|
|
477
|
+
const [dq, dr] = [rightSpace.q - leftSpace.q, rightSpace.r - leftSpace.r];
|
|
478
|
+
const side = Object.entries(HEX_SIDE_OFFSETS).find(([, [sideQ, sideR]]) => sideQ === dq && sideR === dr)?.[0];
|
|
479
|
+
if (!side) {
|
|
480
|
+
throw new Error(`Hex edge ref spaces '${leftId}' and '${rightId}' are not adjacent.`);
|
|
481
|
+
}
|
|
482
|
+
return edgeGeometryKey(leftSpace, side);
|
|
483
|
+
}
|
|
484
|
+
function geometryKeyFromVertexRef(ref, spacesById) {
|
|
485
|
+
return resolveHexVertexGeometryKey(ref, spacesById);
|
|
486
|
+
}
|
|
487
|
+
function deriveHexEdges(spaces) {
|
|
488
|
+
const spacesByCoordinate = new Map();
|
|
489
|
+
for (const space of spaces) {
|
|
490
|
+
spacesByCoordinate.set(`${space.q},${space.r}`, space);
|
|
491
|
+
}
|
|
492
|
+
const edgeMap = new Map();
|
|
493
|
+
for (const space of spaces) {
|
|
494
|
+
for (const side of HEX_SIDES) {
|
|
495
|
+
const [dq, dr] = HEX_SIDE_OFFSETS[side];
|
|
496
|
+
const neighbor = spacesByCoordinate.get(`${space.q + dq},${space.r + dr}`);
|
|
497
|
+
const geometryKey = edgeGeometryKey(space, side);
|
|
498
|
+
const existing = edgeMap.get(geometryKey);
|
|
499
|
+
const nextSpaceIds = dedupeSorted([
|
|
500
|
+
...(existing?.spaceIds ?? []),
|
|
501
|
+
space.id,
|
|
502
|
+
...(neighbor ? [neighbor.id] : []),
|
|
503
|
+
]);
|
|
504
|
+
edgeMap.set(geometryKey, {
|
|
505
|
+
id: edgeIdFromGeometryKey(geometryKey),
|
|
506
|
+
geometryKey,
|
|
507
|
+
spaceIds: nextSpaceIds,
|
|
508
|
+
typeId: existing?.typeId ?? null,
|
|
509
|
+
label: existing?.label ?? null,
|
|
510
|
+
fields: existing?.fields ?? null,
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return [...edgeMap.values()].sort((left, right) => left.id.localeCompare(right.id));
|
|
515
|
+
}
|
|
516
|
+
function deriveHexVertices(spaces) {
|
|
517
|
+
const spacesByCoordinate = new Map();
|
|
518
|
+
for (const space of spaces) {
|
|
519
|
+
spacesByCoordinate.set(`${space.q},${space.r}`, space);
|
|
520
|
+
}
|
|
521
|
+
const vertexMap = new Map();
|
|
522
|
+
for (const space of spaces) {
|
|
523
|
+
for (const corner of HEX_CORNERS) {
|
|
524
|
+
const [leftSide, rightSide] = HEX_CORNER_SIDES[corner];
|
|
525
|
+
const [leftQ, leftR] = HEX_SIDE_OFFSETS[leftSide];
|
|
526
|
+
const [rightQ, rightR] = HEX_SIDE_OFFSETS[rightSide];
|
|
527
|
+
const leftNeighbor = spacesByCoordinate.get(`${space.q + leftQ},${space.r + leftR}`);
|
|
528
|
+
const rightNeighbor = spacesByCoordinate.get(`${space.q + rightQ},${space.r + rightR}`);
|
|
529
|
+
const geometryKey = cornerGeometryKey(space, corner);
|
|
530
|
+
const existing = vertexMap.get(geometryKey);
|
|
531
|
+
const nextSpaceIds = dedupeSorted([
|
|
532
|
+
...(existing?.spaceIds ?? []),
|
|
533
|
+
space.id,
|
|
534
|
+
...(leftNeighbor ? [leftNeighbor.id] : []),
|
|
535
|
+
...(rightNeighbor ? [rightNeighbor.id] : []),
|
|
536
|
+
]);
|
|
537
|
+
vertexMap.set(geometryKey, {
|
|
538
|
+
id: vertexIdFromGeometryKey(geometryKey),
|
|
539
|
+
geometryKey,
|
|
540
|
+
spaceIds: nextSpaceIds,
|
|
541
|
+
typeId: existing?.typeId ?? null,
|
|
542
|
+
label: existing?.label ?? null,
|
|
543
|
+
fields: existing?.fields ?? null,
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return [...vertexMap.values()].sort((left, right) => left.id.localeCompare(right.id));
|
|
548
|
+
}
|
|
549
|
+
function mergeBoardSpaces(templateSpaces, boardSpaces) {
|
|
550
|
+
return Array.from([...templateSpaces, ...boardSpaces]
|
|
551
|
+
.reduce((accumulator, space) => {
|
|
552
|
+
accumulator.set(space.id, space);
|
|
553
|
+
return accumulator;
|
|
554
|
+
}, new Map())
|
|
555
|
+
.values()).sort((left, right) => left.id.localeCompare(right.id));
|
|
556
|
+
}
|
|
557
|
+
function mergeBoardContainers(templateContainers, boardContainers) {
|
|
558
|
+
return Array.from([...templateContainers, ...boardContainers]
|
|
559
|
+
.reduce((accumulator, container) => {
|
|
560
|
+
accumulator.set(container.id, container);
|
|
561
|
+
return accumulator;
|
|
562
|
+
}, new Map())
|
|
563
|
+
.values()).sort((left, right) => left.id.localeCompare(right.id));
|
|
564
|
+
}
|
|
565
|
+
function resolveHexSpaces(board, template) {
|
|
566
|
+
if (!template) {
|
|
567
|
+
return [...(board.spaces ?? [])].sort((left, right) => left.id.localeCompare(right.id));
|
|
568
|
+
}
|
|
569
|
+
const templateSpacesById = new Map((template.spaces ?? []).map((space) => [space.id, space]));
|
|
570
|
+
const overridesById = new Map((board.spaces ?? []).map((space) => [space.id, space]));
|
|
571
|
+
for (const overrideId of overridesById.keys()) {
|
|
572
|
+
if (!templateSpacesById.has(overrideId)) {
|
|
573
|
+
throw new Error(`Hex board '${board.id}' overrides unknown space '${overrideId}' from template '${template.id}'.`);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return (template.spaces ?? [])
|
|
577
|
+
.map((templateSpace) => {
|
|
578
|
+
const override = overridesById.get(templateSpace.id);
|
|
579
|
+
if (!override) {
|
|
580
|
+
return templateSpace;
|
|
581
|
+
}
|
|
582
|
+
const templateMatch = templateSpacesById.get(override.id);
|
|
583
|
+
if (!templateMatch) {
|
|
584
|
+
throw new Error(`Hex board '${board.id}' overrides unknown space '${override.id}' from template '${template.id}'.`);
|
|
585
|
+
}
|
|
586
|
+
if (templateMatch.q !== override.q || templateMatch.r !== override.r) {
|
|
587
|
+
throw new Error(`Hex board '${board.id}' cannot change coordinates for space '${override.id}' from template '${template.id}'.`);
|
|
588
|
+
}
|
|
589
|
+
return {
|
|
590
|
+
...templateSpace,
|
|
591
|
+
...override,
|
|
592
|
+
id: templateSpace.id,
|
|
593
|
+
q: templateSpace.q,
|
|
594
|
+
r: templateSpace.r,
|
|
595
|
+
};
|
|
596
|
+
})
|
|
597
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
598
|
+
}
|
|
599
|
+
function indexHexEdgeMetadata(specs, spacesById, ownerLabel) {
|
|
600
|
+
const metadataByKey = new Map();
|
|
601
|
+
for (const spec of specs) {
|
|
602
|
+
const geometryKey = geometryKeyFromEdgeRef(spec.ref, spacesById);
|
|
603
|
+
if (metadataByKey.has(geometryKey)) {
|
|
604
|
+
throw new Error(`${ownerLabel} contains duplicate hex edge refs.`);
|
|
605
|
+
}
|
|
606
|
+
metadataByKey.set(geometryKey, {
|
|
607
|
+
geometryKey,
|
|
608
|
+
typeId: spec.typeId ?? null,
|
|
609
|
+
label: spec.label ?? null,
|
|
610
|
+
fields: spec.fields ?? null,
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
return metadataByKey;
|
|
614
|
+
}
|
|
615
|
+
function indexHexVertexMetadata(specs, spacesById, ownerLabel) {
|
|
616
|
+
const metadataByKey = new Map();
|
|
617
|
+
for (const spec of specs) {
|
|
618
|
+
const geometryKey = geometryKeyFromVertexRef(spec.ref, spacesById);
|
|
619
|
+
if (metadataByKey.has(geometryKey)) {
|
|
620
|
+
throw new Error(`${ownerLabel} contains duplicate hex vertex refs.`);
|
|
621
|
+
}
|
|
622
|
+
metadataByKey.set(geometryKey, {
|
|
623
|
+
geometryKey,
|
|
624
|
+
typeId: spec.typeId ?? null,
|
|
625
|
+
label: spec.label ?? null,
|
|
626
|
+
fields: spec.fields ?? null,
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
return metadataByKey;
|
|
630
|
+
}
|
|
631
|
+
function resolveAuthoredHexEdges(board, template, spaces) {
|
|
632
|
+
const spacesById = new Map(spaces.map((space) => [space.id, space]));
|
|
633
|
+
const derivedByGeometryKey = new Map(deriveHexEdges(spaces).map((edge) => [edge.geometryKey, edge]));
|
|
634
|
+
const collectSpecsByGeometryKey = (specs, ownerLabel) => {
|
|
635
|
+
const specsByGeometryKey = new Map();
|
|
636
|
+
for (const spec of specs) {
|
|
637
|
+
const geometryKey = geometryKeyFromEdgeRef(spec.ref, spacesById);
|
|
638
|
+
if (specsByGeometryKey.has(geometryKey)) {
|
|
639
|
+
throw new Error(`${ownerLabel} contains duplicate hex edge refs.`);
|
|
640
|
+
}
|
|
641
|
+
specsByGeometryKey.set(geometryKey, spec);
|
|
642
|
+
}
|
|
643
|
+
return specsByGeometryKey;
|
|
644
|
+
};
|
|
645
|
+
const templateSpecsByGeometryKey = template
|
|
646
|
+
? collectSpecsByGeometryKey(template.edges ?? [], `Hex board template '${template.id}'`)
|
|
647
|
+
: new Map();
|
|
648
|
+
const overrideSpecsByGeometryKey = collectSpecsByGeometryKey(board.edges ?? [], `Hex board '${board.id}'`);
|
|
649
|
+
if (template) {
|
|
650
|
+
for (const overrideKey of overrideSpecsByGeometryKey.keys()) {
|
|
651
|
+
if (!templateSpecsByGeometryKey.has(overrideKey)) {
|
|
652
|
+
throw new Error(`Hex board '${board.id}' overrides unknown edge ref from template '${template.id}'.`);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
const mergedSpecs = template
|
|
657
|
+
? Array.from(templateSpecsByGeometryKey.entries()).map(([geometryKey, templateSpec]) => [
|
|
658
|
+
geometryKey,
|
|
659
|
+
overrideSpecsByGeometryKey.get(geometryKey) ?? templateSpec,
|
|
660
|
+
])
|
|
661
|
+
: Array.from(overrideSpecsByGeometryKey.entries());
|
|
662
|
+
return mergedSpecs.map(([geometryKey, spec]) => {
|
|
663
|
+
const derivedEdge = derivedByGeometryKey.get(geometryKey);
|
|
664
|
+
if (!derivedEdge) {
|
|
665
|
+
throw new Error(`Hex edge ref on board '${board.id}' does not resolve to a derived edge.`);
|
|
666
|
+
}
|
|
667
|
+
return {
|
|
668
|
+
id: derivedEdge.id,
|
|
669
|
+
ref: cloneJson(spec.ref),
|
|
670
|
+
typeId: spec.typeId ?? null,
|
|
671
|
+
label: spec.label ?? null,
|
|
672
|
+
fields: spec.fields ?? null,
|
|
673
|
+
};
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
function resolveAuthoredHexVertices(board, template, spaces) {
|
|
677
|
+
const spacesById = new Map(spaces.map((space) => [space.id, space]));
|
|
678
|
+
const derivedByGeometryKey = new Map(deriveHexVertices(spaces).map((vertex) => [vertex.geometryKey, vertex]));
|
|
679
|
+
const collectSpecsByGeometryKey = (specs, ownerLabel) => {
|
|
680
|
+
const specsByGeometryKey = new Map();
|
|
681
|
+
for (const spec of specs) {
|
|
682
|
+
const geometryKey = geometryKeyFromVertexRef(spec.ref, spacesById);
|
|
683
|
+
if (specsByGeometryKey.has(geometryKey)) {
|
|
684
|
+
throw new Error(`${ownerLabel} contains duplicate hex vertex refs.`);
|
|
685
|
+
}
|
|
686
|
+
specsByGeometryKey.set(geometryKey, spec);
|
|
687
|
+
}
|
|
688
|
+
return specsByGeometryKey;
|
|
689
|
+
};
|
|
690
|
+
const templateSpecsByGeometryKey = template
|
|
691
|
+
? collectSpecsByGeometryKey(template.vertices ?? [], `Hex board template '${template.id}'`)
|
|
692
|
+
: new Map();
|
|
693
|
+
const overrideSpecsByGeometryKey = collectSpecsByGeometryKey(board.vertices ?? [], `Hex board '${board.id}'`);
|
|
694
|
+
if (template) {
|
|
695
|
+
for (const overrideKey of overrideSpecsByGeometryKey.keys()) {
|
|
696
|
+
if (!templateSpecsByGeometryKey.has(overrideKey)) {
|
|
697
|
+
throw new Error(`Hex board '${board.id}' overrides unknown vertex ref from template '${template.id}'.`);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
const mergedSpecs = template
|
|
702
|
+
? Array.from(templateSpecsByGeometryKey.entries()).map(([geometryKey, templateSpec]) => [
|
|
703
|
+
geometryKey,
|
|
704
|
+
overrideSpecsByGeometryKey.get(geometryKey) ?? templateSpec,
|
|
705
|
+
])
|
|
706
|
+
: Array.from(overrideSpecsByGeometryKey.entries());
|
|
707
|
+
return mergedSpecs.map(([geometryKey, spec]) => {
|
|
708
|
+
const derivedVertex = derivedByGeometryKey.get(geometryKey);
|
|
709
|
+
if (!derivedVertex) {
|
|
710
|
+
throw new Error(`Hex vertex ref on board '${board.id}' does not resolve to a derived vertex.`);
|
|
711
|
+
}
|
|
712
|
+
return {
|
|
713
|
+
id: derivedVertex.id,
|
|
714
|
+
ref: cloneJson(spec.ref),
|
|
715
|
+
typeId: spec.typeId ?? null,
|
|
716
|
+
label: spec.label ?? null,
|
|
717
|
+
fields: spec.fields ?? null,
|
|
718
|
+
};
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
function resolveHexEdges(board, template, spaces) {
|
|
722
|
+
const derived = deriveHexEdges(spaces);
|
|
723
|
+
const edgesById = new Map(derived.map((edge) => [edge.id, edge]));
|
|
724
|
+
for (const authoredEdge of resolveAuthoredHexEdges(board, template, spaces)) {
|
|
725
|
+
const edge = edgesById.get(authoredEdge.id);
|
|
726
|
+
if (!edge) {
|
|
727
|
+
throw new Error(`Hex edge ref on board '${board.id}' does not resolve to a derived edge.`);
|
|
728
|
+
}
|
|
729
|
+
edgesById.set(authoredEdge.id, {
|
|
730
|
+
...edge,
|
|
731
|
+
typeId: authoredEdge.typeId ?? null,
|
|
732
|
+
label: authoredEdge.label ?? null,
|
|
733
|
+
fields: authoredEdge.fields ?? null,
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
return [...edgesById.values()].sort((left, right) => left.id.localeCompare(right.id));
|
|
737
|
+
}
|
|
738
|
+
function resolveHexVertices(board, template, spaces) {
|
|
739
|
+
const derived = deriveHexVertices(spaces);
|
|
740
|
+
const verticesById = new Map(derived.map((vertex) => [vertex.id, vertex]));
|
|
741
|
+
for (const authoredVertex of resolveAuthoredHexVertices(board, template, spaces)) {
|
|
742
|
+
const vertex = verticesById.get(authoredVertex.id);
|
|
743
|
+
if (!vertex) {
|
|
744
|
+
throw new Error(`Hex vertex ref on board '${board.id}' does not resolve to a derived vertex.`);
|
|
745
|
+
}
|
|
746
|
+
verticesById.set(authoredVertex.id, {
|
|
747
|
+
...vertex,
|
|
748
|
+
typeId: authoredVertex.typeId ?? null,
|
|
749
|
+
label: authoredVertex.label ?? null,
|
|
750
|
+
fields: authoredVertex.fields ?? null,
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
return [...verticesById.values()].sort((left, right) => left.id.localeCompare(right.id));
|
|
754
|
+
}
|
|
755
|
+
function resolveSquareSpaces(board, template) {
|
|
756
|
+
if (!template) {
|
|
757
|
+
return [...(board.spaces ?? [])].sort((left, right) => left.id.localeCompare(right.id));
|
|
758
|
+
}
|
|
759
|
+
const templateSpacesById = new Map((template.spaces ?? []).map((space) => [space.id, space]));
|
|
760
|
+
const overridesById = new Map((board.spaces ?? []).map((space) => [space.id, space]));
|
|
761
|
+
for (const overrideId of overridesById.keys()) {
|
|
762
|
+
if (!templateSpacesById.has(overrideId)) {
|
|
763
|
+
throw new Error(`Square board '${board.id}' overrides unknown space '${overrideId}' from template '${template.id}'.`);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
return (template.spaces ?? [])
|
|
767
|
+
.map((templateSpace) => {
|
|
768
|
+
const override = overridesById.get(templateSpace.id);
|
|
769
|
+
if (!override) {
|
|
770
|
+
return templateSpace;
|
|
771
|
+
}
|
|
772
|
+
const templateMatch = templateSpacesById.get(override.id);
|
|
773
|
+
if (!templateMatch) {
|
|
774
|
+
throw new Error(`Square board '${board.id}' overrides unknown space '${override.id}' from template '${template.id}'.`);
|
|
775
|
+
}
|
|
776
|
+
if (templateMatch.row !== override.row ||
|
|
777
|
+
templateMatch.col !== override.col) {
|
|
778
|
+
throw new Error(`Square board '${board.id}' cannot change coordinates for space '${override.id}' from template '${template.id}'.`);
|
|
779
|
+
}
|
|
780
|
+
return {
|
|
781
|
+
...templateSpace,
|
|
782
|
+
...override,
|
|
783
|
+
id: templateSpace.id,
|
|
784
|
+
row: templateSpace.row,
|
|
785
|
+
col: templateSpace.col,
|
|
786
|
+
};
|
|
787
|
+
})
|
|
788
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
789
|
+
}
|
|
790
|
+
function deriveSquareEdges(spaces) {
|
|
791
|
+
const edgeMap = new Map();
|
|
792
|
+
for (const space of spaces) {
|
|
793
|
+
for (const side of SQUARE_SIDES) {
|
|
794
|
+
const geometryKey = squareEdgeGeometryKey(space, side);
|
|
795
|
+
const existing = edgeMap.get(geometryKey);
|
|
796
|
+
const nextSpaceIds = dedupeSorted([
|
|
797
|
+
...(existing?.spaceIds ?? []),
|
|
798
|
+
space.id,
|
|
799
|
+
]);
|
|
800
|
+
edgeMap.set(geometryKey, {
|
|
801
|
+
id: squareEdgeIdFromGeometryKey(geometryKey),
|
|
802
|
+
geometryKey,
|
|
803
|
+
spaceIds: nextSpaceIds,
|
|
804
|
+
typeId: existing?.typeId ?? null,
|
|
805
|
+
label: existing?.label ?? null,
|
|
806
|
+
fields: existing?.fields ?? null,
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return [...edgeMap.values()].sort((left, right) => left.id.localeCompare(right.id));
|
|
811
|
+
}
|
|
812
|
+
function deriveSquareVertices(spaces) {
|
|
813
|
+
const vertexMap = new Map();
|
|
814
|
+
for (const space of spaces) {
|
|
815
|
+
for (const corner of SQUARE_CORNERS) {
|
|
816
|
+
const geometryKey = squareCornerGeometryKey(space, corner);
|
|
817
|
+
const existing = vertexMap.get(geometryKey);
|
|
818
|
+
const nextSpaceIds = dedupeSorted([
|
|
819
|
+
...(existing?.spaceIds ?? []),
|
|
820
|
+
space.id,
|
|
821
|
+
]);
|
|
822
|
+
vertexMap.set(geometryKey, {
|
|
823
|
+
id: squareVertexIdFromGeometryKey(geometryKey),
|
|
824
|
+
geometryKey,
|
|
825
|
+
spaceIds: nextSpaceIds,
|
|
826
|
+
typeId: existing?.typeId ?? null,
|
|
827
|
+
label: existing?.label ?? null,
|
|
828
|
+
fields: existing?.fields ?? null,
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return [...vertexMap.values()].sort((left, right) => left.id.localeCompare(right.id));
|
|
833
|
+
}
|
|
834
|
+
function indexSquareEdgeMetadata(specs, spacesById, ownerLabel) {
|
|
835
|
+
const metadataByKey = new Map();
|
|
836
|
+
for (const spec of specs) {
|
|
837
|
+
const geometryKey = geometryKeyFromSquareEdgeRef(spec.ref, spacesById);
|
|
838
|
+
if (metadataByKey.has(geometryKey)) {
|
|
839
|
+
throw new Error(`${ownerLabel} contains duplicate square edge refs.`);
|
|
840
|
+
}
|
|
841
|
+
metadataByKey.set(geometryKey, {
|
|
842
|
+
geometryKey,
|
|
843
|
+
typeId: spec.typeId ?? null,
|
|
844
|
+
label: spec.label ?? null,
|
|
845
|
+
fields: spec.fields ?? null,
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
return metadataByKey;
|
|
849
|
+
}
|
|
850
|
+
function indexSquareVertexMetadata(specs, spacesById, ownerLabel) {
|
|
851
|
+
const metadataByKey = new Map();
|
|
852
|
+
for (const spec of specs) {
|
|
853
|
+
const geometryKey = geometryKeyFromSquareVertexRef(spec.ref, spacesById);
|
|
854
|
+
if (metadataByKey.has(geometryKey)) {
|
|
855
|
+
throw new Error(`${ownerLabel} contains duplicate square vertex refs.`);
|
|
856
|
+
}
|
|
857
|
+
metadataByKey.set(geometryKey, {
|
|
858
|
+
geometryKey,
|
|
859
|
+
typeId: spec.typeId ?? null,
|
|
860
|
+
label: spec.label ?? null,
|
|
861
|
+
fields: spec.fields ?? null,
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
return metadataByKey;
|
|
865
|
+
}
|
|
866
|
+
function resolveSquareEdges(board, template, spaces) {
|
|
867
|
+
const spacesById = new Map(spaces.map((space) => [space.id, space]));
|
|
868
|
+
const derived = deriveSquareEdges(spaces);
|
|
869
|
+
const edgesByGeometryKey = new Map(derived.map((edge) => [edge.geometryKey, edge]));
|
|
870
|
+
const templateMetadata = template
|
|
871
|
+
? indexSquareEdgeMetadata(template.edges ?? [], spacesById, `Square board template '${template.id}'`)
|
|
872
|
+
: new Map();
|
|
873
|
+
const overrideMetadata = indexSquareEdgeMetadata(board.edges ?? [], spacesById, `Square board '${board.id}'`);
|
|
874
|
+
if (template) {
|
|
875
|
+
for (const overrideKey of overrideMetadata.keys()) {
|
|
876
|
+
if (!templateMetadata.has(overrideKey)) {
|
|
877
|
+
throw new Error(`Square board '${board.id}' overrides unknown edge ref from template '${template.id}'.`);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
const mergedMetadata = template
|
|
882
|
+
? new Map([...templateMetadata.entries()].map(([key, metadata]) => [
|
|
883
|
+
key,
|
|
884
|
+
{
|
|
885
|
+
...metadata,
|
|
886
|
+
...(overrideMetadata.get(key) ?? {}),
|
|
887
|
+
geometryKey: key,
|
|
888
|
+
},
|
|
889
|
+
]))
|
|
890
|
+
: overrideMetadata;
|
|
891
|
+
for (const [geometryKey, metadata] of mergedMetadata.entries()) {
|
|
892
|
+
const edge = edgesByGeometryKey.get(geometryKey);
|
|
893
|
+
if (!edge) {
|
|
894
|
+
throw new Error(`Square edge ref on board '${board.id}' does not resolve to a derived edge.`);
|
|
895
|
+
}
|
|
896
|
+
edgesByGeometryKey.set(geometryKey, {
|
|
897
|
+
...edge,
|
|
898
|
+
typeId: metadata.typeId ?? null,
|
|
899
|
+
label: metadata.label ?? null,
|
|
900
|
+
fields: metadata.fields ?? null,
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
return [...edgesByGeometryKey.values()].sort((left, right) => left.id.localeCompare(right.id));
|
|
904
|
+
}
|
|
905
|
+
function resolveSquareVertices(board, template, spaces) {
|
|
906
|
+
const spacesById = new Map(spaces.map((space) => [space.id, space]));
|
|
907
|
+
const derived = deriveSquareVertices(spaces);
|
|
908
|
+
const verticesByGeometryKey = new Map(derived.map((vertex) => [vertex.geometryKey, vertex]));
|
|
909
|
+
const templateMetadata = template
|
|
910
|
+
? indexSquareVertexMetadata(template.vertices ?? [], spacesById, `Square board template '${template.id}'`)
|
|
911
|
+
: new Map();
|
|
912
|
+
const overrideMetadata = indexSquareVertexMetadata(board.vertices ?? [], spacesById, `Square board '${board.id}'`);
|
|
913
|
+
if (template) {
|
|
914
|
+
for (const overrideKey of overrideMetadata.keys()) {
|
|
915
|
+
if (!templateMetadata.has(overrideKey)) {
|
|
916
|
+
throw new Error(`Square board '${board.id}' overrides unknown vertex ref from template '${template.id}'.`);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
const mergedMetadata = template
|
|
921
|
+
? new Map([...templateMetadata.entries()].map(([key, metadata]) => [
|
|
922
|
+
key,
|
|
923
|
+
{
|
|
924
|
+
...metadata,
|
|
925
|
+
...(overrideMetadata.get(key) ?? {}),
|
|
926
|
+
geometryKey: key,
|
|
927
|
+
},
|
|
928
|
+
]))
|
|
929
|
+
: overrideMetadata;
|
|
930
|
+
for (const [geometryKey, metadata] of mergedMetadata.entries()) {
|
|
931
|
+
const vertex = verticesByGeometryKey.get(geometryKey);
|
|
932
|
+
if (!vertex) {
|
|
933
|
+
throw new Error(`Square vertex ref on board '${board.id}' does not resolve to a derived vertex.`);
|
|
934
|
+
}
|
|
935
|
+
verticesByGeometryKey.set(geometryKey, {
|
|
936
|
+
...vertex,
|
|
937
|
+
typeId: metadata.typeId ?? null,
|
|
938
|
+
label: metadata.label ?? null,
|
|
939
|
+
fields: metadata.fields ?? null,
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
return [...verticesByGeometryKey.values()].sort((left, right) => left.id.localeCompare(right.id));
|
|
943
|
+
}
|
|
944
|
+
function analyzeBoards(manifest, playerIds) {
|
|
945
|
+
const boardTemplates = manifest.boardTemplates ?? [];
|
|
946
|
+
const genericTemplateById = new Map(boardTemplates
|
|
947
|
+
.filter(isGenericBoardTemplateSpec)
|
|
948
|
+
.map((boardTemplate) => [boardTemplate.id, boardTemplate]));
|
|
949
|
+
const hexTemplateById = new Map(boardTemplates
|
|
950
|
+
.filter(isHexBoardTemplateSpec)
|
|
951
|
+
.map((boardTemplate) => [boardTemplate.id, boardTemplate]));
|
|
952
|
+
const squareTemplateById = new Map(boardTemplates
|
|
953
|
+
.filter(isSquareBoardTemplateSpec)
|
|
954
|
+
.map((boardTemplate) => [boardTemplate.id, boardTemplate]));
|
|
955
|
+
return (manifest.boards ?? []).map((board) => {
|
|
956
|
+
const runtimeBoardIds = board.scope === "perPlayer"
|
|
957
|
+
? playerIds.map((playerId) => `${board.id}:${playerId}`)
|
|
958
|
+
: [board.id];
|
|
959
|
+
if (isHexBoardSpec(board)) {
|
|
960
|
+
const hexBoard = board;
|
|
961
|
+
const template = board.templateId
|
|
962
|
+
? hexTemplateById.get(board.templateId)
|
|
963
|
+
: undefined;
|
|
964
|
+
const spaces = resolveHexSpaces(hexBoard, template);
|
|
965
|
+
return {
|
|
966
|
+
layout: "hex",
|
|
967
|
+
board: hexBoard,
|
|
968
|
+
template,
|
|
969
|
+
boardTypeId: hexBoard.typeId ?? template?.typeId,
|
|
970
|
+
runtimeBoardIds,
|
|
971
|
+
boardFieldsSchema: hexBoard.boardFieldsSchema ?? template?.boardFieldsSchema,
|
|
972
|
+
spaceFieldsSchema: hexBoard.spaceFieldsSchema ?? template?.spaceFieldsSchema,
|
|
973
|
+
edgeFieldsSchema: hexBoard.edgeFieldsSchema ?? template?.edgeFieldsSchema,
|
|
974
|
+
vertexFieldsSchema: hexBoard.vertexFieldsSchema ?? template?.vertexFieldsSchema,
|
|
975
|
+
spaces,
|
|
976
|
+
authoredEdges: resolveAuthoredHexEdges(hexBoard, template, spaces),
|
|
977
|
+
authoredVertices: resolveAuthoredHexVertices(hexBoard, template, spaces),
|
|
978
|
+
edges: resolveHexEdges(hexBoard, template, spaces),
|
|
979
|
+
vertices: resolveHexVertices(hexBoard, template, spaces),
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
if (isSquareBoardSpec(board)) {
|
|
983
|
+
const squareBoard = board;
|
|
984
|
+
const template = board.templateId
|
|
985
|
+
? squareTemplateById.get(board.templateId)
|
|
986
|
+
: undefined;
|
|
987
|
+
const spaces = resolveSquareSpaces(squareBoard, template);
|
|
988
|
+
return {
|
|
989
|
+
layout: "square",
|
|
990
|
+
board: squareBoard,
|
|
991
|
+
template,
|
|
992
|
+
boardTypeId: squareBoard.typeId ?? template?.typeId,
|
|
993
|
+
runtimeBoardIds,
|
|
994
|
+
boardFieldsSchema: squareBoard.boardFieldsSchema ?? template?.boardFieldsSchema,
|
|
995
|
+
spaceFieldsSchema: squareBoard.spaceFieldsSchema ?? template?.spaceFieldsSchema,
|
|
996
|
+
relationFieldsSchema: squareBoard.relationFieldsSchema ?? template?.relationFieldsSchema,
|
|
997
|
+
containerFieldsSchema: squareBoard.containerFieldsSchema ?? template?.containerFieldsSchema,
|
|
998
|
+
edgeFieldsSchema: squareBoard.edgeFieldsSchema ?? template?.edgeFieldsSchema,
|
|
999
|
+
vertexFieldsSchema: squareBoard.vertexFieldsSchema ?? template?.vertexFieldsSchema,
|
|
1000
|
+
spaces,
|
|
1001
|
+
relations: [
|
|
1002
|
+
...(template?.relations ?? []),
|
|
1003
|
+
...(squareBoard.relations ?? []),
|
|
1004
|
+
],
|
|
1005
|
+
containers: mergeBoardContainers(template?.containers ?? [], squareBoard.containers ?? []),
|
|
1006
|
+
edges: resolveSquareEdges(squareBoard, template, spaces),
|
|
1007
|
+
vertices: resolveSquareVertices(squareBoard, template, spaces),
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
const genericBoard = board;
|
|
1011
|
+
const template = board.templateId
|
|
1012
|
+
? genericTemplateById.get(board.templateId)
|
|
1013
|
+
: undefined;
|
|
1014
|
+
return {
|
|
1015
|
+
layout: "generic",
|
|
1016
|
+
board: genericBoard,
|
|
1017
|
+
template,
|
|
1018
|
+
boardTypeId: genericBoard.typeId ?? template?.typeId,
|
|
1019
|
+
runtimeBoardIds,
|
|
1020
|
+
boardFieldsSchema: genericBoard.boardFieldsSchema ?? template?.boardFieldsSchema,
|
|
1021
|
+
spaceFieldsSchema: genericBoard.spaceFieldsSchema ?? template?.spaceFieldsSchema,
|
|
1022
|
+
relationFieldsSchema: genericBoard.relationFieldsSchema ?? template?.relationFieldsSchema,
|
|
1023
|
+
containerFieldsSchema: genericBoard.containerFieldsSchema ?? template?.containerFieldsSchema,
|
|
1024
|
+
spaces: mergeBoardSpaces(template?.spaces ?? [], genericBoard.spaces ?? []),
|
|
1025
|
+
relations: [
|
|
1026
|
+
...(template?.relations ?? []),
|
|
1027
|
+
...(genericBoard.relations ?? []),
|
|
1028
|
+
],
|
|
1029
|
+
containers: mergeBoardContainers(template?.containers ?? [], genericBoard.containers ?? []),
|
|
1030
|
+
};
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
function isSingletonExplicitSeed(seed) {
|
|
1034
|
+
return (typeof seed.id === "string" && seed.id.length > 0 && (seed.count ?? 1) === 1);
|
|
1035
|
+
}
|
|
1036
|
+
function renderStrictSlotLocationSchema(hosts) {
|
|
1037
|
+
const branches = hosts.flatMap((host) => host.slotIds.map((slotId) => `z.object({
|
|
1038
|
+
type: z.literal("InSlot"),
|
|
1039
|
+
host: z.object({
|
|
1040
|
+
kind: z.literal(${quote(host.kind)}),
|
|
1041
|
+
id: z.literal(${quote(host.id)}),
|
|
1042
|
+
}),
|
|
1043
|
+
slotId: z.literal(${quote(slotId)}),
|
|
1044
|
+
position: z.number().int().nullable().optional(),
|
|
1045
|
+
})`));
|
|
1046
|
+
if (branches.length === 0) {
|
|
1047
|
+
return `z.object({
|
|
1048
|
+
type: z.literal("InSlot"),
|
|
1049
|
+
host: z.never(),
|
|
1050
|
+
slotId: z.never(),
|
|
1051
|
+
position: z.number().int().nullable().optional(),
|
|
1052
|
+
})`;
|
|
1053
|
+
}
|
|
1054
|
+
return branches.join(",\n ");
|
|
1055
|
+
}
|
|
1056
|
+
function analyzeManifest(inputManifest) {
|
|
1057
|
+
const manifest = addStandardDecksIfNeeded(inputManifest);
|
|
1058
|
+
const playerIds = Array.from({ length: manifest.players.maxPlayers }, (_, index) => `player-${index + 1}`);
|
|
1059
|
+
const sharedZones = (manifest.zones ?? []).filter((zone) => zone.scope === "shared");
|
|
1060
|
+
const playerZones = (manifest.zones ?? []).filter((zone) => zone.scope === "perPlayer");
|
|
1061
|
+
const zoneIds = dedupeSorted((manifest.zones ?? []).map((zone) => zone.id));
|
|
1062
|
+
const cardSets = manifest.cardSets.map(materializeCardSet);
|
|
1063
|
+
const cardSetIds = dedupeSorted(cardSets.map((cardSet) => cardSet.id));
|
|
1064
|
+
const cardTypes = dedupeSorted(cardSets.flatMap((cardSet) => cardSet.cards.map((card) => card.type)));
|
|
1065
|
+
const cardIds = dedupeSorted(cardSets.flatMap((cardSet) => cardSet.cards.flatMap(renderCardInstanceIds)));
|
|
1066
|
+
const cardSetIdByCardId = new Map();
|
|
1067
|
+
const cardTypeByCardId = new Map();
|
|
1068
|
+
for (const cardSet of cardSets) {
|
|
1069
|
+
for (const card of cardSet.cards) {
|
|
1070
|
+
for (const cardId of renderCardInstanceIds(card)) {
|
|
1071
|
+
cardSetIdByCardId.set(cardId, cardSet.id);
|
|
1072
|
+
cardTypeByCardId.set(cardId, card.cardType ?? card.type);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
const sharedZoneCardSetIds = new Map();
|
|
1077
|
+
for (const zone of sharedZones) {
|
|
1078
|
+
sharedZoneCardSetIds.set(zone.id, dedupeSorted(zone.allowedCardSetIds ?? []));
|
|
1079
|
+
}
|
|
1080
|
+
const playerZoneCardSetIds = new Map();
|
|
1081
|
+
for (const zone of playerZones) {
|
|
1082
|
+
playerZoneCardSetIds.set(zone.id, dedupeSorted(zone.allowedCardSetIds ?? []));
|
|
1083
|
+
}
|
|
1084
|
+
const zoneCardSetIdsById = new Map();
|
|
1085
|
+
for (const [zoneId, cardSetIds] of sharedZoneCardSetIds.entries()) {
|
|
1086
|
+
zoneCardSetIdsById.set(zoneId, cardSetIds);
|
|
1087
|
+
}
|
|
1088
|
+
for (const [zoneId, cardSetIds] of playerZoneCardSetIds.entries()) {
|
|
1089
|
+
zoneCardSetIdsById.set(zoneId, cardSetIds);
|
|
1090
|
+
}
|
|
1091
|
+
const sharedZoneIdsByCardSetId = new Map(cardSetIds.map((cardSetId) => [cardSetId, []]));
|
|
1092
|
+
for (const [zoneId, allowedCardSetIds] of sharedZoneCardSetIds.entries()) {
|
|
1093
|
+
for (const cardSetId of allowedCardSetIds) {
|
|
1094
|
+
const zoneIds = sharedZoneIdsByCardSetId.get(cardSetId) ?? [];
|
|
1095
|
+
zoneIds.push(zoneId);
|
|
1096
|
+
sharedZoneIdsByCardSetId.set(cardSetId, zoneIds);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
for (const [cardSetId, zoneIds] of sharedZoneIdsByCardSetId.entries()) {
|
|
1100
|
+
sharedZoneIdsByCardSetId.set(cardSetId, dedupeSorted(zoneIds));
|
|
1101
|
+
}
|
|
1102
|
+
const sharedZoneIdSet = new Set(sharedZones.map((zone) => zone.id));
|
|
1103
|
+
const homeSharedZoneIdsByCardType = new Map(cardTypes.map((cardType) => [cardType, []]));
|
|
1104
|
+
for (const cardSet of cardSets) {
|
|
1105
|
+
for (const card of cardSet.cards) {
|
|
1106
|
+
if (card.home?.type !== "zone" ||
|
|
1107
|
+
!sharedZoneIdSet.has(card.home.zoneId)) {
|
|
1108
|
+
continue;
|
|
1109
|
+
}
|
|
1110
|
+
const zoneIds = homeSharedZoneIdsByCardType.get(card.type) ?? [];
|
|
1111
|
+
zoneIds.push(card.home.zoneId);
|
|
1112
|
+
homeSharedZoneIdsByCardType.set(card.type, zoneIds);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
const homeSharedZoneIdByCardType = new Map();
|
|
1116
|
+
for (const [cardType, zoneIds] of homeSharedZoneIdsByCardType.entries()) {
|
|
1117
|
+
const uniqueZoneIds = dedupeSorted(zoneIds);
|
|
1118
|
+
homeSharedZoneIdsByCardType.set(cardType, uniqueZoneIds);
|
|
1119
|
+
const [homeZoneId] = uniqueZoneIds;
|
|
1120
|
+
if (uniqueZoneIds.length === 1 && homeZoneId !== undefined) {
|
|
1121
|
+
homeSharedZoneIdByCardType.set(cardType, homeZoneId);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
const zoneVisibilityById = new Map((manifest.zones ?? []).map((zone) => [
|
|
1125
|
+
zone.id,
|
|
1126
|
+
zone.visibility ?? "public",
|
|
1127
|
+
]));
|
|
1128
|
+
const resourceIds = dedupeSorted((manifest.resources ?? []).map((resource) => resource.id));
|
|
1129
|
+
const resourcePresentationById = sortedObject((manifest.resources ?? []).map((resource) => [
|
|
1130
|
+
resource.id,
|
|
1131
|
+
{
|
|
1132
|
+
label: resource.name,
|
|
1133
|
+
...(resource.icon ? { icon: resource.icon } : {}),
|
|
1134
|
+
},
|
|
1135
|
+
]));
|
|
1136
|
+
const setupOptionIds = dedupeSorted((manifest.setupOptions ?? []).map((option) => option.id));
|
|
1137
|
+
const setupProfileIds = dedupeSorted((manifest.setupProfiles ?? []).map((profile) => profile.id));
|
|
1138
|
+
const setupChoiceIdsByOptionId = new Map((manifest.setupOptions ?? []).map((option) => [
|
|
1139
|
+
option.id,
|
|
1140
|
+
dedupeSorted((option.choices ?? []).map((choice) => choice.id)),
|
|
1141
|
+
]));
|
|
1142
|
+
const setupOptionsById = Object.fromEntries((manifest.setupOptions ?? [])
|
|
1143
|
+
.slice()
|
|
1144
|
+
.sort((left, right) => left.id.localeCompare(right.id))
|
|
1145
|
+
.map((option) => [
|
|
1146
|
+
option.id,
|
|
1147
|
+
{
|
|
1148
|
+
id: option.id,
|
|
1149
|
+
name: option.name,
|
|
1150
|
+
description: option.description ?? null,
|
|
1151
|
+
choices: (option.choices ?? []).map((choice) => ({
|
|
1152
|
+
id: choice.id,
|
|
1153
|
+
label: choice.label,
|
|
1154
|
+
description: choice.description ?? null,
|
|
1155
|
+
})),
|
|
1156
|
+
},
|
|
1157
|
+
]));
|
|
1158
|
+
const setupProfilesById = Object.fromEntries((manifest.setupProfiles ?? [])
|
|
1159
|
+
.slice()
|
|
1160
|
+
.sort((left, right) => left.id.localeCompare(right.id))
|
|
1161
|
+
.map((profile) => [
|
|
1162
|
+
profile.id,
|
|
1163
|
+
{
|
|
1164
|
+
id: profile.id,
|
|
1165
|
+
name: profile.name,
|
|
1166
|
+
description: profile.description ?? null,
|
|
1167
|
+
optionValues: profile.optionValues
|
|
1168
|
+
? Object.fromEntries(Object.entries(profile.optionValues).filter((entry) => typeof entry[1] === "string"))
|
|
1169
|
+
: null,
|
|
1170
|
+
},
|
|
1171
|
+
]));
|
|
1172
|
+
const pieceTypeIds = dedupeSorted((manifest.pieceTypes ?? []).map((pieceType) => pieceType.id));
|
|
1173
|
+
const pieceTypeSchemasById = new Map((manifest.pieceTypes ?? []).map((pieceType) => [
|
|
1174
|
+
pieceType.id,
|
|
1175
|
+
pieceType.fieldsSchema,
|
|
1176
|
+
]));
|
|
1177
|
+
const pieceTypeSlotIdsById = new Map((manifest.pieceTypes ?? []).map((pieceType) => [
|
|
1178
|
+
pieceType.id,
|
|
1179
|
+
dedupeSorted((pieceType.slots ?? []).map((slot) => slot.id)),
|
|
1180
|
+
]));
|
|
1181
|
+
const pieceTypeIdByPieceId = new Map();
|
|
1182
|
+
for (const seed of manifest.pieceSeeds ?? []) {
|
|
1183
|
+
for (const pieceId of expandSeedIds([seed])) {
|
|
1184
|
+
pieceTypeIdByPieceId.set(pieceId, seed.typeId);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
const pieceIds = dedupeSorted(pieceTypeIdByPieceId.keys());
|
|
1188
|
+
const dieTypeIds = dedupeSorted((manifest.dieTypes ?? []).map((dieType) => dieType.id));
|
|
1189
|
+
const dieTypeSchemasById = new Map((manifest.dieTypes ?? []).map((dieType) => [
|
|
1190
|
+
dieType.id,
|
|
1191
|
+
dieType.fieldsSchema,
|
|
1192
|
+
]));
|
|
1193
|
+
const dieTypeSlotIdsById = new Map((manifest.dieTypes ?? []).map((dieType) => [
|
|
1194
|
+
dieType.id,
|
|
1195
|
+
dedupeSorted((dieType.slots ?? []).map((slot) => slot.id)),
|
|
1196
|
+
]));
|
|
1197
|
+
const dieTypeIdByDieId = new Map();
|
|
1198
|
+
for (const seed of manifest.dieSeeds ?? []) {
|
|
1199
|
+
for (const dieId of expandSeedIds([seed])) {
|
|
1200
|
+
dieTypeIdByDieId.set(dieId, seed.typeId);
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
const dieIds = dedupeSorted(dieTypeIdByDieId.keys());
|
|
1204
|
+
const strictSlotHosts = [
|
|
1205
|
+
...(manifest.pieceSeeds ?? []).flatMap((seed) => {
|
|
1206
|
+
if (!isSingletonExplicitSeed(seed)) {
|
|
1207
|
+
return [];
|
|
1208
|
+
}
|
|
1209
|
+
const slotIds = pieceTypeSlotIdsById.get(seed.typeId) ?? [];
|
|
1210
|
+
return slotIds.length > 0
|
|
1211
|
+
? [
|
|
1212
|
+
{
|
|
1213
|
+
kind: "piece",
|
|
1214
|
+
id: seed.id,
|
|
1215
|
+
slotIds,
|
|
1216
|
+
},
|
|
1217
|
+
]
|
|
1218
|
+
: [];
|
|
1219
|
+
}),
|
|
1220
|
+
...(manifest.dieSeeds ?? []).flatMap((seed) => {
|
|
1221
|
+
if (!isSingletonExplicitSeed(seed)) {
|
|
1222
|
+
return [];
|
|
1223
|
+
}
|
|
1224
|
+
const slotIds = dieTypeSlotIdsById.get(seed.typeId) ?? [];
|
|
1225
|
+
return slotIds.length > 0
|
|
1226
|
+
? [
|
|
1227
|
+
{
|
|
1228
|
+
kind: "die",
|
|
1229
|
+
id: seed.id,
|
|
1230
|
+
slotIds,
|
|
1231
|
+
},
|
|
1232
|
+
]
|
|
1233
|
+
: [];
|
|
1234
|
+
}),
|
|
1235
|
+
].sort((left, right) => left.kind.localeCompare(right.kind) || left.id.localeCompare(right.id));
|
|
1236
|
+
const analyzedBoards = analyzeBoards(manifest, playerIds);
|
|
1237
|
+
const boardTemplateIds = dedupeSorted((manifest.boardTemplates ?? []).map((boardTemplate) => boardTemplate.id));
|
|
1238
|
+
const boardBaseIds = dedupeSorted(analyzedBoards.map(({ board }) => board.id));
|
|
1239
|
+
const boardIds = dedupeSorted(analyzedBoards.flatMap((entry) => entry.runtimeBoardIds));
|
|
1240
|
+
const boardTypeIds = dedupeSorted(analyzedBoards
|
|
1241
|
+
.map((board) => board.boardTypeId)
|
|
1242
|
+
.filter((typeId) => typeof typeId === "string"));
|
|
1243
|
+
const boardLayoutById = new Map();
|
|
1244
|
+
const boardIdsByLayout = new Map();
|
|
1245
|
+
const boardBaseIdsByLayout = new Map();
|
|
1246
|
+
const boardIdsByBaseId = new Map();
|
|
1247
|
+
const boardBaseIdsByTemplateId = new Map();
|
|
1248
|
+
const boardTemplateLayoutById = new Map();
|
|
1249
|
+
const boardIdsByTypeId = new Map();
|
|
1250
|
+
const spaceIdsByBoardId = new Map();
|
|
1251
|
+
const spaceTypeIdByBoardId = new Map();
|
|
1252
|
+
const spaceIdsByTypeId = new Map();
|
|
1253
|
+
const containerIdsByBoardId = new Map();
|
|
1254
|
+
const containerHostByBoardId = new Map();
|
|
1255
|
+
const relationTypeIdsByBoardId = new Map();
|
|
1256
|
+
const edgeIdsByTypeId = new Map();
|
|
1257
|
+
const edgeIdsByBoardIdAndTypeId = new Map();
|
|
1258
|
+
const vertexIdsByTypeId = new Map();
|
|
1259
|
+
const vertexIdsByBoardIdAndTypeId = new Map();
|
|
1260
|
+
for (const boardTemplate of manifest.boardTemplates ?? []) {
|
|
1261
|
+
boardTemplateLayoutById.set(boardTemplate.id, boardTemplate.layout);
|
|
1262
|
+
boardBaseIdsByLayout.set(boardTemplate.layout, dedupeSorted([...(boardBaseIdsByLayout.get(boardTemplate.layout) ?? [])]));
|
|
1263
|
+
}
|
|
1264
|
+
for (const analyzedBoard of analyzedBoards) {
|
|
1265
|
+
boardIdsByBaseId.set(analyzedBoard.board.id, analyzedBoard.runtimeBoardIds);
|
|
1266
|
+
boardIdsByLayout.set(analyzedBoard.board.layout, dedupeSorted([
|
|
1267
|
+
...(boardIdsByLayout.get(analyzedBoard.board.layout) ?? []),
|
|
1268
|
+
...analyzedBoard.runtimeBoardIds,
|
|
1269
|
+
]));
|
|
1270
|
+
boardBaseIdsByLayout.set(analyzedBoard.board.layout, dedupeSorted([
|
|
1271
|
+
...(boardBaseIdsByLayout.get(analyzedBoard.board.layout) ?? []),
|
|
1272
|
+
analyzedBoard.board.id,
|
|
1273
|
+
]));
|
|
1274
|
+
if (analyzedBoard.board.templateId) {
|
|
1275
|
+
boardBaseIdsByTemplateId.set(analyzedBoard.board.templateId, dedupeSorted([
|
|
1276
|
+
...(boardBaseIdsByTemplateId.get(analyzedBoard.board.templateId) ??
|
|
1277
|
+
[]),
|
|
1278
|
+
analyzedBoard.board.id,
|
|
1279
|
+
]));
|
|
1280
|
+
}
|
|
1281
|
+
const runtimeSpaceIds = analyzedBoard.spaces.map((space) => space.id);
|
|
1282
|
+
const runtimeSpaceTypeIds = sortedObject(analyzedBoard.spaces.map((space) => [space.id, space.typeId ?? null]));
|
|
1283
|
+
const runtimeContainerIds = analyzedBoard.layout === "hex"
|
|
1284
|
+
? []
|
|
1285
|
+
: analyzedBoard.containers.map((container) => container.id);
|
|
1286
|
+
const runtimeContainerHosts = analyzedBoard.layout === "hex"
|
|
1287
|
+
? {}
|
|
1288
|
+
: sortedObject(analyzedBoard.containers.map((container) => [
|
|
1289
|
+
container.id,
|
|
1290
|
+
container.host.type === "space"
|
|
1291
|
+
? { type: "space", spaceId: container.host.spaceId }
|
|
1292
|
+
: { type: "board" },
|
|
1293
|
+
]));
|
|
1294
|
+
const runtimeRelationTypeIds = analyzedBoard.layout === "hex"
|
|
1295
|
+
? ["adjacent"]
|
|
1296
|
+
: analyzedBoard.layout === "square"
|
|
1297
|
+
? dedupeSorted([
|
|
1298
|
+
"adjacent",
|
|
1299
|
+
...analyzedBoard.relations.map((relation) => relation.typeId),
|
|
1300
|
+
])
|
|
1301
|
+
: dedupeSorted(analyzedBoard.relations.map((relation) => relation.typeId));
|
|
1302
|
+
for (const runtimeBoardId of analyzedBoard.runtimeBoardIds) {
|
|
1303
|
+
boardLayoutById.set(runtimeBoardId, analyzedBoard.board.layout);
|
|
1304
|
+
spaceIdsByBoardId.set(runtimeBoardId, runtimeSpaceIds);
|
|
1305
|
+
spaceTypeIdByBoardId.set(runtimeBoardId, runtimeSpaceTypeIds);
|
|
1306
|
+
containerIdsByBoardId.set(runtimeBoardId, runtimeContainerIds);
|
|
1307
|
+
containerHostByBoardId.set(runtimeBoardId, runtimeContainerHosts);
|
|
1308
|
+
relationTypeIdsByBoardId.set(runtimeBoardId, runtimeRelationTypeIds);
|
|
1309
|
+
}
|
|
1310
|
+
if (analyzedBoard.boardTypeId) {
|
|
1311
|
+
boardIdsByTypeId.set(analyzedBoard.boardTypeId, dedupeSorted([
|
|
1312
|
+
...(boardIdsByTypeId.get(analyzedBoard.boardTypeId) ?? []),
|
|
1313
|
+
...analyzedBoard.runtimeBoardIds,
|
|
1314
|
+
]));
|
|
1315
|
+
}
|
|
1316
|
+
for (const space of analyzedBoard.spaces) {
|
|
1317
|
+
if (!space.typeId) {
|
|
1318
|
+
continue;
|
|
1319
|
+
}
|
|
1320
|
+
spaceIdsByTypeId.set(space.typeId, dedupeSorted([...(spaceIdsByTypeId.get(space.typeId) ?? []), space.id]));
|
|
1321
|
+
}
|
|
1322
|
+
if (analyzedBoard.layout !== "generic") {
|
|
1323
|
+
const edgeIdsForBoardByType = {};
|
|
1324
|
+
for (const edge of analyzedBoard.edges) {
|
|
1325
|
+
if (!edge.typeId) {
|
|
1326
|
+
continue;
|
|
1327
|
+
}
|
|
1328
|
+
edgeIdsByTypeId.set(edge.typeId, dedupeSorted([...(edgeIdsByTypeId.get(edge.typeId) ?? []), edge.id]));
|
|
1329
|
+
edgeIdsForBoardByType[edge.typeId] = dedupeSorted([
|
|
1330
|
+
...(edgeIdsForBoardByType[edge.typeId] ?? []),
|
|
1331
|
+
edge.id,
|
|
1332
|
+
]);
|
|
1333
|
+
}
|
|
1334
|
+
const vertexIdsForBoardByType = {};
|
|
1335
|
+
for (const vertex of analyzedBoard.vertices) {
|
|
1336
|
+
if (!vertex.typeId) {
|
|
1337
|
+
continue;
|
|
1338
|
+
}
|
|
1339
|
+
vertexIdsByTypeId.set(vertex.typeId, dedupeSorted([
|
|
1340
|
+
...(vertexIdsByTypeId.get(vertex.typeId) ?? []),
|
|
1341
|
+
vertex.id,
|
|
1342
|
+
]));
|
|
1343
|
+
vertexIdsForBoardByType[vertex.typeId] = dedupeSorted([
|
|
1344
|
+
...(vertexIdsForBoardByType[vertex.typeId] ?? []),
|
|
1345
|
+
vertex.id,
|
|
1346
|
+
]);
|
|
1347
|
+
}
|
|
1348
|
+
for (const runtimeBoardId of analyzedBoard.runtimeBoardIds) {
|
|
1349
|
+
edgeIdsByBoardIdAndTypeId.set(runtimeBoardId, edgeIdsForBoardByType);
|
|
1350
|
+
vertexIdsByBoardIdAndTypeId.set(runtimeBoardId, vertexIdsForBoardByType);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
const relationTypeIds = dedupeSorted(Array.from(relationTypeIdsByBoardId.values()).flat());
|
|
1355
|
+
const edgeIds = dedupeSorted(analyzedBoards.flatMap((board) => board.layout === "generic" ? [] : board.edges.map((edge) => edge.id)));
|
|
1356
|
+
const edgeTypeIds = dedupeSorted(edgeIdsByTypeId.keys());
|
|
1357
|
+
const vertexIds = dedupeSorted(analyzedBoards.flatMap((board) => board.layout === "generic"
|
|
1358
|
+
? []
|
|
1359
|
+
: board.vertices.map((vertex) => vertex.id)));
|
|
1360
|
+
const vertexTypeIds = dedupeSorted(vertexIdsByTypeId.keys());
|
|
1361
|
+
const boardContainerIds = dedupeSorted(analyzedBoards.flatMap((board) => board.layout === "hex"
|
|
1362
|
+
? []
|
|
1363
|
+
: board.containers.map((container) => container.id)));
|
|
1364
|
+
const spaceIds = dedupeSorted(analyzedBoards.flatMap((board) => board.spaces.map((space) => space.id)));
|
|
1365
|
+
const spaceTypeIds = dedupeSorted(analyzedBoards.flatMap((board) => board.spaces
|
|
1366
|
+
.map((space) => space.typeId)
|
|
1367
|
+
.filter((typeId) => typeof typeId === "string")));
|
|
1368
|
+
return {
|
|
1369
|
+
manifest,
|
|
1370
|
+
playerIds,
|
|
1371
|
+
sharedZones,
|
|
1372
|
+
playerZones,
|
|
1373
|
+
zoneIds,
|
|
1374
|
+
cardSets,
|
|
1375
|
+
cardSetIds,
|
|
1376
|
+
cardTypes,
|
|
1377
|
+
cardIds,
|
|
1378
|
+
cardSetIdByCardId,
|
|
1379
|
+
cardTypeByCardId,
|
|
1380
|
+
sharedZoneCardSetIds,
|
|
1381
|
+
sharedZoneIdsByCardSetId,
|
|
1382
|
+
homeSharedZoneIdsByCardType,
|
|
1383
|
+
homeSharedZoneIdByCardType,
|
|
1384
|
+
playerZoneCardSetIds,
|
|
1385
|
+
zoneCardSetIdsById,
|
|
1386
|
+
zoneVisibilityById,
|
|
1387
|
+
resourceIds,
|
|
1388
|
+
resourcePresentationById,
|
|
1389
|
+
setupOptionIds,
|
|
1390
|
+
setupProfileIds,
|
|
1391
|
+
setupChoiceIdsByOptionId,
|
|
1392
|
+
setupOptionsById,
|
|
1393
|
+
setupProfilesById,
|
|
1394
|
+
pieceTypeIds,
|
|
1395
|
+
pieceIds,
|
|
1396
|
+
pieceTypeIdByPieceId,
|
|
1397
|
+
dieTypeIds,
|
|
1398
|
+
dieIds,
|
|
1399
|
+
dieTypeIdByDieId,
|
|
1400
|
+
strictSlotHosts,
|
|
1401
|
+
boardTemplateIds,
|
|
1402
|
+
boardBaseIds,
|
|
1403
|
+
boardIds,
|
|
1404
|
+
boardContainerIds,
|
|
1405
|
+
boardTypeIds,
|
|
1406
|
+
boardLayoutById,
|
|
1407
|
+
boardIdsByLayout,
|
|
1408
|
+
boardBaseIdsByLayout,
|
|
1409
|
+
boardIdsByBaseId,
|
|
1410
|
+
boardBaseIdsByTemplateId,
|
|
1411
|
+
boardTemplateLayoutById,
|
|
1412
|
+
boardIdsByTypeId,
|
|
1413
|
+
spaceIdsByBoardId,
|
|
1414
|
+
spaceTypeIdByBoardId,
|
|
1415
|
+
spaceIdsByTypeId,
|
|
1416
|
+
containerIdsByBoardId,
|
|
1417
|
+
containerHostByBoardId,
|
|
1418
|
+
relationTypeIds,
|
|
1419
|
+
relationTypeIdsByBoardId,
|
|
1420
|
+
edgeIds,
|
|
1421
|
+
edgeTypeIds,
|
|
1422
|
+
edgeIdsByTypeId,
|
|
1423
|
+
edgeIdsByBoardIdAndTypeId,
|
|
1424
|
+
vertexIds,
|
|
1425
|
+
vertexTypeIds,
|
|
1426
|
+
vertexIdsByTypeId,
|
|
1427
|
+
vertexIdsByBoardIdAndTypeId,
|
|
1428
|
+
spaceIds,
|
|
1429
|
+
spaceTypeIds,
|
|
1430
|
+
analyzedBoards,
|
|
1431
|
+
pieceTypeSchemasById,
|
|
1432
|
+
dieTypeSchemasById,
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
function cloneJson(value) {
|
|
1436
|
+
return JSON.parse(JSON.stringify(value));
|
|
1437
|
+
}
|
|
1438
|
+
function boardSpaceRefKey(spaceIds) {
|
|
1439
|
+
return [...spaceIds]
|
|
1440
|
+
.sort((left, right) => left.localeCompare(right))
|
|
1441
|
+
.join("$$");
|
|
1442
|
+
}
|
|
1443
|
+
function materializePropertySchemaDefault(property, analysis, runtimeBoardId) {
|
|
1444
|
+
if (hasPropertySchemaDefault(property)) {
|
|
1445
|
+
return cloneJson(property.default);
|
|
1446
|
+
}
|
|
1447
|
+
if (property.optional) {
|
|
1448
|
+
return undefined;
|
|
1449
|
+
}
|
|
1450
|
+
if (property.nullable) {
|
|
1451
|
+
return null;
|
|
1452
|
+
}
|
|
1453
|
+
switch (property.type) {
|
|
1454
|
+
case "string":
|
|
1455
|
+
return "";
|
|
1456
|
+
case "integer":
|
|
1457
|
+
case "number":
|
|
1458
|
+
return 0;
|
|
1459
|
+
case "boolean":
|
|
1460
|
+
return false;
|
|
1461
|
+
case "enum":
|
|
1462
|
+
return property.enums?.[0] ?? "";
|
|
1463
|
+
case "array":
|
|
1464
|
+
return [];
|
|
1465
|
+
case "object":
|
|
1466
|
+
return materializeObjectSchemaDefaults(property.properties ? { properties: property.properties } : undefined, analysis, runtimeBoardId);
|
|
1467
|
+
case "record":
|
|
1468
|
+
return {};
|
|
1469
|
+
case "zoneId":
|
|
1470
|
+
return analysis.zoneIds[0] ?? "";
|
|
1471
|
+
case "cardId":
|
|
1472
|
+
return analysis.cardIds[0] ?? "";
|
|
1473
|
+
case "playerId":
|
|
1474
|
+
return analysis.playerIds[0] ?? "";
|
|
1475
|
+
case "boardId":
|
|
1476
|
+
return runtimeBoardId ?? analysis.boardIds[0] ?? "";
|
|
1477
|
+
case "edgeId":
|
|
1478
|
+
return analysis.edgeIds[0] ?? "";
|
|
1479
|
+
case "vertexId":
|
|
1480
|
+
return analysis.vertexIds[0] ?? "";
|
|
1481
|
+
case "spaceId":
|
|
1482
|
+
return ((runtimeBoardId
|
|
1483
|
+
? analysis.spaceIdsByBoardId.get(runtimeBoardId)?.[0]
|
|
1484
|
+
: undefined) ??
|
|
1485
|
+
analysis.spaceIds[0] ??
|
|
1486
|
+
"");
|
|
1487
|
+
case "pieceId":
|
|
1488
|
+
return analysis.pieceIds[0] ?? "";
|
|
1489
|
+
case "dieId":
|
|
1490
|
+
return analysis.dieIds[0] ?? "";
|
|
1491
|
+
case "resourceId":
|
|
1492
|
+
return analysis.resourceIds[0] ?? "";
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
function materializeObjectSchemaDefaults(schema, analysis, runtimeBoardId) {
|
|
1496
|
+
if (!schema?.properties) {
|
|
1497
|
+
return {};
|
|
1498
|
+
}
|
|
1499
|
+
return Object.fromEntries(Object.entries(schema.properties).flatMap(([key, property]) => {
|
|
1500
|
+
const value = materializePropertySchemaDefault(property, analysis, runtimeBoardId);
|
|
1501
|
+
return value === undefined ? [] : [[key, value]];
|
|
1502
|
+
}));
|
|
1503
|
+
}
|
|
1504
|
+
function materializeCardPropertiesDefaults(schema, cardType, analysis) {
|
|
1505
|
+
const objectSchema = isCardPropertySchemaVariants(schema)
|
|
1506
|
+
? mergeSharedCardProperties(schema, cardType)
|
|
1507
|
+
: schema;
|
|
1508
|
+
return materializeObjectSchemaDefaults(objectSchema, analysis);
|
|
1509
|
+
}
|
|
1510
|
+
export function materializeManifestTable(options) {
|
|
1511
|
+
const analysis = analyzeManifest(options.manifest);
|
|
1512
|
+
const manifest = analysis.manifest;
|
|
1513
|
+
const playerIds = [...options.playerIds];
|
|
1514
|
+
const cards = {};
|
|
1515
|
+
const pieces = {};
|
|
1516
|
+
const dice = {};
|
|
1517
|
+
const componentLocations = {};
|
|
1518
|
+
const locationOrder = new Map();
|
|
1519
|
+
const cardIdsByCardSetId = new Map();
|
|
1520
|
+
const boardAnalysisByBaseId = new Map(analysis.analyzedBoards.map((board) => [board.board.id, board]));
|
|
1521
|
+
const edgeIdByBoardBaseIdAndSpaces = new Map();
|
|
1522
|
+
const vertexIdByBoardBaseIdAndSpaces = new Map();
|
|
1523
|
+
for (const analyzedBoard of analysis.analyzedBoards) {
|
|
1524
|
+
if (analyzedBoard.layout === "generic") {
|
|
1525
|
+
continue;
|
|
1526
|
+
}
|
|
1527
|
+
edgeIdByBoardBaseIdAndSpaces.set(analyzedBoard.board.id, new Map(analyzedBoard.edges.map((edge) => [
|
|
1528
|
+
boardSpaceRefKey(edge.spaceIds),
|
|
1529
|
+
edge.id,
|
|
1530
|
+
])));
|
|
1531
|
+
vertexIdByBoardBaseIdAndSpaces.set(analyzedBoard.board.id, new Map(analyzedBoard.vertices.map((vertex) => [
|
|
1532
|
+
boardSpaceRefKey(vertex.spaceIds),
|
|
1533
|
+
vertex.id,
|
|
1534
|
+
])));
|
|
1535
|
+
}
|
|
1536
|
+
const nextLocationPosition = (location) => {
|
|
1537
|
+
const key = JSON.stringify(location);
|
|
1538
|
+
const position = locationOrder.get(key) ?? 0;
|
|
1539
|
+
locationOrder.set(key, position + 1);
|
|
1540
|
+
return position;
|
|
1541
|
+
};
|
|
1542
|
+
const zoneScopeById = new Map([
|
|
1543
|
+
...analysis.sharedZones.map((zone) => [zone.id, "shared"]),
|
|
1544
|
+
...analysis.playerZones.map((zone) => [zone.id, "perPlayer"]),
|
|
1545
|
+
]);
|
|
1546
|
+
const nextDeckPositionByZoneId = new Map();
|
|
1547
|
+
const resolveRuntimeBoardId = (boardBaseId, ownerId, context) => {
|
|
1548
|
+
const analyzedBoard = boardAnalysisByBaseId.get(boardBaseId);
|
|
1549
|
+
if (analyzedBoard?.board.scope === "perPlayer") {
|
|
1550
|
+
if (!ownerId) {
|
|
1551
|
+
throw new Error(`${context ?? `Home on board '${boardBaseId}'`} requires ownerId because board '${boardBaseId}' has scope 'perPlayer'. Add ownerId to resolve the player-scoped destination.`);
|
|
1552
|
+
}
|
|
1553
|
+
return `${boardBaseId}:${ownerId}`;
|
|
1554
|
+
}
|
|
1555
|
+
return boardBaseId;
|
|
1556
|
+
};
|
|
1557
|
+
const resolveBoardEdgeId = (boardBaseId, spaceIds) => edgeIdByBoardBaseIdAndSpaces
|
|
1558
|
+
.get(boardBaseId)
|
|
1559
|
+
?.get(boardSpaceRefKey(spaceIds)) ?? boardSpaceRefKey(spaceIds);
|
|
1560
|
+
const resolveBoardVertexId = (boardBaseId, spaceIds) => vertexIdByBoardBaseIdAndSpaces
|
|
1561
|
+
.get(boardBaseId)
|
|
1562
|
+
?.get(boardSpaceRefKey(spaceIds)) ?? boardSpaceRefKey(spaceIds);
|
|
1563
|
+
for (const cardSet of manifest.cardSets) {
|
|
1564
|
+
const materializedCardSet = materializeCardSet(cardSet);
|
|
1565
|
+
const cardIds = [];
|
|
1566
|
+
for (const card of materializedCardSet.cards) {
|
|
1567
|
+
const cardInstanceIds = renderCardInstanceIds(card);
|
|
1568
|
+
for (const cardId of cardInstanceIds) {
|
|
1569
|
+
cardIds.push(cardId);
|
|
1570
|
+
cards[cardId] = {
|
|
1571
|
+
id: cardId,
|
|
1572
|
+
cardSetId: materializedCardSet.id,
|
|
1573
|
+
cardType: card.type,
|
|
1574
|
+
name: card.name,
|
|
1575
|
+
text: card.text,
|
|
1576
|
+
properties: {
|
|
1577
|
+
...materializeCardPropertiesDefaults(materializedCardSet.cardSchema, card.type, analysis),
|
|
1578
|
+
...(card.properties ?? {}),
|
|
1579
|
+
},
|
|
1580
|
+
};
|
|
1581
|
+
if (card.home?.type === "zone") {
|
|
1582
|
+
const nextPosition = nextDeckPositionByZoneId.get(card.home.zoneId) ?? 0;
|
|
1583
|
+
nextDeckPositionByZoneId.set(card.home.zoneId, nextPosition + 1);
|
|
1584
|
+
componentLocations[cardId] = {
|
|
1585
|
+
type: "InDeck",
|
|
1586
|
+
deckId: card.home.zoneId,
|
|
1587
|
+
playedBy: null,
|
|
1588
|
+
position: nextPosition,
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
else if (card.home?.type === "detached") {
|
|
1592
|
+
componentLocations[cardId] = {
|
|
1593
|
+
type: "Detached",
|
|
1594
|
+
position: nextLocationPosition({ type: "Detached" }),
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
cardIdsByCardSetId.set(materializedCardSet.id, cardIds);
|
|
1600
|
+
}
|
|
1601
|
+
for (const zone of analysis.sharedZones) {
|
|
1602
|
+
const primaryCardSetId = zone.allowedCardSetIds?.[0];
|
|
1603
|
+
if (!primaryCardSetId) {
|
|
1604
|
+
continue;
|
|
1605
|
+
}
|
|
1606
|
+
const shuffled = options.shuffleItems((cardIdsByCardSetId.get(primaryCardSetId) ?? []).filter((cardId) => componentLocations[cardId] === undefined));
|
|
1607
|
+
shuffled.forEach((cardId, index) => {
|
|
1608
|
+
componentLocations[cardId] = {
|
|
1609
|
+
type: "InDeck",
|
|
1610
|
+
deckId: zone.id,
|
|
1611
|
+
playedBy: null,
|
|
1612
|
+
position: index,
|
|
1613
|
+
};
|
|
1614
|
+
});
|
|
1615
|
+
}
|
|
1616
|
+
const pieceTypesById = new Map((manifest.pieceTypes ?? []).map((pieceType) => [pieceType.id, pieceType]));
|
|
1617
|
+
const dieTypesById = new Map((manifest.dieTypes ?? []).map((dieType) => [dieType.id, dieType]));
|
|
1618
|
+
const assignSeedLocation = (componentId, ownerId, home, context) => {
|
|
1619
|
+
if (!home) {
|
|
1620
|
+
componentLocations[componentId] = { type: "Detached" };
|
|
1621
|
+
return;
|
|
1622
|
+
}
|
|
1623
|
+
if (home.type === "slot") {
|
|
1624
|
+
componentLocations[componentId] = {
|
|
1625
|
+
type: "InSlot",
|
|
1626
|
+
host: home.host,
|
|
1627
|
+
slotId: home.slotId,
|
|
1628
|
+
position: nextLocationPosition({
|
|
1629
|
+
type: "InSlot",
|
|
1630
|
+
host: home.host,
|
|
1631
|
+
slotId: home.slotId,
|
|
1632
|
+
}),
|
|
1633
|
+
};
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
if (home.type === "zone") {
|
|
1637
|
+
if (zoneScopeById.get(home.zoneId) === "perPlayer") {
|
|
1638
|
+
if (!ownerId) {
|
|
1639
|
+
throw new Error(`${context.path}.zoneId: ${context.label} requires ownerId because zone '${home.zoneId}' has scope 'perPlayer'. Add ownerId to resolve the player-scoped destination.`);
|
|
1640
|
+
}
|
|
1641
|
+
componentLocations[componentId] = {
|
|
1642
|
+
type: "InHand",
|
|
1643
|
+
handId: home.zoneId,
|
|
1644
|
+
playerId: ownerId,
|
|
1645
|
+
position: nextLocationPosition({
|
|
1646
|
+
type: "InHand",
|
|
1647
|
+
handId: home.zoneId,
|
|
1648
|
+
playerId: ownerId,
|
|
1649
|
+
}),
|
|
1650
|
+
};
|
|
1651
|
+
return;
|
|
1652
|
+
}
|
|
1653
|
+
componentLocations[componentId] = {
|
|
1654
|
+
type: "InZone",
|
|
1655
|
+
zoneId: home.zoneId,
|
|
1656
|
+
playedBy: null,
|
|
1657
|
+
position: nextLocationPosition({
|
|
1658
|
+
type: "InZone",
|
|
1659
|
+
zoneId: home.zoneId,
|
|
1660
|
+
}),
|
|
1661
|
+
};
|
|
1662
|
+
return;
|
|
1663
|
+
}
|
|
1664
|
+
if (home.type === "space") {
|
|
1665
|
+
const boardId = resolveRuntimeBoardId(home.boardId, ownerId, `${context.path}.boardId: ${context.label}`);
|
|
1666
|
+
componentLocations[componentId] = {
|
|
1667
|
+
type: "OnSpace",
|
|
1668
|
+
boardId,
|
|
1669
|
+
spaceId: home.spaceId,
|
|
1670
|
+
position: nextLocationPosition({
|
|
1671
|
+
type: "OnSpace",
|
|
1672
|
+
boardId,
|
|
1673
|
+
spaceId: home.spaceId,
|
|
1674
|
+
}),
|
|
1675
|
+
};
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
1678
|
+
if (home.type === "container") {
|
|
1679
|
+
const boardId = resolveRuntimeBoardId(home.boardId, ownerId, `${context.path}.boardId: ${context.label}`);
|
|
1680
|
+
componentLocations[componentId] = {
|
|
1681
|
+
type: "InContainer",
|
|
1682
|
+
boardId,
|
|
1683
|
+
containerId: home.containerId,
|
|
1684
|
+
position: nextLocationPosition({
|
|
1685
|
+
type: "InContainer",
|
|
1686
|
+
boardId,
|
|
1687
|
+
containerId: home.containerId,
|
|
1688
|
+
}),
|
|
1689
|
+
};
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1692
|
+
if (home.type === "edge") {
|
|
1693
|
+
const boardId = resolveRuntimeBoardId(home.boardId, ownerId, `${context.path}.boardId: ${context.label}`);
|
|
1694
|
+
const edgeId = resolveBoardEdgeId(home.boardId, home.ref.spaces);
|
|
1695
|
+
componentLocations[componentId] = {
|
|
1696
|
+
type: "OnEdge",
|
|
1697
|
+
boardId,
|
|
1698
|
+
edgeId,
|
|
1699
|
+
position: nextLocationPosition({
|
|
1700
|
+
type: "OnEdge",
|
|
1701
|
+
boardId,
|
|
1702
|
+
edgeId,
|
|
1703
|
+
}),
|
|
1704
|
+
};
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1707
|
+
if (home.type === "vertex") {
|
|
1708
|
+
const boardId = resolveRuntimeBoardId(home.boardId, ownerId, `${context.path}.boardId: ${context.label}`);
|
|
1709
|
+
const vertexId = resolveBoardVertexId(home.boardId, home.ref.spaces);
|
|
1710
|
+
componentLocations[componentId] = {
|
|
1711
|
+
type: "OnVertex",
|
|
1712
|
+
boardId,
|
|
1713
|
+
vertexId,
|
|
1714
|
+
position: nextLocationPosition({
|
|
1715
|
+
type: "OnVertex",
|
|
1716
|
+
boardId,
|
|
1717
|
+
vertexId,
|
|
1718
|
+
}),
|
|
1719
|
+
};
|
|
1720
|
+
return;
|
|
1721
|
+
}
|
|
1722
|
+
componentLocations[componentId] = { type: "Detached" };
|
|
1723
|
+
};
|
|
1724
|
+
for (const [seedIndex, seed] of (manifest.pieceSeeds ?? []).entries()) {
|
|
1725
|
+
const pieceType = pieceTypesById.get(seed.typeId);
|
|
1726
|
+
for (const componentId of expandSeedIds([seed])) {
|
|
1727
|
+
pieces[componentId] = {
|
|
1728
|
+
componentType: "piece",
|
|
1729
|
+
id: componentId,
|
|
1730
|
+
pieceTypeId: seed.typeId,
|
|
1731
|
+
pieceName: pieceType?.name ?? seed.typeId,
|
|
1732
|
+
ownerId: seed.ownerId ?? null,
|
|
1733
|
+
properties: {
|
|
1734
|
+
...materializeObjectSchemaDefaults(analysis.pieceTypeSchemasById.get(seed.typeId), analysis),
|
|
1735
|
+
...(seed.fields ?? {}),
|
|
1736
|
+
},
|
|
1737
|
+
};
|
|
1738
|
+
assignSeedLocation(componentId, seed.ownerId, seed.home, {
|
|
1739
|
+
path: `manifest.pieceSeeds[${seedIndex}].home`,
|
|
1740
|
+
label: `Piece seed '${seed.id ?? seed.typeId}'`,
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
for (const [seedIndex, seed] of (manifest.dieSeeds ?? []).entries()) {
|
|
1745
|
+
const dieType = dieTypesById.get(seed.typeId);
|
|
1746
|
+
for (const componentId of expandSeedIds([seed])) {
|
|
1747
|
+
dice[componentId] = {
|
|
1748
|
+
componentType: "die",
|
|
1749
|
+
id: componentId,
|
|
1750
|
+
dieTypeId: seed.typeId,
|
|
1751
|
+
dieName: dieType?.name ?? seed.typeId,
|
|
1752
|
+
ownerId: seed.ownerId ?? null,
|
|
1753
|
+
sides: dieType?.sides ?? 6,
|
|
1754
|
+
value: null,
|
|
1755
|
+
properties: {
|
|
1756
|
+
...materializeObjectSchemaDefaults(analysis.dieTypeSchemasById.get(seed.typeId), analysis),
|
|
1757
|
+
...(seed.fields ?? {}),
|
|
1758
|
+
},
|
|
1759
|
+
};
|
|
1760
|
+
assignSeedLocation(componentId, seed.ownerId, seed.home, {
|
|
1761
|
+
path: `manifest.dieSeeds[${seedIndex}].home`,
|
|
1762
|
+
label: `Die seed '${seed.id ?? seed.typeId}'`,
|
|
1763
|
+
});
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
const boardStatesById = {};
|
|
1767
|
+
const hexBoardStatesById = {};
|
|
1768
|
+
const squareBoardStatesById = {};
|
|
1769
|
+
for (const analyzedBoard of analysis.analyzedBoards) {
|
|
1770
|
+
const sharedBoardState = {
|
|
1771
|
+
baseId: analyzedBoard.board.id,
|
|
1772
|
+
layout: analyzedBoard.layout,
|
|
1773
|
+
typeId: analyzedBoard.boardTypeId ?? null,
|
|
1774
|
+
scope: analyzedBoard.board.scope,
|
|
1775
|
+
templateId: analyzedBoard.board.templateId ?? null,
|
|
1776
|
+
fields: {
|
|
1777
|
+
...materializeObjectSchemaDefaults(analyzedBoard.boardFieldsSchema, analysis),
|
|
1778
|
+
...(analyzedBoard.board.fields ?? {}),
|
|
1779
|
+
},
|
|
1780
|
+
};
|
|
1781
|
+
const buildSpaces = () => Object.fromEntries(analysis.spaceIds.map((spaceId) => {
|
|
1782
|
+
const space = analyzedBoard.spaces.find((entry) => entry.id === spaceId);
|
|
1783
|
+
const baseSpaceState = {
|
|
1784
|
+
id: spaceId,
|
|
1785
|
+
name: space && "name" in space ? (space.name ?? null) : null,
|
|
1786
|
+
typeId: space?.typeId ?? null,
|
|
1787
|
+
fields: {
|
|
1788
|
+
...materializeObjectSchemaDefaults(analyzedBoard.spaceFieldsSchema, analysis),
|
|
1789
|
+
...(space?.fields ?? {}),
|
|
1790
|
+
},
|
|
1791
|
+
zoneId: space && "zoneId" in space ? (space.zoneId ?? null) : null,
|
|
1792
|
+
};
|
|
1793
|
+
if (analyzedBoard.layout === "hex") {
|
|
1794
|
+
const hexSpace = space;
|
|
1795
|
+
return [
|
|
1796
|
+
spaceId,
|
|
1797
|
+
{
|
|
1798
|
+
...baseSpaceState,
|
|
1799
|
+
q: hexSpace?.q ?? 0,
|
|
1800
|
+
r: hexSpace?.r ?? 0,
|
|
1801
|
+
},
|
|
1802
|
+
];
|
|
1803
|
+
}
|
|
1804
|
+
if (analyzedBoard.layout === "square") {
|
|
1805
|
+
const squareSpace = space;
|
|
1806
|
+
return [
|
|
1807
|
+
spaceId,
|
|
1808
|
+
{
|
|
1809
|
+
...baseSpaceState,
|
|
1810
|
+
row: squareSpace?.row ?? 0,
|
|
1811
|
+
col: squareSpace?.col ?? 0,
|
|
1812
|
+
},
|
|
1813
|
+
];
|
|
1814
|
+
}
|
|
1815
|
+
return [spaceId, baseSpaceState];
|
|
1816
|
+
}));
|
|
1817
|
+
const relations = analyzedBoard.layout === "hex"
|
|
1818
|
+
? []
|
|
1819
|
+
: analyzedBoard.relations.map((relation) => ({
|
|
1820
|
+
id: relation.id ?? null,
|
|
1821
|
+
typeId: relation.typeId,
|
|
1822
|
+
fromSpaceId: relation.fromSpaceId,
|
|
1823
|
+
toSpaceId: relation.toSpaceId,
|
|
1824
|
+
directed: relation.directed ?? false,
|
|
1825
|
+
fields: {
|
|
1826
|
+
...materializeObjectSchemaDefaults(analyzedBoard.relationFieldsSchema, analysis),
|
|
1827
|
+
...(relation.fields ?? {}),
|
|
1828
|
+
},
|
|
1829
|
+
}));
|
|
1830
|
+
const buildContainers = (runtimeBoardId) => analyzedBoard.layout === "hex"
|
|
1831
|
+
? {}
|
|
1832
|
+
: Object.fromEntries(analysis.boardContainerIds.map((containerId) => {
|
|
1833
|
+
const container = analyzedBoard.containers.find((entry) => entry.id === containerId);
|
|
1834
|
+
return [
|
|
1835
|
+
containerId,
|
|
1836
|
+
{
|
|
1837
|
+
id: containerId,
|
|
1838
|
+
name: container?.name ?? containerId,
|
|
1839
|
+
host: container?.host.type === "space"
|
|
1840
|
+
? { type: "space", spaceId: container.host.spaceId }
|
|
1841
|
+
: { type: "board" },
|
|
1842
|
+
allowedCardSetIds: container?.allowedCardSetIds,
|
|
1843
|
+
zoneId: `board:${runtimeBoardId}:container:${containerId}`,
|
|
1844
|
+
fields: {
|
|
1845
|
+
...materializeObjectSchemaDefaults(analyzedBoard.containerFieldsSchema, analysis, runtimeBoardId),
|
|
1846
|
+
...(container?.fields ?? {}),
|
|
1847
|
+
},
|
|
1848
|
+
},
|
|
1849
|
+
];
|
|
1850
|
+
}));
|
|
1851
|
+
const edges = analyzedBoard.layout === "generic"
|
|
1852
|
+
? []
|
|
1853
|
+
: analyzedBoard.edges.map((edge) => ({
|
|
1854
|
+
id: edge.id,
|
|
1855
|
+
spaceIds: [...edge.spaceIds],
|
|
1856
|
+
typeId: edge.typeId ?? null,
|
|
1857
|
+
label: edge.label ?? null,
|
|
1858
|
+
ownerId: null,
|
|
1859
|
+
fields: {
|
|
1860
|
+
...materializeObjectSchemaDefaults(analyzedBoard.edgeFieldsSchema, analysis),
|
|
1861
|
+
...(edge.fields ?? {}),
|
|
1862
|
+
},
|
|
1863
|
+
}));
|
|
1864
|
+
const vertices = analyzedBoard.layout === "generic"
|
|
1865
|
+
? []
|
|
1866
|
+
: analyzedBoard.vertices.map((vertex) => ({
|
|
1867
|
+
id: vertex.id,
|
|
1868
|
+
spaceIds: [...vertex.spaceIds],
|
|
1869
|
+
typeId: vertex.typeId ?? null,
|
|
1870
|
+
label: vertex.label ?? null,
|
|
1871
|
+
ownerId: null,
|
|
1872
|
+
fields: {
|
|
1873
|
+
...materializeObjectSchemaDefaults(analyzedBoard.vertexFieldsSchema, analysis),
|
|
1874
|
+
...(vertex.fields ?? {}),
|
|
1875
|
+
},
|
|
1876
|
+
}));
|
|
1877
|
+
for (const runtimeBoardId of analyzedBoard.runtimeBoardIds) {
|
|
1878
|
+
const playerId = analyzedBoard.board.scope === "perPlayer"
|
|
1879
|
+
? runtimeBoardId.slice(analyzedBoard.board.id.length + 1)
|
|
1880
|
+
: null;
|
|
1881
|
+
const boardState = {
|
|
1882
|
+
id: runtimeBoardId,
|
|
1883
|
+
...sharedBoardState,
|
|
1884
|
+
playerId,
|
|
1885
|
+
spaces: cloneJson(buildSpaces()),
|
|
1886
|
+
relations: cloneJson(relations),
|
|
1887
|
+
containers: cloneJson(buildContainers(runtimeBoardId)),
|
|
1888
|
+
...(analyzedBoard.layout === "hex"
|
|
1889
|
+
? {
|
|
1890
|
+
orientation: analyzedBoard.board.orientation ?? "pointy-top",
|
|
1891
|
+
edges: cloneJson(edges),
|
|
1892
|
+
vertices: cloneJson(vertices),
|
|
1893
|
+
}
|
|
1894
|
+
: analyzedBoard.layout === "square"
|
|
1895
|
+
? {
|
|
1896
|
+
edges: cloneJson(edges),
|
|
1897
|
+
vertices: cloneJson(vertices),
|
|
1898
|
+
}
|
|
1899
|
+
: {}),
|
|
1900
|
+
};
|
|
1901
|
+
boardStatesById[runtimeBoardId] = boardState;
|
|
1902
|
+
if (analyzedBoard.layout === "hex") {
|
|
1903
|
+
hexBoardStatesById[runtimeBoardId] = boardState;
|
|
1904
|
+
}
|
|
1905
|
+
if (analyzedBoard.layout === "square") {
|
|
1906
|
+
squareBoardStatesById[runtimeBoardId] = boardState;
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
const sharedZones = Object.fromEntries(analysis.sharedZones.map((zone) => [zone.id, []]));
|
|
1911
|
+
const perPlayerZones = Object.fromEntries(analysis.playerZones.map((zone) => [
|
|
1912
|
+
zone.id,
|
|
1913
|
+
Object.fromEntries(playerIds.map((playerId) => [playerId, []])),
|
|
1914
|
+
]));
|
|
1915
|
+
const zoneVisibility = Object.fromEntries((manifest.zones ?? []).map((zone) => [
|
|
1916
|
+
zone.id,
|
|
1917
|
+
zone.visibility ?? "public",
|
|
1918
|
+
]));
|
|
1919
|
+
const zoneCardSetIdsByZoneId = Object.fromEntries(Array.from(analysis.zoneCardSetIdsById.entries()).sort(([left], [right]) => left.localeCompare(right)));
|
|
1920
|
+
const handVisibility = Object.fromEntries(analysis.playerZones.map((zone) => [
|
|
1921
|
+
zone.id,
|
|
1922
|
+
zone.visibility ?? "ownerOnly",
|
|
1923
|
+
]));
|
|
1924
|
+
const componentSortPosition = (position) => typeof position === "number" ? position : Number.MAX_SAFE_INTEGER;
|
|
1925
|
+
const pushSharedComponent = (zoneId, componentId, position) => {
|
|
1926
|
+
const zone = sharedZones[zoneId];
|
|
1927
|
+
if (!zone) {
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1930
|
+
zone.push(componentId);
|
|
1931
|
+
zone.sort((left, right) => componentSortPosition(componentLocations[left]?.position) -
|
|
1932
|
+
componentSortPosition(componentLocations[right]?.position));
|
|
1933
|
+
};
|
|
1934
|
+
const pushPlayerComponent = (zoneId, playerId, componentId, position) => {
|
|
1935
|
+
const zone = perPlayerZones[zoneId]?.[playerId];
|
|
1936
|
+
if (!zone) {
|
|
1937
|
+
return;
|
|
1938
|
+
}
|
|
1939
|
+
zone.push(componentId);
|
|
1940
|
+
zone.sort((left, right) => componentSortPosition(componentLocations[left]?.position) -
|
|
1941
|
+
componentSortPosition(componentLocations[right]?.position));
|
|
1942
|
+
};
|
|
1943
|
+
for (const [componentId, location] of Object.entries(componentLocations)) {
|
|
1944
|
+
switch (location.type) {
|
|
1945
|
+
case "InDeck":
|
|
1946
|
+
pushSharedComponent(location.deckId, componentId, location.position);
|
|
1947
|
+
break;
|
|
1948
|
+
case "InHand":
|
|
1949
|
+
pushPlayerComponent(location.handId, location.playerId, componentId, location.position);
|
|
1950
|
+
break;
|
|
1951
|
+
case "InZone":
|
|
1952
|
+
pushSharedComponent(location.zoneId, componentId, location.position);
|
|
1953
|
+
break;
|
|
1954
|
+
default:
|
|
1955
|
+
break;
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
const ownerOfCard = Object.fromEntries(Object.keys(cards).map((cardId) => [cardId, null]));
|
|
1959
|
+
const visibility = Object.fromEntries(Object.keys(cards).map((cardId) => [cardId, { faceUp: true }]));
|
|
1960
|
+
const resourcesByPlayer = Object.fromEntries(playerIds.map((playerId) => [
|
|
1961
|
+
playerId,
|
|
1962
|
+
Object.fromEntries(analysis.resourceIds.map((resourceId) => [resourceId, 0])),
|
|
1963
|
+
]));
|
|
1964
|
+
// PerPlayer<T> wire shape only: { __perPlayer: true, entries: [[playerId, value], ...] }.
|
|
1965
|
+
const toPerPlayerWireFormat = (entries) => ({
|
|
1966
|
+
__perPlayer: true,
|
|
1967
|
+
entries: playerIds.map((playerId) => [playerId, entries[playerId]]),
|
|
1968
|
+
});
|
|
1969
|
+
const perPlayerZonesWire = Object.fromEntries(Object.entries(perPlayerZones).map(([zoneId, entries]) => [
|
|
1970
|
+
zoneId,
|
|
1971
|
+
toPerPlayerWireFormat(entries),
|
|
1972
|
+
]));
|
|
1973
|
+
const resourcesWire = toPerPlayerWireFormat(resourcesByPlayer);
|
|
1974
|
+
return {
|
|
1975
|
+
playerOrder: playerIds,
|
|
1976
|
+
zones: {
|
|
1977
|
+
shared: sharedZones,
|
|
1978
|
+
perPlayer: perPlayerZonesWire,
|
|
1979
|
+
visibility: zoneVisibility,
|
|
1980
|
+
cardSetIdsByZoneId: zoneCardSetIdsByZoneId,
|
|
1981
|
+
},
|
|
1982
|
+
decks: cloneJson(sharedZones),
|
|
1983
|
+
hands: cloneJson(perPlayerZonesWire),
|
|
1984
|
+
handVisibility,
|
|
1985
|
+
cards,
|
|
1986
|
+
pieces,
|
|
1987
|
+
componentLocations,
|
|
1988
|
+
ownerOfCard,
|
|
1989
|
+
visibility,
|
|
1990
|
+
resources: resourcesWire,
|
|
1991
|
+
boards: {
|
|
1992
|
+
byId: boardStatesById,
|
|
1993
|
+
hex: hexBoardStatesById,
|
|
1994
|
+
square: squareBoardStatesById,
|
|
1995
|
+
network: {},
|
|
1996
|
+
track: {},
|
|
1997
|
+
},
|
|
1998
|
+
dice,
|
|
1999
|
+
};
|
|
2000
|
+
}
|
|
2001
|
+
function renderTypeForPropertySchema(schema) {
|
|
2002
|
+
if (!schema) {
|
|
2003
|
+
return "RuntimePayload";
|
|
2004
|
+
}
|
|
2005
|
+
let rendered;
|
|
2006
|
+
switch (schema.type) {
|
|
2007
|
+
case "string":
|
|
2008
|
+
rendered = "string";
|
|
2009
|
+
break;
|
|
2010
|
+
case "integer":
|
|
2011
|
+
case "number":
|
|
2012
|
+
rendered = "number";
|
|
2013
|
+
break;
|
|
2014
|
+
case "boolean":
|
|
2015
|
+
rendered = "boolean";
|
|
2016
|
+
break;
|
|
2017
|
+
case "cardId":
|
|
2018
|
+
rendered = "CardId";
|
|
2019
|
+
break;
|
|
2020
|
+
case "playerId":
|
|
2021
|
+
rendered = "PlayerId";
|
|
2022
|
+
break;
|
|
2023
|
+
case "zoneId":
|
|
2024
|
+
rendered = "ZoneId";
|
|
2025
|
+
break;
|
|
2026
|
+
case "boardId":
|
|
2027
|
+
rendered = "BoardId";
|
|
2028
|
+
break;
|
|
2029
|
+
case "edgeId":
|
|
2030
|
+
rendered = "EdgeId";
|
|
2031
|
+
break;
|
|
2032
|
+
case "vertexId":
|
|
2033
|
+
rendered = "VertexId";
|
|
2034
|
+
break;
|
|
2035
|
+
case "spaceId":
|
|
2036
|
+
rendered = "SpaceId";
|
|
2037
|
+
break;
|
|
2038
|
+
case "pieceId":
|
|
2039
|
+
rendered = "PieceId";
|
|
2040
|
+
break;
|
|
2041
|
+
case "dieId":
|
|
2042
|
+
rendered = "DieId";
|
|
2043
|
+
break;
|
|
2044
|
+
case "resourceId":
|
|
2045
|
+
rendered = "ResourceId";
|
|
2046
|
+
break;
|
|
2047
|
+
case "enum":
|
|
2048
|
+
rendered =
|
|
2049
|
+
schema.enums && schema.enums.length > 0
|
|
2050
|
+
? schema.enums.map((value) => quote(value)).join(" | ")
|
|
2051
|
+
: "string";
|
|
2052
|
+
break;
|
|
2053
|
+
case "array":
|
|
2054
|
+
rendered = `Array<${renderTypeForPropertySchema(schema.items)}>`;
|
|
2055
|
+
break;
|
|
2056
|
+
case "object":
|
|
2057
|
+
rendered = renderTypeForObjectSchema(schema.properties
|
|
2058
|
+
? {
|
|
2059
|
+
properties: schema.properties,
|
|
2060
|
+
}
|
|
2061
|
+
: null);
|
|
2062
|
+
break;
|
|
2063
|
+
case "record":
|
|
2064
|
+
rendered = `Record<string, ${renderTypeForPropertySchema(schema.values)}>`;
|
|
2065
|
+
break;
|
|
2066
|
+
default:
|
|
2067
|
+
rendered = "RuntimePayload";
|
|
2068
|
+
break;
|
|
2069
|
+
}
|
|
2070
|
+
return schema.nullable ? `${rendered} | null` : rendered;
|
|
2071
|
+
}
|
|
2072
|
+
function renderZodForPropertySchema(schema) {
|
|
2073
|
+
if (!schema) {
|
|
2074
|
+
return "z.unknown()";
|
|
2075
|
+
}
|
|
2076
|
+
let rendered;
|
|
2077
|
+
switch (schema.type) {
|
|
2078
|
+
case "string":
|
|
2079
|
+
rendered = "z.string()";
|
|
2080
|
+
break;
|
|
2081
|
+
case "integer":
|
|
2082
|
+
rendered = "z.number().int()";
|
|
2083
|
+
break;
|
|
2084
|
+
case "number":
|
|
2085
|
+
rendered = "z.number()";
|
|
2086
|
+
break;
|
|
2087
|
+
case "boolean":
|
|
2088
|
+
rendered = "z.boolean()";
|
|
2089
|
+
break;
|
|
2090
|
+
case "cardId":
|
|
2091
|
+
rendered = "ids.cardId";
|
|
2092
|
+
break;
|
|
2093
|
+
case "playerId":
|
|
2094
|
+
rendered = "ids.playerId";
|
|
2095
|
+
break;
|
|
2096
|
+
case "zoneId":
|
|
2097
|
+
rendered = "ids.zoneId";
|
|
2098
|
+
break;
|
|
2099
|
+
case "boardId":
|
|
2100
|
+
rendered = "ids.boardId";
|
|
2101
|
+
break;
|
|
2102
|
+
case "edgeId":
|
|
2103
|
+
rendered = "ids.edgeId";
|
|
2104
|
+
break;
|
|
2105
|
+
case "vertexId":
|
|
2106
|
+
rendered = "ids.vertexId";
|
|
2107
|
+
break;
|
|
2108
|
+
case "spaceId":
|
|
2109
|
+
rendered = "ids.spaceId";
|
|
2110
|
+
break;
|
|
2111
|
+
case "pieceId":
|
|
2112
|
+
rendered = "ids.pieceId";
|
|
2113
|
+
break;
|
|
2114
|
+
case "dieId":
|
|
2115
|
+
rendered = "ids.dieId";
|
|
2116
|
+
break;
|
|
2117
|
+
case "resourceId":
|
|
2118
|
+
rendered = "ids.resourceId";
|
|
2119
|
+
break;
|
|
2120
|
+
case "enum":
|
|
2121
|
+
rendered =
|
|
2122
|
+
schema.enums && schema.enums.length > 0
|
|
2123
|
+
? `z.enum([${schema.enums.map((value) => quote(value)).join(", ")}])`
|
|
2124
|
+
: "z.string()";
|
|
2125
|
+
break;
|
|
2126
|
+
case "array":
|
|
2127
|
+
rendered = `z.array(${renderZodForPropertySchema(schema.items)})`;
|
|
2128
|
+
break;
|
|
2129
|
+
case "object":
|
|
2130
|
+
rendered = renderZodForObjectSchema(schema.properties
|
|
2131
|
+
? {
|
|
2132
|
+
properties: schema.properties,
|
|
2133
|
+
}
|
|
2134
|
+
: null);
|
|
2135
|
+
break;
|
|
2136
|
+
case "record":
|
|
2137
|
+
rendered = `z.record(z.string(), ${renderZodForPropertySchema(schema.values)})`;
|
|
2138
|
+
break;
|
|
2139
|
+
default:
|
|
2140
|
+
rendered = "z.unknown()";
|
|
2141
|
+
break;
|
|
2142
|
+
}
|
|
2143
|
+
if (schema.nullable) {
|
|
2144
|
+
rendered = `${rendered}.nullable()`;
|
|
2145
|
+
}
|
|
2146
|
+
if (schema.optional) {
|
|
2147
|
+
rendered = `${rendered}.optional()`;
|
|
2148
|
+
}
|
|
2149
|
+
if (hasPropertySchemaDefault(schema)) {
|
|
2150
|
+
rendered = `${rendered}.default(${JSON.stringify(schema.default)})`;
|
|
2151
|
+
}
|
|
2152
|
+
return rendered;
|
|
2153
|
+
}
|
|
2154
|
+
function renderTypeForObjectSchema(schema) {
|
|
2155
|
+
if (!schema || Object.keys(schema.properties).length === 0) {
|
|
2156
|
+
return "RuntimeRecord";
|
|
2157
|
+
}
|
|
2158
|
+
return `{\n${Object.entries(schema.properties)
|
|
2159
|
+
.map(([key, value]) => {
|
|
2160
|
+
const optional = value?.optional && !hasPropertySchemaDefault(value) ? "?" : "";
|
|
2161
|
+
return ` ${quote(key)}${optional}: ${renderTypeForPropertySchema(value)};`;
|
|
2162
|
+
})
|
|
2163
|
+
.join("\n")}\n}`;
|
|
2164
|
+
}
|
|
2165
|
+
function renderZodForObjectSchema(schema) {
|
|
2166
|
+
if (!schema || Object.keys(schema.properties).length === 0) {
|
|
2167
|
+
return "z.record(z.string(), z.unknown())";
|
|
2168
|
+
}
|
|
2169
|
+
return `z.object({\n${Object.entries(schema.properties)
|
|
2170
|
+
.map(([key, value]) => ` ${quote(key)}: ${renderZodForPropertySchema(value)},`)
|
|
2171
|
+
.join("\n")}\n})`;
|
|
2172
|
+
}
|
|
2173
|
+
function renderCardSetPropertySections(cardSets) {
|
|
2174
|
+
return cardSets
|
|
2175
|
+
.map((cardSet) => {
|
|
2176
|
+
const typeName = `${toPascalCase(cardSet.id)}CardProperties`;
|
|
2177
|
+
const schemaName = `${typeName}Schema`;
|
|
2178
|
+
if (isCardPropertySchemaVariants(cardSet.cardSchema)) {
|
|
2179
|
+
const cardSchema = cardSet.cardSchema;
|
|
2180
|
+
const variantBlocks = Object.entries(cardSchema.variants).map(([cardType]) => {
|
|
2181
|
+
const variantTypeName = cardPropertiesTypeName(cardSet, cardType);
|
|
2182
|
+
const variantSchemaName = cardPropertiesSchemaName(cardSet, cardType);
|
|
2183
|
+
const schema = mergeSharedCardProperties(cardSchema, cardType);
|
|
2184
|
+
return renderBlocks([
|
|
2185
|
+
`export type ${variantTypeName} = ${renderTypeForObjectSchema(schema)};`,
|
|
2186
|
+
`export const ${variantSchemaName} = ${renderZodForObjectSchema(schema)};`,
|
|
2187
|
+
]);
|
|
2188
|
+
});
|
|
2189
|
+
const variantTypeNames = Object.keys(cardSchema.variants).map((cardType) => cardPropertiesTypeName(cardSet, cardType));
|
|
2190
|
+
const variantSchemaNames = Object.keys(cardSchema.variants).map((cardType) => cardPropertiesSchemaName(cardSet, cardType));
|
|
2191
|
+
const unionSchema = variantSchemaNames.length === 0
|
|
2192
|
+
? "z.record(z.string(), z.unknown())"
|
|
2193
|
+
: variantSchemaNames.length === 1
|
|
2194
|
+
? variantSchemaNames[0]
|
|
2195
|
+
: `z.union([${variantSchemaNames.join(", ")}])`;
|
|
2196
|
+
return renderBlocks([
|
|
2197
|
+
...variantBlocks,
|
|
2198
|
+
`export type ${typeName} = ${variantTypeNames.join(" | ") || "RuntimeRecord"};`,
|
|
2199
|
+
`export const ${schemaName} = ${unionSchema};`,
|
|
2200
|
+
`export type ${toPascalCase(cardSet.id)}CardId = ${cardSet.cards
|
|
2201
|
+
.flatMap(renderCardInstanceIds)
|
|
2202
|
+
.map((cardId) => quote(cardId))
|
|
2203
|
+
.join(" | ") || "never"};`,
|
|
2204
|
+
]);
|
|
2205
|
+
}
|
|
2206
|
+
return renderBlocks([
|
|
2207
|
+
`export type ${typeName} = ${renderTypeForObjectSchema(cardSet.cardSchema)};`,
|
|
2208
|
+
`export const ${schemaName} = ${renderZodForObjectSchema(cardSet.cardSchema)};`,
|
|
2209
|
+
`export type ${toPascalCase(cardSet.id)}CardId = ${cardSet.cards
|
|
2210
|
+
.flatMap(renderCardInstanceIds)
|
|
2211
|
+
.map((cardId) => quote(cardId))
|
|
2212
|
+
.join(" | ") || "never"};`,
|
|
2213
|
+
]);
|
|
2214
|
+
})
|
|
2215
|
+
.join("\n\n");
|
|
2216
|
+
}
|
|
2217
|
+
function cardPropertiesTypeName(cardSet, cardType) {
|
|
2218
|
+
if (!isCardPropertySchemaVariants(cardSet.cardSchema)) {
|
|
2219
|
+
return `${toPascalCase(cardSet.id)}CardProperties`;
|
|
2220
|
+
}
|
|
2221
|
+
return `${toPascalCase(cardSet.id)}${toPascalCase(cardType)}CardProperties`;
|
|
2222
|
+
}
|
|
2223
|
+
function cardPropertiesSchemaName(cardSet, cardType) {
|
|
2224
|
+
if (!isCardPropertySchemaVariants(cardSet.cardSchema)) {
|
|
2225
|
+
return `${toPascalCase(cardSet.id)}CardPropertiesSchema`;
|
|
2226
|
+
}
|
|
2227
|
+
return `${cardPropertiesTypeName(cardSet, cardType)}Schema`;
|
|
2228
|
+
}
|
|
2229
|
+
function renderCardPropertiesSchemaByCardSetId(cardSets) {
|
|
2230
|
+
const entries = cardSets.flatMap((cardSet) => {
|
|
2231
|
+
if (isCardPropertySchemaVariants(cardSet.cardSchema)) {
|
|
2232
|
+
return Object.keys(cardSet.cardSchema.variants).map((cardType) => ` ${quote(`${cardSet.id}:${cardType}`)}: ${cardPropertiesSchemaName(cardSet, cardType)},`);
|
|
2233
|
+
}
|
|
2234
|
+
return [
|
|
2235
|
+
` ${quote(cardSet.id)}: ${toPascalCase(cardSet.id)}CardPropertiesSchema,`,
|
|
2236
|
+
];
|
|
2237
|
+
});
|
|
2238
|
+
return `const cardPropertiesSchemaByCardSetId: Record<string, z.ZodTypeAny> = {\n${entries.join("\n")}\n};`;
|
|
2239
|
+
}
|
|
2240
|
+
function renderObjectSchemaSection(typeName, schemaName, schema) {
|
|
2241
|
+
return renderBlocks([
|
|
2242
|
+
`export type ${typeName} = ${renderTypeForObjectSchema(schema)};`,
|
|
2243
|
+
`export const ${schemaName} = ${renderZodForObjectSchema(schema)};`,
|
|
2244
|
+
]);
|
|
2245
|
+
}
|
|
2246
|
+
function boardPrefix(boardId) {
|
|
2247
|
+
return toPascalCase(boardId);
|
|
2248
|
+
}
|
|
2249
|
+
function boardFieldsTypeName(boardId) {
|
|
2250
|
+
return `${boardPrefix(boardId)}BoardFields`;
|
|
2251
|
+
}
|
|
2252
|
+
function boardSpaceFieldsTypeName(boardId) {
|
|
2253
|
+
return `${boardPrefix(boardId)}SpaceFields`;
|
|
2254
|
+
}
|
|
2255
|
+
function boardRelationFieldsTypeName(boardId) {
|
|
2256
|
+
return `${boardPrefix(boardId)}RelationFields`;
|
|
2257
|
+
}
|
|
2258
|
+
function boardContainerFieldsTypeName(boardId) {
|
|
2259
|
+
return `${boardPrefix(boardId)}ContainerFields`;
|
|
2260
|
+
}
|
|
2261
|
+
function hexEdgeFieldsTypeName(boardId) {
|
|
2262
|
+
return `${boardPrefix(boardId)}EdgeFields`;
|
|
2263
|
+
}
|
|
2264
|
+
function hexVertexFieldsTypeName(boardId) {
|
|
2265
|
+
return `${boardPrefix(boardId)}VertexFields`;
|
|
2266
|
+
}
|
|
2267
|
+
function pieceFieldsTypeName(typeId) {
|
|
2268
|
+
return `${toPascalCase(typeId)}PieceFields`;
|
|
2269
|
+
}
|
|
2270
|
+
function dieFieldsTypeName(typeId) {
|
|
2271
|
+
return `${toPascalCase(typeId)}DieFields`;
|
|
2272
|
+
}
|
|
2273
|
+
function renderTopologyFieldSections(analysis) {
|
|
2274
|
+
const sections = [];
|
|
2275
|
+
for (const board of analysis.analyzedBoards) {
|
|
2276
|
+
sections.push(renderObjectSchemaSection(boardFieldsTypeName(board.board.id), `${boardFieldsTypeName(board.board.id)}Schema`, board.boardFieldsSchema));
|
|
2277
|
+
sections.push(renderObjectSchemaSection(boardSpaceFieldsTypeName(board.board.id), `${boardSpaceFieldsTypeName(board.board.id)}Schema`, board.spaceFieldsSchema));
|
|
2278
|
+
if (board.layout !== "hex") {
|
|
2279
|
+
sections.push(renderObjectSchemaSection(boardRelationFieldsTypeName(board.board.id), `${boardRelationFieldsTypeName(board.board.id)}Schema`, board.relationFieldsSchema), renderObjectSchemaSection(boardContainerFieldsTypeName(board.board.id), `${boardContainerFieldsTypeName(board.board.id)}Schema`, board.containerFieldsSchema));
|
|
2280
|
+
}
|
|
2281
|
+
if (board.layout !== "generic") {
|
|
2282
|
+
sections.push(renderObjectSchemaSection(hexEdgeFieldsTypeName(board.board.id), `${hexEdgeFieldsTypeName(board.board.id)}Schema`, board.edgeFieldsSchema), renderObjectSchemaSection(hexVertexFieldsTypeName(board.board.id), `${hexVertexFieldsTypeName(board.board.id)}Schema`, board.vertexFieldsSchema));
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
for (const pieceTypeId of analysis.pieceTypeIds) {
|
|
2286
|
+
sections.push(renderObjectSchemaSection(pieceFieldsTypeName(pieceTypeId), `${pieceFieldsTypeName(pieceTypeId)}Schema`, analysis.pieceTypeSchemasById.get(pieceTypeId)));
|
|
2287
|
+
}
|
|
2288
|
+
for (const dieTypeId of analysis.dieTypeIds) {
|
|
2289
|
+
sections.push(renderObjectSchemaSection(dieFieldsTypeName(dieTypeId), `${dieFieldsTypeName(dieTypeId)}Schema`, analysis.dieTypeSchemasById.get(dieTypeId)));
|
|
2290
|
+
}
|
|
2291
|
+
return sections.join("\n\n");
|
|
2292
|
+
}
|
|
2293
|
+
function renderBoardFieldMapTypes(analysis) {
|
|
2294
|
+
const boardEntries = analysis.analyzedBoards
|
|
2295
|
+
.flatMap((board) => board.runtimeBoardIds.map((runtimeBoardId) => ` ${quote(runtimeBoardId)}: ${boardFieldsTypeName(board.board.id)};`))
|
|
2296
|
+
.join("\n");
|
|
2297
|
+
const spaceEntries = analysis.analyzedBoards
|
|
2298
|
+
.flatMap((board) => board.runtimeBoardIds.map((runtimeBoardId) => ` ${quote(runtimeBoardId)}: ${boardSpaceFieldsTypeName(board.board.id)};`))
|
|
2299
|
+
.join("\n");
|
|
2300
|
+
const relationEntries = analysis.analyzedBoards
|
|
2301
|
+
.flatMap((board) => board.runtimeBoardIds.map((runtimeBoardId) => ` ${quote(runtimeBoardId)}: ${board.layout === "hex"
|
|
2302
|
+
? "RuntimeRecord"
|
|
2303
|
+
: boardRelationFieldsTypeName(board.board.id)};`))
|
|
2304
|
+
.join("\n");
|
|
2305
|
+
const containerEntries = analysis.analyzedBoards
|
|
2306
|
+
.flatMap((board) => board.runtimeBoardIds.map((runtimeBoardId) => ` ${quote(runtimeBoardId)}: ${board.layout === "hex"
|
|
2307
|
+
? "RuntimeRecord"
|
|
2308
|
+
: boardContainerFieldsTypeName(board.board.id)};`))
|
|
2309
|
+
.join("\n");
|
|
2310
|
+
const edgeEntries = analysis.analyzedBoards
|
|
2311
|
+
.filter((board) => board.layout === "hex")
|
|
2312
|
+
.flatMap((board) => board.runtimeBoardIds.map((runtimeBoardId) => ` ${quote(runtimeBoardId)}: ${hexEdgeFieldsTypeName(board.board.id)};`))
|
|
2313
|
+
.join("\n");
|
|
2314
|
+
const squareEdgeEntries = analysis.analyzedBoards
|
|
2315
|
+
.filter((board) => board.layout === "square")
|
|
2316
|
+
.flatMap((board) => board.runtimeBoardIds.map((runtimeBoardId) => ` ${quote(runtimeBoardId)}: ${hexEdgeFieldsTypeName(board.board.id)};`))
|
|
2317
|
+
.join("\n");
|
|
2318
|
+
const tiledEdgeEntries = analysis.analyzedBoards
|
|
2319
|
+
.filter((board) => board.layout !== "generic")
|
|
2320
|
+
.flatMap((board) => board.runtimeBoardIds.map((runtimeBoardId) => ` ${quote(runtimeBoardId)}: ${hexEdgeFieldsTypeName(board.board.id)};`))
|
|
2321
|
+
.join("\n");
|
|
2322
|
+
const vertexEntries = analysis.analyzedBoards
|
|
2323
|
+
.filter((board) => board.layout === "hex")
|
|
2324
|
+
.flatMap((board) => board.runtimeBoardIds.map((runtimeBoardId) => ` ${quote(runtimeBoardId)}: ${hexVertexFieldsTypeName(board.board.id)};`))
|
|
2325
|
+
.join("\n");
|
|
2326
|
+
const squareVertexEntries = analysis.analyzedBoards
|
|
2327
|
+
.filter((board) => board.layout === "square")
|
|
2328
|
+
.flatMap((board) => board.runtimeBoardIds.map((runtimeBoardId) => ` ${quote(runtimeBoardId)}: ${hexVertexFieldsTypeName(board.board.id)};`))
|
|
2329
|
+
.join("\n");
|
|
2330
|
+
const tiledVertexEntries = analysis.analyzedBoards
|
|
2331
|
+
.filter((board) => board.layout !== "generic")
|
|
2332
|
+
.flatMap((board) => board.runtimeBoardIds.map((runtimeBoardId) => ` ${quote(runtimeBoardId)}: ${hexVertexFieldsTypeName(board.board.id)};`))
|
|
2333
|
+
.join("\n");
|
|
2334
|
+
const pieceEntries = analysis.pieceTypeIds
|
|
2335
|
+
.map((typeId) => ` ${quote(typeId)}: ${pieceFieldsTypeName(typeId)};`)
|
|
2336
|
+
.join("\n");
|
|
2337
|
+
const dieEntries = analysis.dieTypeIds
|
|
2338
|
+
.map((typeId) => ` ${quote(typeId)}: ${dieFieldsTypeName(typeId)};`)
|
|
2339
|
+
.join("\n");
|
|
2340
|
+
return renderBlocks([
|
|
2341
|
+
`export type BoardFieldsByBoardId = {\n${boardEntries}\n};`,
|
|
2342
|
+
`export type BoardSpaceFieldsByBoardId = {\n${spaceEntries}\n};`,
|
|
2343
|
+
`export type BoardRelationFieldsByBoardId = {\n${relationEntries}\n};`,
|
|
2344
|
+
`export type BoardContainerFieldsByBoardId = {\n${containerEntries}\n};`,
|
|
2345
|
+
`export type HexEdgeFieldsByBoardId = ${edgeEntries.length > 0 ? `{\n${edgeEntries}\n}` : "Record<string, never>"};`,
|
|
2346
|
+
`export type HexVertexFieldsByBoardId = ${vertexEntries.length > 0
|
|
2347
|
+
? `{\n${vertexEntries}\n}`
|
|
2348
|
+
: "Record<string, never>"};`,
|
|
2349
|
+
`export type SquareEdgeFieldsByBoardId = ${squareEdgeEntries.length > 0
|
|
2350
|
+
? `{\n${squareEdgeEntries}\n}`
|
|
2351
|
+
: "Record<string, never>"};`,
|
|
2352
|
+
`export type SquareVertexFieldsByBoardId = ${squareVertexEntries.length > 0
|
|
2353
|
+
? `{\n${squareVertexEntries}\n}`
|
|
2354
|
+
: "Record<string, never>"};`,
|
|
2355
|
+
`export type TiledEdgeFieldsByBoardId = ${tiledEdgeEntries.length > 0
|
|
2356
|
+
? `{\n${tiledEdgeEntries}\n}`
|
|
2357
|
+
: "Record<string, never>"};`,
|
|
2358
|
+
`export type TiledVertexFieldsByBoardId = ${tiledVertexEntries.length > 0
|
|
2359
|
+
? `{\n${tiledVertexEntries}\n}`
|
|
2360
|
+
: "Record<string, never>"};`,
|
|
2361
|
+
`export type PieceFieldsByTypeId = ${pieceEntries.length > 0
|
|
2362
|
+
? `{\n${pieceEntries}\n}`
|
|
2363
|
+
: "Record<string, RuntimeRecord>"};`,
|
|
2364
|
+
`export type DieFieldsByTypeId = ${dieEntries.length > 0
|
|
2365
|
+
? `{\n${dieEntries}\n}`
|
|
2366
|
+
: "Record<string, RuntimeRecord>"};`,
|
|
2367
|
+
]);
|
|
2368
|
+
}
|
|
2369
|
+
function renderCardStateSchemaById(analysis) {
|
|
2370
|
+
if (analysis.cardIds.length === 0) {
|
|
2371
|
+
return "z.object({})";
|
|
2372
|
+
}
|
|
2373
|
+
return `z.object(
|
|
2374
|
+
Object.fromEntries(
|
|
2375
|
+
literals.cardIds.map((cardId) => [cardId, createCardStateSchema(cardId)]),
|
|
2376
|
+
) as Record<CardId, z.ZodTypeAny>,
|
|
2377
|
+
)`;
|
|
2378
|
+
}
|
|
2379
|
+
function renderCardStateSchemaFactory(analysis) {
|
|
2380
|
+
if (analysis.cardIds.length === 0) {
|
|
2381
|
+
return "";
|
|
2382
|
+
}
|
|
2383
|
+
return `function createCardStateSchema<CardIdValue extends CardId>(
|
|
2384
|
+
cardId: CardIdValue,
|
|
2385
|
+
): z.ZodType<CardStateById[CardIdValue]> {
|
|
2386
|
+
const cardSetId = literals.cardSetIdByCardId[cardId];
|
|
2387
|
+
const cardType = literals.cardTypeByCardId[cardId];
|
|
2388
|
+
const cardPropertiesSchema =
|
|
2389
|
+
cardPropertiesSchemaByCardSetId[cardSetId + ":" + cardType] ??
|
|
2390
|
+
cardPropertiesSchemaByCardSetId[cardSetId] ??
|
|
2391
|
+
unknownRecordSchema;
|
|
2392
|
+
return assumeManifestSchema<CardStateById[CardIdValue]>(
|
|
2393
|
+
cardStateSchema.extend({
|
|
2394
|
+
id: z.literal(cardId),
|
|
2395
|
+
cardSetId: z.literal(cardSetId),
|
|
2396
|
+
cardType: z.literal(cardType),
|
|
2397
|
+
properties: cardPropertiesSchema,
|
|
2398
|
+
}),
|
|
2399
|
+
);
|
|
2400
|
+
}`;
|
|
2401
|
+
}
|
|
2402
|
+
function renderPieceStateSchemaById(analysis) {
|
|
2403
|
+
if (analysis.pieceIds.length === 0) {
|
|
2404
|
+
return "z.object({})";
|
|
2405
|
+
}
|
|
2406
|
+
return `z.object({\n${analysis.pieceIds
|
|
2407
|
+
.map((pieceId) => {
|
|
2408
|
+
const pieceTypeId = analysis.pieceTypeIdByPieceId.get(pieceId) ?? "";
|
|
2409
|
+
return ` ${quote(pieceId)}: z.object({
|
|
2410
|
+
componentType: z.string().optional(),
|
|
2411
|
+
id: z.literal(${quote(pieceId)}),
|
|
2412
|
+
pieceTypeId: z.literal(${quote(pieceTypeId)}),
|
|
2413
|
+
pieceName: z.string().nullable().optional(),
|
|
2414
|
+
ownerId: ids.playerId.nullable().optional(),
|
|
2415
|
+
properties: ${pieceTypeId
|
|
2416
|
+
? `${pieceFieldsTypeName(pieceTypeId)}Schema`
|
|
2417
|
+
: "unknownRecordSchema"},
|
|
2418
|
+
}),`;
|
|
2419
|
+
})
|
|
2420
|
+
.join("\n")}\n})`;
|
|
2421
|
+
}
|
|
2422
|
+
function renderDieStateSchemaById(analysis) {
|
|
2423
|
+
if (analysis.dieIds.length === 0) {
|
|
2424
|
+
return "z.object({})";
|
|
2425
|
+
}
|
|
2426
|
+
return `z.object({\n${analysis.dieIds
|
|
2427
|
+
.map((dieId) => {
|
|
2428
|
+
const dieTypeId = analysis.dieTypeIdByDieId.get(dieId) ?? "";
|
|
2429
|
+
const dieType = (analysis.manifest.dieTypes ?? []).find((candidate) => candidate.id === dieTypeId);
|
|
2430
|
+
return ` ${quote(dieId)}: z.object({
|
|
2431
|
+
componentType: z.string().optional(),
|
|
2432
|
+
id: z.literal(${quote(dieId)}),
|
|
2433
|
+
dieTypeId: z.literal(${quote(dieTypeId)}),
|
|
2434
|
+
dieName: z.string().nullable().optional(),
|
|
2435
|
+
ownerId: ids.playerId.nullable().optional(),
|
|
2436
|
+
sides: z.literal(${dieType?.sides ?? 6}),
|
|
2437
|
+
value: z.number().int().nullable().optional(),
|
|
2438
|
+
properties: ${dieTypeId
|
|
2439
|
+
? `${dieFieldsTypeName(dieTypeId)}Schema`
|
|
2440
|
+
: "unknownRecordSchema"},
|
|
2441
|
+
}),`;
|
|
2442
|
+
})
|
|
2443
|
+
.join("\n")}\n})`;
|
|
2444
|
+
}
|
|
2445
|
+
function renderGenericBoardStateSchema(board, runtimeBoardId) {
|
|
2446
|
+
const playerId = board.board.scope === "perPlayer"
|
|
2447
|
+
? runtimeBoardId.slice(board.board.id.length + 1)
|
|
2448
|
+
: null;
|
|
2449
|
+
return `z.object({
|
|
2450
|
+
id: z.literal(${quote(runtimeBoardId)}),
|
|
2451
|
+
baseId: z.literal(${quote(board.board.id)}),
|
|
2452
|
+
layout: z.literal("generic"),
|
|
2453
|
+
typeId: ${board.boardTypeId
|
|
2454
|
+
? `z.literal(${quote(board.boardTypeId)})`
|
|
2455
|
+
: "ids.boardTypeId.nullable().optional()"},
|
|
2456
|
+
scope: z.literal(${quote(board.board.scope)}),
|
|
2457
|
+
playerId: ${playerId
|
|
2458
|
+
? `z.literal(${quote(playerId)})`
|
|
2459
|
+
: "ids.playerId.nullable().optional()"},
|
|
2460
|
+
templateId: z.string().nullable().optional(),
|
|
2461
|
+
fields: ${`${boardFieldsTypeName(board.board.id)}Schema`},
|
|
2462
|
+
spaces: z.record(
|
|
2463
|
+
ids.spaceId,
|
|
2464
|
+
z.object({
|
|
2465
|
+
id: ids.spaceId,
|
|
2466
|
+
name: z.string().nullable().optional(),
|
|
2467
|
+
typeId: ids.spaceTypeId.nullable().optional(),
|
|
2468
|
+
fields: ${`${boardSpaceFieldsTypeName(board.board.id)}Schema`},
|
|
2469
|
+
zoneId: z.string().nullable().optional(),
|
|
2470
|
+
}),
|
|
2471
|
+
),
|
|
2472
|
+
relations: z.array(
|
|
2473
|
+
z.object({
|
|
2474
|
+
id: z.string().nullable().optional(),
|
|
2475
|
+
typeId: ids.relationTypeId,
|
|
2476
|
+
fromSpaceId: ids.spaceId,
|
|
2477
|
+
toSpaceId: ids.spaceId,
|
|
2478
|
+
directed: z.boolean(),
|
|
2479
|
+
fields: ${`${boardRelationFieldsTypeName(board.board.id)}Schema`},
|
|
2480
|
+
}),
|
|
2481
|
+
),
|
|
2482
|
+
containers: z.record(
|
|
2483
|
+
ids.boardContainerId,
|
|
2484
|
+
z.object({
|
|
2485
|
+
id: ids.boardContainerId,
|
|
2486
|
+
name: z.string(),
|
|
2487
|
+
host: z.discriminatedUnion("type", [
|
|
2488
|
+
z.object({ type: z.literal("board") }),
|
|
2489
|
+
z.object({ type: z.literal("space"), spaceId: ids.spaceId }),
|
|
2490
|
+
]),
|
|
2491
|
+
allowedCardSetIds: z.array(ids.cardSetId).optional(),
|
|
2492
|
+
zoneId: z.string(),
|
|
2493
|
+
fields: ${`${boardContainerFieldsTypeName(board.board.id)}Schema`},
|
|
2494
|
+
}),
|
|
2495
|
+
),
|
|
2496
|
+
})`;
|
|
2497
|
+
}
|
|
2498
|
+
function renderHexBoardStateSchema(board, runtimeBoardId) {
|
|
2499
|
+
const playerId = board.board.scope === "perPlayer"
|
|
2500
|
+
? runtimeBoardId.slice(board.board.id.length + 1)
|
|
2501
|
+
: null;
|
|
2502
|
+
return `z.object({
|
|
2503
|
+
id: z.literal(${quote(runtimeBoardId)}),
|
|
2504
|
+
baseId: z.literal(${quote(board.board.id)}),
|
|
2505
|
+
layout: z.literal("hex"),
|
|
2506
|
+
typeId: ${board.boardTypeId
|
|
2507
|
+
? `z.literal(${quote(board.boardTypeId)})`
|
|
2508
|
+
: "ids.boardTypeId.nullable().optional()"},
|
|
2509
|
+
scope: z.literal(${quote(board.board.scope)}),
|
|
2510
|
+
playerId: ${playerId
|
|
2511
|
+
? `z.literal(${quote(playerId)})`
|
|
2512
|
+
: "ids.playerId.nullable().optional()"},
|
|
2513
|
+
templateId: z.string().nullable().optional(),
|
|
2514
|
+
fields: ${`${boardFieldsTypeName(board.board.id)}Schema`},
|
|
2515
|
+
spaces: z.record(
|
|
2516
|
+
ids.spaceId,
|
|
2517
|
+
z.object({
|
|
2518
|
+
id: ids.spaceId,
|
|
2519
|
+
name: z.string().nullable().optional(),
|
|
2520
|
+
typeId: ids.spaceTypeId.nullable().optional(),
|
|
2521
|
+
q: z.number().int(),
|
|
2522
|
+
r: z.number().int(),
|
|
2523
|
+
fields: ${`${boardSpaceFieldsTypeName(board.board.id)}Schema`},
|
|
2524
|
+
zoneId: z.string().nullable().optional(),
|
|
2525
|
+
}),
|
|
2526
|
+
),
|
|
2527
|
+
relations: z.array(
|
|
2528
|
+
z.object({
|
|
2529
|
+
id: z.string().nullable().optional(),
|
|
2530
|
+
typeId: z.literal("adjacent"),
|
|
2531
|
+
fromSpaceId: ids.spaceId,
|
|
2532
|
+
toSpaceId: ids.spaceId,
|
|
2533
|
+
directed: z.boolean(),
|
|
2534
|
+
fields: unknownRecordSchema,
|
|
2535
|
+
}),
|
|
2536
|
+
),
|
|
2537
|
+
containers: z.object({}),
|
|
2538
|
+
orientation: z.enum(["pointy-top", "flat-top"]),
|
|
2539
|
+
edges: z.array(
|
|
2540
|
+
z.object({
|
|
2541
|
+
id: ids.edgeId,
|
|
2542
|
+
spaceIds: z.array(ids.spaceId).min(1).max(2),
|
|
2543
|
+
typeId: ids.edgeTypeId.nullable().optional(),
|
|
2544
|
+
label: z.string().nullable().optional(),
|
|
2545
|
+
ownerId: ids.playerId.nullable().optional(),
|
|
2546
|
+
fields: ${`${hexEdgeFieldsTypeName(board.board.id)}Schema`},
|
|
2547
|
+
}),
|
|
2548
|
+
),
|
|
2549
|
+
vertices: z.array(
|
|
2550
|
+
z.object({
|
|
2551
|
+
id: ids.vertexId,
|
|
2552
|
+
spaceIds: z.array(ids.spaceId).min(1).max(3),
|
|
2553
|
+
typeId: ids.vertexTypeId.nullable().optional(),
|
|
2554
|
+
label: z.string().nullable().optional(),
|
|
2555
|
+
ownerId: ids.playerId.nullable().optional(),
|
|
2556
|
+
fields: ${`${hexVertexFieldsTypeName(board.board.id)}Schema`},
|
|
2557
|
+
}),
|
|
2558
|
+
),
|
|
2559
|
+
})`;
|
|
2560
|
+
}
|
|
2561
|
+
function renderSquareBoardStateSchema(board, runtimeBoardId) {
|
|
2562
|
+
const playerId = board.board.scope === "perPlayer"
|
|
2563
|
+
? runtimeBoardId.slice(board.board.id.length + 1)
|
|
2564
|
+
: null;
|
|
2565
|
+
return `z.object({
|
|
2566
|
+
id: z.literal(${quote(runtimeBoardId)}),
|
|
2567
|
+
baseId: z.literal(${quote(board.board.id)}),
|
|
2568
|
+
layout: z.literal("square"),
|
|
2569
|
+
typeId: ${board.boardTypeId
|
|
2570
|
+
? `z.literal(${quote(board.boardTypeId)})`
|
|
2571
|
+
: "ids.boardTypeId.nullable().optional()"},
|
|
2572
|
+
scope: z.literal(${quote(board.board.scope)}),
|
|
2573
|
+
playerId: ${playerId
|
|
2574
|
+
? `z.literal(${quote(playerId)})`
|
|
2575
|
+
: "ids.playerId.nullable().optional()"},
|
|
2576
|
+
templateId: z.string().nullable().optional(),
|
|
2577
|
+
fields: ${`${boardFieldsTypeName(board.board.id)}Schema`},
|
|
2578
|
+
spaces: z.record(
|
|
2579
|
+
ids.spaceId,
|
|
2580
|
+
z.object({
|
|
2581
|
+
id: ids.spaceId,
|
|
2582
|
+
name: z.string().nullable().optional(),
|
|
2583
|
+
typeId: ids.spaceTypeId.nullable().optional(),
|
|
2584
|
+
row: z.number().int(),
|
|
2585
|
+
col: z.number().int(),
|
|
2586
|
+
fields: ${`${boardSpaceFieldsTypeName(board.board.id)}Schema`},
|
|
2587
|
+
zoneId: z.string().nullable().optional(),
|
|
2588
|
+
}),
|
|
2589
|
+
),
|
|
2590
|
+
relations: z.array(
|
|
2591
|
+
z.object({
|
|
2592
|
+
id: z.string().nullable().optional(),
|
|
2593
|
+
typeId: ids.relationTypeId,
|
|
2594
|
+
fromSpaceId: ids.spaceId,
|
|
2595
|
+
toSpaceId: ids.spaceId,
|
|
2596
|
+
directed: z.boolean(),
|
|
2597
|
+
fields: ${`${boardRelationFieldsTypeName(board.board.id)}Schema`},
|
|
2598
|
+
}),
|
|
2599
|
+
),
|
|
2600
|
+
containers: z.record(
|
|
2601
|
+
ids.boardContainerId,
|
|
2602
|
+
z.object({
|
|
2603
|
+
id: ids.boardContainerId,
|
|
2604
|
+
name: z.string(),
|
|
2605
|
+
host: z.discriminatedUnion("type", [
|
|
2606
|
+
z.object({ type: z.literal("board") }),
|
|
2607
|
+
z.object({ type: z.literal("space"), spaceId: ids.spaceId }),
|
|
2608
|
+
]),
|
|
2609
|
+
allowedCardSetIds: z.array(ids.cardSetId).optional(),
|
|
2610
|
+
zoneId: z.string(),
|
|
2611
|
+
fields: ${`${boardContainerFieldsTypeName(board.board.id)}Schema`},
|
|
2612
|
+
}),
|
|
2613
|
+
),
|
|
2614
|
+
edges: z.array(
|
|
2615
|
+
z.object({
|
|
2616
|
+
id: ids.edgeId,
|
|
2617
|
+
spaceIds: z.array(ids.spaceId).min(1).max(2),
|
|
2618
|
+
typeId: ids.edgeTypeId.nullable().optional(),
|
|
2619
|
+
label: z.string().nullable().optional(),
|
|
2620
|
+
ownerId: ids.playerId.nullable().optional(),
|
|
2621
|
+
fields: ${`${hexEdgeFieldsTypeName(board.board.id)}Schema`},
|
|
2622
|
+
}),
|
|
2623
|
+
),
|
|
2624
|
+
vertices: z.array(
|
|
2625
|
+
z.object({
|
|
2626
|
+
id: ids.vertexId,
|
|
2627
|
+
spaceIds: z.array(ids.spaceId).min(1).max(4),
|
|
2628
|
+
typeId: ids.vertexTypeId.nullable().optional(),
|
|
2629
|
+
label: z.string().nullable().optional(),
|
|
2630
|
+
ownerId: ids.playerId.nullable().optional(),
|
|
2631
|
+
fields: ${`${hexVertexFieldsTypeName(board.board.id)}Schema`},
|
|
2632
|
+
}),
|
|
2633
|
+
),
|
|
2634
|
+
})`;
|
|
2635
|
+
}
|
|
2636
|
+
function renderBoardStateSchemaById(analysis) {
|
|
2637
|
+
if (analysis.boardIds.length === 0) {
|
|
2638
|
+
return "z.object({})";
|
|
2639
|
+
}
|
|
2640
|
+
return `z.object({\n${analysis.analyzedBoards
|
|
2641
|
+
.flatMap((board) => board.runtimeBoardIds.map((runtimeBoardId) => {
|
|
2642
|
+
const schema = board.layout === "hex"
|
|
2643
|
+
? renderHexBoardStateSchema(board, runtimeBoardId)
|
|
2644
|
+
: board.layout === "square"
|
|
2645
|
+
? renderSquareBoardStateSchema(board, runtimeBoardId)
|
|
2646
|
+
: renderGenericBoardStateSchema(board, runtimeBoardId);
|
|
2647
|
+
return ` ${quote(runtimeBoardId)}: ${schema},`;
|
|
2648
|
+
}))
|
|
2649
|
+
.join("\n")}\n})`;
|
|
2650
|
+
}
|
|
2651
|
+
function renderHexBoardStateSchemaById(analysis) {
|
|
2652
|
+
const hexBoards = analysis.analyzedBoards.filter((board) => board.layout === "hex");
|
|
2653
|
+
if (hexBoards.length === 0) {
|
|
2654
|
+
return "z.object({})";
|
|
2655
|
+
}
|
|
2656
|
+
return `z.object({\n${hexBoards
|
|
2657
|
+
.flatMap((board) => board.runtimeBoardIds.map((runtimeBoardId) => ` ${quote(runtimeBoardId)}: ${renderHexBoardStateSchema(board, runtimeBoardId)},`))
|
|
2658
|
+
.join("\n")}\n})`;
|
|
2659
|
+
}
|
|
2660
|
+
function renderSquareBoardStateSchemaById(analysis) {
|
|
2661
|
+
const squareBoards = analysis.analyzedBoards.filter((board) => board.layout === "square");
|
|
2662
|
+
if (squareBoards.length === 0) {
|
|
2663
|
+
return "z.object({})";
|
|
2664
|
+
}
|
|
2665
|
+
return `z.object({\n${squareBoards
|
|
2666
|
+
.flatMap((board) => board.runtimeBoardIds.map((runtimeBoardId) => ` ${quote(runtimeBoardId)}: ${renderSquareBoardStateSchema(board, runtimeBoardId)},`))
|
|
2667
|
+
.join("\n")}\n})`;
|
|
2668
|
+
}
|
|
2669
|
+
function renderBoardLiteralHelpers(analysis) {
|
|
2670
|
+
const boardIdsByLayout = renderReadonlyArrayRecord(analysis.boardIdsByLayout);
|
|
2671
|
+
const boardBaseIdsByLayout = renderReadonlyArrayRecord(analysis.boardBaseIdsByLayout);
|
|
2672
|
+
const boardIdsByBaseId = renderReadonlyArrayRecord(analysis.boardIdsByBaseId);
|
|
2673
|
+
const boardBaseIdsByTemplateId = renderReadonlyArrayRecord(analysis.boardBaseIdsByTemplateId);
|
|
2674
|
+
const boardLayoutById = renderStringRecord(analysis.boardLayoutById);
|
|
2675
|
+
const boardTemplateLayoutById = renderStringRecord(analysis.boardTemplateLayoutById);
|
|
2676
|
+
const boardIdsByTypeId = renderReadonlyArrayRecord(analysis.boardIdsByTypeId);
|
|
2677
|
+
const spaceIdsByBoardId = renderReadonlyArrayRecord(analysis.spaceIdsByBoardId);
|
|
2678
|
+
const spaceTypeIdByBoardId = renderJsonConst(sortedObject(analysis.spaceTypeIdByBoardId));
|
|
2679
|
+
const spaceIdsByTypeId = renderReadonlyArrayRecord(analysis.spaceIdsByTypeId);
|
|
2680
|
+
const containerIdsByBoardId = renderReadonlyArrayRecord(analysis.containerIdsByBoardId);
|
|
2681
|
+
const containerHostByBoardId = renderJsonConst(sortedObject(analysis.containerHostByBoardId));
|
|
2682
|
+
const relationTypeIdsByBoardId = renderReadonlyArrayRecord(analysis.relationTypeIdsByBoardId);
|
|
2683
|
+
const edgeIdsByTypeId = renderReadonlyArrayRecord(analysis.edgeIdsByTypeId);
|
|
2684
|
+
const edgeIdsByBoardIdAndTypeId = renderJsonConst(sortedObject(analysis.edgeIdsByBoardIdAndTypeId));
|
|
2685
|
+
const vertexIdsByTypeId = renderReadonlyArrayRecord(analysis.vertexIdsByTypeId);
|
|
2686
|
+
const vertexIdsByBoardIdAndTypeId = renderJsonConst(sortedObject(analysis.vertexIdsByBoardIdAndTypeId));
|
|
2687
|
+
const authoredHexEdgesByBoardId = renderJsonConst(sortedObject(analysis.analyzedBoards
|
|
2688
|
+
.filter((board) => board.layout === "hex")
|
|
2689
|
+
.flatMap((board) => board.runtimeBoardIds.map((runtimeBoardId) => [
|
|
2690
|
+
runtimeBoardId,
|
|
2691
|
+
board.authoredEdges.map((edge) => {
|
|
2692
|
+
const renderedSite = {
|
|
2693
|
+
ref: cloneJson(edge.ref),
|
|
2694
|
+
fields: {
|
|
2695
|
+
...materializeObjectSchemaDefaults(board.edgeFieldsSchema, analysis),
|
|
2696
|
+
...(edge.fields ?? {}),
|
|
2697
|
+
},
|
|
2698
|
+
};
|
|
2699
|
+
if (edge.typeId !== null && edge.typeId !== undefined) {
|
|
2700
|
+
renderedSite.typeId = edge.typeId;
|
|
2701
|
+
}
|
|
2702
|
+
if (edge.label !== null && edge.label !== undefined) {
|
|
2703
|
+
renderedSite.label = edge.label;
|
|
2704
|
+
}
|
|
2705
|
+
return renderedSite;
|
|
2706
|
+
}),
|
|
2707
|
+
]))));
|
|
2708
|
+
const authoredHexVerticesByBoardId = renderJsonConst(sortedObject(analysis.analyzedBoards
|
|
2709
|
+
.filter((board) => board.layout === "hex")
|
|
2710
|
+
.flatMap((board) => board.runtimeBoardIds.map((runtimeBoardId) => [
|
|
2711
|
+
runtimeBoardId,
|
|
2712
|
+
board.authoredVertices.map((vertex) => {
|
|
2713
|
+
const renderedSite = {
|
|
2714
|
+
ref: cloneJson(vertex.ref),
|
|
2715
|
+
fields: {
|
|
2716
|
+
...materializeObjectSchemaDefaults(board.vertexFieldsSchema, analysis),
|
|
2717
|
+
...(vertex.fields ?? {}),
|
|
2718
|
+
},
|
|
2719
|
+
};
|
|
2720
|
+
if (vertex.typeId !== null && vertex.typeId !== undefined) {
|
|
2721
|
+
renderedSite.typeId = vertex.typeId;
|
|
2722
|
+
}
|
|
2723
|
+
if (vertex.label !== null && vertex.label !== undefined) {
|
|
2724
|
+
renderedSite.label = vertex.label;
|
|
2725
|
+
}
|
|
2726
|
+
return renderedSite;
|
|
2727
|
+
}),
|
|
2728
|
+
]))));
|
|
2729
|
+
const authoredHexEdgeIdsByBoardIdAndRef = renderJsonConst(sortedObject(analysis.analyzedBoards
|
|
2730
|
+
.filter((board) => board.layout === "hex")
|
|
2731
|
+
.flatMap((board) => board.runtimeBoardIds.map((runtimeBoardId) => [
|
|
2732
|
+
runtimeBoardId,
|
|
2733
|
+
sortedObject(board.authoredEdges.map((edge) => [
|
|
2734
|
+
boardSpaceRefKey(edge.ref.spaces),
|
|
2735
|
+
edge.id,
|
|
2736
|
+
])),
|
|
2737
|
+
]))));
|
|
2738
|
+
const authoredHexVertexIdsByBoardIdAndRef = renderJsonConst(sortedObject(analysis.analyzedBoards
|
|
2739
|
+
.filter((board) => board.layout === "hex")
|
|
2740
|
+
.flatMap((board) => board.runtimeBoardIds.map((runtimeBoardId) => [
|
|
2741
|
+
runtimeBoardId,
|
|
2742
|
+
sortedObject(board.authoredVertices.map((vertex) => [
|
|
2743
|
+
boardSpaceRefKey(vertex.ref.spaces),
|
|
2744
|
+
vertex.id,
|
|
2745
|
+
])),
|
|
2746
|
+
]))));
|
|
2747
|
+
// `perPlayerBoardIdsByBaseIdAndPlayerIdLookup` has been retired. The old
|
|
2748
|
+
// static lookup keyed on the max-players roster at generate time, which is
|
|
2749
|
+
// incompatible with the PerPlayer model where the runtime roster decides
|
|
2750
|
+
// which seats exist. `boardRefForPlayer(baseId, playerId)` now produces a
|
|
2751
|
+
// `PerPlayerBoardRef` at runtime using the active session's seats.
|
|
2752
|
+
return `const boardIdsByLayoutLookup = ${boardIdsByLayout};
|
|
2753
|
+
const boardBaseIdsByLayoutLookup = ${boardBaseIdsByLayout};
|
|
2754
|
+
const boardIdsByBaseIdLookup = ${boardIdsByBaseId};
|
|
2755
|
+
const boardBaseIdsByTemplateIdLookup = ${boardBaseIdsByTemplateId};
|
|
2756
|
+
const boardLayoutByIdLookup = ${boardLayoutById};
|
|
2757
|
+
const boardTemplateLayoutByIdLookup = ${boardTemplateLayoutById};
|
|
2758
|
+
const boardIdsByTypeIdLookup = ${boardIdsByTypeId};
|
|
2759
|
+
const spaceIdsByBoardIdLookup = ${spaceIdsByBoardId};
|
|
2760
|
+
const spaceTypeIdByBoardIdLookup = ${spaceTypeIdByBoardId};
|
|
2761
|
+
const spaceIdsByTypeIdLookup = ${spaceIdsByTypeId};
|
|
2762
|
+
const containerIdsByBoardIdLookup = ${containerIdsByBoardId};
|
|
2763
|
+
const containerHostByBoardIdLookup = ${containerHostByBoardId};
|
|
2764
|
+
const relationTypeIdsByBoardIdLookup = ${relationTypeIdsByBoardId};
|
|
2765
|
+
const edgeIdsByTypeIdLookup = ${edgeIdsByTypeId};
|
|
2766
|
+
const edgeIdsByBoardIdAndTypeIdLookup = ${edgeIdsByBoardIdAndTypeId};
|
|
2767
|
+
const vertexIdsByTypeIdLookup = ${vertexIdsByTypeId};
|
|
2768
|
+
const vertexIdsByBoardIdAndTypeIdLookup = ${vertexIdsByBoardIdAndTypeId};
|
|
2769
|
+
const authoredHexEdgesByBoardIdLookup = ${authoredHexEdgesByBoardId};
|
|
2770
|
+
const authoredHexVerticesByBoardIdLookup = ${authoredHexVerticesByBoardId};
|
|
2771
|
+
const authoredHexEdgeIdsByBoardIdAndRefLookup = ${authoredHexEdgeIdsByBoardIdAndRef};
|
|
2772
|
+
const authoredHexVertexIdsByBoardIdAndRefLookup = ${authoredHexVertexIdsByBoardIdAndRef};
|
|
2773
|
+
|
|
2774
|
+
function authoredHexRefKey(spaceIds: readonly string[]): string {
|
|
2775
|
+
return [...spaceIds]
|
|
2776
|
+
.sort((left, right) => left.localeCompare(right))
|
|
2777
|
+
.join("$$");
|
|
2778
|
+
}
|
|
2779
|
+
|
|
2780
|
+
type BoardLookupIdValue<
|
|
2781
|
+
Lookup extends Record<string, Record<string, readonly string[]>>,
|
|
2782
|
+
Key extends keyof Lookup,
|
|
2783
|
+
> = Extract<Lookup[Key][keyof Lookup[Key]], readonly string[]>[number];
|
|
2784
|
+
|
|
2785
|
+
function flattenBoardScopedIds<
|
|
2786
|
+
Lookup extends Record<string, Record<string, readonly string[]>>,
|
|
2787
|
+
Key extends keyof Lookup,
|
|
2788
|
+
>(lookup: Lookup, key: Key): ReadonlyArray<BoardLookupIdValue<Lookup, Key>> {
|
|
2789
|
+
return Object.values(lookup[key] ?? {}).flat() as ReadonlyArray<
|
|
2790
|
+
BoardLookupIdValue<Lookup, Key>
|
|
2791
|
+
>;
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
export const boardHelpers = {
|
|
2795
|
+
boardIdsForLayout<
|
|
2796
|
+
LayoutValue extends keyof typeof boardIdsByLayoutLookup,
|
|
2797
|
+
>(layout: LayoutValue): (typeof boardIdsByLayoutLookup)[LayoutValue] {
|
|
2798
|
+
return boardIdsByLayoutLookup[layout];
|
|
2799
|
+
},
|
|
2800
|
+
boardBaseIdsForLayout<
|
|
2801
|
+
LayoutValue extends keyof typeof boardBaseIdsByLayoutLookup,
|
|
2802
|
+
>(layout: LayoutValue): (typeof boardBaseIdsByLayoutLookup)[LayoutValue] {
|
|
2803
|
+
return boardBaseIdsByLayoutLookup[layout];
|
|
2804
|
+
},
|
|
2805
|
+
boardIdsForBase<
|
|
2806
|
+
BoardBaseIdValue extends keyof typeof boardIdsByBaseIdLookup,
|
|
2807
|
+
>(
|
|
2808
|
+
boardBaseId: BoardBaseIdValue,
|
|
2809
|
+
): (typeof boardIdsByBaseIdLookup)[BoardBaseIdValue] {
|
|
2810
|
+
return boardIdsByBaseIdLookup[boardBaseId];
|
|
2811
|
+
},
|
|
2812
|
+
boardBaseIdsForTemplate<
|
|
2813
|
+
TemplateIdValue extends keyof typeof boardBaseIdsByTemplateIdLookup,
|
|
2814
|
+
>(
|
|
2815
|
+
templateId: TemplateIdValue,
|
|
2816
|
+
): (typeof boardBaseIdsByTemplateIdLookup)[TemplateIdValue] {
|
|
2817
|
+
return boardBaseIdsByTemplateIdLookup[templateId];
|
|
2818
|
+
},
|
|
2819
|
+
boardIdsForType<TypeIdValue extends keyof typeof boardIdsByTypeIdLookup>(
|
|
2820
|
+
typeId: TypeIdValue,
|
|
2821
|
+
): (typeof boardIdsByTypeIdLookup)[TypeIdValue] {
|
|
2822
|
+
return boardIdsByTypeIdLookup[typeId];
|
|
2823
|
+
},
|
|
2824
|
+
boardLayout<BoardIdValue extends keyof typeof boardLayoutByIdLookup>(
|
|
2825
|
+
boardId: BoardIdValue,
|
|
2826
|
+
): (typeof boardLayoutByIdLookup)[BoardIdValue] {
|
|
2827
|
+
return boardLayoutByIdLookup[boardId];
|
|
2828
|
+
},
|
|
2829
|
+
boardTemplateLayout<
|
|
2830
|
+
TemplateIdValue extends keyof typeof boardTemplateLayoutByIdLookup,
|
|
2831
|
+
>(
|
|
2832
|
+
templateId: TemplateIdValue,
|
|
2833
|
+
): (typeof boardTemplateLayoutByIdLookup)[TemplateIdValue] {
|
|
2834
|
+
return boardTemplateLayoutByIdLookup[templateId];
|
|
2835
|
+
},
|
|
2836
|
+
spaceIds<BoardIdValue extends keyof typeof spaceIdsByBoardIdLookup>(
|
|
2837
|
+
boardId: BoardIdValue,
|
|
2838
|
+
): (typeof spaceIdsByBoardIdLookup)[BoardIdValue] {
|
|
2839
|
+
return spaceIdsByBoardIdLookup[boardId];
|
|
2840
|
+
},
|
|
2841
|
+
spaceRecord<
|
|
2842
|
+
BoardIdValue extends keyof typeof spaceIdsByBoardIdLookup,
|
|
2843
|
+
Value,
|
|
2844
|
+
>(
|
|
2845
|
+
boardId: BoardIdValue,
|
|
2846
|
+
initial:
|
|
2847
|
+
| Value
|
|
2848
|
+
| ((
|
|
2849
|
+
spaceId: (typeof spaceIdsByBoardIdLookup)[BoardIdValue][number],
|
|
2850
|
+
) => Value),
|
|
2851
|
+
): Record<(typeof spaceIdsByBoardIdLookup)[BoardIdValue][number], Value> {
|
|
2852
|
+
const spaceIds = spaceIdsByBoardIdLookup[boardId];
|
|
2853
|
+
if (!spaceIds) {
|
|
2854
|
+
throw new Error(\`Unknown board '\${String(boardId)}'.\`);
|
|
2855
|
+
}
|
|
2856
|
+
return buildTypedRecord(spaceIds, initial) as Record<
|
|
2857
|
+
(typeof spaceIdsByBoardIdLookup)[BoardIdValue][number],
|
|
2858
|
+
Value
|
|
2859
|
+
>;
|
|
2860
|
+
},
|
|
2861
|
+
isSpaceId<BoardIdValue extends keyof typeof spaceIdsByBoardIdLookup>(
|
|
2862
|
+
boardId: BoardIdValue,
|
|
2863
|
+
value: string,
|
|
2864
|
+
): value is (typeof spaceIdsByBoardIdLookup)[BoardIdValue][number] {
|
|
2865
|
+
const spaceIds = spaceIdsByBoardIdLookup[boardId];
|
|
2866
|
+
return spaceIds ? isTypedId(spaceIds, value) : false;
|
|
2867
|
+
},
|
|
2868
|
+
expectSpaceId<BoardIdValue extends keyof typeof spaceIdsByBoardIdLookup>(
|
|
2869
|
+
boardId: BoardIdValue,
|
|
2870
|
+
value: string,
|
|
2871
|
+
): (typeof spaceIdsByBoardIdLookup)[BoardIdValue][number] {
|
|
2872
|
+
const spaceIds = spaceIdsByBoardIdLookup[boardId];
|
|
2873
|
+
if (!spaceIds || !isTypedId(spaceIds, value)) {
|
|
2874
|
+
throw new Error(
|
|
2875
|
+
\`Unknown space id '\${value}' on board '\${String(boardId)}'.\`,
|
|
2876
|
+
);
|
|
2877
|
+
}
|
|
2878
|
+
return value as (typeof spaceIdsByBoardIdLookup)[BoardIdValue][number];
|
|
2879
|
+
},
|
|
2880
|
+
spaceKinds<BoardIdValue extends keyof typeof spaceTypeIdByBoardIdLookup>(
|
|
2881
|
+
boardId: BoardIdValue,
|
|
2882
|
+
): (typeof spaceTypeIdByBoardIdLookup)[BoardIdValue] {
|
|
2883
|
+
return spaceTypeIdByBoardIdLookup[boardId];
|
|
2884
|
+
},
|
|
2885
|
+
spaceIdsForType<TypeIdValue extends keyof typeof spaceIdsByTypeIdLookup>(
|
|
2886
|
+
typeId: TypeIdValue,
|
|
2887
|
+
): (typeof spaceIdsByTypeIdLookup)[TypeIdValue] {
|
|
2888
|
+
return spaceIdsByTypeIdLookup[typeId];
|
|
2889
|
+
},
|
|
2890
|
+
containerIds<BoardIdValue extends keyof typeof containerIdsByBoardIdLookup>(
|
|
2891
|
+
boardId: BoardIdValue,
|
|
2892
|
+
): (typeof containerIdsByBoardIdLookup)[BoardIdValue] {
|
|
2893
|
+
return containerIdsByBoardIdLookup[boardId];
|
|
2894
|
+
},
|
|
2895
|
+
containerRecord<
|
|
2896
|
+
BoardIdValue extends keyof typeof containerIdsByBoardIdLookup,
|
|
2897
|
+
Value,
|
|
2898
|
+
>(
|
|
2899
|
+
boardId: BoardIdValue,
|
|
2900
|
+
initial:
|
|
2901
|
+
| Value
|
|
2902
|
+
| ((
|
|
2903
|
+
containerId: (typeof containerIdsByBoardIdLookup)[BoardIdValue][number],
|
|
2904
|
+
) => Value),
|
|
2905
|
+
): Record<
|
|
2906
|
+
(typeof containerIdsByBoardIdLookup)[BoardIdValue][number],
|
|
2907
|
+
Value
|
|
2908
|
+
> {
|
|
2909
|
+
const containerIds = containerIdsByBoardIdLookup[boardId];
|
|
2910
|
+
if (!containerIds) {
|
|
2911
|
+
throw new Error(\`Unknown board '\${String(boardId)}'.\`);
|
|
2912
|
+
}
|
|
2913
|
+
return buildTypedRecord(containerIds, initial) as Record<
|
|
2914
|
+
(typeof containerIdsByBoardIdLookup)[BoardIdValue][number],
|
|
2915
|
+
Value
|
|
2916
|
+
>;
|
|
2917
|
+
},
|
|
2918
|
+
isContainerId<
|
|
2919
|
+
BoardIdValue extends keyof typeof containerIdsByBoardIdLookup,
|
|
2920
|
+
>(
|
|
2921
|
+
boardId: BoardIdValue,
|
|
2922
|
+
value: string,
|
|
2923
|
+
): value is (typeof containerIdsByBoardIdLookup)[BoardIdValue][number] {
|
|
2924
|
+
const containerIds = containerIdsByBoardIdLookup[boardId];
|
|
2925
|
+
return containerIds ? isTypedId(containerIds, value) : false;
|
|
2926
|
+
},
|
|
2927
|
+
expectContainerId<
|
|
2928
|
+
BoardIdValue extends keyof typeof containerIdsByBoardIdLookup,
|
|
2929
|
+
>(
|
|
2930
|
+
boardId: BoardIdValue,
|
|
2931
|
+
value: string,
|
|
2932
|
+
): (typeof containerIdsByBoardIdLookup)[BoardIdValue][number] {
|
|
2933
|
+
const containerIds = containerIdsByBoardIdLookup[boardId];
|
|
2934
|
+
if (!containerIds || !isTypedId(containerIds, value)) {
|
|
2935
|
+
throw new Error(
|
|
2936
|
+
\`Unknown container id '\${value}' on board '\${String(boardId)}'.\`,
|
|
2937
|
+
);
|
|
2938
|
+
}
|
|
2939
|
+
return value as (typeof containerIdsByBoardIdLookup)[BoardIdValue][number];
|
|
2940
|
+
},
|
|
2941
|
+
containerHost<
|
|
2942
|
+
BoardIdValue extends keyof typeof containerHostByBoardIdLookup,
|
|
2943
|
+
ContainerIdValue extends keyof (typeof containerHostByBoardIdLookup)[BoardIdValue],
|
|
2944
|
+
>(
|
|
2945
|
+
boardId: BoardIdValue,
|
|
2946
|
+
containerId: ContainerIdValue,
|
|
2947
|
+
): (typeof containerHostByBoardIdLookup)[BoardIdValue][ContainerIdValue] {
|
|
2948
|
+
const containers = containerHostByBoardIdLookup[boardId];
|
|
2949
|
+
const containerHost = containers?.[containerId];
|
|
2950
|
+
if (!containerHost) {
|
|
2951
|
+
throw new Error(
|
|
2952
|
+
\`Unknown container '\${String(containerId)}' on board '\${String(boardId)}'.\`,
|
|
2953
|
+
);
|
|
2954
|
+
}
|
|
2955
|
+
return containerHost as (typeof containerHostByBoardIdLookup)[BoardIdValue][ContainerIdValue];
|
|
2956
|
+
},
|
|
2957
|
+
relationTypeIds<
|
|
2958
|
+
BoardIdValue extends keyof typeof relationTypeIdsByBoardIdLookup,
|
|
2959
|
+
>(
|
|
2960
|
+
boardId: BoardIdValue,
|
|
2961
|
+
): (typeof relationTypeIdsByBoardIdLookup)[BoardIdValue] {
|
|
2962
|
+
return relationTypeIdsByBoardIdLookup[boardId];
|
|
2963
|
+
},
|
|
2964
|
+
relationTypeRecord<
|
|
2965
|
+
BoardIdValue extends keyof typeof relationTypeIdsByBoardIdLookup,
|
|
2966
|
+
Value,
|
|
2967
|
+
>(
|
|
2968
|
+
boardId: BoardIdValue,
|
|
2969
|
+
initial:
|
|
2970
|
+
| Value
|
|
2971
|
+
| ((
|
|
2972
|
+
relationTypeId: (typeof relationTypeIdsByBoardIdLookup)[BoardIdValue][number],
|
|
2973
|
+
) => Value),
|
|
2974
|
+
): Record<
|
|
2975
|
+
(typeof relationTypeIdsByBoardIdLookup)[BoardIdValue][number],
|
|
2976
|
+
Value
|
|
2977
|
+
> {
|
|
2978
|
+
const relationTypeIds = relationTypeIdsByBoardIdLookup[boardId];
|
|
2979
|
+
if (!relationTypeIds) {
|
|
2980
|
+
throw new Error(\`Unknown board '\${String(boardId)}'.\`);
|
|
2981
|
+
}
|
|
2982
|
+
return buildTypedRecord(relationTypeIds, initial) as Record<
|
|
2983
|
+
(typeof relationTypeIdsByBoardIdLookup)[BoardIdValue][number],
|
|
2984
|
+
Value
|
|
2985
|
+
>;
|
|
2986
|
+
},
|
|
2987
|
+
isRelationTypeId<
|
|
2988
|
+
BoardIdValue extends keyof typeof relationTypeIdsByBoardIdLookup,
|
|
2989
|
+
>(
|
|
2990
|
+
boardId: BoardIdValue,
|
|
2991
|
+
value: string,
|
|
2992
|
+
): value is (typeof relationTypeIdsByBoardIdLookup)[BoardIdValue][number] {
|
|
2993
|
+
const relationTypeIds = relationTypeIdsByBoardIdLookup[boardId];
|
|
2994
|
+
return relationTypeIds ? isTypedId(relationTypeIds, value) : false;
|
|
2995
|
+
},
|
|
2996
|
+
expectRelationTypeId<
|
|
2997
|
+
BoardIdValue extends keyof typeof relationTypeIdsByBoardIdLookup,
|
|
2998
|
+
>(
|
|
2999
|
+
boardId: BoardIdValue,
|
|
3000
|
+
value: string,
|
|
3001
|
+
): (typeof relationTypeIdsByBoardIdLookup)[BoardIdValue][number] {
|
|
3002
|
+
const relationTypeIds = relationTypeIdsByBoardIdLookup[boardId];
|
|
3003
|
+
if (!relationTypeIds || !isTypedId(relationTypeIds, value)) {
|
|
3004
|
+
throw new Error(
|
|
3005
|
+
\`Unknown relation type id '\${value}' on board '\${String(boardId)}'.\`,
|
|
3006
|
+
);
|
|
3007
|
+
}
|
|
3008
|
+
return value as (typeof relationTypeIdsByBoardIdLookup)[BoardIdValue][number];
|
|
3009
|
+
},
|
|
3010
|
+
authoredHexEdges<
|
|
3011
|
+
BoardIdValue extends keyof typeof authoredHexEdgesByBoardIdLookup,
|
|
3012
|
+
>(
|
|
3013
|
+
boardId: BoardIdValue,
|
|
3014
|
+
): (typeof authoredHexEdgesByBoardIdLookup)[BoardIdValue] {
|
|
3015
|
+
const authoredHexEdges = authoredHexEdgesByBoardIdLookup[boardId];
|
|
3016
|
+
if (!authoredHexEdges) {
|
|
3017
|
+
throw new Error(\`Unknown hex board '\${String(boardId)}'.\`);
|
|
3018
|
+
}
|
|
3019
|
+
return authoredHexEdges;
|
|
3020
|
+
},
|
|
3021
|
+
authoredHexVertices<
|
|
3022
|
+
BoardIdValue extends keyof typeof authoredHexVerticesByBoardIdLookup,
|
|
3023
|
+
>(
|
|
3024
|
+
boardId: BoardIdValue,
|
|
3025
|
+
): (typeof authoredHexVerticesByBoardIdLookup)[BoardIdValue] {
|
|
3026
|
+
const authoredHexVertices = authoredHexVerticesByBoardIdLookup[boardId];
|
|
3027
|
+
if (!authoredHexVertices) {
|
|
3028
|
+
throw new Error(\`Unknown hex board '\${String(boardId)}'.\`);
|
|
3029
|
+
}
|
|
3030
|
+
return authoredHexVertices;
|
|
3031
|
+
},
|
|
3032
|
+
resolveHexEdgeId<
|
|
3033
|
+
BoardIdValue extends keyof typeof authoredHexEdgeIdsByBoardIdAndRefLookup,
|
|
3034
|
+
>(
|
|
3035
|
+
boardId: BoardIdValue,
|
|
3036
|
+
ref: HexAuthoredEdgeRef<BoardIdValue>,
|
|
3037
|
+
): HexEdgeState<BoardIdValue>["id"] {
|
|
3038
|
+
const boardEdges = authoredHexEdgeIdsByBoardIdAndRefLookup[boardId];
|
|
3039
|
+
if (!boardEdges) {
|
|
3040
|
+
throw new Error(\`Unknown hex board '\${String(boardId)}'.\`);
|
|
3041
|
+
}
|
|
3042
|
+
const edgeRef = ref as { spaces: readonly string[] };
|
|
3043
|
+
const edgeId = (boardEdges as Record<string, HexEdgeState<BoardIdValue>["id"]>)[
|
|
3044
|
+
authoredHexRefKey(edgeRef.spaces)
|
|
3045
|
+
];
|
|
3046
|
+
if (!edgeId) {
|
|
3047
|
+
throw new Error(
|
|
3048
|
+
\`Unknown authored hex edge ref '\${edgeRef.spaces.join(", ")}' on board '\${String(boardId)}'.\`,
|
|
3049
|
+
);
|
|
3050
|
+
}
|
|
3051
|
+
return edgeId as HexEdgeState<BoardIdValue>["id"];
|
|
3052
|
+
},
|
|
3053
|
+
resolveHexVertexId<
|
|
3054
|
+
BoardIdValue extends keyof typeof authoredHexVertexIdsByBoardIdAndRefLookup,
|
|
3055
|
+
>(
|
|
3056
|
+
boardId: BoardIdValue,
|
|
3057
|
+
ref: HexAuthoredVertexRef<BoardIdValue>,
|
|
3058
|
+
): HexVertexState<BoardIdValue>["id"] {
|
|
3059
|
+
const boardVertices = authoredHexVertexIdsByBoardIdAndRefLookup[boardId];
|
|
3060
|
+
if (!boardVertices) {
|
|
3061
|
+
throw new Error(\`Unknown hex board '\${String(boardId)}'.\`);
|
|
3062
|
+
}
|
|
3063
|
+
const vertexRef = ref as { spaces: readonly string[] };
|
|
3064
|
+
const vertexId = (
|
|
3065
|
+
boardVertices as Record<string, HexVertexState<BoardIdValue>["id"]>
|
|
3066
|
+
)[authoredHexRefKey(vertexRef.spaces)];
|
|
3067
|
+
if (!vertexId) {
|
|
3068
|
+
throw new Error(
|
|
3069
|
+
\`Unknown authored hex vertex ref '\${vertexRef.spaces.join(", ")}' on board '\${String(boardId)}'.\`,
|
|
3070
|
+
);
|
|
3071
|
+
}
|
|
3072
|
+
return vertexId as HexVertexState<BoardIdValue>["id"];
|
|
3073
|
+
},
|
|
3074
|
+
edgeIdsForType<TypeIdValue extends keyof typeof edgeIdsByTypeIdLookup>(
|
|
3075
|
+
typeId: TypeIdValue,
|
|
3076
|
+
): (typeof edgeIdsByTypeIdLookup)[TypeIdValue] {
|
|
3077
|
+
return edgeIdsByTypeIdLookup[typeId];
|
|
3078
|
+
},
|
|
3079
|
+
edgeRecord<
|
|
3080
|
+
BoardIdValue extends keyof typeof edgeIdsByBoardIdAndTypeIdLookup,
|
|
3081
|
+
Value,
|
|
3082
|
+
>(
|
|
3083
|
+
boardId: BoardIdValue,
|
|
3084
|
+
initial:
|
|
3085
|
+
| Value
|
|
3086
|
+
| ((
|
|
3087
|
+
edgeId: BoardLookupIdValue<
|
|
3088
|
+
typeof edgeIdsByBoardIdAndTypeIdLookup,
|
|
3089
|
+
BoardIdValue
|
|
3090
|
+
>,
|
|
3091
|
+
) => Value),
|
|
3092
|
+
): Record<
|
|
3093
|
+
BoardLookupIdValue<typeof edgeIdsByBoardIdAndTypeIdLookup, BoardIdValue>,
|
|
3094
|
+
Value
|
|
3095
|
+
> {
|
|
3096
|
+
const boardEdges = edgeIdsByBoardIdAndTypeIdLookup[boardId];
|
|
3097
|
+
const edgeIds = boardEdges
|
|
3098
|
+
? flattenBoardScopedIds(edgeIdsByBoardIdAndTypeIdLookup, boardId)
|
|
3099
|
+
: undefined;
|
|
3100
|
+
if (!edgeIds) {
|
|
3101
|
+
throw new Error(\`Unknown board '\${String(boardId)}'.\`);
|
|
3102
|
+
}
|
|
3103
|
+
return buildTypedRecord(edgeIds, initial) as Record<
|
|
3104
|
+
BoardLookupIdValue<typeof edgeIdsByBoardIdAndTypeIdLookup, BoardIdValue>,
|
|
3105
|
+
Value
|
|
3106
|
+
>;
|
|
3107
|
+
},
|
|
3108
|
+
isEdgeId<BoardIdValue extends keyof typeof edgeIdsByBoardIdAndTypeIdLookup>(
|
|
3109
|
+
boardId: BoardIdValue,
|
|
3110
|
+
value: string,
|
|
3111
|
+
): value is BoardLookupIdValue<
|
|
3112
|
+
typeof edgeIdsByBoardIdAndTypeIdLookup,
|
|
3113
|
+
BoardIdValue
|
|
3114
|
+
> {
|
|
3115
|
+
const boardEdges = edgeIdsByBoardIdAndTypeIdLookup[boardId];
|
|
3116
|
+
const edgeIds = boardEdges
|
|
3117
|
+
? flattenBoardScopedIds(edgeIdsByBoardIdAndTypeIdLookup, boardId)
|
|
3118
|
+
: undefined;
|
|
3119
|
+
return edgeIds ? isTypedId(edgeIds, value) : false;
|
|
3120
|
+
},
|
|
3121
|
+
expectEdgeId<
|
|
3122
|
+
BoardIdValue extends keyof typeof edgeIdsByBoardIdAndTypeIdLookup,
|
|
3123
|
+
>(
|
|
3124
|
+
boardId: BoardIdValue,
|
|
3125
|
+
value: string,
|
|
3126
|
+
): BoardLookupIdValue<
|
|
3127
|
+
typeof edgeIdsByBoardIdAndTypeIdLookup,
|
|
3128
|
+
BoardIdValue
|
|
3129
|
+
> {
|
|
3130
|
+
const boardEdges = edgeIdsByBoardIdAndTypeIdLookup[boardId];
|
|
3131
|
+
const edgeIds = boardEdges
|
|
3132
|
+
? flattenBoardScopedIds(edgeIdsByBoardIdAndTypeIdLookup, boardId)
|
|
3133
|
+
: undefined;
|
|
3134
|
+
if (!edgeIds || !isTypedId(edgeIds, value)) {
|
|
3135
|
+
throw new Error(
|
|
3136
|
+
\`Unknown edge id '\${value}' on board '\${String(boardId)}'.\`,
|
|
3137
|
+
);
|
|
3138
|
+
}
|
|
3139
|
+
return value as BoardLookupIdValue<
|
|
3140
|
+
typeof edgeIdsByBoardIdAndTypeIdLookup,
|
|
3141
|
+
BoardIdValue
|
|
3142
|
+
>;
|
|
3143
|
+
},
|
|
3144
|
+
edgeIds<
|
|
3145
|
+
BoardIdValue extends keyof typeof edgeIdsByBoardIdAndTypeIdLookup,
|
|
3146
|
+
TypeIdValue extends keyof (typeof edgeIdsByBoardIdAndTypeIdLookup)[BoardIdValue],
|
|
3147
|
+
>(
|
|
3148
|
+
boardId: BoardIdValue,
|
|
3149
|
+
typeId: TypeIdValue,
|
|
3150
|
+
): (typeof edgeIdsByBoardIdAndTypeIdLookup)[BoardIdValue][TypeIdValue] {
|
|
3151
|
+
const boardEdges = edgeIdsByBoardIdAndTypeIdLookup[boardId];
|
|
3152
|
+
const edgeIds = boardEdges?.[typeId];
|
|
3153
|
+
if (!edgeIds) {
|
|
3154
|
+
throw new Error(
|
|
3155
|
+
\`Unknown edge type '\${String(typeId)}' on board '\${String(boardId)}'.\`,
|
|
3156
|
+
);
|
|
3157
|
+
}
|
|
3158
|
+
return edgeIds as (typeof edgeIdsByBoardIdAndTypeIdLookup)[BoardIdValue][TypeIdValue];
|
|
3159
|
+
},
|
|
3160
|
+
vertexIdsForType<TypeIdValue extends keyof typeof vertexIdsByTypeIdLookup>(
|
|
3161
|
+
typeId: TypeIdValue,
|
|
3162
|
+
): (typeof vertexIdsByTypeIdLookup)[TypeIdValue] {
|
|
3163
|
+
return vertexIdsByTypeIdLookup[typeId];
|
|
3164
|
+
},
|
|
3165
|
+
vertexRecord<
|
|
3166
|
+
BoardIdValue extends keyof typeof vertexIdsByBoardIdAndTypeIdLookup,
|
|
3167
|
+
Value,
|
|
3168
|
+
>(
|
|
3169
|
+
boardId: BoardIdValue,
|
|
3170
|
+
initial:
|
|
3171
|
+
| Value
|
|
3172
|
+
| ((
|
|
3173
|
+
vertexId: BoardLookupIdValue<
|
|
3174
|
+
typeof vertexIdsByBoardIdAndTypeIdLookup,
|
|
3175
|
+
BoardIdValue
|
|
3176
|
+
>,
|
|
3177
|
+
) => Value),
|
|
3178
|
+
): Record<
|
|
3179
|
+
BoardLookupIdValue<typeof vertexIdsByBoardIdAndTypeIdLookup, BoardIdValue>,
|
|
3180
|
+
Value
|
|
3181
|
+
> {
|
|
3182
|
+
const boardVertices = vertexIdsByBoardIdAndTypeIdLookup[boardId];
|
|
3183
|
+
const vertexIds = boardVertices
|
|
3184
|
+
? flattenBoardScopedIds(vertexIdsByBoardIdAndTypeIdLookup, boardId)
|
|
3185
|
+
: undefined;
|
|
3186
|
+
if (!vertexIds) {
|
|
3187
|
+
throw new Error(\`Unknown board '\${String(boardId)}'.\`);
|
|
3188
|
+
}
|
|
3189
|
+
return buildTypedRecord(vertexIds, initial) as Record<
|
|
3190
|
+
BoardLookupIdValue<typeof vertexIdsByBoardIdAndTypeIdLookup, BoardIdValue>,
|
|
3191
|
+
Value
|
|
3192
|
+
>;
|
|
3193
|
+
},
|
|
3194
|
+
isVertexId<
|
|
3195
|
+
BoardIdValue extends keyof typeof vertexIdsByBoardIdAndTypeIdLookup,
|
|
3196
|
+
>(
|
|
3197
|
+
boardId: BoardIdValue,
|
|
3198
|
+
value: string,
|
|
3199
|
+
): value is BoardLookupIdValue<
|
|
3200
|
+
typeof vertexIdsByBoardIdAndTypeIdLookup,
|
|
3201
|
+
BoardIdValue
|
|
3202
|
+
> {
|
|
3203
|
+
const boardVertices = vertexIdsByBoardIdAndTypeIdLookup[boardId];
|
|
3204
|
+
const vertexIds = boardVertices
|
|
3205
|
+
? flattenBoardScopedIds(vertexIdsByBoardIdAndTypeIdLookup, boardId)
|
|
3206
|
+
: undefined;
|
|
3207
|
+
return vertexIds ? isTypedId(vertexIds, value) : false;
|
|
3208
|
+
},
|
|
3209
|
+
expectVertexId<
|
|
3210
|
+
BoardIdValue extends keyof typeof vertexIdsByBoardIdAndTypeIdLookup,
|
|
3211
|
+
>(
|
|
3212
|
+
boardId: BoardIdValue,
|
|
3213
|
+
value: string,
|
|
3214
|
+
): BoardLookupIdValue<
|
|
3215
|
+
typeof vertexIdsByBoardIdAndTypeIdLookup,
|
|
3216
|
+
BoardIdValue
|
|
3217
|
+
> {
|
|
3218
|
+
const boardVertices = vertexIdsByBoardIdAndTypeIdLookup[boardId];
|
|
3219
|
+
const vertexIds = boardVertices
|
|
3220
|
+
? flattenBoardScopedIds(vertexIdsByBoardIdAndTypeIdLookup, boardId)
|
|
3221
|
+
: undefined;
|
|
3222
|
+
if (!vertexIds || !isTypedId(vertexIds, value)) {
|
|
3223
|
+
throw new Error(
|
|
3224
|
+
\`Unknown vertex id '\${value}' on board '\${String(boardId)}'.\`,
|
|
3225
|
+
);
|
|
3226
|
+
}
|
|
3227
|
+
return value as BoardLookupIdValue<
|
|
3228
|
+
typeof vertexIdsByBoardIdAndTypeIdLookup,
|
|
3229
|
+
BoardIdValue
|
|
3230
|
+
>;
|
|
3231
|
+
},
|
|
3232
|
+
vertexIds<
|
|
3233
|
+
BoardIdValue extends keyof typeof vertexIdsByBoardIdAndTypeIdLookup,
|
|
3234
|
+
TypeIdValue extends keyof (typeof vertexIdsByBoardIdAndTypeIdLookup)[BoardIdValue],
|
|
3235
|
+
>(
|
|
3236
|
+
boardId: BoardIdValue,
|
|
3237
|
+
typeId: TypeIdValue,
|
|
3238
|
+
): (typeof vertexIdsByBoardIdAndTypeIdLookup)[BoardIdValue][TypeIdValue] {
|
|
3239
|
+
const boardVertices = vertexIdsByBoardIdAndTypeIdLookup[boardId];
|
|
3240
|
+
const vertexIds = boardVertices?.[typeId];
|
|
3241
|
+
if (!vertexIds) {
|
|
3242
|
+
throw new Error(
|
|
3243
|
+
\`Unknown vertex type '\${String(typeId)}' on board '\${String(boardId)}'.\`,
|
|
3244
|
+
);
|
|
3245
|
+
}
|
|
3246
|
+
return vertexIds as (typeof vertexIdsByBoardIdAndTypeIdLookup)[BoardIdValue][TypeIdValue];
|
|
3247
|
+
},
|
|
3248
|
+
// Returns a \`BoardRef\` describing a per-player board scoped to the supplied
|
|
3249
|
+
// seat. The old \`boardIdForPlayer\` returned a concrete runtime-board-id
|
|
3250
|
+
// string, which encoded the "one board per maxPlayers seat" assumption in
|
|
3251
|
+
// static data. Under the PerPlayer model the runtime roster is not known at
|
|
3252
|
+
// generate time, so consumers deal with \`BoardRef\` and let the runtime
|
|
3253
|
+
// resolve the actual owner seat.
|
|
3254
|
+
boardRefForPlayer(
|
|
3255
|
+
boardBaseId: BoardBaseId,
|
|
3256
|
+
playerId: PlayerId,
|
|
3257
|
+
): PerPlayerBoardRef<BoardBaseId, PlayerId> {
|
|
3258
|
+
return boardRef(boardBaseId, playerId) as PerPlayerBoardRef<
|
|
3259
|
+
BoardBaseId,
|
|
3260
|
+
PlayerId
|
|
3261
|
+
>;
|
|
3262
|
+
},
|
|
3263
|
+
sharedBoardRef(
|
|
3264
|
+
boardBaseId: BoardBaseId,
|
|
3265
|
+
): SharedBoardRef<BoardBaseId> {
|
|
3266
|
+
return boardRef(boardBaseId) as SharedBoardRef<BoardBaseId>;
|
|
3267
|
+
},
|
|
3268
|
+
} as const;`;
|
|
3269
|
+
}
|
|
3270
|
+
function renderManifestContractSource(manifest) {
|
|
3271
|
+
const analysis = analyzeManifest(manifest);
|
|
3272
|
+
const initialTableTemplate = materializeManifestTable({
|
|
3273
|
+
manifest,
|
|
3274
|
+
playerIds: analysis.playerIds,
|
|
3275
|
+
shuffleItems: (values) => [...values],
|
|
3276
|
+
});
|
|
3277
|
+
const initialTableBoards = initialTableTemplate.boards;
|
|
3278
|
+
const staticBoardsTemplate = {
|
|
3279
|
+
byId: initialTableBoards.byId,
|
|
3280
|
+
hex: initialTableBoards.hex,
|
|
3281
|
+
square: initialTableBoards.square,
|
|
3282
|
+
};
|
|
3283
|
+
const sharedZoneIds = analysis.sharedZones.map((zone) => zone.id).sort();
|
|
3284
|
+
const playerZoneIds = analysis.playerZones.map((zone) => zone.id).sort();
|
|
3285
|
+
const zoneVisibilityById = Object.fromEntries(Array.from(analysis.zoneVisibilityById.entries()).sort(([left], [right]) => left.localeCompare(right)));
|
|
3286
|
+
const emptySharedZonesTemplate = Object.fromEntries(sharedZoneIds.map((zoneId) => [zoneId, []]));
|
|
3287
|
+
const defaultVisibilityTemplate = Object.fromEntries(analysis.cardIds.map((cardId) => [cardId, { faceUp: true }]));
|
|
3288
|
+
const defaultOwnerTemplate = Object.fromEntries(analysis.cardIds.map((cardId) => [cardId, null]));
|
|
3289
|
+
const cardIdsByCardSetId = new Map();
|
|
3290
|
+
for (const cardId of analysis.cardIds) {
|
|
3291
|
+
const cardSetId = analysis.cardSetIdByCardId.get(cardId);
|
|
3292
|
+
if (!cardSetId) {
|
|
3293
|
+
continue;
|
|
3294
|
+
}
|
|
3295
|
+
const cardIds = cardIdsByCardSetId.get(cardSetId) ?? [];
|
|
3296
|
+
cardIds.push(cardId);
|
|
3297
|
+
cardIdsByCardSetId.set(cardSetId, cardIds);
|
|
3298
|
+
}
|
|
3299
|
+
const renderZoneCardIdArrayType = (cardSetIds) => `Array<${renderStringUnion(cardSetIds.flatMap((cardSetId) => cardIdsByCardSetId.get(cardSetId) ?? []))}>`;
|
|
3300
|
+
const sharedZoneCardIdEntries = sharedZoneIds
|
|
3301
|
+
.map((zoneId) => {
|
|
3302
|
+
const allowedCardSetIds = analysis.sharedZoneCardSetIds.get(zoneId) ?? [];
|
|
3303
|
+
return ` ${quote(zoneId)}: ${renderZoneCardIdArrayType(allowedCardSetIds)};`;
|
|
3304
|
+
})
|
|
3305
|
+
.join("\n");
|
|
3306
|
+
const playerZoneCardIdEntries = playerZoneIds
|
|
3307
|
+
.map((zoneId) => {
|
|
3308
|
+
const allowedCardSetIds = analysis.playerZoneCardSetIds.get(zoneId) ?? [];
|
|
3309
|
+
return ` ${quote(zoneId)}: PerPlayer<${renderZoneCardIdArrayType(allowedCardSetIds)}>;`;
|
|
3310
|
+
})
|
|
3311
|
+
.join("\n");
|
|
3312
|
+
const cardSetById = new Map(analysis.cardSets.map((cardSet) => [cardSet.id, cardSet]));
|
|
3313
|
+
const perCardStateEntries = analysis.cardIds
|
|
3314
|
+
.map((cardId) => {
|
|
3315
|
+
const cardSetId = analysis.cardSetIdByCardId.get(cardId) ?? "";
|
|
3316
|
+
const cardType = analysis.cardTypeByCardId.get(cardId) ?? cardId;
|
|
3317
|
+
const cardSet = cardSetById.get(cardSetId);
|
|
3318
|
+
const propertiesType = cardSet
|
|
3319
|
+
? cardPropertiesTypeName(cardSet, cardType)
|
|
3320
|
+
: cardSetId
|
|
3321
|
+
? `${toPascalCase(cardSetId)}CardProperties`
|
|
3322
|
+
: "RuntimeRecord";
|
|
3323
|
+
return ` ${quote(cardId)}: CardStateRecord<${quote(cardId)}, ${quote(cardSetId)}, ${quote(cardType)}, ${propertiesType}>;`;
|
|
3324
|
+
})
|
|
3325
|
+
.join("\n");
|
|
3326
|
+
const perPieceStateEntries = analysis.pieceIds
|
|
3327
|
+
.map((pieceId) => {
|
|
3328
|
+
const pieceTypeId = analysis.pieceTypeIdByPieceId.get(pieceId) ?? "";
|
|
3329
|
+
return ` ${quote(pieceId)}: PieceStateRecord<${quote(pieceId)}, ${quote(pieceTypeId)}, ${pieceTypeId ? pieceFieldsTypeName(pieceTypeId) : "RuntimeRecord"}>;`;
|
|
3330
|
+
})
|
|
3331
|
+
.join("\n");
|
|
3332
|
+
const perDieStateEntries = analysis.dieIds
|
|
3333
|
+
.map((dieId) => {
|
|
3334
|
+
const dieTypeId = analysis.dieTypeIdByDieId.get(dieId) ?? "";
|
|
3335
|
+
return ` ${quote(dieId)}: DieStateRecord<${quote(dieId)}, ${quote(dieTypeId)}, ${dieTypeId ? dieFieldsTypeName(dieTypeId) : "RuntimeRecord"}>;`;
|
|
3336
|
+
})
|
|
3337
|
+
.join("\n");
|
|
3338
|
+
const perBoardStateEntries = analysis.analyzedBoards
|
|
3339
|
+
.flatMap((board) => board.runtimeBoardIds.map((runtimeBoardId) => {
|
|
3340
|
+
if (board.layout === "hex") {
|
|
3341
|
+
return ` ${quote(runtimeBoardId)}: HexBoardStateRecord<${quote(runtimeBoardId)}, ${renderStringUnion(board.spaces.map((space) => space.id))}, ${renderStringUnion(board.edges.map((edge) => edge.id))}, ${renderStringUnion(board.vertices.map((vertex) => vertex.id))}, ${boardFieldsTypeName(board.board.id)}, ${boardSpaceFieldsTypeName(board.board.id)}, ${hexEdgeFieldsTypeName(board.board.id)}, ${hexVertexFieldsTypeName(board.board.id)}>;`;
|
|
3342
|
+
}
|
|
3343
|
+
if (board.layout === "square") {
|
|
3344
|
+
return ` ${quote(runtimeBoardId)}: SquareBoardStateRecord<${quote(runtimeBoardId)}, ${renderStringUnion(board.spaces.map((space) => space.id))}, ${renderStringUnion(board.containers.map((container) => container.id))}, ${renderStringUnion(board.edges.map((edge) => edge.id))}, ${renderStringUnion(board.vertices.map((vertex) => vertex.id))}, ${boardFieldsTypeName(board.board.id)}, ${boardSpaceFieldsTypeName(board.board.id)}, ${boardRelationFieldsTypeName(board.board.id)}, ${boardContainerFieldsTypeName(board.board.id)}, ${hexEdgeFieldsTypeName(board.board.id)}, ${hexVertexFieldsTypeName(board.board.id)}>;`;
|
|
3345
|
+
}
|
|
3346
|
+
return ` ${quote(runtimeBoardId)}: GenericBoardStateRecord<${quote(runtimeBoardId)}, ${renderStringUnion(board.spaces.map((space) => space.id))}, ${renderStringUnion(board.containers.map((container) => container.id))}, ${boardFieldsTypeName(board.board.id)}, ${boardSpaceFieldsTypeName(board.board.id)}, ${boardRelationFieldsTypeName(board.board.id)}, ${boardContainerFieldsTypeName(board.board.id)}>;`;
|
|
3347
|
+
}))
|
|
3348
|
+
.join("\n");
|
|
3349
|
+
const perHexBoardStateEntries = analysis.analyzedBoards
|
|
3350
|
+
.filter((board) => board.layout === "hex")
|
|
3351
|
+
.flatMap((board) => board.runtimeBoardIds.map((runtimeBoardId) => ` ${quote(runtimeBoardId)}: BoardStateById[${quote(runtimeBoardId)}];`))
|
|
3352
|
+
.join("\n");
|
|
3353
|
+
const perSquareBoardStateEntries = analysis.analyzedBoards
|
|
3354
|
+
.filter((board) => board.layout === "square")
|
|
3355
|
+
.flatMap((board) => board.runtimeBoardIds.map((runtimeBoardId) => ` ${quote(runtimeBoardId)}: BoardStateById[${quote(runtimeBoardId)}];`))
|
|
3356
|
+
.join("\n");
|
|
3357
|
+
return `/**
|
|
3358
|
+
* Generated file.
|
|
3359
|
+
* Do not edit directly.
|
|
3360
|
+
*/
|
|
3361
|
+
|
|
3362
|
+
import { z } from "zod";
|
|
3363
|
+
import {
|
|
3364
|
+
buildTypedRecord,
|
|
3365
|
+
expectTypedId,
|
|
3366
|
+
isTypedId,
|
|
3367
|
+
} from "@dreamboard/sdk-types";
|
|
3368
|
+
import {
|
|
3369
|
+
asPlayerId,
|
|
3370
|
+
assumeManifestSchema,
|
|
3371
|
+
boardRef,
|
|
3372
|
+
boardRefKey,
|
|
3373
|
+
boardRefSchema,
|
|
3374
|
+
cloneManifestDefault,
|
|
3375
|
+
createManifestGameStateSchema,
|
|
3376
|
+
createManifestRuntimeSchema,
|
|
3377
|
+
createManifestStringLiteralSchema,
|
|
3378
|
+
dealToPlayerBoardContainer as createDealToPlayerBoardContainerStep,
|
|
3379
|
+
dealToPlayerZone as createDealToPlayerZoneStep,
|
|
3380
|
+
perPlayer,
|
|
3381
|
+
perPlayerEntries,
|
|
3382
|
+
perPlayerGet,
|
|
3383
|
+
perPlayerHas,
|
|
3384
|
+
perPlayerKeys,
|
|
3385
|
+
perPlayerSchema,
|
|
3386
|
+
markManifestScopedSchema,
|
|
3387
|
+
resolveManifestPlayerIds,
|
|
3388
|
+
seedSharedBoardContainer as createSeedSharedBoardContainerStep,
|
|
3389
|
+
seedSharedBoardSpace as createSeedSharedBoardSpaceStep,
|
|
3390
|
+
shuffle as createShuffleStep,
|
|
3391
|
+
type CardIdOfManifest,
|
|
3392
|
+
type DieIdOfManifest,
|
|
3393
|
+
type PieceIdOfManifest,
|
|
3394
|
+
type BoardRef,
|
|
3395
|
+
type PerPlayer,
|
|
3396
|
+
type PerPlayerBoardRef,
|
|
3397
|
+
type PlayerId,
|
|
3398
|
+
type ReducerManifestContract,
|
|
3399
|
+
type RuntimeCardData,
|
|
3400
|
+
type RuntimeCardVisibility,
|
|
3401
|
+
type RuntimeComponentLocation,
|
|
3402
|
+
type RuntimeDieData,
|
|
3403
|
+
type RuntimeHandVisibilityMode,
|
|
3404
|
+
type RuntimePieceData,
|
|
3405
|
+
type RuntimeRecord,
|
|
3406
|
+
type RuntimeTableRecord,
|
|
3407
|
+
type SetupBootstrapContainerRef,
|
|
3408
|
+
type SetupBootstrapDestinationRef,
|
|
3409
|
+
type SetupBootstrapPerPlayerContainerTemplateRef,
|
|
3410
|
+
type SetupBootstrapStep,
|
|
3411
|
+
type SetupProfileDefinition,
|
|
3412
|
+
type SharedBoardRef,
|
|
3413
|
+
type StaticBoards,
|
|
3414
|
+
} from "@dreamboard/app-sdk/reducer";
|
|
3415
|
+
|
|
3416
|
+
const unknownRecordSchema = assumeManifestSchema<RuntimeRecord>(
|
|
3417
|
+
z.record(z.string(), z.unknown()),
|
|
3418
|
+
);
|
|
3419
|
+
|
|
3420
|
+
function resolveDefaultPlayerIds(
|
|
3421
|
+
playerIds: readonly string[] | undefined,
|
|
3422
|
+
): readonly PlayerId[] {
|
|
3423
|
+
return resolveManifestPlayerIds(
|
|
3424
|
+
literals.playerIds as unknown as readonly PlayerId[],
|
|
3425
|
+
playerIds,
|
|
3426
|
+
);
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3429
|
+
export const literals = {
|
|
3430
|
+
// literals satisfy \`ManifestLiterals<PlayerId, ...>\`. The cast is safe
|
|
3431
|
+
// because the runtime values are the exact player-id strings the manifest
|
|
3432
|
+
// authored; branding is purely a type-level discipline.
|
|
3433
|
+
playerIds: ${renderConstArray(analysis.playerIds)} as unknown as readonly PlayerId[],
|
|
3434
|
+
phaseNames: [] as readonly string[],
|
|
3435
|
+
boardLayouts: ["generic", "hex", "square"] as const,
|
|
3436
|
+
setupOptionIds: ${renderConstArray(analysis.setupOptionIds)},
|
|
3437
|
+
setupProfileIds: ${renderConstArray(analysis.setupProfileIds)},
|
|
3438
|
+
cardSetIds: ${renderConstArray(analysis.cardSetIds)},
|
|
3439
|
+
cardTypes: ${renderConstArray(analysis.cardTypes)},
|
|
3440
|
+
cardIds: ${renderConstArray(analysis.cardIds)},
|
|
3441
|
+
deckIds: ${renderConstArray(sharedZoneIds)},
|
|
3442
|
+
handIds: ${renderConstArray(playerZoneIds)},
|
|
3443
|
+
sharedZoneIds: ${renderConstArray(sharedZoneIds)},
|
|
3444
|
+
playerZoneIds: ${renderConstArray(playerZoneIds)},
|
|
3445
|
+
zoneIds: ${renderConstArray(analysis.zoneIds)},
|
|
3446
|
+
resourceIds: ${renderConstArray(analysis.resourceIds)},
|
|
3447
|
+
resourcePresentationById: ${renderJsonConst(analysis.resourcePresentationById)},
|
|
3448
|
+
pieceTypeIds: ${renderConstArray(analysis.pieceTypeIds)},
|
|
3449
|
+
pieceIds: ${renderConstArray(analysis.pieceIds)},
|
|
3450
|
+
dieTypeIds: ${renderConstArray(analysis.dieTypeIds)},
|
|
3451
|
+
dieIds: ${renderConstArray(analysis.dieIds)},
|
|
3452
|
+
boardTemplateIds: ${renderConstArray(analysis.boardTemplateIds)},
|
|
3453
|
+
boardTypeIds: ${renderConstArray(analysis.boardTypeIds)},
|
|
3454
|
+
boardBaseIds: ${renderConstArray(analysis.boardBaseIds)},
|
|
3455
|
+
boardIds: ${renderConstArray(analysis.boardIds)},
|
|
3456
|
+
boardContainerIds: ${renderConstArray(analysis.boardContainerIds)},
|
|
3457
|
+
relationTypeIds: ${renderConstArray(analysis.relationTypeIds)},
|
|
3458
|
+
edgeIds: ${renderConstArray(analysis.edgeIds)},
|
|
3459
|
+
edgeTypeIds: ${renderConstArray(analysis.edgeTypeIds)},
|
|
3460
|
+
vertexIds: ${renderConstArray(analysis.vertexIds)},
|
|
3461
|
+
vertexTypeIds: ${renderConstArray(analysis.vertexTypeIds)},
|
|
3462
|
+
spaceIds: ${renderConstArray(analysis.spaceIds)},
|
|
3463
|
+
spaceTypeIds: ${renderConstArray(analysis.spaceTypeIds)},
|
|
3464
|
+
handVisibilityById: ${renderStringRecord(playerZoneIds.map((zoneId) => [zoneId, zoneVisibilityById[zoneId] ?? "ownerOnly"]))},
|
|
3465
|
+
zoneVisibilityById: ${renderStringRecord(analysis.zoneVisibilityById)},
|
|
3466
|
+
cardSetIdByCardId: ${renderStringRecord(analysis.cardSetIdByCardId)},
|
|
3467
|
+
cardTypeByCardId: ${renderStringRecord(analysis.cardTypeByCardId)},
|
|
3468
|
+
setupChoiceIdsByOptionId: ${renderReadonlyArrayRecord(analysis.setupChoiceIdsByOptionId)},
|
|
3469
|
+
cardSetIdsBySharedZoneId: ${renderReadonlyArrayRecord(analysis.sharedZoneCardSetIds)},
|
|
3470
|
+
sharedZoneIdsByCardSetId: ${renderReadonlyArrayRecord(analysis.sharedZoneIdsByCardSetId)},
|
|
3471
|
+
homeSharedZoneIdsByCardType: ${renderReadonlyArrayRecord(analysis.homeSharedZoneIdsByCardType)},
|
|
3472
|
+
homeSharedZoneIdByCardType: ${renderStringRecord(analysis.homeSharedZoneIdByCardType)},
|
|
3473
|
+
cardSetIdsByPlayerZoneId: ${renderReadonlyArrayRecord(analysis.playerZoneCardSetIds)},
|
|
3474
|
+
} as const;
|
|
3475
|
+
|
|
3476
|
+
// PlayerId is an opaque brand imported from @dreamboard/app-sdk/reducer.
|
|
3477
|
+
// We intentionally do NOT enumerate the manifest's max-players roster here:
|
|
3478
|
+
// the runtime session may have fewer active seats than the manifest declares,
|
|
3479
|
+
// and requiring ingress to pick a literal from the max-players set reintroduces
|
|
3480
|
+
// the "total-record" assumption the refactor is meant to eliminate. Runtime
|
|
3481
|
+
// roster validation is done through perPlayerSchema(runtimePlayerIds, ...)
|
|
3482
|
+
// instead, which can be bound to the actual active roster.
|
|
3483
|
+
const playerIdSchema = markManifestScopedSchema(
|
|
3484
|
+
z
|
|
3485
|
+
.string()
|
|
3486
|
+
.min(1)
|
|
3487
|
+
.transform((value) => asPlayerId(value)),
|
|
3488
|
+
);
|
|
3489
|
+
const phaseNameSchema = markManifestScopedSchema(z.string());
|
|
3490
|
+
const boardLayoutSchema = createManifestStringLiteralSchema(literals.boardLayouts);
|
|
3491
|
+
const setupOptionIdSchema = createManifestStringLiteralSchema(literals.setupOptionIds);
|
|
3492
|
+
const setupProfileIdSchema = createManifestStringLiteralSchema(
|
|
3493
|
+
literals.setupProfileIds,
|
|
3494
|
+
);
|
|
3495
|
+
const cardSetIdSchema = createManifestStringLiteralSchema(literals.cardSetIds);
|
|
3496
|
+
const cardTypeSchema = createManifestStringLiteralSchema(literals.cardTypes);
|
|
3497
|
+
const cardIdSchema = createManifestStringLiteralSchema(literals.cardIds);
|
|
3498
|
+
const deckIdSchema = createManifestStringLiteralSchema(literals.deckIds);
|
|
3499
|
+
const handIdSchema = createManifestStringLiteralSchema(literals.handIds);
|
|
3500
|
+
const sharedZoneIdSchema = createManifestStringLiteralSchema(literals.sharedZoneIds);
|
|
3501
|
+
const playerZoneIdSchema = createManifestStringLiteralSchema(literals.playerZoneIds);
|
|
3502
|
+
const zoneIdSchema = createManifestStringLiteralSchema(literals.zoneIds);
|
|
3503
|
+
const resourceIdSchema = createManifestStringLiteralSchema(literals.resourceIds);
|
|
3504
|
+
const pieceTypeIdSchema = createManifestStringLiteralSchema(literals.pieceTypeIds);
|
|
3505
|
+
const pieceIdSchema = createManifestStringLiteralSchema(literals.pieceIds);
|
|
3506
|
+
const dieTypeIdSchema = createManifestStringLiteralSchema(literals.dieTypeIds);
|
|
3507
|
+
const dieIdSchema = createManifestStringLiteralSchema(literals.dieIds);
|
|
3508
|
+
const boardTypeIdSchema = createManifestStringLiteralSchema(literals.boardTypeIds);
|
|
3509
|
+
const boardBaseIdSchema = createManifestStringLiteralSchema(literals.boardBaseIds);
|
|
3510
|
+
const boardIdSchema = createManifestStringLiteralSchema(literals.boardIds);
|
|
3511
|
+
const boardContainerIdSchema = createManifestStringLiteralSchema(
|
|
3512
|
+
literals.boardContainerIds,
|
|
3513
|
+
);
|
|
3514
|
+
const relationTypeIdSchema = createManifestStringLiteralSchema(literals.relationTypeIds);
|
|
3515
|
+
const edgeIdSchema = createManifestStringLiteralSchema(literals.edgeIds);
|
|
3516
|
+
const edgeTypeIdSchema = createManifestStringLiteralSchema(literals.edgeTypeIds);
|
|
3517
|
+
const vertexIdSchema = createManifestStringLiteralSchema(literals.vertexIds);
|
|
3518
|
+
const vertexTypeIdSchema = createManifestStringLiteralSchema(literals.vertexTypeIds);
|
|
3519
|
+
const spaceIdSchema = createManifestStringLiteralSchema(literals.spaceIds);
|
|
3520
|
+
const spaceTypeIdSchema = createManifestStringLiteralSchema(literals.spaceTypeIds);
|
|
3521
|
+
|
|
3522
|
+
export const ids = {
|
|
3523
|
+
playerId: playerIdSchema,
|
|
3524
|
+
phaseName: phaseNameSchema,
|
|
3525
|
+
boardLayout: boardLayoutSchema,
|
|
3526
|
+
setupOptionId: setupOptionIdSchema,
|
|
3527
|
+
setupProfileId: setupProfileIdSchema,
|
|
3528
|
+
cardSetId: cardSetIdSchema,
|
|
3529
|
+
cardType: cardTypeSchema,
|
|
3530
|
+
cardId: cardIdSchema as unknown as z.ZodType<CardId>,
|
|
3531
|
+
deckId: deckIdSchema as unknown as z.ZodType<DeckId>,
|
|
3532
|
+
handId: handIdSchema as unknown as z.ZodType<HandId>,
|
|
3533
|
+
sharedZoneId: sharedZoneIdSchema,
|
|
3534
|
+
playerZoneId: playerZoneIdSchema,
|
|
3535
|
+
zoneId: zoneIdSchema,
|
|
3536
|
+
resourceId: resourceIdSchema,
|
|
3537
|
+
pieceTypeId: pieceTypeIdSchema,
|
|
3538
|
+
pieceId: pieceIdSchema,
|
|
3539
|
+
dieTypeId: dieTypeIdSchema,
|
|
3540
|
+
dieId: dieIdSchema,
|
|
3541
|
+
boardTypeId: boardTypeIdSchema,
|
|
3542
|
+
boardBaseId: boardBaseIdSchema,
|
|
3543
|
+
boardId: boardIdSchema,
|
|
3544
|
+
boardContainerId: boardContainerIdSchema,
|
|
3545
|
+
relationTypeId: relationTypeIdSchema,
|
|
3546
|
+
edgeId: edgeIdSchema,
|
|
3547
|
+
edgeTypeId: edgeTypeIdSchema,
|
|
3548
|
+
vertexId: vertexIdSchema,
|
|
3549
|
+
vertexTypeId: vertexTypeIdSchema,
|
|
3550
|
+
spaceId: spaceIdSchema,
|
|
3551
|
+
spaceTypeId: spaceTypeIdSchema,
|
|
3552
|
+
} as const;
|
|
3553
|
+
|
|
3554
|
+
export type { PlayerId };
|
|
3555
|
+
export type PhaseName = string;
|
|
3556
|
+
export type BoardLayout = (typeof literals.boardLayouts)[number];
|
|
3557
|
+
export type SetupOptionId = (typeof literals.setupOptionIds)[number];
|
|
3558
|
+
export type SetupProfileId = (typeof literals.setupProfileIds)[number];
|
|
3559
|
+
export type CardSetId = (typeof literals.cardSetIds)[number];
|
|
3560
|
+
export type CardType = (typeof literals.cardTypes)[number];
|
|
3561
|
+
export type CardId = (typeof literals.cardIds)[number];
|
|
3562
|
+
export type DeckId = (typeof literals.deckIds)[number];
|
|
3563
|
+
export type HandId = (typeof literals.handIds)[number];
|
|
3564
|
+
export type SharedZoneId = (typeof literals.sharedZoneIds)[number];
|
|
3565
|
+
export type PlayerZoneId = (typeof literals.playerZoneIds)[number];
|
|
3566
|
+
export type ZoneId = (typeof literals.zoneIds)[number];
|
|
3567
|
+
export type ResourceId = (typeof literals.resourceIds)[number];
|
|
3568
|
+
export type PieceTypeId = (typeof literals.pieceTypeIds)[number];
|
|
3569
|
+
export type PieceId = (typeof literals.pieceIds)[number];
|
|
3570
|
+
export type DieTypeId = (typeof literals.dieTypeIds)[number];
|
|
3571
|
+
export type DieId = (typeof literals.dieIds)[number];
|
|
3572
|
+
export type BoardTypeId = (typeof literals.boardTypeIds)[number];
|
|
3573
|
+
export type BoardBaseId = (typeof literals.boardBaseIds)[number];
|
|
3574
|
+
export type BoardId = (typeof literals.boardIds)[number];
|
|
3575
|
+
export type BoardContainerId = (typeof literals.boardContainerIds)[number];
|
|
3576
|
+
export type RelationTypeId = (typeof literals.relationTypeIds)[number];
|
|
3577
|
+
export type EdgeId = (typeof literals.edgeIds)[number];
|
|
3578
|
+
export type EdgeTypeId = (typeof literals.edgeTypeIds)[number];
|
|
3579
|
+
export type VertexId = (typeof literals.vertexIds)[number];
|
|
3580
|
+
export type VertexTypeId = (typeof literals.vertexTypeIds)[number];
|
|
3581
|
+
export type SpaceId = (typeof literals.spaceIds)[number];
|
|
3582
|
+
export type SpaceTypeId = (typeof literals.spaceTypeIds)[number];
|
|
3583
|
+
|
|
3584
|
+
${renderGeneratedIdHandles(analysis)}
|
|
3585
|
+
|
|
3586
|
+
${renderGeneratedRecordsHelpers()}
|
|
3587
|
+
|
|
3588
|
+
${renderGeneratedIdGuards()}
|
|
3589
|
+
|
|
3590
|
+
// Historically this emitted PlayerRecord<T> = Record<PlayerId, T>, but that
|
|
3591
|
+
// type reified the "total roster" assumption (one entry per max-player). It has
|
|
3592
|
+
// been replaced throughout the generated contract with PerPlayer<T> from
|
|
3593
|
+
// @dreamboard/app-sdk/reducer, whose entries array matches the
|
|
3594
|
+
// actual runtime seat list.
|
|
3595
|
+
export type SharedZoneRecord<T> = Record<SharedZoneId, T>;
|
|
3596
|
+
export type PlayerZoneRecord<T> = Record<PlayerZoneId, PerPlayer<T>>;
|
|
3597
|
+
export type ComponentId = CardId | PieceId | DieId;
|
|
3598
|
+
export type ComponentIdsBySharedZoneId = {
|
|
3599
|
+
${sharedZoneIds.map((zoneId) => ` ${quote(zoneId)}: ComponentId[];`).join("\n")}
|
|
3600
|
+
};
|
|
3601
|
+
export type ComponentIdsByPlayerZoneId = {
|
|
3602
|
+
${playerZoneIds.map((zoneId) => ` ${quote(zoneId)}: PerPlayer<ComponentId[]>;`).join("\n")}
|
|
3603
|
+
};
|
|
3604
|
+
export type SetupOptionChoice = {
|
|
3605
|
+
id: string;
|
|
3606
|
+
label: string;
|
|
3607
|
+
description?: string | null;
|
|
3608
|
+
};
|
|
3609
|
+
export type SetupOption = {
|
|
3610
|
+
id: SetupOptionId;
|
|
3611
|
+
name: string;
|
|
3612
|
+
description?: string | null;
|
|
3613
|
+
choices: readonly SetupOptionChoice[];
|
|
3614
|
+
};
|
|
3615
|
+
export type SetupProfile = {
|
|
3616
|
+
id: SetupProfileId;
|
|
3617
|
+
name: string;
|
|
3618
|
+
description?: string | null;
|
|
3619
|
+
optionValues?: Partial<Record<SetupOptionId, string>> | null;
|
|
3620
|
+
};
|
|
3621
|
+
export const setupOptionsById = ${renderJsonConst(analysis.setupOptionsById)};
|
|
3622
|
+
export const setupChoiceIdsByOptionId = ${renderReadonlyArrayRecord(analysis.setupChoiceIdsByOptionId)};
|
|
3623
|
+
export const setupProfilesById = ${renderJsonConst(analysis.setupProfilesById)};
|
|
3624
|
+
|
|
3625
|
+
${renderCardSetPropertySections(analysis.cardSets)}
|
|
3626
|
+
|
|
3627
|
+
${renderTopologyFieldSections(analysis)}
|
|
3628
|
+
|
|
3629
|
+
${renderBoardFieldMapTypes(analysis)}
|
|
3630
|
+
|
|
3631
|
+
export type CardProperties = ${analysis.cardSets.length > 0
|
|
3632
|
+
? analysis.cardSets
|
|
3633
|
+
.map((cardSet) => `${toPascalCase(cardSet.id)}CardProperties`)
|
|
3634
|
+
.join(" | ")
|
|
3635
|
+
: "RuntimeRecord"};
|
|
3636
|
+
|
|
3637
|
+
export type CardStateRecord<
|
|
3638
|
+
CardIdValue extends CardId = CardId,
|
|
3639
|
+
CardSetIdValue extends CardSetId = CardSetId,
|
|
3640
|
+
CardTypeValue extends CardType = CardType,
|
|
3641
|
+
Properties = RuntimeRecord,
|
|
3642
|
+
> = Omit<RuntimeCardData, "id" | "cardSetId" | "cardType" | "properties"> & {
|
|
3643
|
+
id: CardIdValue;
|
|
3644
|
+
cardSetId: CardSetIdValue;
|
|
3645
|
+
cardType: CardTypeValue;
|
|
3646
|
+
properties: Properties;
|
|
3647
|
+
};
|
|
3648
|
+
|
|
3649
|
+
export type CardStateById = ${perCardStateEntries.length > 0
|
|
3650
|
+
? `{\n${perCardStateEntries}\n}`
|
|
3651
|
+
: "Record<string, never>"};
|
|
3652
|
+
|
|
3653
|
+
export type PieceStateRecord<
|
|
3654
|
+
PieceIdValue extends PieceId = PieceId,
|
|
3655
|
+
PieceTypeIdValue extends PieceTypeId = PieceTypeId,
|
|
3656
|
+
Fields = RuntimeRecord,
|
|
3657
|
+
> = Omit<RuntimePieceData, "id" | "pieceTypeId" | "properties"> & {
|
|
3658
|
+
id: PieceIdValue;
|
|
3659
|
+
pieceTypeId: PieceTypeIdValue;
|
|
3660
|
+
properties: Fields;
|
|
3661
|
+
};
|
|
3662
|
+
|
|
3663
|
+
export type DieStateRecord<
|
|
3664
|
+
DieIdValue extends DieId = DieId,
|
|
3665
|
+
DieTypeIdValue extends DieTypeId = DieTypeId,
|
|
3666
|
+
Fields = RuntimeRecord,
|
|
3667
|
+
> = Omit<RuntimeDieData, "id" | "dieTypeId" | "properties"> & {
|
|
3668
|
+
id: DieIdValue;
|
|
3669
|
+
dieTypeId: DieTypeIdValue;
|
|
3670
|
+
properties: Fields;
|
|
3671
|
+
};
|
|
3672
|
+
|
|
3673
|
+
export type PieceStateById = ${perPieceStateEntries.length > 0
|
|
3674
|
+
? `{\n${perPieceStateEntries}\n}`
|
|
3675
|
+
: "Record<string, never>"};
|
|
3676
|
+
|
|
3677
|
+
export type DieStateById = ${perDieStateEntries.length > 0
|
|
3678
|
+
? `{\n${perDieStateEntries}\n}`
|
|
3679
|
+
: "Record<string, never>"};
|
|
3680
|
+
export type CardIdsBySharedZoneId = {
|
|
3681
|
+
${sharedZoneCardIdEntries}
|
|
3682
|
+
};
|
|
3683
|
+
export type CardIdsByPlayerZoneId = {
|
|
3684
|
+
${playerZoneCardIdEntries}
|
|
3685
|
+
};
|
|
3686
|
+
export type CardIdsByDeckId = CardIdsBySharedZoneId;
|
|
3687
|
+
|
|
3688
|
+
export interface BoardSpaceStateRecord<
|
|
3689
|
+
SpaceIdValue extends SpaceId = SpaceId,
|
|
3690
|
+
Fields = RuntimeRecord,
|
|
3691
|
+
> {
|
|
3692
|
+
id: SpaceIdValue;
|
|
3693
|
+
name?: string | null;
|
|
3694
|
+
typeId?: SpaceTypeId | null;
|
|
3695
|
+
fields: Fields;
|
|
3696
|
+
zoneId?: string | null;
|
|
3697
|
+
}
|
|
3698
|
+
|
|
3699
|
+
export interface BoardRelationStateRecord<
|
|
3700
|
+
SpaceIdValue extends SpaceId = SpaceId,
|
|
3701
|
+
Fields = RuntimeRecord,
|
|
3702
|
+
> {
|
|
3703
|
+
id?: string | null;
|
|
3704
|
+
typeId: RelationTypeId;
|
|
3705
|
+
fromSpaceId: SpaceIdValue;
|
|
3706
|
+
toSpaceId: SpaceIdValue;
|
|
3707
|
+
directed: boolean;
|
|
3708
|
+
fields: Fields;
|
|
3709
|
+
}
|
|
3710
|
+
|
|
3711
|
+
export interface BoardContainerStateRecord<
|
|
3712
|
+
SpaceIdValue extends SpaceId = SpaceId,
|
|
3713
|
+
ContainerIdValue extends BoardContainerId = BoardContainerId,
|
|
3714
|
+
Fields = RuntimeRecord,
|
|
3715
|
+
> {
|
|
3716
|
+
id: ContainerIdValue;
|
|
3717
|
+
name: string;
|
|
3718
|
+
host:
|
|
3719
|
+
| { type: "board" }
|
|
3720
|
+
| {
|
|
3721
|
+
type: "space";
|
|
3722
|
+
spaceId: SpaceIdValue;
|
|
3723
|
+
};
|
|
3724
|
+
allowedCardSetIds?: readonly CardSetId[];
|
|
3725
|
+
zoneId: string;
|
|
3726
|
+
fields: Fields;
|
|
3727
|
+
}
|
|
3728
|
+
|
|
3729
|
+
export interface BoardStateRecordBase<
|
|
3730
|
+
BoardIdValue extends BoardId = BoardId,
|
|
3731
|
+
SpaceIdValue extends SpaceId = SpaceId,
|
|
3732
|
+
ContainerIdValue extends BoardContainerId = BoardContainerId,
|
|
3733
|
+
BoardFields = RuntimeRecord,
|
|
3734
|
+
SpaceFields = RuntimeRecord,
|
|
3735
|
+
RelationFields = RuntimeRecord,
|
|
3736
|
+
ContainerFields = RuntimeRecord,
|
|
3737
|
+
> {
|
|
3738
|
+
id: BoardIdValue;
|
|
3739
|
+
baseId: BoardBaseId;
|
|
3740
|
+
typeId?: BoardTypeId | null;
|
|
3741
|
+
scope: "shared" | "perPlayer";
|
|
3742
|
+
playerId?: PlayerId | null;
|
|
3743
|
+
templateId?: string | null;
|
|
3744
|
+
fields: BoardFields;
|
|
3745
|
+
}
|
|
3746
|
+
|
|
3747
|
+
export interface GenericBoardStateRecord<
|
|
3748
|
+
BoardIdValue extends BoardId = BoardId,
|
|
3749
|
+
SpaceIdValue extends SpaceId = SpaceId,
|
|
3750
|
+
ContainerIdValue extends BoardContainerId = BoardContainerId,
|
|
3751
|
+
BoardFields = RuntimeRecord,
|
|
3752
|
+
SpaceFields = RuntimeRecord,
|
|
3753
|
+
RelationFields = RuntimeRecord,
|
|
3754
|
+
ContainerFields = RuntimeRecord,
|
|
3755
|
+
> extends BoardStateRecordBase<
|
|
3756
|
+
BoardIdValue,
|
|
3757
|
+
SpaceIdValue,
|
|
3758
|
+
ContainerIdValue,
|
|
3759
|
+
BoardFields,
|
|
3760
|
+
SpaceFields,
|
|
3761
|
+
RelationFields,
|
|
3762
|
+
ContainerFields
|
|
3763
|
+
> {
|
|
3764
|
+
layout: "generic";
|
|
3765
|
+
spaces: Record<
|
|
3766
|
+
SpaceIdValue,
|
|
3767
|
+
BoardSpaceStateRecord<SpaceIdValue, SpaceFields>
|
|
3768
|
+
>;
|
|
3769
|
+
relations: Array<BoardRelationStateRecord<SpaceIdValue, RelationFields>>;
|
|
3770
|
+
containers: Record<
|
|
3771
|
+
ContainerIdValue,
|
|
3772
|
+
BoardContainerStateRecord<SpaceIdValue, ContainerIdValue, ContainerFields>
|
|
3773
|
+
>;
|
|
3774
|
+
}
|
|
3775
|
+
|
|
3776
|
+
export interface HexSpaceStateRecord<
|
|
3777
|
+
SpaceIdValue extends SpaceId = SpaceId,
|
|
3778
|
+
Fields = RuntimeRecord,
|
|
3779
|
+
> extends BoardSpaceStateRecord<SpaceIdValue, Fields> {
|
|
3780
|
+
q: number;
|
|
3781
|
+
r: number;
|
|
3782
|
+
}
|
|
3783
|
+
|
|
3784
|
+
export interface SquareSpaceStateRecord<
|
|
3785
|
+
SpaceIdValue extends SpaceId = SpaceId,
|
|
3786
|
+
Fields = RuntimeRecord,
|
|
3787
|
+
> extends BoardSpaceStateRecord<SpaceIdValue, Fields> {
|
|
3788
|
+
row: number;
|
|
3789
|
+
col: number;
|
|
3790
|
+
}
|
|
3791
|
+
|
|
3792
|
+
export interface TiledEdgeStateRecord<
|
|
3793
|
+
SpaceIdValue extends SpaceId = SpaceId,
|
|
3794
|
+
EdgeIdValue extends EdgeId = EdgeId,
|
|
3795
|
+
Fields = RuntimeRecord,
|
|
3796
|
+
> {
|
|
3797
|
+
id: EdgeIdValue;
|
|
3798
|
+
spaceIds: readonly SpaceIdValue[];
|
|
3799
|
+
typeId?: EdgeTypeId | null;
|
|
3800
|
+
label?: string | null;
|
|
3801
|
+
ownerId?: PlayerId | null;
|
|
3802
|
+
fields: Fields;
|
|
3803
|
+
}
|
|
3804
|
+
|
|
3805
|
+
export interface TiledVertexStateRecord<
|
|
3806
|
+
SpaceIdValue extends SpaceId = SpaceId,
|
|
3807
|
+
VertexIdValue extends VertexId = VertexId,
|
|
3808
|
+
Fields = RuntimeRecord,
|
|
3809
|
+
> {
|
|
3810
|
+
id: VertexIdValue;
|
|
3811
|
+
spaceIds: readonly SpaceIdValue[];
|
|
3812
|
+
typeId?: VertexTypeId | null;
|
|
3813
|
+
label?: string | null;
|
|
3814
|
+
ownerId?: PlayerId | null;
|
|
3815
|
+
fields: Fields;
|
|
3816
|
+
}
|
|
3817
|
+
|
|
3818
|
+
export type HexEdgeStateRecord<
|
|
3819
|
+
SpaceIdValue extends SpaceId = SpaceId,
|
|
3820
|
+
EdgeIdValue extends EdgeId = EdgeId,
|
|
3821
|
+
Fields = RuntimeRecord,
|
|
3822
|
+
> = TiledEdgeStateRecord<SpaceIdValue, EdgeIdValue, Fields>;
|
|
3823
|
+
|
|
3824
|
+
export type HexVertexStateRecord<
|
|
3825
|
+
SpaceIdValue extends SpaceId = SpaceId,
|
|
3826
|
+
VertexIdValue extends VertexId = VertexId,
|
|
3827
|
+
Fields = RuntimeRecord,
|
|
3828
|
+
> = TiledVertexStateRecord<SpaceIdValue, VertexIdValue, Fields>;
|
|
3829
|
+
|
|
3830
|
+
export interface HexBoardStateRecord<
|
|
3831
|
+
BoardIdValue extends BoardId = BoardId,
|
|
3832
|
+
SpaceIdValue extends SpaceId = SpaceId,
|
|
3833
|
+
EdgeIdValue extends EdgeId = EdgeId,
|
|
3834
|
+
VertexIdValue extends VertexId = VertexId,
|
|
3835
|
+
BoardFields = RuntimeRecord,
|
|
3836
|
+
SpaceFields = RuntimeRecord,
|
|
3837
|
+
EdgeFields = RuntimeRecord,
|
|
3838
|
+
VertexFields = RuntimeRecord,
|
|
3839
|
+
> extends BoardStateRecordBase<
|
|
3840
|
+
BoardIdValue,
|
|
3841
|
+
SpaceIdValue,
|
|
3842
|
+
never,
|
|
3843
|
+
BoardFields,
|
|
3844
|
+
SpaceFields,
|
|
3845
|
+
RuntimeRecord,
|
|
3846
|
+
RuntimeRecord
|
|
3847
|
+
> {
|
|
3848
|
+
layout: "hex";
|
|
3849
|
+
spaces: Record<SpaceIdValue, HexSpaceStateRecord<SpaceIdValue, SpaceFields>>;
|
|
3850
|
+
relations: Array<BoardRelationStateRecord<SpaceIdValue, RuntimeRecord>>;
|
|
3851
|
+
containers: Record<never, never>;
|
|
3852
|
+
orientation: "pointy-top" | "flat-top";
|
|
3853
|
+
edges: Array<HexEdgeStateRecord<SpaceIdValue, EdgeIdValue, EdgeFields>>;
|
|
3854
|
+
vertices: Array<
|
|
3855
|
+
HexVertexStateRecord<SpaceIdValue, VertexIdValue, VertexFields>
|
|
3856
|
+
>;
|
|
3857
|
+
}
|
|
3858
|
+
|
|
3859
|
+
export interface SquareBoardStateRecord<
|
|
3860
|
+
BoardIdValue extends BoardId = BoardId,
|
|
3861
|
+
SpaceIdValue extends SpaceId = SpaceId,
|
|
3862
|
+
ContainerIdValue extends BoardContainerId = BoardContainerId,
|
|
3863
|
+
EdgeIdValue extends EdgeId = EdgeId,
|
|
3864
|
+
VertexIdValue extends VertexId = VertexId,
|
|
3865
|
+
BoardFields = RuntimeRecord,
|
|
3866
|
+
SpaceFields = RuntimeRecord,
|
|
3867
|
+
RelationFields = RuntimeRecord,
|
|
3868
|
+
ContainerFields = RuntimeRecord,
|
|
3869
|
+
EdgeFields = RuntimeRecord,
|
|
3870
|
+
VertexFields = RuntimeRecord,
|
|
3871
|
+
> extends BoardStateRecordBase<
|
|
3872
|
+
BoardIdValue,
|
|
3873
|
+
SpaceIdValue,
|
|
3874
|
+
ContainerIdValue,
|
|
3875
|
+
BoardFields,
|
|
3876
|
+
SpaceFields,
|
|
3877
|
+
RelationFields,
|
|
3878
|
+
ContainerFields
|
|
3879
|
+
> {
|
|
3880
|
+
layout: "square";
|
|
3881
|
+
spaces: Record<
|
|
3882
|
+
SpaceIdValue,
|
|
3883
|
+
SquareSpaceStateRecord<SpaceIdValue, SpaceFields>
|
|
3884
|
+
>;
|
|
3885
|
+
relations: Array<BoardRelationStateRecord<SpaceIdValue, RelationFields>>;
|
|
3886
|
+
containers: Record<
|
|
3887
|
+
ContainerIdValue,
|
|
3888
|
+
BoardContainerStateRecord<SpaceIdValue, ContainerIdValue, ContainerFields>
|
|
3889
|
+
>;
|
|
3890
|
+
edges: Array<TiledEdgeStateRecord<SpaceIdValue, EdgeIdValue, EdgeFields>>;
|
|
3891
|
+
vertices: Array<
|
|
3892
|
+
TiledVertexStateRecord<SpaceIdValue, VertexIdValue, VertexFields>
|
|
3893
|
+
>;
|
|
3894
|
+
}
|
|
3895
|
+
|
|
3896
|
+
export type TiledBoardStateRecord =
|
|
3897
|
+
| HexBoardStateRecord
|
|
3898
|
+
| SquareBoardStateRecord;
|
|
3899
|
+
|
|
3900
|
+
export type BoardStateById = {
|
|
3901
|
+
${perBoardStateEntries}
|
|
3902
|
+
};
|
|
3903
|
+
|
|
3904
|
+
export type HexBoardStateById = ${perHexBoardStateEntries.length > 0
|
|
3905
|
+
? `{\n${perHexBoardStateEntries}\n}`
|
|
3906
|
+
: "Record<string, never>"};
|
|
3907
|
+
|
|
3908
|
+
export type SquareBoardStateById = ${perSquareBoardStateEntries.length > 0
|
|
3909
|
+
? `{\n${perSquareBoardStateEntries}\n}`
|
|
3910
|
+
: "Record<string, never>"};
|
|
3911
|
+
|
|
3912
|
+
type ManifestRecordValue<T> = T[keyof T];
|
|
3913
|
+
type ManifestArrayElement<T> =
|
|
3914
|
+
T extends readonly (infer Item)[]
|
|
3915
|
+
? Item
|
|
3916
|
+
: T extends (infer Item)[]
|
|
3917
|
+
? Item
|
|
3918
|
+
: never;
|
|
3919
|
+
|
|
3920
|
+
export type BoardState<BoardIdValue extends BoardId = BoardId> =
|
|
3921
|
+
BoardIdValue extends keyof BoardStateById ? BoardStateById[BoardIdValue] : never;
|
|
3922
|
+
|
|
3923
|
+
export type BoardFields<BoardIdValue extends BoardId = BoardId> =
|
|
3924
|
+
BoardState<BoardIdValue> extends { fields: infer Fields } ? Fields : never;
|
|
3925
|
+
|
|
3926
|
+
export type BoardSpaceStateByBoardId = {
|
|
3927
|
+
[BoardIdValue in keyof BoardStateById]: ManifestRecordValue<
|
|
3928
|
+
BoardStateById[BoardIdValue]["spaces"]
|
|
3929
|
+
>;
|
|
3930
|
+
};
|
|
3931
|
+
|
|
3932
|
+
export type BoardSpaceState<BoardIdValue extends BoardId = BoardId> =
|
|
3933
|
+
BoardIdValue extends keyof BoardSpaceStateByBoardId
|
|
3934
|
+
? BoardSpaceStateByBoardId[BoardIdValue]
|
|
3935
|
+
: never;
|
|
3936
|
+
|
|
3937
|
+
export type BoardSpaceFields<BoardIdValue extends BoardId = BoardId> =
|
|
3938
|
+
BoardSpaceState<BoardIdValue> extends { fields: infer Fields }
|
|
3939
|
+
? Fields
|
|
3940
|
+
: never;
|
|
3941
|
+
|
|
3942
|
+
export type BoardRelationStateByBoardId = {
|
|
3943
|
+
[BoardIdValue in keyof BoardStateById]: ManifestArrayElement<
|
|
3944
|
+
BoardStateById[BoardIdValue]["relations"]
|
|
3945
|
+
>;
|
|
3946
|
+
};
|
|
3947
|
+
|
|
3948
|
+
export type BoardRelationState<BoardIdValue extends BoardId = BoardId> =
|
|
3949
|
+
BoardIdValue extends keyof BoardRelationStateByBoardId
|
|
3950
|
+
? BoardRelationStateByBoardId[BoardIdValue]
|
|
3951
|
+
: never;
|
|
3952
|
+
|
|
3953
|
+
export type BoardRelationFields<BoardIdValue extends BoardId = BoardId> =
|
|
3954
|
+
BoardRelationState<BoardIdValue> extends { fields: infer Fields }
|
|
3955
|
+
? Fields
|
|
3956
|
+
: never;
|
|
3957
|
+
|
|
3958
|
+
export type BoardContainerStateByBoardId = {
|
|
3959
|
+
[BoardIdValue in keyof BoardStateById]: ManifestRecordValue<
|
|
3960
|
+
BoardStateById[BoardIdValue]["containers"]
|
|
3961
|
+
>;
|
|
3962
|
+
};
|
|
3963
|
+
|
|
3964
|
+
export type BoardContainerState<BoardIdValue extends BoardId = BoardId> =
|
|
3965
|
+
BoardIdValue extends keyof BoardContainerStateByBoardId
|
|
3966
|
+
? BoardContainerStateByBoardId[BoardIdValue]
|
|
3967
|
+
: never;
|
|
3968
|
+
|
|
3969
|
+
export type BoardContainerFields<BoardIdValue extends BoardId = BoardId> =
|
|
3970
|
+
BoardContainerState<BoardIdValue> extends { fields: infer Fields }
|
|
3971
|
+
? Fields
|
|
3972
|
+
: never;
|
|
3973
|
+
|
|
3974
|
+
type HexAuthoredEdgesByBoardId = typeof authoredHexEdgesByBoardIdLookup;
|
|
3975
|
+
type HexAuthoredVerticesByBoardId = typeof authoredHexVerticesByBoardIdLookup;
|
|
3976
|
+
|
|
3977
|
+
export type HexAuthoredEdgeState<
|
|
3978
|
+
BoardIdValue extends keyof HexAuthoredEdgesByBoardId = keyof HexAuthoredEdgesByBoardId,
|
|
3979
|
+
> = BoardIdValue extends keyof HexAuthoredEdgesByBoardId
|
|
3980
|
+
? ManifestArrayElement<HexAuthoredEdgesByBoardId[BoardIdValue]>
|
|
3981
|
+
: never;
|
|
3982
|
+
|
|
3983
|
+
export type HexAuthoredEdgeRef<
|
|
3984
|
+
BoardIdValue extends keyof HexAuthoredEdgesByBoardId = keyof HexAuthoredEdgesByBoardId,
|
|
3985
|
+
> = HexAuthoredEdgeState<BoardIdValue> extends { ref: infer Ref } ? Ref : never;
|
|
3986
|
+
|
|
3987
|
+
export type HexAuthoredVertexState<
|
|
3988
|
+
BoardIdValue extends keyof HexAuthoredVerticesByBoardId = keyof HexAuthoredVerticesByBoardId,
|
|
3989
|
+
> = BoardIdValue extends keyof HexAuthoredVerticesByBoardId
|
|
3990
|
+
? ManifestArrayElement<HexAuthoredVerticesByBoardId[BoardIdValue]>
|
|
3991
|
+
: never;
|
|
3992
|
+
|
|
3993
|
+
export type HexAuthoredVertexRef<
|
|
3994
|
+
BoardIdValue extends keyof HexAuthoredVerticesByBoardId = keyof HexAuthoredVerticesByBoardId,
|
|
3995
|
+
> = HexAuthoredVertexState<BoardIdValue> extends { ref: infer Ref }
|
|
3996
|
+
? Ref
|
|
3997
|
+
: never;
|
|
3998
|
+
|
|
3999
|
+
export type HexEdgeState<
|
|
4000
|
+
BoardIdValue extends keyof HexBoardStateById = keyof HexBoardStateById,
|
|
4001
|
+
> = BoardIdValue extends keyof HexBoardStateById
|
|
4002
|
+
? ManifestArrayElement<HexBoardStateById[BoardIdValue]["edges"]>
|
|
4003
|
+
: never;
|
|
4004
|
+
|
|
4005
|
+
export type HexEdgeFields<
|
|
4006
|
+
BoardIdValue extends keyof HexBoardStateById = keyof HexBoardStateById,
|
|
4007
|
+
> = HexEdgeState<BoardIdValue> extends { fields: infer Fields }
|
|
4008
|
+
? Fields
|
|
4009
|
+
: never;
|
|
4010
|
+
|
|
4011
|
+
export type HexVertexState<
|
|
4012
|
+
BoardIdValue extends keyof HexBoardStateById = keyof HexBoardStateById,
|
|
4013
|
+
> = BoardIdValue extends keyof HexBoardStateById
|
|
4014
|
+
? ManifestArrayElement<HexBoardStateById[BoardIdValue]["vertices"]>
|
|
4015
|
+
: never;
|
|
4016
|
+
|
|
4017
|
+
export type HexVertexFields<
|
|
4018
|
+
BoardIdValue extends keyof HexBoardStateById = keyof HexBoardStateById,
|
|
4019
|
+
> = HexVertexState<BoardIdValue> extends { fields: infer Fields }
|
|
4020
|
+
? Fields
|
|
4021
|
+
: never;
|
|
4022
|
+
|
|
4023
|
+
export type SquareEdgeState<
|
|
4024
|
+
BoardIdValue extends keyof SquareBoardStateById = keyof SquareBoardStateById,
|
|
4025
|
+
> = BoardIdValue extends keyof SquareBoardStateById
|
|
4026
|
+
? ManifestArrayElement<SquareBoardStateById[BoardIdValue]["edges"]>
|
|
4027
|
+
: never;
|
|
4028
|
+
|
|
4029
|
+
export type SquareEdgeFields<
|
|
4030
|
+
BoardIdValue extends keyof SquareBoardStateById = keyof SquareBoardStateById,
|
|
4031
|
+
> = SquareEdgeState<BoardIdValue> extends { fields: infer Fields }
|
|
4032
|
+
? Fields
|
|
4033
|
+
: never;
|
|
4034
|
+
|
|
4035
|
+
export type SquareVertexState<
|
|
4036
|
+
BoardIdValue extends keyof SquareBoardStateById = keyof SquareBoardStateById,
|
|
4037
|
+
> = BoardIdValue extends keyof SquareBoardStateById
|
|
4038
|
+
? ManifestArrayElement<SquareBoardStateById[BoardIdValue]["vertices"]>
|
|
4039
|
+
: never;
|
|
4040
|
+
|
|
4041
|
+
export type SquareVertexFields<
|
|
4042
|
+
BoardIdValue extends keyof SquareBoardStateById = keyof SquareBoardStateById,
|
|
4043
|
+
> = SquareVertexState<BoardIdValue> extends { fields: infer Fields }
|
|
4044
|
+
? Fields
|
|
4045
|
+
: never;
|
|
4046
|
+
|
|
4047
|
+
export type TiledBoardId = keyof HexBoardStateById | keyof SquareBoardStateById;
|
|
4048
|
+
|
|
4049
|
+
export type TiledEdgeState<BoardIdValue extends TiledBoardId = TiledBoardId> =
|
|
4050
|
+
BoardIdValue extends keyof HexBoardStateById
|
|
4051
|
+
? HexEdgeState<BoardIdValue>
|
|
4052
|
+
: BoardIdValue extends keyof SquareBoardStateById
|
|
4053
|
+
? SquareEdgeState<BoardIdValue>
|
|
4054
|
+
: never;
|
|
4055
|
+
|
|
4056
|
+
export type TiledEdgeFields<BoardIdValue extends TiledBoardId = TiledBoardId> =
|
|
4057
|
+
TiledEdgeState<BoardIdValue> extends { fields: infer Fields }
|
|
4058
|
+
? Fields
|
|
4059
|
+
: never;
|
|
4060
|
+
|
|
4061
|
+
export type TiledVertexState<
|
|
4062
|
+
BoardIdValue extends TiledBoardId = TiledBoardId,
|
|
4063
|
+
> = BoardIdValue extends keyof HexBoardStateById
|
|
4064
|
+
? HexVertexState<BoardIdValue>
|
|
4065
|
+
: BoardIdValue extends keyof SquareBoardStateById
|
|
4066
|
+
? SquareVertexState<BoardIdValue>
|
|
4067
|
+
: never;
|
|
4068
|
+
|
|
4069
|
+
export type TiledVertexFields<
|
|
4070
|
+
BoardIdValue extends TiledBoardId = TiledBoardId,
|
|
4071
|
+
> = TiledVertexState<BoardIdValue> extends { fields: infer Fields }
|
|
4072
|
+
? Fields
|
|
4073
|
+
: never;
|
|
4074
|
+
|
|
4075
|
+
export type BoardStateRecord = ${analysis.boardIds.length > 0 ? "BoardStateById[BoardId]" : "never"};
|
|
4076
|
+
|
|
4077
|
+
export type TableState = RuntimeTableRecord & {
|
|
4078
|
+
playerOrder: PlayerId[];
|
|
4079
|
+
zones: RuntimeTableRecord["zones"] & {
|
|
4080
|
+
visibility: Record<ZoneId, RuntimeHandVisibilityMode>;
|
|
4081
|
+
cardSetIdsByZoneId?: Record<ZoneId, readonly CardSetId[]>;
|
|
4082
|
+
};
|
|
4083
|
+
decks: Record<SharedZoneId, CardId[]>;
|
|
4084
|
+
hands: Record<PlayerZoneId, PerPlayer<CardId[]>>;
|
|
4085
|
+
handVisibility: Record<PlayerZoneId, RuntimeHandVisibilityMode>;
|
|
4086
|
+
cards: CardStateById;
|
|
4087
|
+
pieces: Record<PieceId, RuntimePieceData>;
|
|
4088
|
+
componentLocations: Record<ComponentId, RuntimeComponentLocation>;
|
|
4089
|
+
ownerOfCard: Record<CardId, PlayerId | null>;
|
|
4090
|
+
visibility: Record<CardId, RuntimeCardVisibility>;
|
|
4091
|
+
resources: PerPlayer<Record<ResourceId, number>>;
|
|
4092
|
+
boards: RuntimeTableRecord["boards"];
|
|
4093
|
+
dice: Record<DieId, RuntimeDieData>;
|
|
4094
|
+
};
|
|
4095
|
+
|
|
4096
|
+
const sharedZoneSchema = z.record(sharedZoneIdSchema, z.array(z.string()));
|
|
4097
|
+
// PerPlayer<Array<string>> wire shape only: { __perPlayer: true, entries: [[playerId, value], ...] }.
|
|
4098
|
+
const playerZoneSchema = z.record(
|
|
4099
|
+
playerZoneIdSchema,
|
|
4100
|
+
perPlayerSchema(z.array(z.string())),
|
|
4101
|
+
);
|
|
4102
|
+
const cardStateSchema = z.object({
|
|
4103
|
+
componentType: z.string().optional(),
|
|
4104
|
+
id: ids.cardId,
|
|
4105
|
+
cardSetId: ids.cardSetId,
|
|
4106
|
+
cardType: ids.cardType,
|
|
4107
|
+
name: z.string().optional(),
|
|
4108
|
+
text: z.string().optional(),
|
|
4109
|
+
properties: unknownRecordSchema,
|
|
4110
|
+
});
|
|
4111
|
+
${renderCardPropertiesSchemaByCardSetId(analysis.cardSets)}
|
|
4112
|
+
${renderCardStateSchemaFactory(analysis)}
|
|
4113
|
+
const cardStateByIdSchema = ${renderCardStateSchemaById(analysis)};
|
|
4114
|
+
const pieceStateByIdSchema = ${renderPieceStateSchemaById(analysis)};
|
|
4115
|
+
const dieStateByIdSchema = ${renderDieStateSchemaById(analysis)};
|
|
4116
|
+
const boardStateByIdSchema = ${renderBoardStateSchemaById(analysis)};
|
|
4117
|
+
const hexBoardStateByIdSchema = ${renderHexBoardStateSchemaById(analysis)};
|
|
4118
|
+
const squareBoardStateByIdSchema = ${renderSquareBoardStateSchemaById(analysis)};
|
|
4119
|
+
const boardSpaceTypeIdSchema = ids.spaceTypeId.nullable().optional();
|
|
4120
|
+
const boardSpaceStateSchema = z.object({
|
|
4121
|
+
id: ids.spaceId,
|
|
4122
|
+
name: z.string().nullable().optional(),
|
|
4123
|
+
typeId: boardSpaceTypeIdSchema,
|
|
4124
|
+
fields: unknownRecordSchema,
|
|
4125
|
+
zoneId: z.string().nullable().optional(),
|
|
4126
|
+
});
|
|
4127
|
+
const hexSpaceStateSchema = boardSpaceStateSchema.extend({
|
|
4128
|
+
q: z.number().int(),
|
|
4129
|
+
r: z.number().int(),
|
|
4130
|
+
});
|
|
4131
|
+
const squareSpaceStateSchema = boardSpaceStateSchema.extend({
|
|
4132
|
+
row: z.number().int(),
|
|
4133
|
+
col: z.number().int(),
|
|
4134
|
+
});
|
|
4135
|
+
const boardRelationStateSchema = z.object({
|
|
4136
|
+
id: z.string().nullable().optional(),
|
|
4137
|
+
typeId: ids.relationTypeId,
|
|
4138
|
+
fromSpaceId: ids.spaceId,
|
|
4139
|
+
toSpaceId: ids.spaceId,
|
|
4140
|
+
directed: z.boolean(),
|
|
4141
|
+
fields: unknownRecordSchema,
|
|
4142
|
+
});
|
|
4143
|
+
const boardContainerStateSchema = z.object({
|
|
4144
|
+
id: ids.boardContainerId,
|
|
4145
|
+
name: z.string(),
|
|
4146
|
+
host: z.discriminatedUnion("type", [
|
|
4147
|
+
z.object({ type: z.literal("board") }),
|
|
4148
|
+
z.object({ type: z.literal("space"), spaceId: ids.spaceId }),
|
|
4149
|
+
]),
|
|
4150
|
+
allowedCardSetIds: z.array(ids.cardSetId).optional(),
|
|
4151
|
+
zoneId: z.string(),
|
|
4152
|
+
fields: unknownRecordSchema,
|
|
4153
|
+
});
|
|
4154
|
+
const runtimeGenericBoardStateSchema = z.object({
|
|
4155
|
+
id: ids.boardId,
|
|
4156
|
+
baseId: ids.boardBaseId,
|
|
4157
|
+
layout: z.literal("generic"),
|
|
4158
|
+
typeId: ids.boardTypeId.nullable().optional(),
|
|
4159
|
+
scope: z.enum(["shared", "perPlayer"]),
|
|
4160
|
+
playerId: ids.playerId.nullable().optional(),
|
|
4161
|
+
templateId: z.string().nullable().optional(),
|
|
4162
|
+
fields: unknownRecordSchema,
|
|
4163
|
+
spaces: z.record(ids.spaceId, boardSpaceStateSchema),
|
|
4164
|
+
relations: z.array(boardRelationStateSchema),
|
|
4165
|
+
containers: z.record(ids.boardContainerId, boardContainerStateSchema),
|
|
4166
|
+
});
|
|
4167
|
+
const hexEdgeStateSchema = z.object({
|
|
4168
|
+
id: ids.edgeId,
|
|
4169
|
+
spaceIds: z.array(ids.spaceId).min(1).max(2),
|
|
4170
|
+
typeId: ids.edgeTypeId.nullable().optional(),
|
|
4171
|
+
label: z.string().nullable().optional(),
|
|
4172
|
+
ownerId: ids.playerId.nullable().optional(),
|
|
4173
|
+
fields: unknownRecordSchema,
|
|
4174
|
+
});
|
|
4175
|
+
const hexVertexStateSchema = z.object({
|
|
4176
|
+
id: ids.vertexId,
|
|
4177
|
+
spaceIds: z.array(ids.spaceId).min(1).max(3),
|
|
4178
|
+
typeId: ids.vertexTypeId.nullable().optional(),
|
|
4179
|
+
label: z.string().nullable().optional(),
|
|
4180
|
+
ownerId: ids.playerId.nullable().optional(),
|
|
4181
|
+
fields: unknownRecordSchema,
|
|
4182
|
+
});
|
|
4183
|
+
const squareVertexStateSchema = z.object({
|
|
4184
|
+
id: ids.vertexId,
|
|
4185
|
+
spaceIds: z.array(ids.spaceId).min(1).max(4),
|
|
4186
|
+
typeId: ids.vertexTypeId.nullable().optional(),
|
|
4187
|
+
label: z.string().nullable().optional(),
|
|
4188
|
+
ownerId: ids.playerId.nullable().optional(),
|
|
4189
|
+
fields: unknownRecordSchema,
|
|
4190
|
+
});
|
|
4191
|
+
const runtimeHexBoardStateSchema = runtimeGenericBoardStateSchema.extend({
|
|
4192
|
+
layout: z.literal("hex"),
|
|
4193
|
+
spaces: z.record(ids.spaceId, hexSpaceStateSchema),
|
|
4194
|
+
relations: z.array(boardRelationStateSchema),
|
|
4195
|
+
containers: z.object({}),
|
|
4196
|
+
orientation: z.enum(["pointy-top", "flat-top"]),
|
|
4197
|
+
edges: z.array(hexEdgeStateSchema),
|
|
4198
|
+
vertices: z.array(hexVertexStateSchema),
|
|
4199
|
+
});
|
|
4200
|
+
const runtimeSquareBoardStateSchema = runtimeGenericBoardStateSchema.extend({
|
|
4201
|
+
layout: z.literal("square"),
|
|
4202
|
+
spaces: z.record(ids.spaceId, squareSpaceStateSchema),
|
|
4203
|
+
relations: z.array(boardRelationStateSchema),
|
|
4204
|
+
containers: z.record(ids.boardContainerId, boardContainerStateSchema),
|
|
4205
|
+
edges: z.array(hexEdgeStateSchema),
|
|
4206
|
+
vertices: z.array(squareVertexStateSchema),
|
|
4207
|
+
});
|
|
4208
|
+
const runtimeBoardStateSchema = z.union([
|
|
4209
|
+
runtimeGenericBoardStateSchema,
|
|
4210
|
+
runtimeHexBoardStateSchema,
|
|
4211
|
+
runtimeSquareBoardStateSchema,
|
|
4212
|
+
]);
|
|
4213
|
+
const rawTableSchema = z.object({
|
|
4214
|
+
playerOrder: z.array(ids.playerId),
|
|
4215
|
+
zones: z.object({
|
|
4216
|
+
shared: sharedZoneSchema,
|
|
4217
|
+
perPlayer: playerZoneSchema,
|
|
4218
|
+
visibility: z.record(zoneIdSchema, z.enum(["all", "ownerOnly", "public", "hidden"])),
|
|
4219
|
+
cardSetIdsByZoneId: z.record(zoneIdSchema, z.array(ids.cardSetId)).optional(),
|
|
4220
|
+
}),
|
|
4221
|
+
decks: sharedZoneSchema,
|
|
4222
|
+
hands: playerZoneSchema,
|
|
4223
|
+
handVisibility: z.record(
|
|
4224
|
+
playerZoneIdSchema,
|
|
4225
|
+
z.enum(["all", "ownerOnly", "public", "hidden"]),
|
|
4226
|
+
),
|
|
4227
|
+
cards: cardStateByIdSchema,
|
|
4228
|
+
pieces: pieceStateByIdSchema,
|
|
4229
|
+
componentLocations: z.record(
|
|
4230
|
+
z.string(),
|
|
4231
|
+
z.union([
|
|
4232
|
+
z.object({ type: z.literal("Detached") }),
|
|
4233
|
+
z.object({
|
|
4234
|
+
type: z.literal("InDeck"),
|
|
4235
|
+
deckId: ids.deckId,
|
|
4236
|
+
playedBy: ids.playerId.nullable(),
|
|
4237
|
+
position: z.number().int().nullable().optional(),
|
|
4238
|
+
}),
|
|
4239
|
+
z.object({
|
|
4240
|
+
type: z.literal("InHand"),
|
|
4241
|
+
handId: ids.handId,
|
|
4242
|
+
playerId: ids.playerId,
|
|
4243
|
+
position: z.number().int().nullable().optional(),
|
|
4244
|
+
}),
|
|
4245
|
+
z.object({
|
|
4246
|
+
type: z.literal("InZone"),
|
|
4247
|
+
zoneId: z.string(),
|
|
4248
|
+
playedBy: ids.playerId.nullable().optional(),
|
|
4249
|
+
position: z.number().int().nullable().optional(),
|
|
4250
|
+
}),
|
|
4251
|
+
z.object({
|
|
4252
|
+
type: z.literal("OnSpace"),
|
|
4253
|
+
boardId: ids.boardId,
|
|
4254
|
+
spaceId: ids.spaceId,
|
|
4255
|
+
position: z.number().int().nullable().optional(),
|
|
4256
|
+
}),
|
|
4257
|
+
z.object({
|
|
4258
|
+
type: z.literal("InContainer"),
|
|
4259
|
+
boardId: ids.boardId,
|
|
4260
|
+
containerId: ids.boardContainerId,
|
|
4261
|
+
position: z.number().int().nullable().optional(),
|
|
4262
|
+
}),
|
|
4263
|
+
z.object({
|
|
4264
|
+
type: z.literal("OnEdge"),
|
|
4265
|
+
boardId: ids.boardId,
|
|
4266
|
+
edgeId: ids.edgeId,
|
|
4267
|
+
position: z.number().int().nullable().optional(),
|
|
4268
|
+
}),
|
|
4269
|
+
z.object({
|
|
4270
|
+
type: z.literal("OnVertex"),
|
|
4271
|
+
boardId: ids.boardId,
|
|
4272
|
+
vertexId: ids.vertexId,
|
|
4273
|
+
position: z.number().int().nullable().optional(),
|
|
4274
|
+
}),
|
|
4275
|
+
${renderStrictSlotLocationSchema(analysis.strictSlotHosts)},
|
|
4276
|
+
]),
|
|
4277
|
+
),
|
|
4278
|
+
ownerOfCard: z.record(ids.cardId, ids.playerId.nullable()),
|
|
4279
|
+
visibility: z.record(
|
|
4280
|
+
ids.cardId,
|
|
4281
|
+
z.object({
|
|
4282
|
+
faceUp: z.boolean(),
|
|
4283
|
+
visibleTo: z.array(ids.playerId).nullable().optional(),
|
|
4284
|
+
}),
|
|
4285
|
+
),
|
|
4286
|
+
resources: perPlayerSchema(z.record(ids.resourceId, z.number().int())),
|
|
4287
|
+
boards: z.object({
|
|
4288
|
+
byId: boardStateByIdSchema,
|
|
4289
|
+
hex: hexBoardStateByIdSchema,
|
|
4290
|
+
square: squareBoardStateByIdSchema,
|
|
4291
|
+
}),
|
|
4292
|
+
dice: dieStateByIdSchema,
|
|
4293
|
+
});
|
|
4294
|
+
|
|
4295
|
+
export const tableSchema = assumeManifestSchema<TableState>(rawTableSchema);
|
|
4296
|
+
|
|
4297
|
+
export const runtimeSchema = createManifestRuntimeSchema({
|
|
4298
|
+
phaseNameSchema: z.string(),
|
|
4299
|
+
playerIdSchema: ids.playerId,
|
|
4300
|
+
setupProfileIdSchema: ids.setupProfileId,
|
|
4301
|
+
});
|
|
4302
|
+
|
|
4303
|
+
// Produces an empty PerPlayer<CardId[]> for every player zone. The entries
|
|
4304
|
+
// array is seeded from the resolved runtime roster so the generated default
|
|
4305
|
+
// never creates a record for a player the session does not have.
|
|
4306
|
+
function buildPerPlayerCardIds(
|
|
4307
|
+
playerIds: readonly PlayerId[],
|
|
4308
|
+
): CardIdsByPlayerZoneId {
|
|
4309
|
+
return Object.fromEntries(
|
|
4310
|
+
literals.playerZoneIds.map((zoneId) => [
|
|
4311
|
+
zoneId,
|
|
4312
|
+
perPlayer(playerIds, () => [] as CardId[]),
|
|
4313
|
+
]),
|
|
4314
|
+
) as CardIdsByPlayerZoneId;
|
|
4315
|
+
}
|
|
4316
|
+
|
|
4317
|
+
function buildPlayerResources(
|
|
4318
|
+
playerIds: readonly PlayerId[],
|
|
4319
|
+
): PerPlayer<Record<ResourceId, number>> {
|
|
4320
|
+
return perPlayer(playerIds, () =>
|
|
4321
|
+
Object.fromEntries(
|
|
4322
|
+
literals.resourceIds.map((resourceId) => [resourceId, 0]),
|
|
4323
|
+
) as Record<ResourceId, number>,
|
|
4324
|
+
);
|
|
4325
|
+
}
|
|
4326
|
+
|
|
4327
|
+
export const defaults = {
|
|
4328
|
+
zones: (playerIds?: readonly string[]) => ({
|
|
4329
|
+
shared: cloneManifestDefault(${JSON.stringify(emptySharedZonesTemplate)}),
|
|
4330
|
+
perPlayer: buildPerPlayerCardIds(resolveDefaultPlayerIds(playerIds)),
|
|
4331
|
+
visibility: cloneManifestDefault(${JSON.stringify(zoneVisibilityById)}),
|
|
4332
|
+
cardSetIdsByZoneId: cloneManifestDefault(${JSON.stringify(Object.fromEntries(Array.from(analysis.zoneCardSetIdsById.entries()).sort(([left], [right]) => left.localeCompare(right))))}),
|
|
4333
|
+
}) as TableState["zones"],
|
|
4334
|
+
decks: () => cloneManifestDefault(${JSON.stringify(emptySharedZonesTemplate)}) as TableState["decks"],
|
|
4335
|
+
hands: (playerIds?: readonly string[]) =>
|
|
4336
|
+
buildPerPlayerCardIds(resolveDefaultPlayerIds(playerIds)) as TableState["hands"],
|
|
4337
|
+
handVisibility: () => cloneManifestDefault(${JSON.stringify(Object.fromEntries(playerZoneIds.map((zoneId) => [
|
|
4338
|
+
zoneId,
|
|
4339
|
+
zoneVisibilityById[zoneId] ?? "ownerOnly",
|
|
4340
|
+
])))}) as TableState["handVisibility"],
|
|
4341
|
+
ownerOfCard: () => cloneManifestDefault(${JSON.stringify(defaultOwnerTemplate)}) as TableState["ownerOfCard"],
|
|
4342
|
+
visibility: () => cloneManifestDefault(${JSON.stringify(defaultVisibilityTemplate)}) as TableState["visibility"],
|
|
4343
|
+
resources: (playerIds?: readonly string[]) =>
|
|
4344
|
+
buildPlayerResources(resolveDefaultPlayerIds(playerIds)),
|
|
4345
|
+
} as const;
|
|
4346
|
+
|
|
4347
|
+
export const staticBoards = ${renderJsonConst(staticBoardsTemplate)};
|
|
4348
|
+
|
|
4349
|
+
const baseInitialTable = ${renderJsonConst(initialTableTemplate)} as unknown as TableState;
|
|
4350
|
+
const baseDeckCardsByZoneId: Record<SharedZoneId, readonly CardId[]> = ${renderJsonConst(Object.fromEntries(sharedZoneIds.map((zoneId) => [
|
|
4351
|
+
zoneId,
|
|
4352
|
+
initialTableTemplate.decks[zoneId] ?? [],
|
|
4353
|
+
])))};
|
|
4354
|
+
|
|
4355
|
+
export function createInitialTable(options: {
|
|
4356
|
+
playerIds?: readonly string[];
|
|
4357
|
+
shuffleItems?: <Value>(values: readonly Value[]) => Value[];
|
|
4358
|
+
} = {}): TableState {
|
|
4359
|
+
const resolvedPlayerIds = resolveDefaultPlayerIds(options.playerIds);
|
|
4360
|
+
const shuffleItems =
|
|
4361
|
+
options.shuffleItems ?? (<Value>(values: readonly Value[]) => [...values]);
|
|
4362
|
+
const table = cloneManifestDefault(baseInitialTable) as TableState;
|
|
4363
|
+
table.playerOrder = [...resolvedPlayerIds];
|
|
4364
|
+
table.zones = defaults.zones(resolvedPlayerIds);
|
|
4365
|
+
table.decks = defaults.decks();
|
|
4366
|
+
table.hands = defaults.hands(resolvedPlayerIds);
|
|
4367
|
+
table.resources = defaults.resources(resolvedPlayerIds);
|
|
4368
|
+
const componentLocations = cloneManifestDefault(
|
|
4369
|
+
baseInitialTable.componentLocations,
|
|
4370
|
+
) as TableState["componentLocations"];
|
|
4371
|
+
|
|
4372
|
+
for (const [zoneId, baseDeckCardIds] of Object.entries(
|
|
4373
|
+
baseDeckCardsByZoneId,
|
|
4374
|
+
) as Array<[SharedZoneId, readonly CardId[]]>) {
|
|
4375
|
+
const shuffled = shuffleItems(baseDeckCardIds);
|
|
4376
|
+
(table.decks as Record<string, CardId[]>)[zoneId] = [...shuffled];
|
|
4377
|
+
(table.zones.shared as Record<string, CardId[]>)[zoneId] = [...shuffled];
|
|
4378
|
+
shuffled.forEach((componentId, position) => {
|
|
4379
|
+
const location = componentLocations[componentId];
|
|
4380
|
+
if (!location || location.type !== "InDeck") {
|
|
4381
|
+
return;
|
|
4382
|
+
}
|
|
4383
|
+
componentLocations[componentId] = {
|
|
4384
|
+
...location,
|
|
4385
|
+
position,
|
|
4386
|
+
};
|
|
4387
|
+
});
|
|
4388
|
+
}
|
|
4389
|
+
|
|
4390
|
+
table.componentLocations = componentLocations;
|
|
4391
|
+
return tableSchema.parse(table);
|
|
4392
|
+
}
|
|
4393
|
+
|
|
4394
|
+
export const schemas = {
|
|
4395
|
+
table: tableSchema,
|
|
4396
|
+
runtime: runtimeSchema,
|
|
4397
|
+
} as const;
|
|
4398
|
+
|
|
4399
|
+
export function createGameStateSchema<
|
|
4400
|
+
PhaseNameSchema extends z.ZodTypeAny,
|
|
4401
|
+
PublicSchema extends z.ZodTypeAny,
|
|
4402
|
+
PrivateSchema extends z.ZodTypeAny,
|
|
4403
|
+
HiddenSchema extends z.ZodTypeAny,
|
|
4404
|
+
PhasesSchema extends z.ZodTypeAny,
|
|
4405
|
+
>({
|
|
4406
|
+
phaseNameSchema,
|
|
4407
|
+
publicSchema,
|
|
4408
|
+
privateSchema,
|
|
4409
|
+
hiddenSchema,
|
|
4410
|
+
phasesSchema,
|
|
4411
|
+
}: {
|
|
4412
|
+
phaseNameSchema: PhaseNameSchema;
|
|
4413
|
+
publicSchema: PublicSchema;
|
|
4414
|
+
privateSchema: PrivateSchema;
|
|
4415
|
+
hiddenSchema: HiddenSchema;
|
|
4416
|
+
phasesSchema: PhasesSchema;
|
|
4417
|
+
}) {
|
|
4418
|
+
return createManifestGameStateSchema({
|
|
4419
|
+
tableSchema,
|
|
4420
|
+
playerIdSchema: ids.playerId,
|
|
4421
|
+
setupProfileIdSchema: ids.setupProfileId,
|
|
4422
|
+
phaseNameSchema,
|
|
4423
|
+
publicSchema,
|
|
4424
|
+
privateSchema,
|
|
4425
|
+
hiddenSchema,
|
|
4426
|
+
phasesSchema,
|
|
4427
|
+
});
|
|
4428
|
+
}
|
|
4429
|
+
|
|
4430
|
+
export const manifestContract: ReducerManifestContract<
|
|
4431
|
+
RuntimeTableRecord,
|
|
4432
|
+
string,
|
|
4433
|
+
PlayerId,
|
|
4434
|
+
DeckId,
|
|
4435
|
+
HandId,
|
|
4436
|
+
CardId
|
|
4437
|
+
> = {
|
|
4438
|
+
literals,
|
|
4439
|
+
ids,
|
|
4440
|
+
defaults,
|
|
4441
|
+
staticBoards: staticBoards as unknown as StaticBoards<TableState>,
|
|
4442
|
+
setupOptionsById,
|
|
4443
|
+
setupChoiceIdsByOptionId,
|
|
4444
|
+
setupProfilesById,
|
|
4445
|
+
tableSchema,
|
|
4446
|
+
runtimeSchema,
|
|
4447
|
+
createGameStateSchema,
|
|
4448
|
+
};
|
|
4449
|
+
|
|
4450
|
+
${renderBoardLiteralHelpers(analysis)}
|
|
4451
|
+
|
|
4452
|
+
export type SetupProfilesDefinition = Record<
|
|
4453
|
+
SetupProfileId,
|
|
4454
|
+
SetupProfileDefinition<string, typeof manifestContract>
|
|
4455
|
+
>;
|
|
4456
|
+
|
|
4457
|
+
export function setupProfiles<const Profiles extends SetupProfilesDefinition>(
|
|
4458
|
+
profiles: Profiles,
|
|
4459
|
+
): Profiles {
|
|
4460
|
+
return profiles;
|
|
4461
|
+
}
|
|
4462
|
+
|
|
4463
|
+
export function shuffle(
|
|
4464
|
+
container: SetupBootstrapContainerRef<typeof manifestContract>,
|
|
4465
|
+
): SetupBootstrapStep<typeof manifestContract> {
|
|
4466
|
+
return createShuffleStep<typeof manifestContract>(container);
|
|
4467
|
+
}
|
|
4468
|
+
|
|
4469
|
+
export function dealToPlayerZone(options: {
|
|
4470
|
+
from: Extract<
|
|
4471
|
+
SetupBootstrapContainerRef<typeof manifestContract>,
|
|
4472
|
+
{ type: "sharedZone" | "sharedBoardContainer" }
|
|
4473
|
+
>;
|
|
4474
|
+
zoneId: Extract<
|
|
4475
|
+
SetupBootstrapPerPlayerContainerTemplateRef<typeof manifestContract>,
|
|
4476
|
+
{ type: "playerZone" }
|
|
4477
|
+
>["zoneId"];
|
|
4478
|
+
count: number;
|
|
4479
|
+
playerIds?: readonly PlayerId[];
|
|
4480
|
+
}): SetupBootstrapStep<typeof manifestContract> {
|
|
4481
|
+
return createDealToPlayerZoneStep<typeof manifestContract>(options);
|
|
4482
|
+
}
|
|
4483
|
+
|
|
4484
|
+
export function dealToPlayerBoardContainer(options: {
|
|
4485
|
+
from: Extract<
|
|
4486
|
+
SetupBootstrapContainerRef<typeof manifestContract>,
|
|
4487
|
+
{ type: "sharedZone" | "sharedBoardContainer" }
|
|
4488
|
+
>;
|
|
4489
|
+
boardId: Extract<
|
|
4490
|
+
SetupBootstrapPerPlayerContainerTemplateRef<typeof manifestContract>,
|
|
4491
|
+
{ type: "playerBoardContainer" }
|
|
4492
|
+
>["boardId"];
|
|
4493
|
+
containerId: Extract<
|
|
4494
|
+
SetupBootstrapPerPlayerContainerTemplateRef<typeof manifestContract>,
|
|
4495
|
+
{ type: "playerBoardContainer" }
|
|
4496
|
+
>["containerId"];
|
|
4497
|
+
count: number;
|
|
4498
|
+
playerIds?: readonly PlayerId[];
|
|
4499
|
+
}): SetupBootstrapStep<typeof manifestContract> {
|
|
4500
|
+
return createDealToPlayerBoardContainerStep<typeof manifestContract>(options);
|
|
4501
|
+
}
|
|
4502
|
+
|
|
4503
|
+
export function seedSharedBoardContainer(options: {
|
|
4504
|
+
from: SetupBootstrapContainerRef<typeof manifestContract>;
|
|
4505
|
+
boardId: Extract<
|
|
4506
|
+
SetupBootstrapDestinationRef<typeof manifestContract>,
|
|
4507
|
+
{ type: "sharedBoardContainer" }
|
|
4508
|
+
>["boardId"];
|
|
4509
|
+
containerId: Extract<
|
|
4510
|
+
SetupBootstrapDestinationRef<typeof manifestContract>,
|
|
4511
|
+
{ type: "sharedBoardContainer" }
|
|
4512
|
+
>["containerId"];
|
|
4513
|
+
count?: number;
|
|
4514
|
+
componentIds?: readonly (
|
|
4515
|
+
| CardIdOfManifest<typeof manifestContract>
|
|
4516
|
+
| PieceIdOfManifest<typeof manifestContract>
|
|
4517
|
+
| DieIdOfManifest<typeof manifestContract>
|
|
4518
|
+
)[];
|
|
4519
|
+
}): SetupBootstrapStep<typeof manifestContract> {
|
|
4520
|
+
return createSeedSharedBoardContainerStep<typeof manifestContract>(options);
|
|
4521
|
+
}
|
|
4522
|
+
|
|
4523
|
+
export function seedSharedBoardSpace(options: {
|
|
4524
|
+
from: SetupBootstrapContainerRef<typeof manifestContract>;
|
|
4525
|
+
boardId: Extract<
|
|
4526
|
+
SetupBootstrapDestinationRef<typeof manifestContract>,
|
|
4527
|
+
{ type: "sharedBoardSpace" }
|
|
4528
|
+
>["boardId"];
|
|
4529
|
+
spaceId: Extract<
|
|
4530
|
+
SetupBootstrapDestinationRef<typeof manifestContract>,
|
|
4531
|
+
{ type: "sharedBoardSpace" }
|
|
4532
|
+
>["spaceId"];
|
|
4533
|
+
count?: number;
|
|
4534
|
+
componentIds?: readonly (
|
|
4535
|
+
| CardIdOfManifest<typeof manifestContract>
|
|
4536
|
+
| PieceIdOfManifest<typeof manifestContract>
|
|
4537
|
+
| DieIdOfManifest<typeof manifestContract>
|
|
4538
|
+
)[];
|
|
4539
|
+
}): SetupBootstrapStep<typeof manifestContract> {
|
|
4540
|
+
return createSeedSharedBoardSpaceStep<typeof manifestContract>(options);
|
|
4541
|
+
}
|
|
4542
|
+
|
|
4543
|
+
export default manifestContract;
|
|
4544
|
+
`;
|
|
4545
|
+
}
|
|
4546
|
+
export function generateManifestContractSource(manifest) {
|
|
4547
|
+
return renderManifestContractSource(manifest);
|
|
4548
|
+
}
|
|
4549
|
+
const generatedFileBanner = `/**
|
|
4550
|
+
* Generated file.
|
|
4551
|
+
* Do not edit directly.
|
|
4552
|
+
*/`;
|
|
4553
|
+
function extractGeneratedLiteralsBlock(source) {
|
|
4554
|
+
const startMarker = "export const literals = {";
|
|
4555
|
+
const endMarker = "\n\n// PlayerId is an opaque brand imported from @dreamboard/app-sdk/reducer.";
|
|
4556
|
+
const start = source.indexOf(startMarker);
|
|
4557
|
+
const end = source.indexOf(endMarker, start);
|
|
4558
|
+
if (start === -1 || end === -1) {
|
|
4559
|
+
throw new Error("Unable to locate generated manifest literals block.");
|
|
4560
|
+
}
|
|
4561
|
+
return source.slice(start, end).trim();
|
|
4562
|
+
}
|
|
4563
|
+
function renderSplitLiteralsBlock(legacySource) {
|
|
4564
|
+
return extractGeneratedLiteralsBlock(legacySource).replace(/ \/\/ literals satisfy `ManifestLiterals<PlayerId, \.\.\.>`\. The cast is safe\n \/\/ because the runtime values are the exact player-id strings the manifest\n \/\/ authored; branding is purely a type-level discipline\.\n playerIds: ([\s\S]*?) as unknown as readonly PlayerId\[],\n phaseNames:/, " playerIds: $1,\n phaseNames:");
|
|
4565
|
+
}
|
|
4566
|
+
function renderManifestLiteralsSource(legacySource) {
|
|
4567
|
+
return `${generatedFileBanner}
|
|
4568
|
+
|
|
4569
|
+
${renderSplitLiteralsBlock(legacySource)}
|
|
4570
|
+
`;
|
|
4571
|
+
}
|
|
4572
|
+
function renderManifestRuntimeSource(legacySource) {
|
|
4573
|
+
const literalsBlock = extractGeneratedLiteralsBlock(legacySource);
|
|
4574
|
+
const withoutLiterals = legacySource.replace(literalsBlock, "export { literals };");
|
|
4575
|
+
return withoutLiterals
|
|
4576
|
+
.replace(generatedFileBanner, `${generatedFileBanner}\n// @ts-nocheck`)
|
|
4577
|
+
.replace(`} from "@dreamboard/app-sdk/reducer";\n\nconst unknownRecordSchema`, `} from "@dreamboard/app-sdk/reducer";\nimport { literals } from "./manifest-literals";\nimport type { PlayerId as PublicPlayerId, TableState as PublicTableState } from "./manifest-types";\n\nconst unknownRecordSchema`)
|
|
4578
|
+
.replace(`export const manifestContract: ReducerManifestContract<\n RuntimeTableRecord,`, `export const manifestContract: ReducerManifestContract<\n PublicTableState,`)
|
|
4579
|
+
.replace(`export const manifestContract: ReducerManifestContract<\n PublicTableState,\n string,\n PlayerId,`, `export const manifestContract: ReducerManifestContract<\n PublicTableState,\n string,\n PublicPlayerId,`)
|
|
4580
|
+
.replace(`const playerIdSchema = markManifestScopedSchema(\n z\n .string()\n .min(1)\n .transform((value) => asPlayerId(value)),\n);`, `const playerIdSchema = markManifestScopedSchema(\n z\n .string()\n .min(1)\n .transform((value) => asPlayerId(value)),\n) as unknown as z.ZodType<PublicPlayerId>;`);
|
|
4581
|
+
}
|
|
4582
|
+
function renderCardSetTypeSections(analysis) {
|
|
4583
|
+
return analysis.cardSets
|
|
4584
|
+
.map((cardSet) => {
|
|
4585
|
+
const cardIdType = `export type ${toPascalCase(cardSet.id)}CardId = ${cardSet.cards
|
|
4586
|
+
.flatMap(renderCardInstanceIds)
|
|
4587
|
+
.map((cardId) => quote(cardId))
|
|
4588
|
+
.join(" | ") || "never"};`;
|
|
4589
|
+
if (isCardPropertySchemaVariants(cardSet.cardSchema)) {
|
|
4590
|
+
const variantBlocks = Object.keys(cardSet.cardSchema.variants).map((cardType) => `export type ${cardPropertiesTypeName(cardSet, cardType)} = ${renderTypeForObjectSchema(mergeSharedCardProperties(cardSet.cardSchema, cardType))};`);
|
|
4591
|
+
const variantTypeNames = Object.keys(cardSet.cardSchema.variants).map((cardType) => cardPropertiesTypeName(cardSet, cardType));
|
|
4592
|
+
return renderBlocks([
|
|
4593
|
+
...variantBlocks,
|
|
4594
|
+
`export type ${toPascalCase(cardSet.id)}CardProperties = ${variantTypeNames.join(" | ") || "RuntimeRecord"};`,
|
|
4595
|
+
cardIdType,
|
|
4596
|
+
]);
|
|
4597
|
+
}
|
|
4598
|
+
return renderBlocks([
|
|
4599
|
+
`export type ${toPascalCase(cardSet.id)}CardProperties = ${renderTypeForObjectSchema(cardSet.cardSchema)};`,
|
|
4600
|
+
cardIdType,
|
|
4601
|
+
]);
|
|
4602
|
+
})
|
|
4603
|
+
.join("\n\n");
|
|
4604
|
+
}
|
|
4605
|
+
function renderTopologyFieldTypeSections(analysis) {
|
|
4606
|
+
const sections = [];
|
|
4607
|
+
for (const board of analysis.analyzedBoards) {
|
|
4608
|
+
sections.push(`export type ${boardFieldsTypeName(board.board.id)} = ${renderTypeForObjectSchema(board.boardFieldsSchema)};`, `export type ${boardSpaceFieldsTypeName(board.board.id)} = ${renderTypeForObjectSchema(board.spaceFieldsSchema)};`);
|
|
4609
|
+
if (board.layout !== "hex") {
|
|
4610
|
+
sections.push(`export type ${boardRelationFieldsTypeName(board.board.id)} = ${renderTypeForObjectSchema(board.relationFieldsSchema)};`, `export type ${boardContainerFieldsTypeName(board.board.id)} = ${renderTypeForObjectSchema(board.containerFieldsSchema)};`);
|
|
4611
|
+
}
|
|
4612
|
+
if (board.layout !== "generic") {
|
|
4613
|
+
sections.push(`export type ${hexEdgeFieldsTypeName(board.board.id)} = ${renderTypeForObjectSchema(board.edgeFieldsSchema)};`, `export type ${hexVertexFieldsTypeName(board.board.id)} = ${renderTypeForObjectSchema(board.vertexFieldsSchema)};`);
|
|
4614
|
+
}
|
|
4615
|
+
}
|
|
4616
|
+
for (const pieceTypeId of analysis.pieceTypeIds) {
|
|
4617
|
+
sections.push(`export type ${pieceFieldsTypeName(pieceTypeId)} = ${renderTypeForObjectSchema(analysis.pieceTypeSchemasById.get(pieceTypeId))};`);
|
|
4618
|
+
}
|
|
4619
|
+
for (const dieTypeId of analysis.dieTypeIds) {
|
|
4620
|
+
sections.push(`export type ${dieFieldsTypeName(dieTypeId)} = ${renderTypeForObjectSchema(analysis.dieTypeSchemasById.get(dieTypeId))};`);
|
|
4621
|
+
}
|
|
4622
|
+
return sections.join("\n");
|
|
4623
|
+
}
|
|
4624
|
+
function renderManifestTypesSource(manifest) {
|
|
4625
|
+
const analysis = analyzeManifest(manifest);
|
|
4626
|
+
const sharedZoneIds = analysis.sharedZones.map((zone) => zone.id).sort();
|
|
4627
|
+
const playerZoneIds = analysis.playerZones.map((zone) => zone.id).sort();
|
|
4628
|
+
const cardSetCardIdAlias = (cardSetId) => `${toPascalCase(cardSetId)}CardId`;
|
|
4629
|
+
const renderCardIdsForCardSets = (cardSetIds) => cardSetIds.map(cardSetCardIdAlias).join(" | ") || "never";
|
|
4630
|
+
const sharedZoneCardIdEntries = sharedZoneIds
|
|
4631
|
+
.map((zoneId) => {
|
|
4632
|
+
const allowedCardSetIds = analysis.sharedZoneCardSetIds.get(zoneId) ?? [];
|
|
4633
|
+
return ` ${quote(zoneId)}: Array<${renderCardIdsForCardSets(allowedCardSetIds)}>;`;
|
|
4634
|
+
})
|
|
4635
|
+
.join("\n");
|
|
4636
|
+
const playerZoneCardIdEntries = playerZoneIds
|
|
4637
|
+
.map((zoneId) => {
|
|
4638
|
+
const allowedCardSetIds = analysis.playerZoneCardSetIds.get(zoneId) ?? [];
|
|
4639
|
+
return ` ${quote(zoneId)}: PerPlayer<Array<${renderCardIdsForCardSets(allowedCardSetIds)}>>;`;
|
|
4640
|
+
})
|
|
4641
|
+
.join("\n");
|
|
4642
|
+
const perBoardStateEntries = analysis.analyzedBoards
|
|
4643
|
+
.flatMap((board) => board.runtimeBoardIds.map((runtimeBoardId) => {
|
|
4644
|
+
if (board.layout === "hex") {
|
|
4645
|
+
return ` ${quote(runtimeBoardId)}: HexBoardStateRecord<${quote(runtimeBoardId)}, ${renderStringUnion(board.spaces.map((space) => space.id))}, ${renderStringUnion(board.edges.map((edge) => edge.id))}, ${renderStringUnion(board.vertices.map((vertex) => vertex.id))}, ${boardFieldsTypeName(board.board.id)}, ${boardSpaceFieldsTypeName(board.board.id)}, ${hexEdgeFieldsTypeName(board.board.id)}, ${hexVertexFieldsTypeName(board.board.id)}>;`;
|
|
4646
|
+
}
|
|
4647
|
+
if (board.layout === "square") {
|
|
4648
|
+
return ` ${quote(runtimeBoardId)}: SquareBoardStateRecord<${quote(runtimeBoardId)}, ${renderStringUnion(board.spaces.map((space) => space.id))}, ${renderStringUnion(board.containers.map((container) => container.id))}, ${renderStringUnion(board.edges.map((edge) => edge.id))}, ${renderStringUnion(board.vertices.map((vertex) => vertex.id))}, ${boardFieldsTypeName(board.board.id)}, ${boardSpaceFieldsTypeName(board.board.id)}, ${boardRelationFieldsTypeName(board.board.id)}, ${boardContainerFieldsTypeName(board.board.id)}, ${hexEdgeFieldsTypeName(board.board.id)}, ${hexVertexFieldsTypeName(board.board.id)}>;`;
|
|
4649
|
+
}
|
|
4650
|
+
return ` ${quote(runtimeBoardId)}: GenericBoardStateRecord<${quote(runtimeBoardId)}, ${renderStringUnion(board.spaces.map((space) => space.id))}, ${renderStringUnion(board.containers.map((container) => container.id))}, ${boardFieldsTypeName(board.board.id)}, ${boardSpaceFieldsTypeName(board.board.id)}, ${boardRelationFieldsTypeName(board.board.id)}, ${boardContainerFieldsTypeName(board.board.id)}>;`;
|
|
4651
|
+
}))
|
|
4652
|
+
.join("\n");
|
|
4653
|
+
return `${generatedFileBanner}
|
|
4654
|
+
|
|
4655
|
+
import type {
|
|
4656
|
+
PerPlayer,
|
|
4657
|
+
RuntimeCardData,
|
|
4658
|
+
RuntimeCardVisibility,
|
|
4659
|
+
RuntimeComponentLocation,
|
|
4660
|
+
RuntimeDieData,
|
|
4661
|
+
RuntimeHandVisibilityMode,
|
|
4662
|
+
RuntimePieceData,
|
|
4663
|
+
RuntimeRecord,
|
|
4664
|
+
RuntimeTableRecord,
|
|
4665
|
+
} from "@dreamboard/app-sdk/reducer";
|
|
4666
|
+
import { literals } from "./manifest-literals";
|
|
4667
|
+
|
|
4668
|
+
export type PlayerId = (typeof literals.playerIds)[number];
|
|
4669
|
+
export type PhaseName = string;
|
|
4670
|
+
export type BoardLayout = (typeof literals.boardLayouts)[number];
|
|
4671
|
+
export type SetupOptionId = (typeof literals.setupOptionIds)[number];
|
|
4672
|
+
export type SetupProfileId = (typeof literals.setupProfileIds)[number];
|
|
4673
|
+
export type CardSetId = (typeof literals.cardSetIds)[number];
|
|
4674
|
+
export type CardType = (typeof literals.cardTypes)[number];
|
|
4675
|
+
export type CardId = (typeof literals.cardIds)[number];
|
|
4676
|
+
export type DeckId = (typeof literals.deckIds)[number];
|
|
4677
|
+
export type HandId = (typeof literals.handIds)[number];
|
|
4678
|
+
export type SharedZoneId = (typeof literals.sharedZoneIds)[number];
|
|
4679
|
+
export type PlayerZoneId = (typeof literals.playerZoneIds)[number];
|
|
4680
|
+
export type ZoneId = (typeof literals.zoneIds)[number];
|
|
4681
|
+
export type ResourceId = (typeof literals.resourceIds)[number];
|
|
4682
|
+
export type PieceTypeId = (typeof literals.pieceTypeIds)[number];
|
|
4683
|
+
export type PieceId = (typeof literals.pieceIds)[number];
|
|
4684
|
+
export type DieTypeId = (typeof literals.dieTypeIds)[number];
|
|
4685
|
+
export type DieId = (typeof literals.dieIds)[number];
|
|
4686
|
+
export type BoardTypeId = (typeof literals.boardTypeIds)[number];
|
|
4687
|
+
export type BoardBaseId = (typeof literals.boardBaseIds)[number];
|
|
4688
|
+
export type BoardId = (typeof literals.boardIds)[number];
|
|
4689
|
+
export type BoardContainerId = (typeof literals.boardContainerIds)[number];
|
|
4690
|
+
export type RelationTypeId = (typeof literals.relationTypeIds)[number];
|
|
4691
|
+
export type EdgeId = (typeof literals.edgeIds)[number];
|
|
4692
|
+
export type EdgeTypeId = (typeof literals.edgeTypeIds)[number];
|
|
4693
|
+
export type VertexId = (typeof literals.vertexIds)[number];
|
|
4694
|
+
export type VertexTypeId = (typeof literals.vertexTypeIds)[number];
|
|
4695
|
+
export type SpaceId = (typeof literals.spaceIds)[number];
|
|
4696
|
+
export type SpaceTypeId = (typeof literals.spaceTypeIds)[number];
|
|
4697
|
+
|
|
4698
|
+
${renderCardSetTypeSections(analysis)}
|
|
4699
|
+
|
|
4700
|
+
${renderTopologyFieldTypeSections(analysis)}
|
|
4701
|
+
|
|
4702
|
+
${renderBoardFieldMapTypes(analysis)}
|
|
4703
|
+
|
|
4704
|
+
export type CardProperties = ${analysis.cardSets.length > 0
|
|
4705
|
+
? analysis.cardSets
|
|
4706
|
+
.map((cardSet) => `${toPascalCase(cardSet.id)}CardProperties`)
|
|
4707
|
+
.join(" | ")
|
|
4708
|
+
: "RuntimeRecord"};
|
|
4709
|
+
|
|
4710
|
+
export type CardState = Omit<RuntimeCardData, "id" | "cardSetId" | "cardType" | "properties"> & {
|
|
4711
|
+
id: CardId;
|
|
4712
|
+
cardSetId: CardSetId;
|
|
4713
|
+
cardType: CardType;
|
|
4714
|
+
properties: CardProperties;
|
|
4715
|
+
};
|
|
4716
|
+
export type CardStateById = Record<CardId, CardState>;
|
|
4717
|
+
export type PieceStateById = Record<PieceId, RuntimePieceData>;
|
|
4718
|
+
export type DieStateById = Record<DieId, RuntimeDieData>;
|
|
4719
|
+
export type CardIdsBySharedZoneId = {
|
|
4720
|
+
${sharedZoneCardIdEntries}
|
|
4721
|
+
};
|
|
4722
|
+
export type CardIdsByPlayerZoneId = {
|
|
4723
|
+
${playerZoneCardIdEntries}
|
|
4724
|
+
};
|
|
4725
|
+
export type CardIdsByDeckId = CardIdsBySharedZoneId;
|
|
4726
|
+
export type ComponentId = CardId | PieceId | DieId;
|
|
4727
|
+
|
|
4728
|
+
export interface BoardSpaceStateRecord<SpaceIdValue extends SpaceId = SpaceId, Fields = RuntimeRecord> {
|
|
4729
|
+
id: SpaceIdValue;
|
|
4730
|
+
name?: string | null;
|
|
4731
|
+
typeId?: SpaceTypeId | null;
|
|
4732
|
+
fields: Fields;
|
|
4733
|
+
zoneId?: string | null;
|
|
4734
|
+
}
|
|
4735
|
+
export interface BoardRelationStateRecord<SpaceIdValue extends SpaceId = SpaceId, Fields = RuntimeRecord> {
|
|
4736
|
+
id?: string | null;
|
|
4737
|
+
typeId: RelationTypeId;
|
|
4738
|
+
fromSpaceId: SpaceIdValue;
|
|
4739
|
+
toSpaceId: SpaceIdValue;
|
|
4740
|
+
directed: boolean;
|
|
4741
|
+
fields: Fields;
|
|
4742
|
+
}
|
|
4743
|
+
export interface BoardContainerStateRecord<SpaceIdValue extends SpaceId = SpaceId, ContainerIdValue extends BoardContainerId = BoardContainerId, Fields = RuntimeRecord> {
|
|
4744
|
+
id: ContainerIdValue;
|
|
4745
|
+
name: string;
|
|
4746
|
+
host: { type: "board" } | { type: "space"; spaceId: SpaceIdValue };
|
|
4747
|
+
allowedCardSetIds?: readonly CardSetId[];
|
|
4748
|
+
zoneId: string;
|
|
4749
|
+
fields: Fields;
|
|
4750
|
+
}
|
|
4751
|
+
export interface BoardStateRecordBase<BoardIdValue extends BoardId = BoardId, BoardFields = RuntimeRecord> {
|
|
4752
|
+
id: BoardIdValue;
|
|
4753
|
+
baseId: BoardBaseId;
|
|
4754
|
+
typeId?: BoardTypeId | null;
|
|
4755
|
+
scope: "shared" | "perPlayer";
|
|
4756
|
+
playerId?: PlayerId | null;
|
|
4757
|
+
templateId?: string | null;
|
|
4758
|
+
fields: BoardFields;
|
|
4759
|
+
}
|
|
4760
|
+
export interface GenericBoardStateRecord<BoardIdValue extends BoardId = BoardId, SpaceIdValue extends SpaceId = SpaceId, ContainerIdValue extends BoardContainerId = BoardContainerId, BoardFields = RuntimeRecord, SpaceFields = RuntimeRecord, RelationFields = RuntimeRecord, ContainerFields = RuntimeRecord> extends BoardStateRecordBase<BoardIdValue, BoardFields> {
|
|
4761
|
+
layout: "generic";
|
|
4762
|
+
spaces: Record<SpaceIdValue, BoardSpaceStateRecord<SpaceIdValue, SpaceFields>>;
|
|
4763
|
+
relations: Array<BoardRelationStateRecord<SpaceIdValue, RelationFields>>;
|
|
4764
|
+
containers: Record<ContainerIdValue, BoardContainerStateRecord<SpaceIdValue, ContainerIdValue, ContainerFields>>;
|
|
4765
|
+
}
|
|
4766
|
+
export interface HexSpaceStateRecord<SpaceIdValue extends SpaceId = SpaceId, Fields = RuntimeRecord> extends BoardSpaceStateRecord<SpaceIdValue, Fields> {
|
|
4767
|
+
q: number;
|
|
4768
|
+
r: number;
|
|
4769
|
+
}
|
|
4770
|
+
export interface SquareSpaceStateRecord<SpaceIdValue extends SpaceId = SpaceId, Fields = RuntimeRecord> extends BoardSpaceStateRecord<SpaceIdValue, Fields> {
|
|
4771
|
+
row: number;
|
|
4772
|
+
col: number;
|
|
4773
|
+
}
|
|
4774
|
+
export interface TiledEdgeStateRecord<SpaceIdValue extends SpaceId = SpaceId, EdgeIdValue extends EdgeId = EdgeId, Fields = RuntimeRecord> {
|
|
4775
|
+
id: EdgeIdValue;
|
|
4776
|
+
spaceIds: readonly SpaceIdValue[];
|
|
4777
|
+
typeId?: EdgeTypeId | null;
|
|
4778
|
+
label?: string | null;
|
|
4779
|
+
ownerId?: PlayerId | null;
|
|
4780
|
+
fields: Fields;
|
|
4781
|
+
}
|
|
4782
|
+
export interface TiledVertexStateRecord<SpaceIdValue extends SpaceId = SpaceId, VertexIdValue extends VertexId = VertexId, Fields = RuntimeRecord> {
|
|
4783
|
+
id: VertexIdValue;
|
|
4784
|
+
spaceIds: readonly SpaceIdValue[];
|
|
4785
|
+
typeId?: VertexTypeId | null;
|
|
4786
|
+
label?: string | null;
|
|
4787
|
+
ownerId?: PlayerId | null;
|
|
4788
|
+
fields: Fields;
|
|
4789
|
+
}
|
|
4790
|
+
export type HexEdgeStateRecord<SpaceIdValue extends SpaceId = SpaceId, EdgeIdValue extends EdgeId = EdgeId, Fields = RuntimeRecord> = TiledEdgeStateRecord<SpaceIdValue, EdgeIdValue, Fields>;
|
|
4791
|
+
export type HexVertexStateRecord<SpaceIdValue extends SpaceId = SpaceId, VertexIdValue extends VertexId = VertexId, Fields = RuntimeRecord> = TiledVertexStateRecord<SpaceIdValue, VertexIdValue, Fields>;
|
|
4792
|
+
export interface HexBoardStateRecord<BoardIdValue extends BoardId = BoardId, SpaceIdValue extends SpaceId = SpaceId, EdgeIdValue extends EdgeId = EdgeId, VertexIdValue extends VertexId = VertexId, BoardFields = RuntimeRecord, SpaceFields = RuntimeRecord, EdgeFields = RuntimeRecord, VertexFields = RuntimeRecord> extends BoardStateRecordBase<BoardIdValue, BoardFields> {
|
|
4793
|
+
layout: "hex";
|
|
4794
|
+
spaces: Record<SpaceIdValue, HexSpaceStateRecord<SpaceIdValue, SpaceFields>>;
|
|
4795
|
+
relations: Array<BoardRelationStateRecord<SpaceIdValue, RuntimeRecord>>;
|
|
4796
|
+
containers: Record<never, never>;
|
|
4797
|
+
orientation: "pointy-top" | "flat-top";
|
|
4798
|
+
edges: Array<HexEdgeStateRecord<SpaceIdValue, EdgeIdValue, EdgeFields>>;
|
|
4799
|
+
vertices: Array<HexVertexStateRecord<SpaceIdValue, VertexIdValue, VertexFields>>;
|
|
4800
|
+
}
|
|
4801
|
+
export interface SquareBoardStateRecord<BoardIdValue extends BoardId = BoardId, SpaceIdValue extends SpaceId = SpaceId, ContainerIdValue extends BoardContainerId = BoardContainerId, EdgeIdValue extends EdgeId = EdgeId, VertexIdValue extends VertexId = VertexId, BoardFields = RuntimeRecord, SpaceFields = RuntimeRecord, RelationFields = RuntimeRecord, ContainerFields = RuntimeRecord, EdgeFields = RuntimeRecord, VertexFields = RuntimeRecord> extends BoardStateRecordBase<BoardIdValue, BoardFields> {
|
|
4802
|
+
layout: "square";
|
|
4803
|
+
spaces: Record<SpaceIdValue, SquareSpaceStateRecord<SpaceIdValue, SpaceFields>>;
|
|
4804
|
+
relations: Array<BoardRelationStateRecord<SpaceIdValue, RelationFields>>;
|
|
4805
|
+
containers: Record<ContainerIdValue, BoardContainerStateRecord<SpaceIdValue, ContainerIdValue, ContainerFields>>;
|
|
4806
|
+
edges: Array<TiledEdgeStateRecord<SpaceIdValue, EdgeIdValue, EdgeFields>>;
|
|
4807
|
+
vertices: Array<TiledVertexStateRecord<SpaceIdValue, VertexIdValue, VertexFields>>;
|
|
4808
|
+
}
|
|
4809
|
+
export type BoardStateById = {
|
|
4810
|
+
${perBoardStateEntries}
|
|
4811
|
+
};
|
|
4812
|
+
export type BoardState<BoardIdValue extends BoardId = BoardId> = BoardIdValue extends keyof BoardStateById ? BoardStateById[BoardIdValue] : never;
|
|
4813
|
+
export type BoardFields<BoardIdValue extends BoardId = BoardId> = BoardIdValue extends keyof BoardFieldsByBoardId ? BoardFieldsByBoardId[BoardIdValue] : RuntimeRecord;
|
|
4814
|
+
export type BoardSpaceState<BoardIdValue extends BoardId = BoardId> = BoardState<BoardIdValue> extends { spaces: Record<string, infer Space> } ? Space : never;
|
|
4815
|
+
export type BoardSpaceFields<BoardIdValue extends BoardId = BoardId> = BoardIdValue extends keyof BoardSpaceFieldsByBoardId ? BoardSpaceFieldsByBoardId[BoardIdValue] : RuntimeRecord;
|
|
4816
|
+
export type BoardRelationState<BoardIdValue extends BoardId = BoardId> = BoardState<BoardIdValue> extends { relations: Array<infer Relation> } ? Relation : never;
|
|
4817
|
+
export type BoardRelationFields<BoardIdValue extends BoardId = BoardId> = BoardIdValue extends keyof BoardRelationFieldsByBoardId ? BoardRelationFieldsByBoardId[BoardIdValue] : RuntimeRecord;
|
|
4818
|
+
export type BoardContainerState<BoardIdValue extends BoardId = BoardId> = BoardState<BoardIdValue> extends { containers: Record<string, infer Container> } ? Container : never;
|
|
4819
|
+
export type BoardContainerFields<BoardIdValue extends BoardId = BoardId> = BoardIdValue extends keyof BoardContainerFieldsByBoardId ? BoardContainerFieldsByBoardId[BoardIdValue] : RuntimeRecord;
|
|
4820
|
+
export type HexBoardStateById = {
|
|
4821
|
+
[BoardIdValue in keyof BoardStateById as BoardStateById[BoardIdValue] extends { layout: "hex" } ? BoardIdValue : never]: BoardStateById[BoardIdValue];
|
|
4822
|
+
};
|
|
4823
|
+
export type SquareBoardStateById = {
|
|
4824
|
+
[BoardIdValue in keyof BoardStateById as BoardStateById[BoardIdValue] extends { layout: "square" } ? BoardIdValue : never]: BoardStateById[BoardIdValue];
|
|
4825
|
+
};
|
|
4826
|
+
export type HexEdgeState<BoardIdValue extends BoardId = BoardId> = BoardState<BoardIdValue> extends { layout: "hex"; edges: Array<infer Edge> } ? Edge : never;
|
|
4827
|
+
export type HexEdgeFields<BoardIdValue extends BoardId = BoardId> = BoardIdValue extends keyof HexEdgeFieldsByBoardId ? HexEdgeFieldsByBoardId[BoardIdValue] : RuntimeRecord;
|
|
4828
|
+
export type HexVertexState<BoardIdValue extends BoardId = BoardId> = BoardState<BoardIdValue> extends { layout: "hex"; vertices: Array<infer Vertex> } ? Vertex : never;
|
|
4829
|
+
export type HexVertexFields<BoardIdValue extends BoardId = BoardId> = BoardIdValue extends keyof HexVertexFieldsByBoardId ? HexVertexFieldsByBoardId[BoardIdValue] : RuntimeRecord;
|
|
4830
|
+
export type SquareEdgeState<BoardIdValue extends BoardId = BoardId> = BoardState<BoardIdValue> extends { layout: "square"; edges: Array<infer Edge> } ? Edge : never;
|
|
4831
|
+
export type SquareEdgeFields<BoardIdValue extends BoardId = BoardId> = BoardIdValue extends keyof SquareEdgeFieldsByBoardId ? SquareEdgeFieldsByBoardId[BoardIdValue] : RuntimeRecord;
|
|
4832
|
+
export type SquareVertexState<BoardIdValue extends BoardId = BoardId> = BoardState<BoardIdValue> extends { layout: "square"; vertices: Array<infer Vertex> } ? Vertex : never;
|
|
4833
|
+
export type SquareVertexFields<BoardIdValue extends BoardId = BoardId> = BoardIdValue extends keyof SquareVertexFieldsByBoardId ? SquareVertexFieldsByBoardId[BoardIdValue] : RuntimeRecord;
|
|
4834
|
+
export type TiledBoardId = keyof TiledEdgeFieldsByBoardId | keyof TiledVertexFieldsByBoardId;
|
|
4835
|
+
export type TiledEdgeState<BoardIdValue extends TiledBoardId = TiledBoardId> = BoardIdValue extends BoardId ? HexEdgeState<BoardIdValue> | SquareEdgeState<BoardIdValue> : never;
|
|
4836
|
+
export type TiledEdgeFields<BoardIdValue extends TiledBoardId = TiledBoardId> = BoardIdValue extends keyof TiledEdgeFieldsByBoardId ? TiledEdgeFieldsByBoardId[BoardIdValue] : RuntimeRecord;
|
|
4837
|
+
export type TiledVertexState<BoardIdValue extends TiledBoardId = TiledBoardId> = BoardIdValue extends BoardId ? HexVertexState<BoardIdValue> | SquareVertexState<BoardIdValue> : never;
|
|
4838
|
+
export type TiledVertexFields<BoardIdValue extends TiledBoardId = TiledBoardId> = BoardIdValue extends keyof TiledVertexFieldsByBoardId ? TiledVertexFieldsByBoardId[BoardIdValue] : RuntimeRecord;
|
|
4839
|
+
export type BoardStateRecord = BoardStateById[BoardId];
|
|
4840
|
+
|
|
4841
|
+
export type TableState = Omit<RuntimeTableRecord, "playerOrder" | "zones" | "decks" | "hands" | "handVisibility" | "cards" | "pieces" | "componentLocations" | "ownerOfCard" | "visibility" | "resources" | "boards" | "dice"> & {
|
|
4842
|
+
playerOrder: PlayerId[];
|
|
4843
|
+
zones: RuntimeTableRecord["zones"] & {
|
|
4844
|
+
visibility: Record<ZoneId, RuntimeHandVisibilityMode>;
|
|
4845
|
+
cardSetIdsByZoneId?: Record<ZoneId, readonly CardSetId[]>;
|
|
4846
|
+
};
|
|
4847
|
+
decks: CardIdsBySharedZoneId;
|
|
4848
|
+
hands: CardIdsByPlayerZoneId;
|
|
4849
|
+
handVisibility: Record<PlayerZoneId, RuntimeHandVisibilityMode>;
|
|
4850
|
+
cards: CardStateById;
|
|
4851
|
+
pieces: PieceStateById;
|
|
4852
|
+
componentLocations: Record<ComponentId, RuntimeComponentLocation>;
|
|
4853
|
+
ownerOfCard: Record<CardId, PlayerId | null>;
|
|
4854
|
+
visibility: Record<CardId, RuntimeCardVisibility>;
|
|
4855
|
+
resources: PerPlayer<Record<ResourceId, number>>;
|
|
4856
|
+
boards: {
|
|
4857
|
+
byId: BoardStateById;
|
|
4858
|
+
hex: HexBoardStateById;
|
|
4859
|
+
square: SquareBoardStateById;
|
|
4860
|
+
network: Record<string, RuntimeRecord>;
|
|
4861
|
+
track: Record<string, RuntimeRecord>;
|
|
4862
|
+
};
|
|
4863
|
+
dice: DieStateById;
|
|
4864
|
+
};
|
|
4865
|
+
`;
|
|
4866
|
+
}
|
|
4867
|
+
function extractGeneratedRuntimeValueExports(source) {
|
|
4868
|
+
const exports = new Set();
|
|
4869
|
+
for (const match of source.matchAll(/^export (?:const|function) ([A-Za-z_$][\w$]*)/gm)) {
|
|
4870
|
+
const name = match[1];
|
|
4871
|
+
if (name !== "literals") {
|
|
4872
|
+
exports.add(name);
|
|
4873
|
+
}
|
|
4874
|
+
}
|
|
4875
|
+
return [...exports].sort((left, right) => left.localeCompare(right));
|
|
4876
|
+
}
|
|
4877
|
+
function renderManifestContractBarrelSource(legacySource) {
|
|
4878
|
+
const runtimeExports = extractGeneratedRuntimeValueExports(legacySource);
|
|
4879
|
+
return `${generatedFileBanner}
|
|
4880
|
+
|
|
4881
|
+
export { literals } from "./manifest-literals";
|
|
4882
|
+
export type * from "./manifest-types";
|
|
4883
|
+
export {
|
|
4884
|
+
${runtimeExports.map((name) => ` ${name},`).join("\n")}
|
|
4885
|
+
} from "./manifest-runtime";
|
|
4886
|
+
export { default } from "./manifest-runtime";
|
|
4887
|
+
`;
|
|
4888
|
+
}
|
|
4889
|
+
export function generateManifestContractSources(manifest) {
|
|
4890
|
+
const legacySource = renderManifestContractSource(manifest);
|
|
4891
|
+
return {
|
|
4892
|
+
"shared/manifest-literals.ts": renderManifestLiteralsSource(legacySource),
|
|
4893
|
+
"shared/manifest-types.ts": renderManifestTypesSource(manifest),
|
|
4894
|
+
"shared/manifest-runtime.ts": renderManifestRuntimeSource(legacySource),
|
|
4895
|
+
"shared/manifest-contract.ts": renderManifestContractBarrelSource(legacySource),
|
|
4896
|
+
};
|
|
4897
|
+
}
|