@dreamboard-games/sdk 0.3.0-alpha.1 → 0.4.0-alpha.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.
Files changed (56) hide show
  1. package/REFERENCE.md +19935 -0
  2. package/dist/{chunk-3RQEICD3.js → chunk-2LDZ5C3T.js} +2 -2
  3. package/dist/chunk-3OZMHZK3.js +40 -0
  4. package/dist/chunk-3OZMHZK3.js.map +1 -0
  5. package/dist/{chunk-TXXLS3OI.js → chunk-4G7552LO.js} +3 -3
  6. package/dist/{chunk-P7VMTJ5D.js → chunk-MNKDSGIA.js} +2 -2
  7. package/dist/{chunk-P7VMTJ5D.js.map → chunk-MNKDSGIA.js.map} +1 -1
  8. package/dist/{chunk-W6SGFWXH.js → chunk-NBAEEHAU.js} +101 -60
  9. package/dist/chunk-NBAEEHAU.js.map +1 -0
  10. package/dist/{chunk-ADYH6PVT.js → chunk-P7F4L2FF.js} +49 -25
  11. package/dist/chunk-P7F4L2FF.js.map +1 -0
  12. package/dist/{chunk-QZ6X4B5P.js → chunk-Q5O7GPVY.js} +1 -1
  13. package/dist/{chunk-QZ6X4B5P.js.map → chunk-Q5O7GPVY.js.map} +1 -1
  14. package/dist/chunk-U5KBV2BA.js +2654 -0
  15. package/dist/chunk-U5KBV2BA.js.map +1 -0
  16. package/dist/{chunk-Y2AFYBMB.js → chunk-Y6Y2YABD.js} +9 -5
  17. package/dist/chunk-Y6Y2YABD.js.map +1 -0
  18. package/dist/codegen.d.ts +44 -51
  19. package/dist/codegen.js +1 -1
  20. package/dist/{index.d-BGqVifr_.d.ts → index.d-BL3bT5lt.d.ts} +82 -3
  21. package/dist/index.js +1 -1
  22. package/dist/package-set.d.ts +2 -2
  23. package/dist/package-set.js +1 -1
  24. package/dist/reducer/advanced.d.ts +78 -0
  25. package/dist/reducer/advanced.js +139 -0
  26. package/dist/reducer/advanced.js.map +1 -0
  27. package/dist/reducer-contract.d.ts +1 -1
  28. package/dist/reducer-contract.js +2 -2
  29. package/dist/reducer.d.ts +363 -3263
  30. package/dist/reducer.js +2778 -4081
  31. package/dist/reducer.js.map +1 -1
  32. package/dist/runtime/primitives.d.ts +3 -3
  33. package/dist/runtime/primitives.js +3 -3
  34. package/dist/runtime/runtime-api.d.ts +1 -1
  35. package/dist/runtime/workspace-contract.d.ts +18 -4
  36. package/dist/runtime/workspace-contract.js +4 -4
  37. package/dist/{runtime-api-tGL3ArsB.d.ts → runtime-api-BXd70c2e.d.ts} +6 -0
  38. package/dist/runtime.d.ts +2 -2
  39. package/dist/runtime.js +85 -20
  40. package/dist/runtime.js.map +1 -1
  41. package/dist/stale-contract-artifact-error-BelRiIDR.d.ts +66 -0
  42. package/dist/testing.d.ts +59 -5
  43. package/dist/testing.js +170 -6
  44. package/dist/testing.js.map +1 -1
  45. package/dist/types.d.ts +112 -119
  46. package/dist/ui/components.js +1 -1
  47. package/dist/ui/plugin-styles.css +2 -250
  48. package/dist/{ui-contract-SctM4ibF.d.ts → ui-contract-BUC6iS3s.d.ts} +1 -1
  49. package/dist/ui.js +2 -2
  50. package/dist/views-B0hlW6IT.d.ts +3153 -0
  51. package/package.json +18 -10
  52. package/dist/chunk-ADYH6PVT.js.map +0 -1
  53. package/dist/chunk-W6SGFWXH.js.map +0 -1
  54. package/dist/chunk-Y2AFYBMB.js.map +0 -1
  55. /package/dist/{chunk-3RQEICD3.js.map → chunk-2LDZ5C3T.js.map} +0 -0
  56. /package/dist/{chunk-TXXLS3OI.js.map → chunk-4G7552LO.js.map} +0 -0
