@dreamboard-games/sdk 0.5.0-alpha.2 → 0.5.0-alpha.4

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