@@ -0,0 +1,2654 @@
1
+ // src/reducer/model/manifest.ts
2
+ import { z } from "zod";
3
+ function toNonEmptyStringTuple(values) {
4
+ const [first, ...rest] = Array.from(values);
5
+ if (typeof first === "undefined") {
6
+ throw new Error("Expected a non-empty literal tuple");
7
+ }
8
+ return [first, ...rest];
9
+ }
10
+ var manifestScopedSchemas = /* @__PURE__ */ new WeakSet();
11
+ function markManifestScopedSchema(schema) {
12
+ manifestScopedSchemas.add(schema);
13
+ return schema;
14
+ }
15
+ function isManifestScopedSchema(schema) {
16
+ return typeof schema === "object" && schema !== null && manifestScopedSchemas.has(schema);
17
+ }
18
+ function createManifestStringLiteralSchema(values) {
19
+ const schema = values.length === 0 ? z.string() : z.enum(toNonEmptyStringTuple(values));
20
+ return markManifestScopedSchema(schema);
21
+ }
22
+ function assumeManifestSchema(schema) {
23
+ return schema;
24
+ }
25
+ function cloneManifestDefault(value) {
26
+ if (typeof globalThis.structuredClone === "function") {
27
+ return globalThis.structuredClone(value);
28
+ }
29
+ return JSON.parse(JSON.stringify(value));
30
+ }
31
+ function resolveManifestPlayerIds(manifestPlayerIds, playerIds) {
32
+ if (!playerIds || playerIds.length === 0) {
33
+ return manifestPlayerIds;
34
+ }
35
+ const knownPlayerIds = new Set(manifestPlayerIds);
36
+ return playerIds.filter(
37
+ (playerId) => knownPlayerIds.has(playerId)
38
+ );
39
+ }
40
+ function createManifestRuntimeSchema({
41
+ phaseNameSchema,
42
+ playerIdSchema,
43
+ setupProfileIdSchema
44
+ }) {
45
+ return z.object({
46
+ prompts: z.array(
47
+ z.object({
48
+ id: z.string(),
49
+ promptId: z.string(),
50
+ to: playerIdSchema,
51
+ title: z.string().optional(),
52
+ payload: z.unknown().optional(),
53
+ options: z.array(z.object({ id: z.string(), label: z.string() })).optional(),
54
+ resume: z.object({ id: z.string(), data: z.unknown() })
55
+ })
56
+ ).default([]),
57
+ rng: z.object({
58
+ seed: z.number().nullable().optional(),
59
+ cursor: z.number().int().default(0),
60
+ trace: z.array(z.string()).default([])
61
+ }).default({
62
+ seed: null,
63
+ cursor: 0,
64
+ trace: []
65
+ }),
66
+ setup: z.object({
67
+ profileId: setupProfileIdSchema,
68
+ optionValues: z.record(z.string(), z.string().nullable()).default({})
69
+ }).nullable().default(null),
70
+ simultaneous: z.object({
71
+ current: z.object({
72
+ phaseName: phaseNameSchema,
73
+ actors: z.array(playerIdSchema),
74
+ submissions: z.record(
75
+ z.string(),
76
+ z.object({
77
+ interactionId: z.string(),
78
+ params: z.unknown()
79
+ })
80
+ )
81
+ }).nullable()
82
+ }).default({ current: null }),
83
+ lastTransition: z.object({
84
+ from: phaseNameSchema,
85
+ to: phaseNameSchema
86
+ }).nullable().default(null),
87
+ nextInstanceId: z.number().int().default(1)
88
+ });
89
+ }
90
+ function createManifestGameStateSchema({
91
+ tableSchema,
92
+ playerIdSchema,
93
+ setupProfileIdSchema,
94
+ phaseNameSchema,
95
+ publicSchema,
96
+ privateSchema,
97
+ hiddenSchema,
98
+ phasesSchema
99
+ }) {
100
+ return z.object({
101
+ table: tableSchema,
102
+ public: publicSchema,
103
+ private: z.record(z.string(), privateSchema),
104
+ hidden: hiddenSchema,
105
+ flow: z.object({
106
+ currentPhase: phaseNameSchema,
107
+ turn: z.number().int(),
108
+ round: z.number().int(),
109
+ activePlayers: z.array(playerIdSchema)
110
+ }),
111
+ phase: phasesSchema,
112
+ runtime: createManifestRuntimeSchema({
113
+ phaseNameSchema,
114
+ playerIdSchema,
115
+ setupProfileIdSchema
116
+ })
117
+ });
118
+ }
119
+
120
+ // src/reducer/model/error-codes.ts
121
+ var FrameworkErrorCodes = {
122
+ NOT_YOUR_TURN: "NOT_YOUR_TURN",
123
+ WRONG_PHASE: "WRONG_PHASE",
124
+ WRONG_STEP: "WRONG_STEP",
125
+ INVALID_PARAMS: "INVALID_PARAMS",
126
+ UNKNOWN_INTERACTION: "UNKNOWN_INTERACTION",
127
+ INTERNAL_ERROR: "INTERNAL_ERROR"
128
+ };
129
+
130
+ // src/reducer/per-player.ts
131
+ import { z as z2 } from "zod";
132
+ function asPlayerId(raw) {
133
+ return raw;
134
+ }
135
+ function isPlayerId(value) {
136
+ return typeof value === "string" && value.length > 0;
137
+ }
138
+ function perPlayer(ids, init) {
139
+ const seen = /* @__PURE__ */ new Set();
140
+ const entries = [];
141
+ for (const [index, id] of ids.entries()) {
142
+ if (seen.has(id)) {
143
+ throw new Error(`perPlayer: duplicate player id '${id}'`);
144
+ }
145
+ seen.add(id);
146
+ entries.push([id, init(id, index)]);
147
+ }
148
+ return { __perPlayer: true, entries };
149
+ }
150
+ function perPlayerKeys(value) {
151
+ return value.entries.map((entry) => entry[0]);
152
+ }
153
+ function perPlayerValues(value) {
154
+ return value.entries.map((entry) => entry[1]);
155
+ }
156
+ function perPlayerEntries(value) {
157
+ return value.entries;
158
+ }
159
+ function perPlayerSize(value) {
160
+ return value.entries.length;
161
+ }
162
+ function perPlayerHas(value, id) {
163
+ for (const [candidate] of value.entries) {
164
+ if (candidate === id) {
165
+ return true;
166
+ }
167
+ }
168
+ return false;
169
+ }
170
+ function perPlayerGet(value, id) {
171
+ for (const [candidate, v] of value.entries) {
172
+ if (candidate === id) {
173
+ return v;
174
+ }
175
+ }
176
+ return void 0;
177
+ }
178
+ function perPlayerRequire(value, id) {
179
+ const found = perPlayerGet(value, id);
180
+ if (found === void 0 && !perPlayerHas(value, id)) {
181
+ throw new Error(`perPlayerRequire: missing entry for player id '${id}'`);
182
+ }
183
+ return found;
184
+ }
185
+ function perPlayerSet(value, id, next) {
186
+ const entries = value.entries.slice();
187
+ const index = entries.findIndex((entry) => entry[0] === id);
188
+ if (index >= 0) {
189
+ entries[index] = [id, next];
190
+ } else {
191
+ entries.push([id, next]);
192
+ }
193
+ return { __perPlayer: true, entries };
194
+ }
195
+ function perPlayerMap(value, f) {
196
+ return {
197
+ __perPlayer: true,
198
+ entries: value.entries.map(
199
+ ([id, v], index) => [id, f(v, id, index)]
200
+ )
201
+ };
202
+ }
203
+ function isPerPlayer(value) {
204
+ if (typeof value !== "object" || value === null) {
205
+ return false;
206
+ }
207
+ const candidate = value;
208
+ if (candidate.__perPlayer !== true) {
209
+ return false;
210
+ }
211
+ if (!Array.isArray(candidate.entries)) {
212
+ return false;
213
+ }
214
+ return candidate.entries.every(
215
+ (entry) => Array.isArray(entry) && entry.length === 2 && typeof entry[0] === "string"
216
+ );
217
+ }
218
+ function perPlayerSchema(valueSchema, options = {}) {
219
+ const keySchema = options.playerIdSchema ?? z2.string().min(1);
220
+ const base = z2.object({
221
+ __perPlayer: z2.literal(true),
222
+ entries: z2.array(z2.tuple([keySchema, valueSchema]))
223
+ }).transform(
224
+ (value) => ({
225
+ __perPlayer: true,
226
+ entries: value.entries
227
+ })
228
+ );
229
+ if (!options.players) {
230
+ return base;
231
+ }
232
+ const expected = options.players;
233
+ return base.superRefine((value, ctx) => {
234
+ const seen = /* @__PURE__ */ new Set();
235
+ for (const [id] of value.entries) {
236
+ if (seen.has(id)) {
237
+ ctx.addIssue({
238
+ code: "custom",
239
+ message: `Duplicate player id '${id}'`
240
+ });
241
+ }
242
+ seen.add(id);
243
+ }
244
+ for (const expectedId of expected) {
245
+ if (!seen.has(expectedId)) {
246
+ ctx.addIssue({
247
+ code: "custom",
248
+ message: `Missing entry for player id '${expectedId}'`
249
+ });
250
+ }
251
+ }
252
+ for (const id of seen) {
253
+ if (!expected.some((expectedId) => expectedId === id)) {
254
+ ctx.addIssue({
255
+ code: "custom",
256
+ message: `Unexpected player id '${id}' (allowed: ${expected.join(", ")})`
257
+ });
258
+ }
259
+ }
260
+ });
261
+ }
262
+ function sharedBoardRef(baseId) {
263
+ return { baseId };
264
+ }
265
+ function perPlayerBoardRef(baseId, seat) {
266
+ return { baseId, seat };
267
+ }
268
+ function boardRef(baseId, seat) {
269
+ if (seat === void 0) {
270
+ return { baseId };
271
+ }
272
+ return { baseId, seat };
273
+ }
274
+ function boardRefKey(ref) {
275
+ return ref.seat === void 0 ? ref.baseId : `${ref.baseId}:${ref.seat}`;
276
+ }
277
+ function parseBoardRefKey(key) {
278
+ if (!key.length) {
279
+ return null;
280
+ }
281
+ const colon = key.indexOf(":");
282
+ if (colon < 0) {
283
+ return { baseId: key };
284
+ }
285
+ const baseId = key.slice(0, colon);
286
+ const seat = key.slice(colon + 1);
287
+ if (!baseId.length || !seat.length) {
288
+ return null;
289
+ }
290
+ return { baseId, seat };
291
+ }
292
+ function boardRefSchema(options = {}) {
293
+ const baseSchema = options.baseIdSchema ?? z2.string().min(1);
294
+ const playerSchema = options.playerIdSchema ?? z2.string().min(1);
295
+ const shared = z2.strictObject({ baseId: baseSchema });
296
+ const perPlayer2 = z2.strictObject({ baseId: baseSchema, seat: playerSchema });
297
+ return z2.union([perPlayer2, shared]);
298
+ }
299
+ function isSharedBoardRef(ref) {
300
+ return ref.seat === void 0;
301
+ }
302
+ function isPerPlayerBoardRef(ref) {
303
+ return ref.seat !== void 0;
304
+ }
305
+
306
+ // src/reducer/inputs/targetRule.ts
307
+ var DEFAULT_MISSING_CANDIDATE_ISSUE = {
308
+ errorCode: "TARGET_NOT_ELIGIBLE",
309
+ message: "Target is not eligible."
310
+ };
311
+ function createTargetRule(candidates, predicates, options = {}) {
312
+ const missingCandidateIssue = options.missingCandidateIssue ?? DEFAULT_MISSING_CANDIDATE_ISSUE;
313
+ const equals = options.equals ?? Object.is;
314
+ const validate = (ctx, target) => {
315
+ if (!candidates(ctx).some((candidate) => equals(candidate, target))) {
316
+ return missingCandidateIssue;
317
+ }
318
+ for (const predicate of predicates) {
319
+ if (!predicate.test({ ...ctx, targetId: target, target })) {
320
+ return {
321
+ errorCode: predicate.errorCode,
322
+ message: predicate.message
323
+ };
324
+ }
325
+ }
326
+ return null;
327
+ };
328
+ const rule = {
329
+ eligible: (ctx) => candidates(ctx).filter((targetId) => validate(ctx, targetId) == null),
330
+ validate,
331
+ isEligible: (ctx, target) => validate(ctx, target) == null,
332
+ bind: (ctx) => ({
333
+ eligible: () => rule.eligible(ctx),
334
+ validate: (id) => rule.validate(ctx, id),
335
+ isEligible: (id) => rule.isEligible(ctx, id)
336
+ })
337
+ };
338
+ return rule;
339
+ }
340
+ function createTargetRuleBuilder(buildRule, predicates = []) {
341
+ return {
342
+ where: (predicate) => createTargetRuleBuilder(buildRule, [...predicates, predicate]),
343
+ build: () => buildRule(predicates)
344
+ };
345
+ }
346
+
347
+ // src/reducer/inputs/cardTarget.ts
348
+ function cardIdsForZone(state, playerId, q, zoneId) {
349
+ const table = state.table;
350
+ if (zoneId in (table.hands ?? {}) || zoneId in (table.zones?.perPlayer ?? {})) {
351
+ return q.zone.playerCards(
352
+ playerId,
353
+ zoneId
354
+ );
355
+ }
356
+ if (zoneId in (table.decks ?? {}) || zoneId in (table.zones?.shared ?? {})) {
357
+ return q.zone.sharedCards(zoneId);
358
+ }
359
+ return [];
360
+ }
361
+ function createCardTargetBuilder(zoneIds) {
362
+ return createTargetRuleBuilder(
363
+ (predicates) => ({
364
+ ...createTargetRule(
365
+ ({ state, playerId, q }) => zoneIds.flatMap(
366
+ (zoneId) => cardIdsForZone(state, playerId, q, zoneId)
367
+ ),
368
+ predicates,
369
+ {
370
+ missingCandidateIssue: {
371
+ errorCode: "CARD_TARGET_NOT_ELIGIBLE",
372
+ message: "Card target is not eligible."
373
+ }
374
+ }
375
+ ),
376
+ zoneIds,
377
+ zoneId: zoneIds[0],
378
+ targetKind: "card"
379
+ })
380
+ );
381
+ }
382
+ var cardTarget = {
383
+ zones(zoneIds) {
384
+ return createCardTargetBuilder(zoneIds);
385
+ }
386
+ };
387
+
388
+ // src/reducer/inputs/cardInput.ts
389
+ import { z as z3 } from "zod";
390
+ function cardInput(options) {
391
+ const target = options.target;
392
+ const dependsOn = options.dependsOn?.map((dependency) => dependency.key);
393
+ return {
394
+ kind: "card",
395
+ schema: z3.string(),
396
+ eligibleTargets: ((state, playerId, q, values) => target.eligible({
397
+ state,
398
+ playerId,
399
+ q,
400
+ values
401
+ })),
402
+ validateTarget: ((state, playerId, q, targetId, values) => target.validate(
403
+ {
404
+ state,
405
+ playerId,
406
+ q,
407
+ values
408
+ },
409
+ targetId
410
+ )),
411
+ ...dependsOn ? { dependsOn } : {},
412
+ domain: (state, playerId, q, _derived, values) => ({
413
+ type: "cardTarget",
414
+ projection: "resolved",
415
+ targetKind: target.targetKind,
416
+ zoneIds: target.zoneIds,
417
+ eligibleTargets: target.eligible({
418
+ state,
419
+ playerId,
420
+ q,
421
+ values
422
+ }).map(String)
423
+ }),
424
+ meta: {
425
+ zoneId: target.zoneId,
426
+ zoneIds: target.zoneIds,
427
+ targetKind: target.targetKind
428
+ }
429
+ };
430
+ }
431
+
432
+ // src/reducer/table/internal.ts
433
+ function ppRead(value, playerId) {
434
+ if (value === void 0) return void 0;
435
+ return perPlayerGet(value, playerId);
436
+ }
437
+ function ppWrite(value, playerId, next) {
438
+ const base = value ?? { __perPlayer: true, entries: [] };
439
+ return perPlayerSet(base, playerId, next);
440
+ }
441
+ function ensureArray(value) {
442
+ return Array.isArray(value) ? [...value] : [];
443
+ }
444
+ function locationPosition(location) {
445
+ return "position" in location && typeof location.position === "number" ? location.position : Number.MAX_SAFE_INTEGER;
446
+ }
447
+ function orderedComponentIdsForLocation(table, predicate) {
448
+ return Object.entries(table.componentLocations).filter(([, location]) => predicate(location)).sort(
449
+ (left, right) => locationPosition(left[1]) - locationPosition(right[1])
450
+ ).map(([componentId]) => componentId);
451
+ }
452
+ function hasOwnKey(record, key) {
453
+ return Object.prototype.hasOwnProperty.call(record, key);
454
+ }
455
+ function isSharedZoneId(table, zoneId) {
456
+ return hasOwnKey(table.decks, zoneId) || hasOwnKey(table.zones.shared, zoneId);
457
+ }
458
+ function isPlayerZoneId(table, zoneId) {
459
+ return hasOwnKey(table.hands, zoneId) || hasOwnKey(table.zones.perPlayer, zoneId) || hasOwnKey(table.handVisibility, zoneId);
460
+ }
461
+ function zoneScopeForId(table, zoneId) {
462
+ if (isPlayerZoneId(table, zoneId)) {
463
+ return "perPlayer";
464
+ }
465
+ if (isSharedZoneId(table, zoneId)) {
466
+ return "shared";
467
+ }
468
+ return null;
469
+ }
470
+ function assertZoneScope(table, zoneId, expectedScope, operation, argumentName) {
471
+ const actualScope = zoneScopeForId(table, zoneId);
472
+ if (actualScope === expectedScope) {
473
+ return;
474
+ }
475
+ if (actualScope === null) {
476
+ throw new Error(
477
+ `Unknown zone '${zoneId}' passed as ${argumentName} to ${operation}.`
478
+ );
479
+ }
480
+ throw new Error(
481
+ `Zone '${zoneId}' has scope '${actualScope}', but ${operation} requires ${argumentName} to be a ${expectedScope === "shared" ? "shared" : "perPlayer"} zone.`
482
+ );
483
+ }
484
+ function syncSharedZoneWithDeck(table, zoneId, nextCards) {
485
+ table.decks[zoneId] = [...nextCards];
486
+ table.zones.shared[zoneId] = [
487
+ ...nextCards
488
+ ];
489
+ }
490
+ function syncPlayerZoneWithHand(table, zoneId, playerId, nextCards) {
491
+ table.hands[zoneId] = ppWrite(table.hands[zoneId], playerId, [
492
+ ...nextCards
493
+ ]);
494
+ table.zones.perPlayer[zoneId] = ppWrite(
495
+ table.zones.perPlayer[zoneId],
496
+ playerId,
497
+ [...nextCards]
498
+ );
499
+ }
500
+
501
+ // src/reducer/table/board-queries.ts
502
+ function getBoard(table, boardId) {
503
+ return table.boards.byId[boardId];
504
+ }
505
+ function getHexBoard(table, boardId) {
506
+ return getBoard(table, boardId);
507
+ }
508
+ function getTiledBoard(table, boardId) {
509
+ return getBoard(table, boardId);
510
+ }
511
+ function getSquareBoard(table, boardId) {
512
+ return getBoard(table, boardId);
513
+ }
514
+ function getSpace(table, boardId, spaceId) {
515
+ return getBoard(table, boardId).spaces[spaceId];
516
+ }
517
+ function getHexSpace(table, boardId, spaceId) {
518
+ return getHexBoard(table, boardId).spaces[spaceId];
519
+ }
520
+ function getSquareSpace(table, boardId, spaceId) {
521
+ return getSquareBoard(table, boardId).spaces[spaceId];
522
+ }
523
+ function getContainer(table, boardId, containerId) {
524
+ return getBoard(table, boardId).containers[containerId];
525
+ }
526
+ function getEdge(table, boardId, edgeId) {
527
+ const edge = getTiledBoard(table, boardId).edges.find(
528
+ (candidate) => candidate.id === edgeId
529
+ );
530
+ if (!edge) {
531
+ throw new Error(`Unknown edge '${edgeId}' on board '${boardId}'.`);
532
+ }
533
+ return edge;
534
+ }
535
+ function getVertex(table, boardId, vertexId) {
536
+ const vertex = getTiledBoard(table, boardId).vertices.find(
537
+ (candidate) => candidate.id === vertexId
538
+ );
539
+ if (!vertex) {
540
+ throw new Error(`Unknown vertex '${vertexId}' on board '${boardId}'.`);
541
+ }
542
+ return vertex;
543
+ }
544
+ function getHexSpaceAt(table, boardId, q, r) {
545
+ return Object.values(getHexBoard(table, boardId).spaces).find(
546
+ (space) => space.q === q && space.r === r
547
+ );
548
+ }
549
+ function getSquareSpaceAt(table, boardId, row, col) {
550
+ return Object.values(getSquareBoard(table, boardId).spaces).find(
551
+ (space) => space.row === row && space.col === col
552
+ );
553
+ }
554
+ function getSpaceEdges(table, boardId, spaceId) {
555
+ return getTiledBoard(table, boardId).edges.filter((edge) => edge.spaceIds.includes(spaceId)).map((edge) => edge.id);
556
+ }
557
+ function getSpaceVertices(table, boardId, spaceId) {
558
+ return getTiledBoard(table, boardId).vertices.filter((vertex) => vertex.spaceIds.includes(spaceId)).map((vertex) => vertex.id);
559
+ }
560
+ function getIncidentEdges(table, boardId, vertexId) {
561
+ const tiledBoard = getTiledBoard(table, boardId);
562
+ const vertex = tiledBoard.vertices.find(
563
+ (candidate) => candidate.id === vertexId
564
+ );
565
+ if (!vertex) {
566
+ throw new Error(`Unknown vertex '${vertexId}' on board '${boardId}'.`);
567
+ }
568
+ const vertexSpaceIds = new Set(vertex.spaceIds);
569
+ return tiledBoard.edges.filter(
570
+ (edge) => edge.spaceIds.every((spaceId) => vertexSpaceIds.has(spaceId))
571
+ ).map((edge) => edge.id);
572
+ }
573
+ function getIncidentVertices(table, boardId, edgeId) {
574
+ const tiledBoard = getTiledBoard(table, boardId);
575
+ const edge = tiledBoard.edges.find((candidate) => candidate.id === edgeId);
576
+ if (!edge) {
577
+ throw new Error(`Unknown edge '${edgeId}' on board '${boardId}'.`);
578
+ }
579
+ const edgeSpaceIds = new Set(edge.spaceIds);
580
+ return tiledBoard.vertices.filter(
581
+ (vertex) => Array.from(edgeSpaceIds).every(
582
+ (spaceId) => vertex.spaceIds.includes(spaceId)
583
+ )
584
+ ).map((vertex) => vertex.id);
585
+ }
586
+ function getRelatedSpaces(table, boardId, spaceId, relationTypeId) {
587
+ const board = getBoard(table, boardId);
588
+ const related = /* @__PURE__ */ new Set();
589
+ for (const relation of board.relations) {
590
+ if (relation.typeId !== relationTypeId) {
591
+ continue;
592
+ }
593
+ if (relation.fromSpaceId === spaceId) {
594
+ related.add(relation.toSpaceId);
595
+ continue;
596
+ }
597
+ if (!relation.directed && relation.toSpaceId === spaceId) {
598
+ related.add(relation.fromSpaceId);
599
+ }
600
+ }
601
+ return [...related];
602
+ }
603
+ function getAdjacentSpaces(table, boardId, spaceId) {
604
+ return getRelatedSpaces(
605
+ table,
606
+ boardId,
607
+ spaceId,
608
+ "adjacent"
609
+ );
610
+ }
611
+ function getSpaceDistance(table, boardId, fromSpaceId, toSpaceId) {
612
+ if (fromSpaceId === toSpaceId) {
613
+ return 0;
614
+ }
615
+ const visited = /* @__PURE__ */ new Set([fromSpaceId]);
616
+ let frontier = [fromSpaceId];
617
+ let distance = 0;
618
+ while (frontier.length > 0) {
619
+ distance += 1;
620
+ const nextFrontier = [];
621
+ for (const currentSpaceId of frontier) {
622
+ for (const neighborId of getAdjacentSpaces(
623
+ table,
624
+ boardId,
625
+ currentSpaceId
626
+ )) {
627
+ if (neighborId === toSpaceId) {
628
+ return distance;
629
+ }
630
+ if (!visited.has(neighborId)) {
631
+ visited.add(neighborId);
632
+ nextFrontier.push(neighborId);
633
+ }
634
+ }
635
+ }
636
+ frontier = nextFrontier;
637
+ }
638
+ return Number.POSITIVE_INFINITY;
639
+ }
640
+ function getSquareNeighbors(table, boardId, spaceId, options = {}) {
641
+ const board = getSquareBoard(table, boardId);
642
+ const space = getSquareSpace(table, boardId, spaceId);
643
+ const offsets = options.mode === "diagonal" ? [
644
+ [-1, -1],
645
+ [-1, 1],
646
+ [1, -1],
647
+ [1, 1]
648
+ ] : options.mode === "all" ? [
649
+ [-1, 0],
650
+ [0, 1],
651
+ [1, 0],
652
+ [0, -1],
653
+ [-1, -1],
654
+ [-1, 1],
655
+ [1, -1],
656
+ [1, 1]
657
+ ] : [
658
+ [-1, 0],
659
+ [0, 1],
660
+ [1, 0],
661
+ [0, -1]
662
+ ];
663
+ return offsets.map(
664
+ ([rowOffset, colOffset]) => Object.values(board.spaces).find(
665
+ (candidate) => candidate.row === space.row + rowOffset && candidate.col === space.col + colOffset
666
+ )
667
+ ).filter((candidate) => candidate !== void 0).map((candidate) => candidate.id);
668
+ }
669
+ function getSquareDistance(table, boardId, fromSpaceId, toSpaceId, options = {}) {
670
+ const from = getSquareSpace(table, boardId, fromSpaceId);
671
+ const to = getSquareSpace(table, boardId, toSpaceId);
672
+ const rowDistance = Math.abs(from.row - to.row);
673
+ const colDistance = Math.abs(from.col - to.col);
674
+ return options.metric === "chebyshev" ? Math.max(rowDistance, colDistance) : rowDistance + colDistance;
675
+ }
676
+ function getBoardsByTypeId(table, typeId) {
677
+ return Object.entries(table.boards.byId).filter(([, board]) => board.typeId === typeId).map(([boardId]) => boardId);
678
+ }
679
+ function getSpacesByTypeId(table, boardId, typeId) {
680
+ return Object.entries(getBoard(table, boardId).spaces).filter(([, space]) => space.typeId === typeId).map(([spaceId]) => spaceId);
681
+ }
682
+ function getEdgesByTypeId(table, boardId, typeId) {
683
+ return getTiledBoard(table, boardId).edges.filter((edge) => edge.typeId === typeId).map((edge) => edge.id);
684
+ }
685
+ function getVerticesByTypeId(table, boardId, typeId) {
686
+ return getTiledBoard(table, boardId).vertices.filter((vertex) => vertex.typeId === typeId).map((vertex) => vertex.id);
687
+ }
688
+ function getComponentsOnSpace(table, boardId, spaceId) {
689
+ const zoneId = getSpace(table, boardId, spaceId).zoneId;
690
+ return orderedComponentIdsForLocation(
691
+ table,
692
+ (location) => location.type === "OnSpace" && location.boardId === boardId && location.spaceId === spaceId || location.type === "InZone" && typeof zoneId === "string" && zoneId.length > 0 && location.zoneId === zoneId
693
+ );
694
+ }
695
+ function getComponentsInContainer(table, boardId, containerId) {
696
+ const zoneId = getContainer(table, boardId, containerId).zoneId;
697
+ return orderedComponentIdsForLocation(
698
+ table,
699
+ (location) => location.type === "InContainer" && location.boardId === boardId && location.containerId === containerId || location.type === "InZone" && typeof zoneId === "string" && zoneId.length > 0 && location.zoneId === zoneId
700
+ );
701
+ }
702
+ function getComponentsOnEdge(table, boardId, edgeId) {
703
+ return orderedComponentIdsForLocation(
704
+ table,
705
+ (location) => location.type === "OnEdge" && location.boardId === boardId && location.edgeId === edgeId
706
+ );
707
+ }
708
+ function getComponentsOnVertex(table, boardId, vertexId) {
709
+ return orderedComponentIdsForLocation(
710
+ table,
711
+ (location) => location.type === "OnVertex" && location.boardId === boardId && location.vertexId === vertexId
712
+ );
713
+ }
714
+
715
+ // src/reducer/table/card-validation.ts
716
+ function allowedCardSetIdsForZone(table, zoneId) {
717
+ return table.zones.cardSetIdsByZoneId?.[zoneId] ?? [];
718
+ }
719
+ function assertCardAllowedInZone(table, zoneId, componentId) {
720
+ const card = table.cards[componentId];
721
+ if (!card) {
722
+ return;
723
+ }
724
+ const allowedCardSetIds = allowedCardSetIdsForZone(table, zoneId);
725
+ if (allowedCardSetIds.length > 0 && !allowedCardSetIds.includes(card.cardSetId)) {
726
+ throw new Error(
727
+ `Card '${componentId}' from card set '${card.cardSetId}' cannot enter zone '${zoneId}'.`
728
+ );
729
+ }
730
+ }
731
+ function assertCardAllowedInContainer(table, boardId, containerId, componentId) {
732
+ const card = table.cards[componentId];
733
+ if (!card) {
734
+ return;
735
+ }
736
+ const allowedCardSetIds = getContainer(table, boardId, containerId).allowedCardSetIds ?? [];
737
+ if (allowedCardSetIds.length > 0 && !allowedCardSetIds.includes(card.cardSetId)) {
738
+ throw new Error(
739
+ `Card '${componentId}' from card set '${card.cardSetId}' cannot enter container '${containerId}' on board '${boardId}'.`
740
+ );
741
+ }
742
+ }
743
+
744
+ // src/reducer/table/phase-state.ts
745
+ function setActivePlayers(state, activePlayers) {
746
+ return {
747
+ ...state,
748
+ flow: {
749
+ ...state.flow,
750
+ activePlayers
751
+ }
752
+ };
753
+ }
754
+ function setPhaseState(state, phaseState) {
755
+ return {
756
+ ...state,
757
+ phase: phaseState
758
+ };
759
+ }
760
+
761
+ // src/reducer/table/clone.ts
762
+ var cloneRuntimeTableCallCount = 0;
763
+ function cloneRuntimeBoardState(board) {
764
+ if (board.layout !== "generic") {
765
+ return {
766
+ ...board,
767
+ fields: { ...board.fields },
768
+ spaces: Object.fromEntries(
769
+ Object.entries(board.spaces).map(([spaceId, space]) => [
770
+ spaceId,
771
+ {
772
+ ...space,
773
+ fields: { ...space.fields }
774
+ }
775
+ ])
776
+ ),
777
+ relations: board.relations.map((relation) => ({
778
+ ...relation,
779
+ fields: { ...relation.fields }
780
+ })),
781
+ containers: Object.fromEntries(
782
+ Object.entries(board.containers).map(([containerId, container]) => [
783
+ containerId,
784
+ {
785
+ ...container,
786
+ fields: { ...container.fields }
787
+ }
788
+ ])
789
+ ),
790
+ edges: board.edges.map((edge) => ({
791
+ ...edge,
792
+ spaceIds: [...edge.spaceIds],
793
+ fields: { ...edge.fields }
794
+ })),
795
+ vertices: board.vertices.map((vertex) => ({
796
+ ...vertex,
797
+ spaceIds: [...vertex.spaceIds],
798
+ fields: { ...vertex.fields }
799
+ }))
800
+ };
801
+ }
802
+ return {
803
+ ...board,
804
+ fields: { ...board.fields },
805
+ spaces: Object.fromEntries(
806
+ Object.entries(board.spaces).map(([spaceId, space]) => [
807
+ spaceId,
808
+ {
809
+ ...space,
810
+ fields: { ...space.fields }
811
+ }
812
+ ])
813
+ ),
814
+ relations: board.relations.map((relation) => ({
815
+ ...relation,
816
+ fields: { ...relation.fields }
817
+ })),
818
+ containers: Object.fromEntries(
819
+ Object.entries(board.containers).map(([containerId, container]) => [
820
+ containerId,
821
+ {
822
+ ...container,
823
+ fields: { ...container.fields }
824
+ }
825
+ ])
826
+ )
827
+ };
828
+ }
829
+ function cloneRuntimeTable(table) {
830
+ cloneRuntimeTableCallCount += 1;
831
+ return {
832
+ ...table,
833
+ zones: {
834
+ shared: Object.fromEntries(
835
+ Object.entries(table.zones.shared).map(([zoneId, componentIds]) => [
836
+ zoneId,
837
+ [...componentIds]
838
+ ])
839
+ ),
840
+ perPlayer: Object.fromEntries(
841
+ Object.entries(table.zones.perPlayer).map(([zoneId, players]) => [
842
+ zoneId,
843
+ perPlayerMap(players, (componentIds) => [
844
+ ...componentIds
845
+ ])
846
+ ])
847
+ ),
848
+ visibility: { ...table.zones.visibility },
849
+ cardSetIdsByZoneId: table.zones.cardSetIdsByZoneId ? Object.fromEntries(
850
+ Object.entries(table.zones.cardSetIdsByZoneId).map(
851
+ ([zoneId, cardSetIds]) => [zoneId, [...cardSetIds]]
852
+ )
853
+ ) : table.zones.cardSetIdsByZoneId
854
+ },
855
+ decks: Object.fromEntries(
856
+ Object.entries(table.decks).map(([deckId, cards]) => [
857
+ deckId,
858
+ [...cards]
859
+ ])
860
+ ),
861
+ hands: Object.fromEntries(
862
+ Object.entries(table.hands).map(([handId, players]) => [
863
+ handId,
864
+ perPlayerMap(players, (cards) => [...cards])
865
+ ])
866
+ ),
867
+ handVisibility: { ...table.handVisibility },
868
+ pieces: Object.fromEntries(
869
+ Object.entries(table.pieces).map(([pieceId, piece]) => [
870
+ pieceId,
871
+ { ...piece }
872
+ ])
873
+ ),
874
+ componentLocations: { ...table.componentLocations },
875
+ ownerOfCard: { ...table.ownerOfCard },
876
+ visibility: { ...table.visibility },
877
+ resources: perPlayerMap(
878
+ table.resources,
879
+ (resources) => ({ ...resources })
880
+ ),
881
+ boards: {
882
+ ...table.boards,
883
+ byId: Object.fromEntries(
884
+ Object.entries(table.boards.byId).map(([boardId, board]) => [
885
+ boardId,
886
+ cloneRuntimeBoardState(board)
887
+ ])
888
+ ),
889
+ hex: Object.fromEntries(
890
+ Object.entries(table.boards.hex ?? {}).map(([boardId, board]) => [
891
+ boardId,
892
+ cloneRuntimeBoardState(board)
893
+ ])
894
+ ),
895
+ square: Object.fromEntries(
896
+ Object.entries(table.boards.square ?? {}).map(([boardId, board]) => [
897
+ boardId,
898
+ cloneRuntimeBoardState(board)
899
+ ])
900
+ )
901
+ },
902
+ dice: Object.fromEntries(
903
+ Object.entries(table.dice).map(([dieId, die]) => [dieId, { ...die }])
904
+ )
905
+ };
906
+ }
907
+
908
+ // src/reducer/table/component-locations.ts
909
+ function getComponentLocation(table, componentId) {
910
+ return table.componentLocations[componentId];
911
+ }
912
+ function getComponentDeckLocation(table, componentId) {
913
+ const location = getComponentLocation(table, componentId);
914
+ if (location?.type !== "InDeck") {
915
+ return null;
916
+ }
917
+ return {
918
+ componentId,
919
+ deckId: location.deckId,
920
+ cards: table.decks[location.deckId],
921
+ location
922
+ };
923
+ }
924
+ function getComponentHandLocation(table, componentId) {
925
+ const location = getComponentLocation(table, componentId);
926
+ if (location?.type !== "InHand") {
927
+ return null;
928
+ }
929
+ return {
930
+ componentId,
931
+ handId: location.handId,
932
+ playerId: location.playerId,
933
+ cards: ppRead(
934
+ table.hands[location.handId],
935
+ location.playerId
936
+ ),
937
+ location
938
+ };
939
+ }
940
+ function getComponentZoneLocation(table, componentId) {
941
+ const location = getComponentLocation(table, componentId);
942
+ if (location?.type !== "InZone") {
943
+ return null;
944
+ }
945
+ return {
946
+ componentId,
947
+ zoneId: location.zoneId,
948
+ location
949
+ };
950
+ }
951
+ function getComponentSpaceLocation(table, componentId) {
952
+ const location = getComponentLocation(table, componentId);
953
+ if (location?.type !== "OnSpace") {
954
+ return null;
955
+ }
956
+ const boardId = location.boardId;
957
+ const spaceId = location.spaceId;
958
+ return {
959
+ componentId,
960
+ boardId,
961
+ board: getBoard(table, boardId),
962
+ spaceId,
963
+ space: getSpace(table, boardId, spaceId),
964
+ location
965
+ };
966
+ }
967
+ function getComponentContainerLocation(table, componentId) {
968
+ const location = getComponentLocation(table, componentId);
969
+ if (location?.type !== "InContainer") {
970
+ return null;
971
+ }
972
+ const boardId = location.boardId;
973
+ const containerId = location.containerId;
974
+ return {
975
+ componentId,
976
+ boardId,
977
+ board: getBoard(table, boardId),
978
+ containerId,
979
+ container: getContainer(table, boardId, containerId),
980
+ location
981
+ };
982
+ }
983
+ function getComponentEdgeLocation(table, componentId) {
984
+ const location = getComponentLocation(table, componentId);
985
+ if (location?.type !== "OnEdge") {
986
+ return null;
987
+ }
988
+ const boardId = location.boardId;
989
+ const edgeId = location.edgeId;
990
+ return {
991
+ componentId,
992
+ boardId,
993
+ board: getTiledBoard(table, boardId),
994
+ edgeId,
995
+ edge: getEdge(table, boardId, edgeId),
996
+ location
997
+ };
998
+ }
999
+ function getComponentVertexLocation(table, componentId) {
1000
+ const location = getComponentLocation(table, componentId);
1001
+ if (location?.type !== "OnVertex") {
1002
+ return null;
1003
+ }
1004
+ const boardId = location.boardId;
1005
+ const vertexId = location.vertexId;
1006
+ return {
1007
+ componentId,
1008
+ boardId,
1009
+ board: getTiledBoard(table, boardId),
1010
+ vertexId,
1011
+ vertex: getVertex(table, boardId, vertexId),
1012
+ location
1013
+ };
1014
+ }
1015
+ function getComponentSlotLocation(table, componentId) {
1016
+ const location = getComponentLocation(table, componentId);
1017
+ if (location?.type !== "InSlot") {
1018
+ return null;
1019
+ }
1020
+ return {
1021
+ componentId,
1022
+ host: location.host,
1023
+ slotId: location.slotId,
1024
+ location
1025
+ };
1026
+ }
1027
+
1028
+ // src/reducer/table/zone-queries.ts
1029
+ function matchesSlotHost(location, host, slotId) {
1030
+ return location.type === "InSlot" && location.host.kind === host.kind && location.host.id === host.id && (slotId === void 0 || location.slotId === slotId);
1031
+ }
1032
+ function componentPlayerId(table, componentId) {
1033
+ const piece = table.pieces[componentId];
1034
+ if (piece) {
1035
+ return piece.ownerId ?? null;
1036
+ }
1037
+ const die = table.dice[componentId];
1038
+ if (die) {
1039
+ return die.ownerId ?? null;
1040
+ }
1041
+ const owner = table.ownerOfCard[componentId];
1042
+ if (owner !== void 0) {
1043
+ return owner ?? null;
1044
+ }
1045
+ return null;
1046
+ }
1047
+ function componentData(table, componentId) {
1048
+ const piece = table.pieces[componentId];
1049
+ if (piece) {
1050
+ return piece.properties;
1051
+ }
1052
+ const die = table.dice[componentId];
1053
+ if (die) {
1054
+ return die.properties;
1055
+ }
1056
+ const card = table.cards[componentId];
1057
+ if (card) {
1058
+ return card.properties;
1059
+ }
1060
+ return void 0;
1061
+ }
1062
+ function getSharedZoneCards(table, zoneId) {
1063
+ assertZoneScope(
1064
+ table,
1065
+ zoneId,
1066
+ "shared",
1067
+ "getSharedZoneCards",
1068
+ "zoneId"
1069
+ );
1070
+ return [
1071
+ ...ensureArray(table.zones.shared[zoneId] ?? table.decks[zoneId])
1072
+ ];
1073
+ }
1074
+ function getPlayerZoneCards(table, playerId, zoneId) {
1075
+ assertZoneScope(
1076
+ table,
1077
+ zoneId,
1078
+ "perPlayer",
1079
+ "getPlayerZoneCards",
1080
+ "zoneId"
1081
+ );
1082
+ const cards = ppRead(table.zones.perPlayer[zoneId], playerId) ?? ppRead(table.hands[zoneId], playerId);
1083
+ return [
1084
+ ...ensureArray(cards)
1085
+ ];
1086
+ }
1087
+ function collectZoneIds(...sources) {
1088
+ const seen = /* @__PURE__ */ new Set();
1089
+ for (const source of sources) {
1090
+ if (!source) continue;
1091
+ for (const key of Object.keys(source)) {
1092
+ seen.add(key);
1093
+ }
1094
+ }
1095
+ return [...seen];
1096
+ }
1097
+ function getAllSharedZoneCards(table) {
1098
+ const zoneIds = collectZoneIds(
1099
+ table.zones.shared,
1100
+ table.decks
1101
+ );
1102
+ const result = {};
1103
+ for (const zoneId of zoneIds) {
1104
+ result[zoneId] = getSharedZoneCards(
1105
+ table,
1106
+ zoneId
1107
+ );
1108
+ }
1109
+ return result;
1110
+ }
1111
+ function getAllPlayerZoneCards(table, zoneId) {
1112
+ const result = {};
1113
+ for (const playerId of table.playerOrder) {
1114
+ result[playerId] = getPlayerZoneCards(
1115
+ table,
1116
+ playerId,
1117
+ zoneId
1118
+ );
1119
+ }
1120
+ return result;
1121
+ }
1122
+ function getCard(table, cardId) {
1123
+ const card = table.cards[cardId];
1124
+ return {
1125
+ id: card.id,
1126
+ cardType: card.cardType,
1127
+ name: card.name,
1128
+ text: card.text,
1129
+ properties: card.properties
1130
+ };
1131
+ }
1132
+ function getCardsById(table, cardIds) {
1133
+ return Object.fromEntries(
1134
+ cardIds.map((cardId) => [
1135
+ cardId,
1136
+ table.cards[cardId] ? getCard(table, cardId) : void 0
1137
+ ])
1138
+ );
1139
+ }
1140
+ function getSharedZoneCardCollection(table, zoneId) {
1141
+ const cardIds = getSharedZoneCards(table, zoneId);
1142
+ return {
1143
+ cardIds,
1144
+ cardsById: getCardsById(table, cardIds)
1145
+ };
1146
+ }
1147
+ function getPlayerZoneCardCollection(table, playerId, zoneId) {
1148
+ const cardIds = getPlayerZoneCards(table, playerId, zoneId);
1149
+ return {
1150
+ cardIds,
1151
+ cardsById: getCardsById(table, cardIds)
1152
+ };
1153
+ }
1154
+ function getSlotOccupants(table, host, slotId) {
1155
+ return orderedComponentIdsForLocation(
1156
+ table,
1157
+ (location) => matchesSlotHost(location, host, slotId)
1158
+ ).map((componentId) => ({
1159
+ pieceId: componentId,
1160
+ playerId: componentPlayerId(
1161
+ table,
1162
+ componentId
1163
+ ),
1164
+ slotId,
1165
+ data: componentData(table, componentId)
1166
+ }));
1167
+ }
1168
+ function getSlotOccupantsByHost(table, host) {
1169
+ const occupantsBySlot = {};
1170
+ orderedComponentIdsForLocation(
1171
+ table,
1172
+ (location) => matchesSlotHost(location, host)
1173
+ ).forEach((componentId) => {
1174
+ const location = table.componentLocations[componentId];
1175
+ if (!location || !matchesSlotHost(location, host)) {
1176
+ return;
1177
+ }
1178
+ const slotOccupant = {
1179
+ pieceId: componentId,
1180
+ playerId: componentPlayerId(
1181
+ table,
1182
+ componentId
1183
+ ),
1184
+ slotId: location.slotId,
1185
+ data: componentData(table, componentId)
1186
+ };
1187
+ (occupantsBySlot[location.slotId] ??= []).push(slotOccupant);
1188
+ });
1189
+ return occupantsBySlot;
1190
+ }
1191
+ function getCardOwner(table, cardId) {
1192
+ return table.ownerOfCard[cardId];
1193
+ }
1194
+ function getCardVisibility(table, cardId) {
1195
+ return table.visibility[cardId];
1196
+ }
1197
+
1198
+ // src/reducer/table/resource-ops.ts
1199
+ function getPlayerOrder(table) {
1200
+ return table.playerOrder;
1201
+ }
1202
+ function getPlayerResources(table, playerId) {
1203
+ return perPlayerGet(
1204
+ table.resources,
1205
+ playerId
1206
+ );
1207
+ }
1208
+ function getPlayerResourceAmount(table, playerId, resourceId) {
1209
+ const playerResources = perPlayerGet(
1210
+ table.resources,
1211
+ playerId
1212
+ );
1213
+ if (!playerResources) return 0;
1214
+ const value = playerResources[resourceId];
1215
+ return typeof value === "number" ? value : 0;
1216
+ }
1217
+ function getPlayerResourceTotal(table, playerId) {
1218
+ const playerResources = perPlayerGet(
1219
+ table.resources,
1220
+ playerId
1221
+ );
1222
+ if (!playerResources) return 0;
1223
+ let total = 0;
1224
+ for (const key of Object.keys(playerResources)) {
1225
+ const value = playerResources[key];
1226
+ if (typeof value === "number") total += value;
1227
+ }
1228
+ return total;
1229
+ }
1230
+ function getNextPlayerInOrder(table, playerId) {
1231
+ const order = table.playerOrder;
1232
+ if (order.length === 0) return null;
1233
+ const idx = order.indexOf(playerId);
1234
+ if (idx < 0) return null;
1235
+ return order[(idx + 1) % order.length] ?? null;
1236
+ }
1237
+ function forEachResourceEntry(amounts, visit) {
1238
+ for (const resourceId of Object.keys(amounts)) {
1239
+ const amount = amounts[resourceId];
1240
+ if (typeof amount !== "number" || amount === 0) continue;
1241
+ visit(resourceId, amount);
1242
+ }
1243
+ }
1244
+ function canAffordResources(table, playerId, amounts) {
1245
+ for (const resourceId of Object.keys(amounts)) {
1246
+ const required = amounts[resourceId];
1247
+ if (typeof required !== "number" || required <= 0) continue;
1248
+ if (getPlayerResourceAmount(table, playerId, resourceId) < required) {
1249
+ return false;
1250
+ }
1251
+ }
1252
+ return true;
1253
+ }
1254
+ function getMissingResources(table, playerId, amounts) {
1255
+ const missing = {};
1256
+ for (const resourceId of Object.keys(amounts)) {
1257
+ const required = amounts[resourceId];
1258
+ if (typeof required !== "number" || required <= 0) continue;
1259
+ const have = getPlayerResourceAmount(table, playerId, resourceId);
1260
+ if (have < required) missing[resourceId] = required - have;
1261
+ }
1262
+ return missing;
1263
+ }
1264
+ function writePlayerResources(table, playerId, nextForPlayer) {
1265
+ table.resources = perPlayerSet(
1266
+ table.resources,
1267
+ playerId,
1268
+ nextForPlayer
1269
+ );
1270
+ }
1271
+ function addPlayerResources(table, playerId, amounts) {
1272
+ const nextTable = { ...table };
1273
+ addPlayerResourcesInPlace(nextTable, playerId, amounts);
1274
+ return nextTable;
1275
+ }
1276
+ function addPlayerResourcesInPlace(table, playerId, amounts) {
1277
+ const prev = perPlayerGet(
1278
+ table.resources,
1279
+ playerId
1280
+ ) ?? {};
1281
+ const next = { ...prev };
1282
+ forEachResourceEntry(amounts, (resourceId, amount) => {
1283
+ if (amount < 0) {
1284
+ throw new Error(
1285
+ `addPlayerResources: negative amount for resource '${resourceId}'. Use spendPlayerResources or transferPlayerResources instead.`
1286
+ );
1287
+ }
1288
+ next[resourceId] = (next[resourceId] ?? 0) + amount;
1289
+ });
1290
+ writePlayerResources(table, playerId, next);
1291
+ }
1292
+ function spendPlayerResources(table, playerId, amounts) {
1293
+ const nextTable = { ...table };
1294
+ spendPlayerResourcesInPlace(nextTable, playerId, amounts);
1295
+ return nextTable;
1296
+ }
1297
+ function spendPlayerResourcesInPlace(table, playerId, amounts) {
1298
+ if (!canAffordResources(table, playerId, amounts)) {
1299
+ const missing = getMissingResources(table, playerId, amounts);
1300
+ throw new Error(
1301
+ `spendPlayerResources: player '${playerId}' cannot afford ${JSON.stringify(
1302
+ missing
1303
+ )}. Check canAfford in your validate step first.`
1304
+ );
1305
+ }
1306
+ const prev = perPlayerGet(
1307
+ table.resources,
1308
+ playerId
1309
+ ) ?? {};
1310
+ const next = { ...prev };
1311
+ forEachResourceEntry(amounts, (resourceId, amount) => {
1312
+ if (amount < 0) {
1313
+ throw new Error(
1314
+ `spendPlayerResources: negative amount for resource '${resourceId}'. Pass positive amounts \u2014 the op deducts them from the player.`
1315
+ );
1316
+ }
1317
+ next[resourceId] = Math.max(0, (next[resourceId] ?? 0) - amount);
1318
+ });
1319
+ writePlayerResources(table, playerId, next);
1320
+ }
1321
+ function transferPlayerResources(table, fromPlayerId, toPlayerId, amounts) {
1322
+ const nextTable = { ...table };
1323
+ transferPlayerResourcesInPlace(nextTable, fromPlayerId, toPlayerId, amounts);
1324
+ return nextTable;
1325
+ }
1326
+ function transferPlayerResourcesInPlace(table, fromPlayerId, toPlayerId, amounts) {
1327
+ spendPlayerResourcesInPlace(table, fromPlayerId, amounts);
1328
+ addPlayerResourcesInPlace(table, toPlayerId, amounts);
1329
+ }
1330
+ function setPlayerResource(table, playerId, resourceId, amount) {
1331
+ const nextTable = { ...table };
1332
+ setPlayerResourceInPlace(nextTable, playerId, resourceId, amount);
1333
+ return nextTable;
1334
+ }
1335
+ function setPlayerResourceInPlace(table, playerId, resourceId, amount) {
1336
+ if (!Number.isFinite(amount) || amount < 0) {
1337
+ throw new Error(
1338
+ `setPlayerResource: amount must be a non-negative finite number, got ${amount}.`
1339
+ );
1340
+ }
1341
+ const prev = perPlayerGet(
1342
+ table.resources,
1343
+ playerId
1344
+ ) ?? {};
1345
+ writePlayerResources(table, playerId, {
1346
+ ...prev,
1347
+ [resourceId]: amount
1348
+ });
1349
+ }
1350
+
1351
+ // src/reducer/table/component-mutations.ts
1352
+ function reindexSpaceOccupants(table, boardId, spaceId) {
1353
+ getComponentsOnSpace(table, boardId, spaceId).forEach(
1354
+ (componentId, index) => {
1355
+ const location = table.componentLocations[componentId];
1356
+ if (location?.type === "OnSpace") {
1357
+ table.componentLocations[componentId] = {
1358
+ ...location,
1359
+ position: index
1360
+ };
1361
+ }
1362
+ }
1363
+ );
1364
+ }
1365
+ function reindexContainerOccupants(table, boardId, containerId) {
1366
+ getComponentsInContainer(table, boardId, containerId).forEach(
1367
+ (componentId, index) => {
1368
+ const location = table.componentLocations[componentId];
1369
+ if (location?.type === "InContainer") {
1370
+ table.componentLocations[componentId] = {
1371
+ ...location,
1372
+ position: index
1373
+ };
1374
+ }
1375
+ }
1376
+ );
1377
+ }
1378
+ function reindexEdgeOccupants(table, boardId, edgeId) {
1379
+ orderedComponentIdsForLocation(
1380
+ table,
1381
+ (location) => location.type === "OnEdge" && location.boardId === boardId && location.edgeId === edgeId
1382
+ ).forEach((componentId, index) => {
1383
+ const location = table.componentLocations[componentId];
1384
+ if (location?.type === "OnEdge") {
1385
+ table.componentLocations[componentId] = {
1386
+ ...location,
1387
+ position: index
1388
+ };
1389
+ }
1390
+ });
1391
+ }
1392
+ function reindexVertexOccupants(table, boardId, vertexId) {
1393
+ orderedComponentIdsForLocation(
1394
+ table,
1395
+ (location) => location.type === "OnVertex" && location.boardId === boardId && location.vertexId === vertexId
1396
+ ).forEach((componentId, index) => {
1397
+ const location = table.componentLocations[componentId];
1398
+ if (location?.type === "OnVertex") {
1399
+ table.componentLocations[componentId] = {
1400
+ ...location,
1401
+ position: index
1402
+ };
1403
+ }
1404
+ });
1405
+ }
1406
+ function reindexSlotOccupants(table, host, slotId) {
1407
+ orderedComponentIdsForLocation(
1408
+ table,
1409
+ (location) => location.type === "InSlot" && location.host.kind === host.kind && location.host.id === host.id && location.slotId === slotId
1410
+ ).forEach((componentId, index) => {
1411
+ const location = table.componentLocations[componentId];
1412
+ if (location?.type === "InSlot") {
1413
+ table.componentLocations[componentId] = {
1414
+ ...location,
1415
+ position: index
1416
+ };
1417
+ }
1418
+ });
1419
+ }
1420
+ function removeComponentFromCurrentLocation(table, componentId) {
1421
+ const currentLocation = table.componentLocations[componentId];
1422
+ if (!currentLocation) {
1423
+ return;
1424
+ }
1425
+ if (currentLocation.type === "InDeck") {
1426
+ const nextCards = ensureArray(table.decks[currentLocation.deckId]).filter(
1427
+ (candidate) => candidate !== componentId
1428
+ );
1429
+ syncSharedZoneWithDeck(
1430
+ table,
1431
+ currentLocation.deckId,
1432
+ nextCards
1433
+ );
1434
+ nextCards.forEach((cardId, index) => {
1435
+ const location = table.componentLocations[cardId];
1436
+ if (location?.type === "InDeck") {
1437
+ table.componentLocations[cardId] = {
1438
+ ...location,
1439
+ position: index
1440
+ };
1441
+ }
1442
+ });
1443
+ return;
1444
+ }
1445
+ if (currentLocation.type === "InHand") {
1446
+ const nextCards = ensureArray(
1447
+ ppRead(table.hands[currentLocation.handId], currentLocation.playerId)
1448
+ ).filter((candidate) => candidate !== componentId);
1449
+ syncPlayerZoneWithHand(
1450
+ table,
1451
+ currentLocation.handId,
1452
+ currentLocation.playerId,
1453
+ nextCards
1454
+ );
1455
+ nextCards.forEach((cardId, index) => {
1456
+ const location = table.componentLocations[cardId];
1457
+ if (location?.type === "InHand") {
1458
+ table.componentLocations[cardId] = {
1459
+ ...location,
1460
+ position: index
1461
+ };
1462
+ }
1463
+ });
1464
+ return;
1465
+ }
1466
+ if (currentLocation.type === "OnSpace") {
1467
+ delete table.componentLocations[componentId];
1468
+ reindexSpaceOccupants(
1469
+ table,
1470
+ currentLocation.boardId,
1471
+ currentLocation.spaceId
1472
+ );
1473
+ return;
1474
+ }
1475
+ if (currentLocation.type === "InZone") {
1476
+ if (currentLocation.zoneId in table.zones.shared) {
1477
+ const nextComponents = ensureArray(
1478
+ table.zones.shared[currentLocation.zoneId]
1479
+ ).filter((candidate) => candidate !== componentId);
1480
+ syncSharedZoneWithDeck(
1481
+ table,
1482
+ currentLocation.zoneId,
1483
+ nextComponents
1484
+ );
1485
+ nextComponents.forEach((currentComponentId, index) => {
1486
+ const location = table.componentLocations[currentComponentId];
1487
+ if (location?.type === "InZone") {
1488
+ table.componentLocations[currentComponentId] = {
1489
+ ...location,
1490
+ position: index
1491
+ };
1492
+ }
1493
+ });
1494
+ }
1495
+ delete table.componentLocations[componentId];
1496
+ return;
1497
+ }
1498
+ if (currentLocation.type === "InContainer") {
1499
+ delete table.componentLocations[componentId];
1500
+ reindexContainerOccupants(
1501
+ table,
1502
+ currentLocation.boardId,
1503
+ currentLocation.containerId
1504
+ );
1505
+ return;
1506
+ }
1507
+ if (currentLocation.type === "OnEdge") {
1508
+ delete table.componentLocations[componentId];
1509
+ reindexEdgeOccupants(
1510
+ table,
1511
+ currentLocation.boardId,
1512
+ currentLocation.edgeId
1513
+ );
1514
+ return;
1515
+ }
1516
+ if (currentLocation.type === "OnVertex") {
1517
+ delete table.componentLocations[componentId];
1518
+ reindexVertexOccupants(
1519
+ table,
1520
+ currentLocation.boardId,
1521
+ currentLocation.vertexId
1522
+ );
1523
+ return;
1524
+ }
1525
+ if (currentLocation.type === "InSlot") {
1526
+ delete table.componentLocations[componentId];
1527
+ reindexSlotOccupants(table, currentLocation.host, currentLocation.slotId);
1528
+ return;
1529
+ }
1530
+ delete table.componentLocations[componentId];
1531
+ }
1532
+ function moveComponentToSpace(table, componentId, boardId, spaceId) {
1533
+ const nextTable = cloneRuntimeTable(table);
1534
+ moveComponentToSpaceInPlace(nextTable, componentId, boardId, spaceId);
1535
+ return nextTable;
1536
+ }
1537
+ function moveComponentToSpaceInPlace(table, componentId, boardId, spaceId) {
1538
+ const position = getComponentsOnSpace(table, boardId, spaceId).length;
1539
+ removeComponentFromCurrentLocation(table, componentId);
1540
+ table.componentLocations[componentId] = {
1541
+ type: "OnSpace",
1542
+ boardId,
1543
+ spaceId,
1544
+ position
1545
+ };
1546
+ }
1547
+ function moveComponentToContainer(table, componentId, boardId, containerId) {
1548
+ const nextTable = cloneRuntimeTable(table);
1549
+ moveComponentToContainerInPlace(nextTable, componentId, boardId, containerId);
1550
+ return nextTable;
1551
+ }
1552
+ function moveComponentToContainerInPlace(table, componentId, boardId, containerId) {
1553
+ assertCardAllowedInContainer(table, boardId, containerId, componentId);
1554
+ const position = getComponentsInContainer(table, boardId, containerId).length;
1555
+ removeComponentFromCurrentLocation(table, componentId);
1556
+ table.componentLocations[componentId] = {
1557
+ type: "InContainer",
1558
+ boardId,
1559
+ containerId,
1560
+ position
1561
+ };
1562
+ }
1563
+ function moveComponentToDetached(table, componentId) {
1564
+ const nextTable = cloneRuntimeTable(table);
1565
+ moveComponentToDetachedInPlace(nextTable, componentId);
1566
+ return nextTable;
1567
+ }
1568
+ function moveComponentToDetachedInPlace(table, componentId) {
1569
+ removeComponentFromCurrentLocation(table, componentId);
1570
+ table.componentLocations[componentId] = { type: "Detached" };
1571
+ }
1572
+ function moveComponentToEdge(table, componentId, boardId, edgeId) {
1573
+ const nextTable = cloneRuntimeTable(table);
1574
+ moveComponentToEdgeInPlace(nextTable, componentId, boardId, edgeId);
1575
+ return nextTable;
1576
+ }
1577
+ function moveComponentToEdgeInPlace(table, componentId, boardId, edgeId) {
1578
+ getEdge(table, boardId, edgeId);
1579
+ const position = getComponentsOnEdge(table, boardId, edgeId).length;
1580
+ removeComponentFromCurrentLocation(table, componentId);
1581
+ table.componentLocations[componentId] = {
1582
+ type: "OnEdge",
1583
+ boardId,
1584
+ edgeId,
1585
+ position
1586
+ };
1587
+ }
1588
+ function moveComponentToVertex(table, componentId, boardId, vertexId) {
1589
+ const nextTable = cloneRuntimeTable(table);
1590
+ moveComponentToVertexInPlace(nextTable, componentId, boardId, vertexId);
1591
+ return nextTable;
1592
+ }
1593
+ function moveComponentToVertexInPlace(table, componentId, boardId, vertexId) {
1594
+ getVertex(table, boardId, vertexId);
1595
+ const position = getComponentsOnVertex(table, boardId, vertexId).length;
1596
+ removeComponentFromCurrentLocation(table, componentId);
1597
+ table.componentLocations[componentId] = {
1598
+ type: "OnVertex",
1599
+ boardId,
1600
+ vertexId,
1601
+ position
1602
+ };
1603
+ }
1604
+
1605
+ // src/reducer/table/card-mutations.ts
1606
+ function shufflePlayerZoneCards(table, zoneId, playerId, nextCards) {
1607
+ if (nextCards === void 0) {
1608
+ const fromZone = ppRead(
1609
+ table.zones.perPlayer[zoneId] ?? table.hands[zoneId],
1610
+ playerId
1611
+ );
1612
+ return [...ensureArray(fromZone)];
1613
+ }
1614
+ table.hands[zoneId] = ppWrite(table.hands[zoneId], playerId, [
1615
+ ...nextCards
1616
+ ]);
1617
+ table.zones.perPlayer[zoneId] = ppWrite(
1618
+ table.zones.perPlayer[zoneId],
1619
+ playerId,
1620
+ [...nextCards]
1621
+ );
1622
+ return [...nextCards];
1623
+ }
1624
+ function appendToDeck(table, deckId, cardId, playedBy = null, position = "bottom") {
1625
+ const nextTable = cloneRuntimeTable(table);
1626
+ appendToDeckInPlace(nextTable, deckId, cardId, playedBy, position);
1627
+ return nextTable;
1628
+ }
1629
+ function appendToDeckInPlace(table, deckId, cardId, playedBy = null, position = "bottom") {
1630
+ assertZoneScope(
1631
+ table,
1632
+ deckId,
1633
+ "shared",
1634
+ "addCardToSharedZone",
1635
+ "zoneId"
1636
+ );
1637
+ const deckCards = [...ensureArray(table.decks[deckId])];
1638
+ assertCardAllowedInZone(table, deckId, cardId);
1639
+ if (position === "top") {
1640
+ deckCards.unshift(cardId);
1641
+ } else {
1642
+ deckCards.push(cardId);
1643
+ }
1644
+ syncSharedZoneWithDeck(table, deckId, deckCards);
1645
+ for (const [index, currentCardId] of deckCards.entries()) {
1646
+ if (currentCardId === cardId) {
1647
+ table.componentLocations[currentCardId] = {
1648
+ type: "InDeck",
1649
+ deckId,
1650
+ playedBy,
1651
+ position: index
1652
+ };
1653
+ continue;
1654
+ }
1655
+ const existing = table.componentLocations[currentCardId];
1656
+ table.componentLocations[currentCardId] = {
1657
+ type: "InDeck",
1658
+ deckId,
1659
+ playedBy: existing?.type === "InDeck" ? existing.playedBy : null,
1660
+ position: index
1661
+ };
1662
+ }
1663
+ table.ownerOfCard[cardId] = playedBy;
1664
+ table.visibility[cardId] = {
1665
+ faceUp: true
1666
+ };
1667
+ }
1668
+ function removeFromDeck(table, deckId, cardId) {
1669
+ const nextTable = cloneRuntimeTable(table);
1670
+ removeFromDeckInPlace(nextTable, deckId, cardId);
1671
+ return nextTable;
1672
+ }
1673
+ function removeFromDeckInPlace(table, deckId, cardId) {
1674
+ assertZoneScope(
1675
+ table,
1676
+ deckId,
1677
+ "shared",
1678
+ "removeCardFromSharedZone",
1679
+ "zoneId"
1680
+ );
1681
+ const remaining = ensureArray(table.decks[deckId]).filter(
1682
+ (candidate) => candidate !== cardId
1683
+ );
1684
+ syncSharedZoneWithDeck(table, deckId, remaining);
1685
+ for (const [index, currentCardId] of remaining.entries()) {
1686
+ const currentLocation = table.componentLocations[currentCardId];
1687
+ if (currentLocation?.type === "InDeck") {
1688
+ table.componentLocations[currentCardId] = {
1689
+ ...currentLocation,
1690
+ position: index
1691
+ };
1692
+ }
1693
+ }
1694
+ }
1695
+ function computeVisibilityForPlayerZone(table, zoneId, playerId) {
1696
+ const mode = table.handVisibility[zoneId];
1697
+ if (mode === "all" || mode === "public") {
1698
+ return { faceUp: true };
1699
+ }
1700
+ return { faceUp: false, visibleTo: [playerId] };
1701
+ }
1702
+ function moveFromHandToDeck(options) {
1703
+ const nextTable = cloneRuntimeTable(options.table);
1704
+ moveFromHandToDeckInPlace({
1705
+ ...options,
1706
+ table: nextTable
1707
+ });
1708
+ return nextTable;
1709
+ }
1710
+ function moveFromHandToDeckInPlace(options) {
1711
+ assertZoneScope(
1712
+ options.table,
1713
+ options.handId,
1714
+ "perPlayer",
1715
+ "moveCardFromPlayerZoneToSharedZone",
1716
+ "fromZoneId"
1717
+ );
1718
+ assertZoneScope(
1719
+ options.table,
1720
+ options.deckId,
1721
+ "shared",
1722
+ "moveCardFromPlayerZoneToSharedZone",
1723
+ "toZoneId"
1724
+ );
1725
+ const currentHand = ensureArray(
1726
+ ppRead(options.table.hands[options.handId], options.playerId)
1727
+ ).filter((candidate) => candidate !== options.cardId);
1728
+ syncPlayerZoneWithHand(
1729
+ options.table,
1730
+ options.handId,
1731
+ options.playerId,
1732
+ currentHand
1733
+ );
1734
+ for (const [index, currentCardId] of currentHand.entries()) {
1735
+ options.table.componentLocations[currentCardId] = {
1736
+ type: "InHand",
1737
+ handId: options.handId,
1738
+ playerId: options.playerId,
1739
+ position: index
1740
+ };
1741
+ }
1742
+ options.table.ownerOfCard[options.cardId] = options.playedBy ?? options.playerId;
1743
+ options.table.visibility[options.cardId] = {
1744
+ faceUp: true
1745
+ };
1746
+ appendToDeckInPlace(
1747
+ options.table,
1748
+ options.deckId,
1749
+ options.cardId,
1750
+ options.playedBy ?? options.playerId,
1751
+ options.position ?? "bottom"
1752
+ );
1753
+ }
1754
+ function moveCardFromPlayerZoneToSharedZone(options) {
1755
+ return moveFromHandToDeck({
1756
+ table: options.table,
1757
+ playerId: options.playerId,
1758
+ handId: options.fromZoneId,
1759
+ cardId: options.cardId,
1760
+ deckId: options.toZoneId,
1761
+ playedBy: options.playedBy,
1762
+ position: options.position
1763
+ });
1764
+ }
1765
+ function moveCardFromPlayerZoneToSharedZoneInPlace(options) {
1766
+ moveFromHandToDeckInPlace({
1767
+ table: options.table,
1768
+ playerId: options.playerId,
1769
+ handId: options.fromZoneId,
1770
+ cardId: options.cardId,
1771
+ deckId: options.toZoneId,
1772
+ playedBy: options.playedBy,
1773
+ position: options.position
1774
+ });
1775
+ }
1776
+ function dealCardsBetweenPlayerZones(options) {
1777
+ const nextTable = cloneRuntimeTable(options.table);
1778
+ dealCardsBetweenPlayerZonesInPlace({
1779
+ ...options,
1780
+ table: nextTable
1781
+ });
1782
+ return nextTable;
1783
+ }
1784
+ function dealCardsBetweenPlayerZonesInPlace(options) {
1785
+ let nextTable = options.table;
1786
+ for (let i = 0; i < options.count; i += 1) {
1787
+ const sourceCards = ensureArray(
1788
+ ppRead(
1789
+ nextTable.zones.perPlayer[options.fromZoneId] ?? nextTable.hands[options.fromZoneId],
1790
+ options.playerId
1791
+ )
1792
+ );
1793
+ const topCardId = sourceCards[0];
1794
+ if (topCardId === void 0) {
1795
+ break;
1796
+ }
1797
+ moveCardBetweenPlayerZonesInPlace({
1798
+ table: nextTable,
1799
+ playerId: options.playerId,
1800
+ fromZoneId: options.fromZoneId,
1801
+ toZoneId: options.toZoneId,
1802
+ cardId: topCardId
1803
+ });
1804
+ }
1805
+ }
1806
+ function moveCardFromSharedZoneToPlayerZone(options) {
1807
+ const nextTable = cloneRuntimeTable(options.table);
1808
+ moveCardFromSharedZoneToPlayerZoneInPlace({
1809
+ ...options,
1810
+ table: nextTable
1811
+ });
1812
+ return nextTable;
1813
+ }
1814
+ function moveCardFromSharedZoneToPlayerZoneInPlace(options) {
1815
+ assertZoneScope(
1816
+ options.table,
1817
+ options.fromZoneId,
1818
+ "shared",
1819
+ "moveCardFromSharedZoneToPlayerZone",
1820
+ "fromZoneId"
1821
+ );
1822
+ assertZoneScope(
1823
+ options.table,
1824
+ options.toZoneId,
1825
+ "perPlayer",
1826
+ "moveCardFromSharedZoneToPlayerZone",
1827
+ "toZoneId"
1828
+ );
1829
+ const sourceCards = ensureArray(
1830
+ options.table.zones.shared[options.fromZoneId] ?? options.table.decks[options.fromZoneId]
1831
+ );
1832
+ if (!sourceCards.includes(options.cardId)) {
1833
+ throw new Error(
1834
+ `Card '${String(options.cardId)}' is not in shared zone '${String(
1835
+ options.fromZoneId
1836
+ )}'.`
1837
+ );
1838
+ }
1839
+ assertCardAllowedInZone(
1840
+ options.table,
1841
+ options.toZoneId,
1842
+ options.cardId
1843
+ );
1844
+ const remainingSource = sourceCards.filter(
1845
+ (candidate) => candidate !== options.cardId
1846
+ );
1847
+ syncSharedZoneWithDeck(options.table, options.fromZoneId, remainingSource);
1848
+ for (const [index, currentCardId] of remainingSource.entries()) {
1849
+ const existing = options.table.componentLocations[currentCardId];
1850
+ if (existing?.type === "InDeck") {
1851
+ options.table.componentLocations[currentCardId] = {
1852
+ ...existing,
1853
+ position: index
1854
+ };
1855
+ }
1856
+ }
1857
+ const destinationCards = ensureArray(
1858
+ ppRead(
1859
+ options.table.zones.perPlayer[options.toZoneId] ?? options.table.hands[options.toZoneId],
1860
+ options.playerId
1861
+ )
1862
+ );
1863
+ const nextDestination = options.position === "top" ? [options.cardId, ...destinationCards] : [...destinationCards, options.cardId];
1864
+ syncPlayerZoneWithHand(
1865
+ options.table,
1866
+ options.toZoneId,
1867
+ options.playerId,
1868
+ nextDestination
1869
+ );
1870
+ for (const [index, currentCardId] of nextDestination.entries()) {
1871
+ options.table.componentLocations[currentCardId] = {
1872
+ type: "InHand",
1873
+ handId: options.toZoneId,
1874
+ playerId: options.playerId,
1875
+ position: index
1876
+ };
1877
+ }
1878
+ options.table.ownerOfCard[options.cardId] = options.playerId;
1879
+ options.table.visibility[options.cardId] = computeVisibilityForPlayerZone(
1880
+ options.table,
1881
+ options.toZoneId,
1882
+ options.playerId
1883
+ );
1884
+ }
1885
+ function moveCardBetweenPlayerZones(options) {
1886
+ const nextTable = cloneRuntimeTable(options.table);
1887
+ moveCardBetweenPlayerZonesInPlace({
1888
+ ...options,
1889
+ table: nextTable
1890
+ });
1891
+ return nextTable;
1892
+ }
1893
+ function moveCardBetweenPlayerZonesInPlace(options) {
1894
+ assertZoneScope(
1895
+ options.table,
1896
+ options.fromZoneId,
1897
+ "perPlayer",
1898
+ "moveCardBetweenPlayerZones",
1899
+ "fromZoneId"
1900
+ );
1901
+ assertZoneScope(
1902
+ options.table,
1903
+ options.toZoneId,
1904
+ "perPlayer",
1905
+ "moveCardBetweenPlayerZones",
1906
+ "toZoneId"
1907
+ );
1908
+ const sourceCards = ensureArray(
1909
+ ppRead(
1910
+ options.table.zones.perPlayer[options.fromZoneId] ?? options.table.hands[options.fromZoneId],
1911
+ options.playerId
1912
+ )
1913
+ );
1914
+ if (!sourceCards.includes(options.cardId)) {
1915
+ throw new Error(
1916
+ `Card '${String(options.cardId)}' is not in zone '${String(
1917
+ options.fromZoneId
1918
+ )}' for player '${String(options.playerId)}'.`
1919
+ );
1920
+ }
1921
+ assertCardAllowedInZone(
1922
+ options.table,
1923
+ options.toZoneId,
1924
+ options.cardId
1925
+ );
1926
+ const remainingSource = sourceCards.filter(
1927
+ (candidate) => candidate !== options.cardId
1928
+ );
1929
+ syncPlayerZoneWithHand(
1930
+ options.table,
1931
+ options.fromZoneId,
1932
+ options.playerId,
1933
+ remainingSource
1934
+ );
1935
+ for (const [index, currentCardId] of remainingSource.entries()) {
1936
+ const existing = options.table.componentLocations[currentCardId];
1937
+ if (existing?.type === "InHand") {
1938
+ options.table.componentLocations[currentCardId] = {
1939
+ ...existing,
1940
+ position: index
1941
+ };
1942
+ }
1943
+ }
1944
+ const destinationCards = ensureArray(
1945
+ ppRead(
1946
+ options.table.zones.perPlayer[options.toZoneId] ?? options.table.hands[options.toZoneId],
1947
+ options.playerId
1948
+ )
1949
+ );
1950
+ const nextDestination = options.position === "top" ? [options.cardId, ...destinationCards] : [...destinationCards, options.cardId];
1951
+ syncPlayerZoneWithHand(
1952
+ options.table,
1953
+ options.toZoneId,
1954
+ options.playerId,
1955
+ nextDestination
1956
+ );
1957
+ for (const [index, currentCardId] of nextDestination.entries()) {
1958
+ options.table.componentLocations[currentCardId] = {
1959
+ type: "InHand",
1960
+ handId: options.toZoneId,
1961
+ playerId: options.playerId,
1962
+ position: index
1963
+ };
1964
+ }
1965
+ options.table.visibility[options.cardId] = computeVisibilityForPlayerZone(
1966
+ options.table,
1967
+ options.toZoneId,
1968
+ options.playerId
1969
+ );
1970
+ }
1971
+ function moveCardBetweenSharedZones(options) {
1972
+ const nextTable = cloneRuntimeTable(options.table);
1973
+ moveCardBetweenSharedZonesInPlace({
1974
+ ...options,
1975
+ table: nextTable
1976
+ });
1977
+ return nextTable;
1978
+ }
1979
+ function moveCardBetweenSharedZonesInPlace(options) {
1980
+ removeFromDeckInPlace(options.table, options.fromZoneId, options.cardId);
1981
+ appendToDeckInPlace(
1982
+ options.table,
1983
+ options.toZoneId,
1984
+ options.cardId,
1985
+ options.playedBy ?? null,
1986
+ options.position ?? "bottom"
1987
+ );
1988
+ }
1989
+ function removeCardFromSharedZone(table, deckId, cardId) {
1990
+ return removeFromDeck(table, deckId, cardId);
1991
+ }
1992
+ function removeCardFromSharedZoneInPlace(table, deckId, cardId) {
1993
+ removeFromDeckInPlace(table, deckId, cardId);
1994
+ }
1995
+ function addCardToSharedZone(table, deckId, cardId, playedBy = null, position = "bottom") {
1996
+ return appendToDeck(table, deckId, cardId, playedBy, position);
1997
+ }
1998
+ function addCardToSharedZoneInPlace(table, deckId, cardId, playedBy = null, position = "bottom") {
1999
+ appendToDeckInPlace(table, deckId, cardId, playedBy, position);
2000
+ }
2001
+ function dealCardsFromDeckToHand(table, fromZoneId, playerId, toZoneId, count) {
2002
+ const nextTable = cloneRuntimeTable(table);
2003
+ dealCardsFromDeckToHandInPlace(
2004
+ nextTable,
2005
+ fromZoneId,
2006
+ playerId,
2007
+ toZoneId,
2008
+ count
2009
+ );
2010
+ return nextTable;
2011
+ }
2012
+ function dealCardsFromDeckToHandInPlace(table, fromZoneId, playerId, toZoneId, count) {
2013
+ const publicHands = new Set(
2014
+ Object.entries(table.handVisibility).filter(([, mode]) => mode === "all" || mode === "public").map(([handId]) => handId)
2015
+ );
2016
+ for (let index = 0; index < count; index += 1) {
2017
+ const nextCard = ensureArray(table.decks[fromZoneId])[0];
2018
+ if (!nextCard) {
2019
+ break;
2020
+ }
2021
+ table.decks[fromZoneId] = ensureArray(table.decks[fromZoneId]).slice(
2022
+ 1
2023
+ );
2024
+ table.zones.shared[fromZoneId] = [
2025
+ ...ensureArray(table.decks[fromZoneId])
2026
+ ];
2027
+ const prevHand = ppRead(table.hands[toZoneId], playerId);
2028
+ const nextHand = [...ensureArray(prevHand), nextCard];
2029
+ assertCardAllowedInZone(table, toZoneId, nextCard);
2030
+ table.hands[toZoneId] = ppWrite(
2031
+ table.hands[toZoneId],
2032
+ playerId,
2033
+ nextHand
2034
+ );
2035
+ table.zones.perPlayer[toZoneId] = ppWrite(
2036
+ table.zones.perPlayer[toZoneId],
2037
+ playerId,
2038
+ [...nextHand]
2039
+ );
2040
+ table.componentLocations[nextCard] = {
2041
+ type: "InHand",
2042
+ handId: toZoneId,
2043
+ playerId,
2044
+ position: nextHand.length - 1
2045
+ };
2046
+ table.ownerOfCard[nextCard] = playerId;
2047
+ table.visibility[nextCard] = publicHands.has(toZoneId) ? {
2048
+ faceUp: true
2049
+ } : {
2050
+ faceUp: false,
2051
+ visibleTo: [playerId]
2052
+ };
2053
+ }
2054
+ }
2055
+
2056
+ // src/reducer/rng.ts
2057
+ function nextRandomInt(bound, rng) {
2058
+ if (bound <= 0) {
2059
+ throw new Error("Random bound must be positive.");
2060
+ }
2061
+ const seed = rng.seed ?? 1;
2062
+ const raw = hashSeedCursor(seed, rng.cursor);
2063
+ const value = raw % bound;
2064
+ return [
2065
+ value,
2066
+ {
2067
+ ...rng,
2068
+ cursor: rng.cursor + 1,
2069
+ trace: [
2070
+ ...rng.trace,
2071
+ `cursor=${rng.cursor};bound=${bound};value=${value}`
2072
+ ]
2073
+ }
2074
+ ];
2075
+ }
2076
+ function hashSeedCursor(seed, cursor) {
2077
+ const seedLo = seed | 0;
2078
+ const seedHi = Math.floor(seed / 4294967296) | 0;
2079
+ let x = Math.imul(seedLo, 2654435761);
2080
+ x = x + Math.imul(seedHi, 2246822519) | 0;
2081
+ x = x + Math.imul(cursor | 0, 3266489917) | 0;
2082
+ x = Math.imul(x ^ x >>> 16, 2246822507);
2083
+ x = Math.imul(x ^ x >>> 13, 3266489909);
2084
+ x = x ^ x >>> 16;
2085
+ return x >>> 0;
2086
+ }
2087
+
2088
+ // src/reducer/setup-bootstrap.ts
2089
+ function isCardComponent(table, componentId) {
2090
+ return componentId in table.cards;
2091
+ }
2092
+ function resolveRuntimeBoardId(table, boardId, playerId) {
2093
+ if (playerId) {
2094
+ const perPlayerBoardId = `${boardId}:${playerId}`;
2095
+ if (table.boards.byId[perPlayerBoardId]) {
2096
+ return perPlayerBoardId;
2097
+ }
2098
+ }
2099
+ if (table.boards.byId[boardId]) {
2100
+ return boardId;
2101
+ }
2102
+ throw new Error(`Unknown board '${boardId}'.`);
2103
+ }
2104
+ function resolveContainerRef(table, ref) {
2105
+ if (ref.type === "sharedZone") {
2106
+ return {
2107
+ kind: "sharedZone",
2108
+ zoneId: ref.zoneId
2109
+ };
2110
+ }
2111
+ if (ref.type === "playerZone") {
2112
+ return {
2113
+ kind: "playerZone",
2114
+ zoneId: ref.zoneId,
2115
+ playerId: ref.playerId
2116
+ };
2117
+ }
2118
+ if (ref.type === "sharedBoardContainer") {
2119
+ const board2 = table.boards.byId[resolveRuntimeBoardId(table, ref.boardId)];
2120
+ const container = board2?.containers[ref.containerId];
2121
+ if (!container) {
2122
+ throw new Error(
2123
+ `Unknown board container '${ref.containerId}' on board '${ref.boardId}'.`
2124
+ );
2125
+ }
2126
+ return {
2127
+ kind: "container",
2128
+ boardId: board2.id,
2129
+ containerId: ref.containerId,
2130
+ zoneId: container.zoneId
2131
+ };
2132
+ }
2133
+ if (ref.type === "playerBoardContainer") {
2134
+ const board2 = table.boards.byId[resolveRuntimeBoardId(table, ref.boardId, ref.playerId)];
2135
+ const container = board2?.containers[ref.containerId];
2136
+ if (!container) {
2137
+ throw new Error(
2138
+ `Unknown board container '${ref.containerId}' on board '${ref.boardId}' for '${ref.playerId}'.`
2139
+ );
2140
+ }
2141
+ return {
2142
+ kind: "container",
2143
+ boardId: board2.id,
2144
+ containerId: ref.containerId,
2145
+ zoneId: container.zoneId
2146
+ };
2147
+ }
2148
+ if (ref.type === "sharedBoardSpace") {
2149
+ const board2 = table.boards.byId[resolveRuntimeBoardId(table, ref.boardId)];
2150
+ if (!board2) {
2151
+ throw new Error(`Unknown board '${ref.boardId}'.`);
2152
+ }
2153
+ const space2 = board2?.spaces[ref.spaceId];
2154
+ if (!space2) {
2155
+ throw new Error(
2156
+ `Unknown board space '${ref.spaceId}' on board '${ref.boardId}'.`
2157
+ );
2158
+ }
2159
+ return {
2160
+ kind: "space",
2161
+ boardId: board2.id,
2162
+ spaceId: ref.spaceId
2163
+ };
2164
+ }
2165
+ const board = table.boards.byId[resolveRuntimeBoardId(table, ref.boardId, ref.playerId)];
2166
+ if (!board) {
2167
+ throw new Error(
2168
+ `Unknown board '${ref.boardId}' for player '${ref.playerId}'.`
2169
+ );
2170
+ }
2171
+ const space = board?.spaces[ref.spaceId];
2172
+ if (!space) {
2173
+ throw new Error(
2174
+ `Unknown board space '${ref.spaceId}' on board '${ref.boardId}' for '${ref.playerId}'.`
2175
+ );
2176
+ }
2177
+ return {
2178
+ kind: "space",
2179
+ boardId: board.id,
2180
+ spaceId: ref.spaceId
2181
+ };
2182
+ }
2183
+ function readContainerComponents(table, ref) {
2184
+ if (ref.kind === "sharedZone") {
2185
+ return [
2186
+ ...ensureArray(table.zones.shared[ref.zoneId] ?? table.decks[ref.zoneId])
2187
+ ];
2188
+ }
2189
+ if (ref.kind === "playerZone") {
2190
+ const zoneValue = table.zones.perPlayer[ref.zoneId];
2191
+ const handValue = table.hands[ref.zoneId];
2192
+ const seat = ref.playerId;
2193
+ return [
2194
+ ...ensureArray(
2195
+ (zoneValue !== void 0 ? perPlayerGet(zoneValue, seat) : void 0) ?? (handValue !== void 0 ? perPlayerGet(handValue, seat) : void 0)
2196
+ )
2197
+ ];
2198
+ }
2199
+ if (ref.kind === "space") {
2200
+ return getComponentsOnSpace(table, ref.boardId, ref.spaceId);
2201
+ }
2202
+ return getComponentsInContainer(table, ref.boardId, ref.containerId);
2203
+ }
2204
+ function sharedZoneLocationFor(table, componentId, zoneId, position) {
2205
+ if (isCardComponent(table, componentId)) {
2206
+ return {
2207
+ type: "InDeck",
2208
+ deckId: zoneId,
2209
+ playedBy: null,
2210
+ position
2211
+ };
2212
+ }
2213
+ return {
2214
+ type: "InZone",
2215
+ zoneId,
2216
+ playedBy: null,
2217
+ position
2218
+ };
2219
+ }
2220
+ function updateCardVisibilityForZone(table, componentId, ref) {
2221
+ if (!isCardComponent(table, componentId)) {
2222
+ return;
2223
+ }
2224
+ if (ref.kind === "playerZone") {
2225
+ const visibility = table.zones.visibility[ref.zoneId];
2226
+ table.ownerOfCard[componentId] = ref.playerId;
2227
+ table.visibility[componentId] = visibility === "all" || visibility === "public" ? { faceUp: true } : { faceUp: false, visibleTo: [ref.playerId] };
2228
+ return;
2229
+ }
2230
+ if (ref.kind === "sharedZone") {
2231
+ const visibility = table.zones.visibility[ref.zoneId];
2232
+ if (visibility === "hidden" || visibility === "ownerOnly") {
2233
+ table.visibility[componentId] = { faceUp: false };
2234
+ return;
2235
+ }
2236
+ table.visibility[componentId] = { faceUp: true };
2237
+ }
2238
+ }
2239
+ function writeContainerComponents(table, ref, componentIds) {
2240
+ if (ref.kind === "sharedZone") {
2241
+ componentIds.forEach((componentId) => {
2242
+ assertCardAllowedInZone(table, ref.zoneId, componentId);
2243
+ });
2244
+ table.decks[ref.zoneId] = [...componentIds];
2245
+ table.zones.shared[ref.zoneId] = [...componentIds];
2246
+ componentIds.forEach((componentId, index) => {
2247
+ table.componentLocations[componentId] = sharedZoneLocationFor(
2248
+ table,
2249
+ componentId,
2250
+ ref.zoneId,
2251
+ index
2252
+ );
2253
+ updateCardVisibilityForZone(table, componentId, ref);
2254
+ });
2255
+ return;
2256
+ }
2257
+ if (ref.kind === "playerZone") {
2258
+ componentIds.forEach((componentId) => {
2259
+ assertCardAllowedInZone(table, ref.zoneId, componentId);
2260
+ });
2261
+ const seat = ref.playerId;
2262
+ const prevHand = table.hands[ref.zoneId] ?? {
2263
+ __perPlayer: true,
2264
+ entries: []
2265
+ };
2266
+ table.hands[ref.zoneId] = perPlayerSet(prevHand, seat, [...componentIds]);
2267
+ const prevZone = table.zones.perPlayer[ref.zoneId] ?? {
2268
+ __perPlayer: true,
2269
+ entries: []
2270
+ };
2271
+ table.zones.perPlayer[ref.zoneId] = perPlayerSet(prevZone, seat, [
2272
+ ...componentIds
2273
+ ]);
2274
+ componentIds.forEach((componentId, index) => {
2275
+ table.componentLocations[componentId] = {
2276
+ type: "InHand",
2277
+ handId: ref.zoneId,
2278
+ playerId: ref.playerId,
2279
+ position: index
2280
+ };
2281
+ updateCardVisibilityForZone(table, componentId, ref);
2282
+ });
2283
+ return;
2284
+ }
2285
+ if (ref.kind === "space") {
2286
+ componentIds.forEach((componentId, index) => {
2287
+ table.componentLocations[componentId] = {
2288
+ type: "OnSpace",
2289
+ boardId: ref.boardId,
2290
+ spaceId: ref.spaceId,
2291
+ position: index
2292
+ };
2293
+ });
2294
+ return;
2295
+ }
2296
+ componentIds.forEach((componentId) => {
2297
+ assertCardAllowedInContainer(
2298
+ table,
2299
+ ref.boardId,
2300
+ ref.containerId,
2301
+ componentId
2302
+ );
2303
+ });
2304
+ componentIds.forEach((componentId, index) => {
2305
+ table.componentLocations[componentId] = {
2306
+ type: "InContainer",
2307
+ boardId: ref.boardId,
2308
+ containerId: ref.containerId,
2309
+ position: index
2310
+ };
2311
+ });
2312
+ }
2313
+ function selectMovedComponents(current, step) {
2314
+ if (step.componentIds && typeof step.count === "number") {
2315
+ throw new Error(
2316
+ "Setup bootstrap move step cannot specify both count and componentIds."
2317
+ );
2318
+ }
2319
+ if (step.componentIds) {
2320
+ const missing = step.componentIds.filter(
2321
+ (componentId) => !current.includes(componentId)
2322
+ );
2323
+ if (missing.length > 0) {
2324
+ throw new Error(
2325
+ `Setup bootstrap move step references missing components: ${missing.join(", ")}`
2326
+ );
2327
+ }
2328
+ return [...step.componentIds];
2329
+ }
2330
+ if (typeof step.count === "number") {
2331
+ return current.slice(0, step.count);
2332
+ }
2333
+ return [...current];
2334
+ }
2335
+ function resolveDealTargetRef(table, ref, playerId) {
2336
+ if (ref.type === "playerZone") {
2337
+ return {
2338
+ kind: "playerZone",
2339
+ zoneId: ref.zoneId,
2340
+ playerId
2341
+ };
2342
+ }
2343
+ const board = table.boards.byId[resolveRuntimeBoardId(table, ref.boardId, playerId)];
2344
+ const container = board?.containers[ref.containerId];
2345
+ if (!container) {
2346
+ throw new Error(
2347
+ `Unknown board container '${ref.containerId}' on board '${ref.boardId}' for '${playerId}'.`
2348
+ );
2349
+ }
2350
+ return {
2351
+ kind: "container",
2352
+ boardId: board.id,
2353
+ containerId: ref.containerId,
2354
+ zoneId: container.zoneId
2355
+ };
2356
+ }
2357
+ function applySetupBootstrap(state, steps) {
2358
+ if (steps.length === 0) {
2359
+ return state;
2360
+ }
2361
+ const nextTable = cloneRuntimeTable(state.table);
2362
+ let nextRng = state.runtime.rng;
2363
+ for (const step of steps) {
2364
+ if (step.type === "shuffle") {
2365
+ const container = resolveContainerRef(nextTable, step.container);
2366
+ const shuffled = readContainerComponents(nextTable, container);
2367
+ for (let lastIndex = shuffled.length - 1; lastIndex > 0; lastIndex -= 1) {
2368
+ const [swapIndex, updatedRng] = nextRandomInt(lastIndex + 1, nextRng);
2369
+ nextRng = updatedRng;
2370
+ const current = shuffled[lastIndex];
2371
+ const swap = shuffled[swapIndex];
2372
+ if (current === void 0 || swap === void 0) {
2373
+ throw new Error(
2374
+ `setup shuffle: invalid index (lastIndex=${lastIndex}, swapIndex=${swapIndex}, length=${shuffled.length})`
2375
+ );
2376
+ }
2377
+ shuffled[lastIndex] = swap;
2378
+ shuffled[swapIndex] = current;
2379
+ }
2380
+ writeContainerComponents(nextTable, container, shuffled);
2381
+ continue;
2382
+ }
2383
+ if (step.type === "move") {
2384
+ const from2 = resolveContainerRef(nextTable, step.from);
2385
+ const to = resolveContainerRef(nextTable, step.to);
2386
+ const currentFrom = readContainerComponents(nextTable, from2);
2387
+ const moved = selectMovedComponents(currentFrom, step);
2388
+ const remaining = currentFrom.filter(
2389
+ (componentId) => !moved.includes(componentId)
2390
+ );
2391
+ const currentTo = readContainerComponents(nextTable, to);
2392
+ writeContainerComponents(nextTable, from2, remaining);
2393
+ writeContainerComponents(nextTable, to, [...currentTo, ...moved]);
2394
+ continue;
2395
+ }
2396
+ const from = resolveContainerRef(nextTable, step.from);
2397
+ if (from.kind === "playerZone") {
2398
+ throw new Error(
2399
+ "Setup bootstrap deal steps require a shared source container."
2400
+ );
2401
+ }
2402
+ const playerIds = step.playerIds ? [...step.playerIds] : [...nextTable.playerOrder];
2403
+ let sourceComponents = readContainerComponents(nextTable, from);
2404
+ for (const playerId of playerIds) {
2405
+ if (sourceComponents.length === 0) {
2406
+ break;
2407
+ }
2408
+ const dealt = sourceComponents.slice(0, step.count);
2409
+ if (dealt.length === 0) {
2410
+ break;
2411
+ }
2412
+ sourceComponents = sourceComponents.slice(dealt.length);
2413
+ const target = resolveDealTargetRef(nextTable, step.to, playerId);
2414
+ const currentTo = readContainerComponents(nextTable, target);
2415
+ writeContainerComponents(nextTable, target, [...currentTo, ...dealt]);
2416
+ }
2417
+ writeContainerComponents(nextTable, from, sourceComponents);
2418
+ }
2419
+ return {
2420
+ ...state,
2421
+ table: nextTable,
2422
+ runtime: {
2423
+ ...state.runtime,
2424
+ rng: nextRng
2425
+ }
2426
+ };
2427
+ }
2428
+
2429
+ // src/reducer/definition-index.ts
2430
+ function phaseEntriesOf(definition) {
2431
+ return Object.entries(definition.phases);
2432
+ }
2433
+ function interactionEntriesOf(phase) {
2434
+ return Object.entries(
2435
+ phase.interactions ?? {}
2436
+ );
2437
+ }
2438
+ function simultaneousSubmitEntriesOf(phase) {
2439
+ const submit = phase.submit;
2440
+ if (!submit) return [];
2441
+ return [
2442
+ ["submit", submit]
2443
+ ];
2444
+ }
2445
+ function cardActionEntriesOf(phase) {
2446
+ return Object.entries(
2447
+ phase.cardActions ?? {}
2448
+ );
2449
+ }
2450
+ function stageEntriesOf(phase) {
2451
+ return Object.entries(
2452
+ phase.stages ?? {}
2453
+ );
2454
+ }
2455
+ function zoneEntriesOf(phase) {
2456
+ return Array.from(
2457
+ phase.zones ?? []
2458
+ );
2459
+ }
2460
+ function effectEntriesOf(phase) {
2461
+ return Object.entries(
2462
+ phase.effects ?? {}
2463
+ );
2464
+ }
2465
+ function collectReducerDefinitionIndex(definition) {
2466
+ const phaseEntries = phaseEntriesOf(definition);
2467
+ const phasesByName = /* @__PURE__ */ new Map();
2468
+ for (const [phaseName, phase] of phaseEntries) {
2469
+ const interactionEntries = [
2470
+ ...interactionEntriesOf(phase),
2471
+ ...simultaneousSubmitEntriesOf(phase)
2472
+ ];
2473
+ const cardActionEntries = cardActionEntriesOf(phase).map(([cardActionId, cardAction]) => [
2474
+ cardActionId,
2475
+ {
2476
+ ...cardAction,
2477
+ __steps: cardAction.__steps,
2478
+ inputs: {
2479
+ cardId: cardInput({
2480
+ target: cardTarget.zones([cardAction.playFrom]).where({
2481
+ id: "card-type",
2482
+ errorCode: "WRONG_CARD_TYPE",
2483
+ message: `Card must be ${cardAction.cardType}.`,
2484
+ test: ({ q, targetId }) => q.card.get(targetId).cardType === cardAction.cardType
2485
+ }).build()
2486
+ }),
2487
+ ...cardAction.inputs ?? {}
2488
+ }
2489
+ }
2490
+ ]);
2491
+ const effects = effectEntriesOf(
2492
+ phase
2493
+ ).map(([effectKey, effectValue]) => [
2494
+ effectKey,
2495
+ {
2496
+ id: effectValue.id ?? effectKey,
2497
+ type: effectValue.type ?? "rollDie",
2498
+ continuation: effectValue.__continuation
2499
+ }
2500
+ ]);
2501
+ phasesByName.set(phaseName, {
2502
+ phaseName,
2503
+ phase,
2504
+ interactions: [...interactionEntries, ...cardActionEntries],
2505
+ stages: stageEntriesOf(phase),
2506
+ zones: zoneEntriesOf(phase),
2507
+ cardActions: cardActionEntriesOf(phase),
2508
+ effects
2509
+ });
2510
+ }
2511
+ return {
2512
+ phaseEntries,
2513
+ phasesByName
2514
+ };
2515
+ }
2516
+
2517
+ export {
2518
+ markManifestScopedSchema,
2519
+ isManifestScopedSchema,
2520
+ createManifestStringLiteralSchema,
2521
+ assumeManifestSchema,
2522
+ cloneManifestDefault,
2523
+ resolveManifestPlayerIds,
2524
+ createManifestRuntimeSchema,
2525
+ createManifestGameStateSchema,
2526
+ FrameworkErrorCodes,
2527
+ asPlayerId,
2528
+ isPlayerId,
2529
+ perPlayer,
2530
+ perPlayerKeys,
2531
+ perPlayerValues,
2532
+ perPlayerEntries,
2533
+ perPlayerSize,
2534
+ perPlayerHas,
2535
+ perPlayerGet,
2536
+ perPlayerRequire,
2537
+ perPlayerSet,
2538
+ perPlayerMap,
2539
+ isPerPlayer,
2540
+ perPlayerSchema,
2541
+ sharedBoardRef,
2542
+ perPlayerBoardRef,
2543
+ boardRef,
2544
+ boardRefKey,
2545
+ parseBoardRefKey,
2546
+ boardRefSchema,
2547
+ isSharedBoardRef,
2548
+ isPerPlayerBoardRef,
2549
+ createTargetRule,
2550
+ createTargetRuleBuilder,
2551
+ cardTarget,
2552
+ cardInput,
2553
+ ensureArray,
2554
+ cloneRuntimeTable,
2555
+ getBoard,
2556
+ getHexBoard,
2557
+ getTiledBoard,
2558
+ getSquareBoard,
2559
+ getSpace,
2560
+ getHexSpace,
2561
+ getSquareSpace,
2562
+ getContainer,
2563
+ getEdge,
2564
+ getVertex,
2565
+ getHexSpaceAt,
2566
+ getSquareSpaceAt,
2567
+ getSpaceEdges,
2568
+ getSpaceVertices,
2569
+ getIncidentEdges,
2570
+ getIncidentVertices,
2571
+ getRelatedSpaces,
2572
+ getAdjacentSpaces,
2573
+ getSpaceDistance,
2574
+ getSquareNeighbors,
2575
+ getSquareDistance,
2576
+ getBoardsByTypeId,
2577
+ getSpacesByTypeId,
2578
+ getEdgesByTypeId,
2579
+ getVerticesByTypeId,
2580
+ getComponentsOnSpace,
2581
+ getComponentsInContainer,
2582
+ getComponentsOnEdge,
2583
+ getComponentsOnVertex,
2584
+ assertCardAllowedInContainer,
2585
+ getComponentLocation,
2586
+ getComponentDeckLocation,
2587
+ getComponentHandLocation,
2588
+ getComponentZoneLocation,
2589
+ getComponentSpaceLocation,
2590
+ getComponentContainerLocation,
2591
+ getComponentEdgeLocation,
2592
+ getComponentVertexLocation,
2593
+ getComponentSlotLocation,
2594
+ getSharedZoneCards,
2595
+ getPlayerZoneCards,
2596
+ getAllSharedZoneCards,
2597
+ getAllPlayerZoneCards,
2598
+ getCard,
2599
+ getCardsById,
2600
+ getSharedZoneCardCollection,
2601
+ getPlayerZoneCardCollection,
2602
+ getSlotOccupants,
2603
+ getSlotOccupantsByHost,
2604
+ getCardOwner,
2605
+ getCardVisibility,
2606
+ getPlayerOrder,
2607
+ getPlayerResources,
2608
+ getPlayerResourceAmount,
2609
+ getPlayerResourceTotal,
2610
+ getNextPlayerInOrder,
2611
+ canAffordResources,
2612
+ getMissingResources,
2613
+ addPlayerResources,
2614
+ addPlayerResourcesInPlace,
2615
+ spendPlayerResources,
2616
+ spendPlayerResourcesInPlace,
2617
+ transferPlayerResources,
2618
+ transferPlayerResourcesInPlace,
2619
+ setPlayerResource,
2620
+ setPlayerResourceInPlace,
2621
+ moveComponentToSpace,
2622
+ moveComponentToSpaceInPlace,
2623
+ moveComponentToContainer,
2624
+ moveComponentToContainerInPlace,
2625
+ moveComponentToDetached,
2626
+ moveComponentToDetachedInPlace,
2627
+ moveComponentToEdge,
2628
+ moveComponentToEdgeInPlace,
2629
+ moveComponentToVertex,
2630
+ moveComponentToVertexInPlace,
2631
+ shufflePlayerZoneCards,
2632
+ moveCardFromPlayerZoneToSharedZone,
2633
+ moveCardFromPlayerZoneToSharedZoneInPlace,
2634
+ dealCardsBetweenPlayerZones,
2635
+ dealCardsBetweenPlayerZonesInPlace,
2636
+ moveCardFromSharedZoneToPlayerZone,
2637
+ moveCardFromSharedZoneToPlayerZoneInPlace,
2638
+ moveCardBetweenPlayerZones,
2639
+ moveCardBetweenPlayerZonesInPlace,
2640
+ moveCardBetweenSharedZones,
2641
+ moveCardBetweenSharedZonesInPlace,
2642
+ removeCardFromSharedZone,
2643
+ removeCardFromSharedZoneInPlace,
2644
+ addCardToSharedZone,
2645
+ addCardToSharedZoneInPlace,
2646
+ dealCardsFromDeckToHand,
2647
+ dealCardsFromDeckToHandInPlace,
2648
+ setActivePlayers,
2649
+ setPhaseState,
2650
+ nextRandomInt,
2651
+ collectReducerDefinitionIndex,
2652
+ applySetupBootstrap
2653
+ };
2654
+ //# sourceMappingURL=chunk-U5KBV2BA.js.map