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

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 (150) hide show
  1. package/README.md +136 -45
  2. package/dist/chunk-DMFVAZI2.js +16 -0
  3. package/dist/chunk-DMFVAZI2.js.map +1 -0
  4. package/dist/chunk-FMM5LVJW.js +833 -0
  5. package/dist/chunk-FMM5LVJW.js.map +1 -0
  6. package/dist/{chunk-S2JEDVWH.js → chunk-LRILEVKC.js} +6113 -6489
  7. package/dist/chunk-LRILEVKC.js.map +1 -0
  8. package/dist/chunk-SP234IY4.js +1363 -0
  9. package/dist/chunk-SP234IY4.js.map +1 -0
  10. package/dist/chunk-VUWNJXLJ.js +52 -0
  11. package/dist/chunk-VUWNJXLJ.js.map +1 -0
  12. package/dist/chunk-ZPRKWPEP.js +511 -0
  13. package/dist/chunk-ZPRKWPEP.js.map +1 -0
  14. package/dist/definition-BaHNPZol.d.ts +4639 -0
  15. package/dist/diagnostics-B0lgnuZ6.d.ts +65 -0
  16. package/dist/index.d.ts +3553 -1
  17. package/dist/index.js +1045 -6
  18. package/dist/index.js.map +1 -1
  19. package/dist/model-BHro6yXu.d.ts +288 -0
  20. package/dist/react.d.ts +32 -0
  21. package/dist/react.js +80 -0
  22. package/dist/react.js.map +1 -0
  23. package/dist/reducer.d.ts +1194 -79
  24. package/dist/reducer.js +4326 -121
  25. package/dist/reducer.js.map +1 -1
  26. package/dist/testing.d.ts +518 -564
  27. package/dist/testing.js +2448 -915
  28. package/dist/testing.js.map +1 -1
  29. package/dist/types-DimqnJBq.d.ts +86 -0
  30. package/package.json +26 -131
  31. package/dist/HandView-Bi2lxzQP.d.ts +0 -79
  32. package/dist/ResourceCounter-BFxJknqp.d.ts +0 -98
  33. package/dist/ThemeProvider-CElaL2ik.d.ts +0 -99
  34. package/dist/attributes-DbvyMbXw.d.ts +0 -68
  35. package/dist/browser-interaction.d.ts +0 -910
  36. package/dist/browser-interaction.js +0 -119
  37. package/dist/browser-interaction.js.map +0 -1
  38. package/dist/chunk-32PFKDV7.js +0 -11705
  39. package/dist/chunk-32PFKDV7.js.map +0 -1
  40. package/dist/chunk-3BAD4VC2.js +0 -263
  41. package/dist/chunk-3BAD4VC2.js.map +0 -1
  42. package/dist/chunk-3SKDNDPG.js +0 -104
  43. package/dist/chunk-3SKDNDPG.js.map +0 -1
  44. package/dist/chunk-5YV2WHJ4.js +0 -25
  45. package/dist/chunk-5YV2WHJ4.js.map +0 -1
  46. package/dist/chunk-ANRB4XKA.js +0 -90
  47. package/dist/chunk-ANRB4XKA.js.map +0 -1
  48. package/dist/chunk-BI4G3PM2.js +0 -1864
  49. package/dist/chunk-BI4G3PM2.js.map +0 -1
  50. package/dist/chunk-EVCLTY4Q.js +0 -935
  51. package/dist/chunk-EVCLTY4Q.js.map +0 -1
  52. package/dist/chunk-GDPKTBUR.js +0 -564
  53. package/dist/chunk-GDPKTBUR.js.map +0 -1
  54. package/dist/chunk-H6VDGFL5.js +0 -2903
  55. package/dist/chunk-H6VDGFL5.js.map +0 -1
  56. package/dist/chunk-LR3ZTWQF.js +0 -5960
  57. package/dist/chunk-LR3ZTWQF.js.map +0 -1
  58. package/dist/chunk-PF7L4BMG.js +0 -117
  59. package/dist/chunk-PF7L4BMG.js.map +0 -1
  60. package/dist/chunk-PN5O6GG2.js +0 -32
  61. package/dist/chunk-PN5O6GG2.js.map +0 -1
  62. package/dist/chunk-PZ5AY32C.js +0 -10
  63. package/dist/chunk-PZ5AY32C.js.map +0 -1
  64. package/dist/chunk-S2JEDVWH.js.map +0 -1
  65. package/dist/chunk-T3ZKNUZ7.js +0 -1
  66. package/dist/chunk-T3ZKNUZ7.js.map +0 -1
  67. package/dist/chunk-UGH54WZ2.js +0 -223
  68. package/dist/chunk-UGH54WZ2.js.map +0 -1
  69. package/dist/chunk-VCDWP5VL.js +0 -746
  70. package/dist/chunk-VCDWP5VL.js.map +0 -1
  71. package/dist/chunk-VDXOF4FW.js +0 -69
  72. package/dist/chunk-VDXOF4FW.js.map +0 -1
  73. package/dist/chunk-WHR5UW3F.js +0 -1988
  74. package/dist/chunk-WHR5UW3F.js.map +0 -1
  75. package/dist/chunk-Y75CFE77.js +0 -17
  76. package/dist/chunk-Y75CFE77.js.map +0 -1
  77. package/dist/chunk-Z7QCREWI.js +0 -3435
  78. package/dist/chunk-Z7QCREWI.js.map +0 -1
  79. package/dist/chunk-ZZGN2ATS.js +0 -1941
  80. package/dist/chunk-ZZGN2ATS.js.map +0 -1
  81. package/dist/components-gAbIz7hK.d.ts +0 -1284
  82. package/dist/definitions-BgmV_GhC.d.ts +0 -300
  83. package/dist/diagnostics-DIi3ina7.d.ts +0 -50
  84. package/dist/digest.d.ts +0 -8
  85. package/dist/game-CsZScpcn.d.ts +0 -763
  86. package/dist/hex-board-view-aKkblDp6.d.ts +0 -1228
  87. package/dist/index-CcT9Q7N7.d.ts +0 -193
  88. package/dist/index.d-DjzoK7zn.d.ts +0 -2101
  89. package/dist/package-set.d.ts +0 -13
  90. package/dist/package-set.js +0 -12
  91. package/dist/package-set.js.map +0 -1
  92. package/dist/player-state-Cqpyeql0.d.ts +0 -371
  93. package/dist/plugin-runtime-contract.d.ts +0 -17
  94. package/dist/plugin-runtime-contract.js +0 -92
  95. package/dist/plugin-runtime-contract.js.map +0 -1
  96. package/dist/primitive-props-BNHDkgd7.d.ts +0 -16
  97. package/dist/protocol-dYgafTYY.d.ts +0 -314
  98. package/dist/reducer/advanced.d.ts +0 -82
  99. package/dist/reducer/advanced.js +0 -51
  100. package/dist/reducer/advanced.js.map +0 -1
  101. package/dist/reducer-contract.d.ts +0 -11
  102. package/dist/reducer-contract.js +0 -16
  103. package/dist/reducer-contract.js.map +0 -1
  104. package/dist/reference-games/index.d.ts +0 -31
  105. package/dist/reference-games/index.js +0 -48
  106. package/dist/reference-games/index.js.map +0 -1
  107. package/dist/runtime/primitives.d.ts +0 -250
  108. package/dist/runtime/primitives.js +0 -189
  109. package/dist/runtime/primitives.js.map +0 -1
  110. package/dist/runtime/runtime-api.d.ts +0 -2
  111. package/dist/runtime/runtime-api.js +0 -1
  112. package/dist/runtime/runtime-api.js.map +0 -1
  113. package/dist/runtime/workspace-contract.d.ts +0 -392
  114. package/dist/runtime/workspace-contract.js +0 -28
  115. package/dist/runtime/workspace-contract.js.map +0 -1
  116. package/dist/runtime-RJ5orDJU.d.ts +0 -1699
  117. package/dist/runtime-api-Bz5pwNU_.d.ts +0 -296
  118. package/dist/runtime-json-CQ9QbLZ5.d.ts +0 -5
  119. package/dist/runtime.d.ts +0 -83
  120. package/dist/runtime.js +0 -242
  121. package/dist/runtime.js.map +0 -1
  122. package/dist/schema.d.ts +0 -379
  123. package/dist/stale-contract-artifact-error-XLaweZtF.d.ts +0 -18
  124. package/dist/testing-compiler.d.ts +0 -37
  125. package/dist/testing-compiler.js +0 -272
  126. package/dist/testing-compiler.js.map +0 -1
  127. package/dist/testing-runtime.d.ts +0 -119
  128. package/dist/testing-runtime.js +0 -212
  129. package/dist/testing-runtime.js.map +0 -1
  130. package/dist/types-DJj5MJkl.d.ts +0 -256
  131. package/dist/types-DR7DoB1x.d.ts +0 -28
  132. package/dist/types-JWCYHmu7.d.ts +0 -122
  133. package/dist/types.d.ts +0 -1958
  134. package/dist/types.js +0 -14
  135. package/dist/types.js.map +0 -1
  136. package/dist/ui/components.d.ts +0 -17
  137. package/dist/ui/components.js +0 -216
  138. package/dist/ui/components.js.map +0 -1
  139. package/dist/ui/defaults.d.ts +0 -19
  140. package/dist/ui/defaults.js +0 -104
  141. package/dist/ui/defaults.js.map +0 -1
  142. package/dist/ui/player-state.d.ts +0 -2
  143. package/dist/ui/player-state.js +0 -1
  144. package/dist/ui/player-state.js.map +0 -1
  145. package/dist/ui/plugin-styles.css +0 -2
  146. package/dist/ui-contract-GTUPkf8z.d.ts +0 -1168
  147. package/dist/ui.d.ts +0 -317
  148. package/dist/ui.js +0 -277
  149. package/dist/ui.js.map +0 -1
  150. package/dist/views-BpuWcOyN.d.ts +0 -1641
@@ -0,0 +1,1363 @@
1
+ import {
2
+ immutableCopy
3
+ } from "./chunk-DMFVAZI2.js";
4
+ import {
5
+ inputValueInDomain
6
+ } from "./chunk-VUWNJXLJ.js";
7
+
8
+ // src/headless/instance.ts
9
+ import { createStore } from "@tanstack/store";
10
+
11
+ // src/headless/interaction-inputs.ts
12
+ function interactionInputKeys(descriptor) {
13
+ return descriptor.inputs.map((input) => input.key);
14
+ }
15
+ function applyInteractionInputDefaults(descriptor, params) {
16
+ const next = descriptor.step ? Object.fromEntries(
17
+ Object.entries(params).filter(
18
+ ([key]) => descriptor.inputs.some((input) => input.key === key)
19
+ )
20
+ ) : { ...params };
21
+ for (const input of descriptor.inputs) {
22
+ if (next[input.key] !== void 0) continue;
23
+ if (!("defaultValue" in input)) continue;
24
+ next[input.key] = input.defaultValue;
25
+ }
26
+ return next;
27
+ }
28
+ function validateInteractionInputDomains(descriptor, params) {
29
+ const fieldErrors = {};
30
+ for (const rawInput of descriptor.inputs) {
31
+ const input = rawInput;
32
+ const value = params[input.key];
33
+ if (value === void 0 || value === null) continue;
34
+ const selectionErrors = validateInputSelection(input, value);
35
+ for (const error of selectionErrors) {
36
+ pushFieldError(fieldErrors, input.key, error);
37
+ }
38
+ if (input.domain.type === "choiceList") {
39
+ if (!Array.isArray(value)) {
40
+ pushFieldError(fieldErrors, input.key, "Expected a list of choices.");
41
+ continue;
42
+ }
43
+ const min = input.domain.min ?? 0;
44
+ const max = input.domain.max ?? input.domain.choices?.length ?? Number.POSITIVE_INFINITY;
45
+ if (value.length < min) {
46
+ pushFieldError(
47
+ fieldErrors,
48
+ input.key,
49
+ `Choose at least ${min} ${pluralize("option", min)}.`
50
+ );
51
+ }
52
+ if (value.length > max) {
53
+ pushFieldError(
54
+ fieldErrors,
55
+ input.key,
56
+ `Choose at most ${max} ${pluralize("option", max)}.`
57
+ );
58
+ }
59
+ const allowed = new Set(
60
+ input.domain.choices?.map((choice) => choice.value)
61
+ );
62
+ if (allowed.size > 0 && value.some((item) => !allowed.has(String(item)))) {
63
+ pushFieldError(
64
+ fieldErrors,
65
+ input.key,
66
+ "Selected choice is not eligible."
67
+ );
68
+ }
69
+ }
70
+ if (input.domain.type === "choice") {
71
+ const allowed = new Set(
72
+ input.domain.choices?.map((choice) => choice.value)
73
+ );
74
+ if (allowed.size > 0 && !allowed.has(value)) {
75
+ pushFieldError(
76
+ fieldErrors,
77
+ input.key,
78
+ "Selected choice is not eligible."
79
+ );
80
+ }
81
+ }
82
+ if (isResolvedTargetDomain(input.domain)) {
83
+ const values = valuesForSelection(input.domain.selection, value);
84
+ for (const item of values) {
85
+ if (!input.domain.eligibleTargets.includes(String(item))) {
86
+ pushFieldError(
87
+ fieldErrors,
88
+ input.key,
89
+ "Selected target is not eligible."
90
+ );
91
+ break;
92
+ }
93
+ }
94
+ }
95
+ }
96
+ return fieldErrors;
97
+ }
98
+ function isInputValueReady(input, value) {
99
+ if (value === void 0) return false;
100
+ if (value === null)
101
+ return input.domain.type === "choice" && (input.domain.choices ?? []).some(
102
+ (choice) => choice.value === null && !choice.disabled
103
+ );
104
+ const selection = inputSelection(input);
105
+ if (selection?.mode !== "many") return true;
106
+ return Array.isArray(value) && value.length >= selection.min;
107
+ }
108
+ function isManyInput(input) {
109
+ return inputSelection(input)?.mode === "many";
110
+ }
111
+ function toggleManyValue(current, value, selection) {
112
+ if (selection.mode !== "many") return [value];
113
+ const previous = Array.isArray(current) ? current.map((item) => String(item)) : [];
114
+ const existing = previous.indexOf(value);
115
+ if (existing >= 0) {
116
+ return previous.filter((item) => item !== value);
117
+ }
118
+ if (selection.max !== void 0 && previous.length >= selection.max) {
119
+ return previous;
120
+ }
121
+ return [...previous, value];
122
+ }
123
+ function isManyTargetSelectable(input, current, targetId) {
124
+ const selection = inputSelection(input);
125
+ if (selection?.mode !== "many") return true;
126
+ const currentValues = Array.isArray(current) ? current.map((item) => String(item)) : [];
127
+ if (currentValues.includes(targetId)) return true;
128
+ if (selection.distinct && currentValues.includes(targetId)) return false;
129
+ return selection.max === void 0 || currentValues.length < selection.max;
130
+ }
131
+ function hasInteractionFieldErrors(fieldErrors) {
132
+ return Object.values(fieldErrors).some(
133
+ (messages) => (messages?.length ?? 0) > 0
134
+ );
135
+ }
136
+ function inputByKey(descriptor, key) {
137
+ return descriptor.inputs.find((input) => input.key === key);
138
+ }
139
+ function isTargetDomain(domain) {
140
+ if (!domain) return false;
141
+ return domain.type === "cardTarget" || domain.type === "boardTarget";
142
+ }
143
+ function isResolvedTargetDomain(domain) {
144
+ return (domain.type === "cardTarget" || domain.type === "boardTarget") && domain.projection === "resolved";
145
+ }
146
+ function validateInputSelection(input, value) {
147
+ const selection = inputSelection(input);
148
+ if (selection?.mode !== "many") return [];
149
+ if (!Array.isArray(value)) return ["Expected a list of values."];
150
+ const errors = [];
151
+ const min = selection.min;
152
+ if (value.length < min) {
153
+ errors.push(`Choose at least ${min} ${pluralize("value", min)}.`);
154
+ }
155
+ if (selection.max !== void 0 && value.length > selection.max) {
156
+ errors.push(
157
+ `Choose at most ${selection.max} ${pluralize("value", selection.max)}.`
158
+ );
159
+ }
160
+ if (selection.distinct) {
161
+ const seen = /* @__PURE__ */ new Set();
162
+ for (const item of value) {
163
+ const key = String(item);
164
+ if (seen.has(key)) {
165
+ errors.push("Choose each value only once.");
166
+ break;
167
+ }
168
+ seen.add(key);
169
+ }
170
+ }
171
+ return errors;
172
+ }
173
+ function valuesForSelection(selection, value) {
174
+ if (selection?.mode === "many") return Array.isArray(value) ? value : [];
175
+ return [value];
176
+ }
177
+ function inputSelection(input) {
178
+ return "selection" in input.domain ? input.domain.selection : void 0;
179
+ }
180
+ function pushFieldError(fieldErrors, key, message) {
181
+ fieldErrors[key] = [...fieldErrors[key] ?? [], message];
182
+ }
183
+ function pluralize(word, count) {
184
+ return count === 1 ? word : `${word}s`;
185
+ }
186
+
187
+ // src/headless/interaction-router.ts
188
+ function applyInteractionDraftMutation(store, descriptor, mutations) {
189
+ const originalDraft = {
190
+ ...store.getDraft(descriptor.interactionKey)
191
+ };
192
+ const nextDraft = {
193
+ ...originalDraft
194
+ };
195
+ const currentKeys = new Set(interactionInputKeys(descriptor));
196
+ for (const { key, value } of mutations) {
197
+ if (!descriptor.step || currentKeys.has(key)) nextDraft[key] = value;
198
+ }
199
+ if (descriptor.step) {
200
+ for (const key of Object.keys(nextDraft))
201
+ if (!currentKeys.has(key)) delete nextDraft[key];
202
+ }
203
+ const allKeys = /* @__PURE__ */ new Set([
204
+ ...Object.keys(originalDraft),
205
+ ...Object.keys(nextDraft)
206
+ ]);
207
+ for (const key of allKeys) {
208
+ if (!(key in nextDraft)) {
209
+ store.clearInput(descriptor.interactionKey, key);
210
+ continue;
211
+ }
212
+ if (originalDraft[key] !== nextDraft[key]) {
213
+ store.setInput(descriptor.interactionKey, key, nextDraft[key]);
214
+ }
215
+ }
216
+ return nextDraft;
217
+ }
218
+ function getInteractionDraftReadiness(descriptor, draft) {
219
+ const values = applyInteractionInputDefaults(
220
+ descriptor,
221
+ draft
222
+ );
223
+ const missingInputs = interactionInputKeys(descriptor).filter((key) => {
224
+ const input = inputByKey(descriptor, key);
225
+ const value = values[key];
226
+ return input ? !isInputValueReady(input, value) : value === null || value === void 0;
227
+ });
228
+ const fieldErrors = validateInteractionInputDomains(descriptor, values);
229
+ return {
230
+ values,
231
+ missingInputs,
232
+ fieldErrors,
233
+ ready: missingInputs.length === 0 && !hasInteractionFieldErrors(fieldErrors)
234
+ };
235
+ }
236
+ function routeInteractionTarget(store, descriptor, target) {
237
+ const input = inputByKey(descriptor, target.inputKey);
238
+ const currentDraft = store.getDraft(descriptor.interactionKey);
239
+ const selection = isTargetDomain(input?.domain) ? input.domain.selection : void 0;
240
+ const targetValue = selection?.mode === "many" ? toggleManyValue(currentDraft[target.inputKey], target.value, selection) : target.value;
241
+ const params = applyInteractionDraftMutation(store, descriptor, [
242
+ ...Object.entries(target.extraInputs ?? {}).map(([key, value]) => ({
243
+ key,
244
+ value
245
+ })),
246
+ { key: target.inputKey, value: targetValue }
247
+ ]);
248
+ return {
249
+ params,
250
+ readiness: getInteractionDraftReadiness(descriptor, params)
251
+ };
252
+ }
253
+ function routeCardInputIntent(store, descriptor, intent) {
254
+ return routeInteractionTarget(store, descriptor, {
255
+ inputKey: intent.cardInputKey,
256
+ value: intent.cardId,
257
+ extraInputs: intent.dropTarget ? { [intent.dropTarget.inputKey]: intent.dropTarget.value } : void 0
258
+ });
259
+ }
260
+ function shouldAutoSubmitInteraction(descriptor) {
261
+ return descriptor.commit.mode === "autoWhenReady" && !descriptor.inputs.some((input) => isManyInput(input));
262
+ }
263
+
264
+ // src/headless/instance.ts
265
+ var EMPTY = Object.freeze({});
266
+ var EMPTY_DRAFTS = Object.freeze({});
267
+ var equalValue = (a, b) => Object.is(a, b) || typeof a === "object" && typeof b === "object" && JSON.stringify(a) === JSON.stringify(b);
268
+ function inDomain(input, value, partial = false) {
269
+ return inputValueInDomain(input.domain, value, input.domain.selection, {
270
+ ignoreMinimum: partial
271
+ });
272
+ }
273
+ function immutableValues(value) {
274
+ return immutableCopy(value);
275
+ }
276
+ function data(action, interaction, input, value) {
277
+ return {
278
+ "data-action": action,
279
+ "data-interaction": interaction,
280
+ ...input ? { "data-input": input } : {},
281
+ ...value === void 0 ? {} : {
282
+ "data-value": typeof value === "string" ? value : JSON.stringify(value)
283
+ }
284
+ };
285
+ }
286
+ var AmbiguousTargetError = class extends Error {
287
+ constructor(id) {
288
+ super(
289
+ `Target '${id}' belongs to more than one interaction. Choose an interaction explicitly.`
290
+ );
291
+ this.name = "AmbiguousTargetError";
292
+ }
293
+ };
294
+ var InteractionObject = class {
295
+ constructor(owner, descriptor, draft, status, connected) {
296
+ this.owner = owner;
297
+ this.descriptor = descriptor;
298
+ this.status = status;
299
+ this.connected = connected;
300
+ this.seat = descriptor.actorSeat ?? owner.sourceState.snapshot?.players.findIndex(
301
+ (player) => player.playerId === owner.sourceState.snapshot?.me
302
+ ) ?? -1;
303
+ this.draftIdentity = draft;
304
+ this.draft = immutableValues(draft);
305
+ this.source = owner.options.source;
306
+ this.epoch = owner.epoch;
307
+ this.key = descriptor.interactionKey;
308
+ this.id = descriptor.interactionId;
309
+ this.phase = descriptor.phaseName;
310
+ this.label = descriptor.label;
311
+ this.help = descriptor.help;
312
+ this.kind = descriptor.step ? "steps" : "inputs";
313
+ this.inputObjects = Object.freeze(
314
+ descriptor.inputs.map(
315
+ (input) => owner.object("input", new InputObject(this, input))
316
+ )
317
+ );
318
+ }
319
+ owner;
320
+ descriptor;
321
+ status;
322
+ connected;
323
+ key;
324
+ id;
325
+ phase;
326
+ label;
327
+ help;
328
+ kind;
329
+ inputObjects;
330
+ seat;
331
+ draft;
332
+ draftIdentity;
333
+ source;
334
+ epoch;
335
+ get game() {
336
+ return this.owner.instance;
337
+ }
338
+ getAvailability() {
339
+ return this.descriptor.availability;
340
+ }
341
+ getIsAvailable() {
342
+ return this.descriptor.availability.status === "available";
343
+ }
344
+ getUnavailableReason() {
345
+ return this.descriptor.availability.status === "available" ? null : this.descriptor.availability.reason;
346
+ }
347
+ getStep() {
348
+ return this.descriptor.step ?? null;
349
+ }
350
+ getStepIndex() {
351
+ return this.descriptor.step?.index ?? null;
352
+ }
353
+ getInput(key) {
354
+ return this.inputObjects.find((input) => input.key === key);
355
+ }
356
+ getInputs() {
357
+ return this.inputObjects;
358
+ }
359
+ readiness() {
360
+ const readiness = getInteractionDraftReadiness(this.descriptor, this.draft);
361
+ return {
362
+ ...readiness,
363
+ ready: readiness.ready && this.descriptor.inputs.every(
364
+ (input) => inDomain(input, readiness.values[input.key])
365
+ )
366
+ };
367
+ }
368
+ getIsReady() {
369
+ return this.readiness().ready;
370
+ }
371
+ getMissingInputs() {
372
+ return this.readiness().missingInputs;
373
+ }
374
+ getStatus() {
375
+ return this.status;
376
+ }
377
+ currentLifetime() {
378
+ return this.source === this.owner.options.source && this.epoch === this.owner.epoch && !this.owner.disposed;
379
+ }
380
+ submit() {
381
+ return this.currentLifetime() ? this.owner.submit(this.key, false) : Promise.resolve({ accepted: false, errorCode: "CLOSED" });
382
+ }
383
+ cancel() {
384
+ return this.currentLifetime() ? this.owner.submit(this.key, true) : Promise.resolve({ accepted: false, errorCode: "CLOSED" });
385
+ }
386
+ reset() {
387
+ if (this.currentLifetime()) this.owner.reset(this.key);
388
+ }
389
+ activate() {
390
+ if (this.currentLifetime()) this.owner.activate(this.key);
391
+ }
392
+ getSubmitHandler() {
393
+ return (event) => {
394
+ event?.preventDefault();
395
+ this.owner.handle(this.submit());
396
+ };
397
+ }
398
+ getSubmitProps() {
399
+ const disabled = !this.getIsAvailable() || !this.getIsReady() || this.status !== "open" || !this.connected;
400
+ return {
401
+ ...data("submit", this.key),
402
+ "data-seat": this.seat,
403
+ type: "button",
404
+ disabled,
405
+ "data-ready": this.getIsReady(),
406
+ "data-disabled": disabled,
407
+ onClick: this.getSubmitHandler()
408
+ };
409
+ }
410
+ };
411
+ var InputObject = class {
412
+ constructor(interaction, descriptor) {
413
+ this.interaction = interaction;
414
+ this.descriptor = descriptor;
415
+ this.key = descriptor.key;
416
+ this.kind = descriptor.kind;
417
+ }
418
+ interaction;
419
+ descriptor;
420
+ key;
421
+ kind;
422
+ get game() {
423
+ return this.interaction.game;
424
+ }
425
+ getDomain() {
426
+ return this.descriptor.domain;
427
+ }
428
+ getValue() {
429
+ return this.interaction.draft[this.key] ?? (Object.hasOwn(this.interaction.draft, this.key) ? null : this.descriptor.defaultValue);
430
+ }
431
+ setValue(value) {
432
+ if (!this.interaction.currentLifetime()) return;
433
+ this.interaction.owner.setInput(this.interaction.key, this.key, value);
434
+ }
435
+ clear() {
436
+ if (!this.interaction.currentLifetime()) return;
437
+ this.interaction.owner.clearInput(this.interaction.key, this.key);
438
+ }
439
+ getIsReady() {
440
+ const descriptor = {
441
+ ...this.interaction.descriptor,
442
+ inputs: [this.descriptor]
443
+ };
444
+ return getInteractionDraftReadiness(descriptor, {
445
+ [this.key]: this.getValue()
446
+ }).ready && inDomain(this.descriptor, this.getValue());
447
+ }
448
+ getEligibleTargets() {
449
+ const domain = this.descriptor.domain;
450
+ if (domain.type === "cardTarget" || domain.type === "boardTarget")
451
+ return domain.eligibleTargets;
452
+ if (domain.type === "choice" || domain.type === "choiceList")
453
+ return domain.choices.filter((choice) => !choice.disabled).map((choice) => choice.value);
454
+ return [];
455
+ }
456
+ getIsEligible(value) {
457
+ const input = this.descriptor;
458
+ const selection = input.domain.selection;
459
+ return inDomain(
460
+ { ...input, domain: { ...input.domain, selection: void 0 } },
461
+ value
462
+ ) && (!selection || selection.mode !== "many" || isManyTargetSelectable(input, this.getValue(), String(value)));
463
+ }
464
+ getIsSelected(value) {
465
+ const current = this.getValue();
466
+ return isManyInput(this.descriptor) && Array.isArray(current) ? current.some((item) => equalValue(item, value)) : equalValue(current, value);
467
+ }
468
+ getSelectHandler(value) {
469
+ return () => {
470
+ if (this.interaction.currentLifetime())
471
+ this.interaction.owner.select(this.interaction.key, this.key, value);
472
+ };
473
+ }
474
+ getTargetProps(value) {
475
+ const eligible = this.getIsEligible(value);
476
+ const disabled = !eligible || !this.interaction.getIsAvailable() || this.interaction.status !== "open" || !this.interaction.connected;
477
+ return {
478
+ ...data("select", this.interaction.key, this.key, value),
479
+ "data-seat": this.interaction.seat,
480
+ type: "button",
481
+ disabled,
482
+ "data-eligible": eligible,
483
+ "data-selected": this.getIsSelected(value),
484
+ "data-disabled": disabled,
485
+ onClick: () => this.getSelectHandler(value)()
486
+ };
487
+ }
488
+ getFieldProps() {
489
+ const disabled = !this.interaction.getIsAvailable() || this.interaction.status !== "open" || !this.interaction.connected;
490
+ return {
491
+ ...data("option", this.interaction.key, this.key),
492
+ "data-seat": this.interaction.seat,
493
+ value: this.getValue() ?? "",
494
+ disabled,
495
+ "data-disabled": disabled,
496
+ onChange: (event) => {
497
+ if (event.currentTarget.value === "") {
498
+ this.clear();
499
+ return;
500
+ }
501
+ this.setValue(
502
+ this.descriptor.domain.type === "boundedNumber" ? Number(event.currentTarget.value) : event.currentTarget.value
503
+ );
504
+ }
505
+ };
506
+ }
507
+ };
508
+ var CardObject = class {
509
+ constructor(owner, id, zone, index, encoded, routes) {
510
+ this.owner = owner;
511
+ this.id = id;
512
+ this.zone = zone;
513
+ this.index = index;
514
+ this.routes = routes;
515
+ this.epoch = owner.epoch;
516
+ this.seat = owner.sourceState.snapshot?.players.findIndex(
517
+ (player) => player.playerId === owner.sourceState.snapshot?.me
518
+ ) ?? -1;
519
+ this.view = encoded ? immutableCopy(JSON.parse(encoded)) : null;
520
+ this.hidden = this.view === null;
521
+ }
522
+ owner;
523
+ id;
524
+ zone;
525
+ index;
526
+ routes;
527
+ epoch;
528
+ seat;
529
+ view;
530
+ hidden;
531
+ get game() {
532
+ return this.owner.instance;
533
+ }
534
+ getInteractions() {
535
+ return this.routes;
536
+ }
537
+ getIsEligible() {
538
+ return this.routes.some((route) => route.getIsAvailable());
539
+ }
540
+ getIsSelected() {
541
+ return this.routes.some(
542
+ (route) => route.getInputs().some(
543
+ (input) => input.descriptor.domain.type === "cardTarget" && input.getIsSelected(this.id)
544
+ )
545
+ );
546
+ }
547
+ getCanSelect() {
548
+ return this.routes.some(
549
+ (route) => route.getIsAvailable() && route.status === "open" && route.connected && route.getInputs().some(
550
+ (input) => input.descriptor.domain.type === "cardTarget" && input.getIsEligible(this.id)
551
+ )
552
+ );
553
+ }
554
+ select(options) {
555
+ if (this.epoch !== this.owner.epoch || this.owner.disposed) return;
556
+ this.owner.selectCard(this.id, options?.interaction);
557
+ }
558
+ getSelectHandler(options) {
559
+ return () => this.select(options);
560
+ }
561
+ getProps(options) {
562
+ return {
563
+ type: "button",
564
+ disabled: !this.getCanSelect(),
565
+ "data-action": "select",
566
+ "data-value": this.id,
567
+ "data-interaction": options?.interaction,
568
+ "data-eligible": this.getIsEligible(),
569
+ "data-selected": this.getIsSelected(),
570
+ "data-disabled": !this.getCanSelect(),
571
+ "data-hidden": this.hidden,
572
+ "data-seat": this.seat,
573
+ onClick: this.getSelectHandler(options)
574
+ };
575
+ }
576
+ };
577
+ var ZoneObject = class {
578
+ constructor(owner, id, cards) {
579
+ this.owner = owner;
580
+ this.id = id;
581
+ this.cards = cards;
582
+ }
583
+ owner;
584
+ id;
585
+ cards;
586
+ get game() {
587
+ return this.owner.instance;
588
+ }
589
+ get count() {
590
+ return this.cards.length;
591
+ }
592
+ getIsEmpty() {
593
+ return this.count === 0;
594
+ }
595
+ getCard(id) {
596
+ return this.cards.find((card) => card.id === id);
597
+ }
598
+ getCards(options) {
599
+ return options?.sort ? [...this.cards].sort(options.sort) : this.cards;
600
+ }
601
+ };
602
+ var PlayerObject = class {
603
+ constructor(owner, summary, index, isMe, active) {
604
+ this.owner = owner;
605
+ this.index = index;
606
+ this.isMe = isMe;
607
+ this.active = active;
608
+ this.id = summary.playerId;
609
+ this.name = summary.displayName;
610
+ this.color = summary.color;
611
+ }
612
+ owner;
613
+ index;
614
+ isMe;
615
+ active;
616
+ id;
617
+ name;
618
+ color;
619
+ get game() {
620
+ return this.owner.instance;
621
+ }
622
+ };
623
+ var PhaseObject = class {
624
+ constructor(current) {
625
+ this.current = current;
626
+ }
627
+ current;
628
+ is(name) {
629
+ return this.current === name;
630
+ }
631
+ switch(routes) {
632
+ return this.current === null ? null : routes[this.current]();
633
+ }
634
+ };
635
+ var Controller = class {
636
+ epoch = 0;
637
+ options;
638
+ localDrafts;
639
+ localActive;
640
+ disposed = false;
641
+ instance;
642
+ store;
643
+ prototypes = {
644
+ interaction: Object.create(InteractionObject.prototype),
645
+ input: Object.create(InputObject.prototype),
646
+ card: Object.create(CardObject.prototype),
647
+ zone: Object.create(ZoneObject.prototype),
648
+ board: {}
649
+ };
650
+ subscription;
651
+ sourceState;
652
+ pending = null;
653
+ building;
654
+ rootProperties = /* @__PURE__ */ new Map();
655
+ featureDisposals = [];
656
+ warned = /* @__PURE__ */ new Set();
657
+ invalidControlledState;
658
+ observed = /* @__PURE__ */ new Set();
659
+ revisions = /* @__PURE__ */ new Map();
660
+ lastDrafts = EMPTY_DRAFTS;
661
+ lastInteractions = [];
662
+ lastSnapshotDrafts;
663
+ trackDrafts() {
664
+ const drafts = this.drafts();
665
+ for (const key of /* @__PURE__ */ new Set([
666
+ ...Object.keys(this.lastDrafts),
667
+ ...Object.keys(drafts)
668
+ ]))
669
+ if (this.lastDrafts[key] !== drafts[key])
670
+ this.revisions.set(key, (this.revisions.get(key) ?? 0) + 1);
671
+ this.lastDrafts = drafts;
672
+ }
673
+ constructor(options, features) {
674
+ this.options = options;
675
+ this.localDrafts = options.initialState?.drafts ?? EMPTY_DRAFTS;
676
+ this.localActive = options.initialState?.activeInteraction ?? null;
677
+ this.sourceState = options.source.store.get();
678
+ const root = {
679
+ getSnapshot: () => this.building ?? this.store.get(),
680
+ getOptions: () => this.options,
681
+ setOptions: (next) => this.setOptions(next),
682
+ subscribe: (listener) => {
683
+ const sub = this.store.subscribe(listener);
684
+ return () => sub.unsubscribe();
685
+ },
686
+ dispose: () => this.dispose(),
687
+ assertCoverage: () => this.assertCoverage(),
688
+ inspect: () => this.store.get()
689
+ };
690
+ this.instance = root;
691
+ for (const key of [
692
+ "snapshot",
693
+ "view",
694
+ "version",
695
+ "connection",
696
+ "request",
697
+ "state",
698
+ "phase",
699
+ "turn",
700
+ "me",
701
+ "players",
702
+ "interactions",
703
+ "inputs",
704
+ "zones",
705
+ "cards",
706
+ "events"
707
+ ])
708
+ Object.defineProperty(root, key, {
709
+ get: () => (this.building ?? this.store.get())[key]
710
+ });
711
+ this.store = createStore(Object.freeze(this.build()));
712
+ Object.defineProperty(root, "store", {
713
+ value: {
714
+ get: () => this.store.get(),
715
+ subscribe: (listener) => this.store.subscribe(listener)
716
+ }
717
+ });
718
+ if ("apply" in options.source)
719
+ Object.defineProperty(root, "apply", {
720
+ value: (action) => this.options.source.apply(action)
721
+ });
722
+ if ("explore" in options.source)
723
+ Object.defineProperty(root, "explore", {
724
+ value: (...args) => this.options.source.explore(...args)
725
+ });
726
+ const context = {
727
+ createBoard: (id, data2) => this.object("board", {
728
+ id,
729
+ data: immutableCopy(data2),
730
+ game: this.instance
731
+ }),
732
+ routeTarget: (kind, id, options2) => this.routeTarget(kind, id, options2?.interaction, options2?.boardId),
733
+ routeCardDrop: (cardId, target) => this.routeCardDrop(cardId, target),
734
+ invalidate: () => {
735
+ if (!this.disposed) this.refresh();
736
+ }
737
+ };
738
+ for (const feature of Object.values(
739
+ features?.(this.instance, context) ?? {}
740
+ )) {
741
+ if (feature.root)
742
+ for (const key of Reflect.ownKeys(feature.root)) {
743
+ if (key in root || this.rootProperties.has(key))
744
+ throw new Error(`Overlapping feature property '${String(key)}'.`);
745
+ this.rootProperties.set(
746
+ key,
747
+ Object.getOwnPropertyDescriptor(feature.root, key)
748
+ );
749
+ Object.defineProperty(root, key, {
750
+ get: () => Reflect.get(this.building ?? this.store.get(), key)
751
+ });
752
+ }
753
+ for (const hook of [
754
+ "interaction",
755
+ "input",
756
+ "zone",
757
+ "card",
758
+ "board"
759
+ ])
760
+ this.install(this.prototypes[hook], feature[hook]);
761
+ if (feature.dispose) this.featureDisposals.push(feature.dispose);
762
+ }
763
+ this.refresh();
764
+ this.subscribeSource();
765
+ }
766
+ install(target, extension) {
767
+ if (!extension) return;
768
+ for (const key of Reflect.ownKeys(extension)) {
769
+ if (key in target || [
770
+ "game",
771
+ "owner",
772
+ "descriptor",
773
+ "draft",
774
+ "inputObjects",
775
+ "status",
776
+ "connected",
777
+ "key",
778
+ "id",
779
+ "phase",
780
+ "label",
781
+ "help",
782
+ "kind",
783
+ "interaction",
784
+ "view",
785
+ "hidden",
786
+ "zone",
787
+ "index",
788
+ "routes",
789
+ "cards"
790
+ ].includes(String(key)))
791
+ throw new Error(`Overlapping feature property '${String(key)}'.`);
792
+ }
793
+ Object.defineProperties(
794
+ target,
795
+ Object.getOwnPropertyDescriptors(extension)
796
+ );
797
+ }
798
+ object(hook, value) {
799
+ return Object.freeze(Object.setPrototypeOf(value, this.prototypes[hook]));
800
+ }
801
+ drafts() {
802
+ if (this.options.state && this.options.state === this.invalidControlledState)
803
+ return EMPTY_DRAFTS;
804
+ return this.options.state?.drafts ?? this.localDrafts;
805
+ }
806
+ active() {
807
+ if (this.options.state && this.options.state === this.invalidControlledState)
808
+ return null;
809
+ return this.options.state?.activeInteraction !== void 0 ? this.options.state.activeInteraction : this.localActive;
810
+ }
811
+ writeDrafts(next) {
812
+ if (this.options.state?.drafts === void 0) this.localDrafts = next;
813
+ this.options.onDraftsChange?.(next);
814
+ this.refresh();
815
+ }
816
+ writeDraft(key, value) {
817
+ const drafts = this.drafts();
818
+ const next = { ...drafts };
819
+ if (value === void 0) delete next[key];
820
+ else next[key] = immutableValues(value);
821
+ this.writeDrafts(Object.freeze(next));
822
+ }
823
+ activate(key) {
824
+ if (this.disposed || key !== null && !this.current(key)) return;
825
+ if (this.options.state?.activeInteraction === void 0)
826
+ this.localActive = key;
827
+ this.options.onActiveInteractionChange?.(key);
828
+ this.refresh();
829
+ }
830
+ descriptorFor(base, draft) {
831
+ const zones = this.sourceState.snapshot?.frame.zones ?? {};
832
+ for (const input of base.inputs) {
833
+ const card = draft[input.key];
834
+ if (input.domain.type !== "cardTarget" || typeof card !== "string")
835
+ continue;
836
+ for (const zone of Object.values(zones)) {
837
+ const route = zone.playableByCardId[card]?.find(
838
+ (value) => value.interactionKey === base.interactionKey
839
+ );
840
+ if (route) return route;
841
+ }
842
+ }
843
+ return base;
844
+ }
845
+ current(key) {
846
+ return this.store.get().interactions.get(key);
847
+ }
848
+ editable(key) {
849
+ const interaction = this.current(key);
850
+ return !this.disposed && this.sourceState.connection === "ready" && !this.sourceState.request && interaction?.getIsAvailable() ? interaction : void 0;
851
+ }
852
+ setInput(key, inputKey, value) {
853
+ const interaction = this.editable(key);
854
+ if (!interaction?.getInput(inputKey)) return;
855
+ this.writeDraft(key, { ...this.drafts()[key], [inputKey]: value });
856
+ }
857
+ clearInput(key, inputKey) {
858
+ if (!this.editable(key)?.getInput(inputKey)) return;
859
+ const value = { ...this.drafts()[key] };
860
+ delete value[inputKey];
861
+ this.writeDraft(key, value);
862
+ }
863
+ reset(key) {
864
+ if (this.disposed) return;
865
+ this.writeDraft(key, void 0);
866
+ if (this.active() === key) this.activate(null);
867
+ }
868
+ select(key, inputKey, value, route) {
869
+ const interaction = route ?? this.editable(key);
870
+ if (route && (!route.currentLifetime() || !route.getIsAvailable() || this.sourceState.connection !== "ready" || this.sourceState.request || this.pending))
871
+ return;
872
+ const input = interaction?.getInput(inputKey);
873
+ if (!interaction || !input?.getIsEligible(value)) return;
874
+ if (typeof value === "string" && (input.descriptor.domain.type === "cardTarget" || input.descriptor.domain.type === "boardTarget")) {
875
+ let next = { ...this.drafts()[key] };
876
+ routeInteractionTarget(
877
+ {
878
+ getDraft: () => next,
879
+ setInput: (_key, k, v) => {
880
+ next = { ...next, [k]: v };
881
+ },
882
+ clearInput: (_key, k) => {
883
+ if (k) delete next[k];
884
+ }
885
+ },
886
+ interaction.descriptor,
887
+ { inputKey, value }
888
+ );
889
+ this.writeDraft(key, next);
890
+ } else this.setInput(key, inputKey, value);
891
+ this.activate(key);
892
+ const current = this.current(key);
893
+ if (current && Object.hasOwn(this.drafts()[key] ?? EMPTY, inputKey) && shouldAutoSubmitInteraction(current.descriptor) && current.getIsReady())
894
+ this.handle(this.submit(key, false));
895
+ }
896
+ selectCard(id, explicit) {
897
+ const card = this.store.get().cards.get(id);
898
+ if (!card) return;
899
+ let routes = card.routes.filter(
900
+ (route2) => route2.getIsAvailable() && route2.currentLifetime() && !this.sourceState.request && this.sourceState.connection === "ready" && route2.getInputs().some(
901
+ (input2) => input2.descriptor.domain.type === "cardTarget" && input2.getIsEligible(id)
902
+ )
903
+ );
904
+ if (explicit) routes = routes.filter((route2) => route2.key === explicit);
905
+ if (routes.length > 1) throw new AmbiguousTargetError(id);
906
+ const route = routes[0];
907
+ const input = route?.getInputs().find(
908
+ (input2) => input2.descriptor.domain.type === "cardTarget" && input2.getIsEligible(id)
909
+ );
910
+ if (route && input) this.select(route.key, input.key, id, route);
911
+ }
912
+ handle(promise) {
913
+ void promise.then(
914
+ (result) => {
915
+ if (!result.accepted)
916
+ this.options.onError?.(
917
+ new Error(result.message ?? result.errorCode, { cause: result })
918
+ );
919
+ },
920
+ (error) => this.options.onError?.(error)
921
+ );
922
+ }
923
+ async submit(key, cancel) {
924
+ const source = this.options.source;
925
+ const interaction = this.current(key);
926
+ if (this.disposed || this.sourceState.connection !== "ready" || this.sourceState.request || this.pending)
927
+ return { accepted: false, errorCode: "BUSY" };
928
+ if (!interaction) return { accepted: false, errorCode: "UNAVAILABLE" };
929
+ if (cancel ? !interaction.getStep()?.canCancel : !interaction.getIsAvailable() || !interaction.getIsReady())
930
+ return { accepted: false, errorCode: "UNAVAILABLE" };
931
+ if (!("submit" in source) || !("cancel" in source))
932
+ return { accepted: false, errorCode: "READ_ONLY" };
933
+ const commandSource = source;
934
+ const operation = {
935
+ source,
936
+ key,
937
+ draft: this.drafts()[key] ?? EMPTY,
938
+ revision: this.revisions.get(key) ?? 0,
939
+ version: this.sourceState.snapshot.version,
940
+ epoch: this.epoch,
941
+ accepted: false,
942
+ cancel
943
+ };
944
+ this.pending = operation;
945
+ try {
946
+ const result = await (cancel ? commandSource.cancel(interaction.id) : commandSource.submit(
947
+ interaction.id,
948
+ interaction.readiness().values
949
+ ));
950
+ if (this.pending !== operation) return result;
951
+ if (!result.accepted) {
952
+ this.pending = null;
953
+ this.refresh();
954
+ return result;
955
+ }
956
+ operation.accepted = true;
957
+ this.finishBarrier();
958
+ return result;
959
+ } catch (error) {
960
+ if (this.pending === operation) {
961
+ this.pending = null;
962
+ this.refresh();
963
+ }
964
+ throw error;
965
+ }
966
+ }
967
+ finishBarrier() {
968
+ const operation = this.pending;
969
+ if (!operation?.accepted || this.sourceState.request || operation.source !== this.options.source || operation.epoch !== this.epoch)
970
+ return;
971
+ if (this.sourceState.connection === "closed") {
972
+ this.pending = null;
973
+ this.refresh();
974
+ return;
975
+ }
976
+ if (this.sourceState.connection !== "ready" || (this.sourceState.snapshot?.version ?? -1) <= operation.version)
977
+ return;
978
+ this.pending = null;
979
+ if ((this.revisions.get(operation.key) ?? 0) === operation.revision)
980
+ this.reset(operation.key);
981
+ else this.refresh();
982
+ }
983
+ subscribeSource() {
984
+ const source = this.options.source;
985
+ this.subscription = source.store.subscribe(() => {
986
+ if (this.disposed || this.options.source !== source) return;
987
+ const next = source.store.get();
988
+ const previousSnapshot = this.sourceState.snapshot;
989
+ this.sourceState = next;
990
+ if (previousSnapshot && next.snapshot && previousSnapshot.me !== next.snapshot.me) {
991
+ this.epoch++;
992
+ this.invalidControlledState = this.options.state;
993
+ this.pending = null;
994
+ this.localDrafts = EMPTY_DRAFTS;
995
+ this.localActive = null;
996
+ this.options.onDraftsChange?.({});
997
+ this.options.onActiveInteractionChange?.(null);
998
+ }
999
+ this.reconcile();
1000
+ this.refresh();
1001
+ this.finishBarrier();
1002
+ });
1003
+ }
1004
+ setOptions(next) {
1005
+ if (this.disposed) throw new Error("Game instance is disposed.");
1006
+ const changed = next.source !== this.options.source;
1007
+ if (changed) {
1008
+ this.epoch++;
1009
+ this.subscription?.unsubscribe();
1010
+ this.options.source.dispose();
1011
+ this.pending = null;
1012
+ }
1013
+ this.options = next;
1014
+ if (changed) {
1015
+ this.invalidControlledState = next.state;
1016
+ this.localDrafts = EMPTY_DRAFTS;
1017
+ this.localActive = null;
1018
+ this.sourceState = next.source.store.get();
1019
+ this.options.onDraftsChange?.({});
1020
+ this.options.onActiveInteractionChange?.(null);
1021
+ this.subscribeSource();
1022
+ }
1023
+ this.reconcile();
1024
+ this.refresh();
1025
+ }
1026
+ reconcile() {
1027
+ const snapshot = this.sourceState.snapshot;
1028
+ if (!snapshot) return;
1029
+ const drafts = this.drafts();
1030
+ let changed = false;
1031
+ const next = { ...drafts };
1032
+ for (const [key, values] of Object.entries(drafts)) {
1033
+ if (!values) continue;
1034
+ const base = snapshot.frame.availableInteractions.find(
1035
+ (value) => value.interactionKey === key
1036
+ );
1037
+ if (!base) {
1038
+ if (this.pending?.key !== key) {
1039
+ delete next[key];
1040
+ changed = true;
1041
+ }
1042
+ continue;
1043
+ }
1044
+ const descriptor = this.descriptorFor(base, values);
1045
+ const kept = { ...values };
1046
+ let fieldChanged = false;
1047
+ for (const [name, value] of Object.entries(values)) {
1048
+ const input = descriptor.inputs.find((input2) => input2.key === name);
1049
+ if (!input && this.pending?.key === key) continue;
1050
+ if (input?.domain.selection?.mode === "many" && Array.isArray(value)) {
1051
+ const eligible = value.filter(
1052
+ (item) => inputValueInDomain(input.domain, item)
1053
+ );
1054
+ const max = input.domain.selection.max;
1055
+ const retained = max === void 0 ? eligible : eligible.slice(0, max);
1056
+ if (retained.length !== value.length) {
1057
+ kept[name] = retained;
1058
+ changed = true;
1059
+ fieldChanged = true;
1060
+ }
1061
+ continue;
1062
+ }
1063
+ if (!input || !inDomain(input, value, true)) {
1064
+ delete kept[name];
1065
+ changed = true;
1066
+ fieldChanged = true;
1067
+ }
1068
+ }
1069
+ if (fieldChanged) next[key] = immutableValues(kept);
1070
+ }
1071
+ if (changed) this.writeDrafts(Object.freeze(next));
1072
+ const active = this.active();
1073
+ if (active && !snapshot.frame.availableInteractions.some(
1074
+ (value) => value.interactionKey === active
1075
+ ))
1076
+ this.activate(null);
1077
+ }
1078
+ dispose() {
1079
+ if (this.disposed) return;
1080
+ this.disposed = true;
1081
+ this.featureDisposals.forEach((dispose) => dispose());
1082
+ this.subscription?.unsubscribe();
1083
+ this.pending = null;
1084
+ this.options.source.dispose();
1085
+ this.sourceState = {
1086
+ ...this.sourceState,
1087
+ connection: "closed",
1088
+ request: null
1089
+ };
1090
+ this.refresh();
1091
+ }
1092
+ assertCoverage() {
1093
+ const missing = this.sourceState.snapshot?.frame.availableInteractions.filter(
1094
+ (d) => d.availability.status === "available" && !this.observed.has(d.interactionKey)
1095
+ ).map((d) => d.interactionKey) ?? [];
1096
+ if (missing.length)
1097
+ throw new Error(`Interactions not read: ${missing.join(", ")}`);
1098
+ }
1099
+ routeTarget(kind, id, explicit, boardId) {
1100
+ if (kind === "card") {
1101
+ this.selectCard(id, explicit);
1102
+ return;
1103
+ }
1104
+ const candidates = this.lastInteractions.flatMap(
1105
+ (interaction) => interaction.getInputs().filter(
1106
+ (input) => input.descriptor.domain.type === "boardTarget" && (input.descriptor.domain.targetKind === kind || kind === "space" && input.descriptor.domain.targetKind === "tile") && (!boardId || input.descriptor.domain.boardId === boardId) && input.getIsEligible(id) && interaction.getIsAvailable() && (!explicit || interaction.key === explicit)
1107
+ ).map((input) => ({ interaction, input }))
1108
+ );
1109
+ if (candidates.length > 1) throw new AmbiguousTargetError(id);
1110
+ const match = candidates[0];
1111
+ if (match) this.select(match.interaction.key, match.input.key, id);
1112
+ }
1113
+ routeCardDrop(cardId, target) {
1114
+ if (this.disposed || this.sourceState.request || this.pending || this.sourceState.connection !== "ready")
1115
+ return;
1116
+ const card = this.store.get().cards.get(cardId);
1117
+ if (!card) return;
1118
+ const candidates = card.routes.flatMap((interaction2) => {
1119
+ if (!interaction2.getIsAvailable() || target.interaction && interaction2.key !== target.interaction)
1120
+ return [];
1121
+ const cardInput2 = interaction2.getInputs().find(
1122
+ (input2) => input2.descriptor.domain.type === "cardTarget" && input2.getIsEligible(cardId)
1123
+ );
1124
+ if (!cardInput2) return [];
1125
+ return interaction2.getInputs().filter(
1126
+ (input2) => input2.descriptor.domain.type === "boardTarget" && (input2.descriptor.domain.targetKind === target.kind || target.kind === "space" && input2.descriptor.domain.targetKind === "tile") && (!target.boardId || input2.descriptor.domain.boardId === target.boardId) && input2.getIsEligible(target.id)
1127
+ ).map((input2) => ({ interaction: interaction2, cardInput: cardInput2, input: input2 }));
1128
+ });
1129
+ if (candidates.length > 1) throw new AmbiguousTargetError(target.id);
1130
+ const candidate = candidates[0];
1131
+ if (!candidate) return;
1132
+ const { interaction, cardInput, input } = candidate;
1133
+ let next = { ...this.drafts()[interaction.key] };
1134
+ routeCardInputIntent(
1135
+ {
1136
+ getDraft: () => next,
1137
+ setInput: (_key, key, value) => {
1138
+ next = { ...next, [key]: value };
1139
+ },
1140
+ clearInput: (_key, key) => {
1141
+ if (key) delete next[key];
1142
+ }
1143
+ },
1144
+ interaction.descriptor,
1145
+ {
1146
+ cardId,
1147
+ cardInputKey: cardInput.key,
1148
+ dropTarget: { inputKey: input.key, value: target.id }
1149
+ }
1150
+ );
1151
+ this.writeDraft(interaction.key, next);
1152
+ this.activate(interaction.key);
1153
+ const current = this.current(interaction.key);
1154
+ if (current && shouldAutoSubmitInteraction(current.descriptor) && current.getIsReady())
1155
+ this.handle(current.submit());
1156
+ }
1157
+ warnUnread() {
1158
+ const environment = import.meta.env?.DEV ?? globalThis.process?.env?.NODE_ENV === "development";
1159
+ if (!(this.options.debug ?? environment)) return;
1160
+ queueMicrotask(() => {
1161
+ if (this.disposed) return;
1162
+ for (const descriptor of this.sourceState.snapshot?.frame.availableInteractions ?? []) {
1163
+ const key = descriptor.interactionKey;
1164
+ if (descriptor.availability.status === "available" && !this.observed.has(key) && !this.warned.has(key)) {
1165
+ this.warned.add(key);
1166
+ console.warn(`Available interaction '${key}' has not been read.`);
1167
+ }
1168
+ }
1169
+ });
1170
+ }
1171
+ build(previous) {
1172
+ this.trackDrafts();
1173
+ const { snapshot, connection, request } = this.sourceState;
1174
+ const old = previous?.snapshot;
1175
+ const drafts = this.drafts();
1176
+ const active = this.active();
1177
+ const sameSnapshot = snapshot === old;
1178
+ const sameActors = sameSnapshot;
1179
+ const summaries = snapshot?.players ?? [];
1180
+ const activeIds = snapshot?.frame.flow.activePlayers ?? [];
1181
+ const playerObjects = sameActors && previous ? previous.players.getAll() : Object.freeze(
1182
+ summaries.map(
1183
+ (player, index) => Object.freeze(
1184
+ new PlayerObject(
1185
+ this,
1186
+ player,
1187
+ index,
1188
+ player.playerId === snapshot?.me,
1189
+ activeIds.includes(player.playerId)
1190
+ )
1191
+ )
1192
+ )
1193
+ );
1194
+ const players = sameActors && previous ? previous.players : {
1195
+ order: Object.freeze(playerObjects.map((p) => p.id)),
1196
+ get: (id) => playerObjects.find((p) => p.id === id),
1197
+ getAll: () => playerObjects,
1198
+ next: (id) => {
1199
+ const index = playerObjects.findIndex((p) => p.id === id);
1200
+ return index < 0 ? void 0 : playerObjects[(index + 1) % playerObjects.length];
1201
+ }
1202
+ };
1203
+ const me = sameActors && previous ? previous.me : snapshot ? {
1204
+ id: snapshot.me,
1205
+ player: players.get(snapshot.me),
1206
+ getCanAct: () => snapshot.frame.availableInteractions.some(
1207
+ (value) => value.availability.status === "available"
1208
+ )
1209
+ } : null;
1210
+ const descriptors = snapshot?.frame.availableInteractions ?? [];
1211
+ const interactionObjects = descriptors.map((base) => {
1212
+ const key = base.interactionKey;
1213
+ const draft = drafts[key] ?? EMPTY;
1214
+ const descriptor = this.descriptorFor(base, draft);
1215
+ const status = request || this.pending ? request?.interactionId === descriptor.interactionId && request.phase === "awaiting-frame" ? "submitted" : "submitting" : "open";
1216
+ const oldObject = this.lastInteractions.find(
1217
+ (value) => value.key === key
1218
+ );
1219
+ return oldObject?.descriptor === descriptor && oldObject.epoch === this.epoch && oldObject.source === this.options.source && oldObject.draftIdentity === draft && oldObject.status === status && oldObject.connected === (connection === "ready") ? oldObject : this.object(
1220
+ "interaction",
1221
+ new InteractionObject(
1222
+ this,
1223
+ descriptor,
1224
+ draft,
1225
+ status,
1226
+ connection === "ready"
1227
+ )
1228
+ );
1229
+ });
1230
+ const interactionsSame = previous && interactionObjects.length === this.lastInteractions.length && interactionObjects.every(
1231
+ (value, index) => value === this.lastInteractions[index]
1232
+ );
1233
+ this.lastInteractions = Object.freeze(interactionObjects);
1234
+ const interactions = interactionsSame ? previous.interactions : {
1235
+ get: (key) => {
1236
+ this.observed.add(key);
1237
+ return interactionObjects.find((value) => value.key === key);
1238
+ },
1239
+ list: () => {
1240
+ interactionObjects.forEach((value) => this.observed.add(value.key));
1241
+ return Object.freeze(interactionObjects);
1242
+ },
1243
+ listAvailable: () => {
1244
+ const available = interactionObjects.filter(
1245
+ (value) => value.getIsAvailable()
1246
+ );
1247
+ available.forEach((value) => this.observed.add(value.key));
1248
+ return Object.freeze(available);
1249
+ }
1250
+ };
1251
+ const inputs = interactionsSame && previous ? previous.inputs : {
1252
+ get: (key, name) => interactions.get(key)?.getInput(name)
1253
+ };
1254
+ const zonesSame = sameSnapshot && interactionsSame && previous;
1255
+ const zones = zonesSame ? previous.zones : (() => {
1256
+ const objects = Object.entries(snapshot?.frame.zones ?? {}).map(
1257
+ ([id, zone]) => this.object(
1258
+ "zone",
1259
+ new ZoneObject(
1260
+ this,
1261
+ id,
1262
+ Object.freeze(
1263
+ zone.cardIds.map(
1264
+ (cardId, index) => this.object(
1265
+ "card",
1266
+ new CardObject(
1267
+ this,
1268
+ cardId,
1269
+ id,
1270
+ index,
1271
+ zone.cardViewsById[cardId],
1272
+ Object.freeze(
1273
+ (zone.playableByCardId[cardId] ?? []).map(
1274
+ (descriptor) => this.object(
1275
+ "interaction",
1276
+ new InteractionObject(
1277
+ this,
1278
+ descriptor,
1279
+ drafts[descriptor.interactionKey] ?? EMPTY,
1280
+ request || this.pending ? "submitting" : "open",
1281
+ connection === "ready"
1282
+ )
1283
+ )
1284
+ )
1285
+ )
1286
+ )
1287
+ )
1288
+ )
1289
+ )
1290
+ )
1291
+ )
1292
+ );
1293
+ return {
1294
+ get: (id) => objects.find((zone) => zone.id === id),
1295
+ getAll: () => Object.freeze(objects)
1296
+ };
1297
+ })();
1298
+ const cards = zonesSame ? previous.cards : {
1299
+ get: (id) => zones.getAll().map((zone) => zone.getCard(id)).find((card) => card !== void 0)
1300
+ };
1301
+ const capturedState = this.lastSnapshotDrafts === drafts && previous?.state.activeInteraction === active ? previous.state : immutableCopy({ drafts, activeInteraction: active });
1302
+ this.lastSnapshotDrafts = drafts;
1303
+ for (const value of [players, me, interactions, inputs, zones, cards])
1304
+ if (value) Object.freeze(value);
1305
+ const model = {
1306
+ snapshot,
1307
+ view: snapshot?.frame.view ?? null,
1308
+ version: snapshot?.version ?? null,
1309
+ connection,
1310
+ request,
1311
+ state: capturedState,
1312
+ phase: sameSnapshot && previous ? previous.phase : Object.freeze(
1313
+ new PhaseObject(snapshot?.frame.flow.currentPhase ?? null)
1314
+ ),
1315
+ turn: sameSnapshot && previous ? previous.turn : Object.freeze({
1316
+ activePlayerIds: activeIds,
1317
+ currentPlayerId: activeIds.length === 1 ? activeIds[0] : null,
1318
+ order: players.order,
1319
+ isMine: snapshot ? activeIds.includes(snapshot.me) : false
1320
+ }),
1321
+ me,
1322
+ players,
1323
+ interactions,
1324
+ inputs,
1325
+ zones,
1326
+ cards,
1327
+ events: sameSnapshot && previous ? previous.events : Object.freeze({
1328
+ recent: snapshot?.frame.events ?? Object.freeze([])
1329
+ })
1330
+ };
1331
+ this.building = model;
1332
+ try {
1333
+ for (const [key, descriptor] of this.rootProperties)
1334
+ Object.defineProperty(model, key, {
1335
+ value: descriptor.get ? descriptor.get.call(this.instance) : descriptor.value,
1336
+ enumerable: true
1337
+ });
1338
+ } finally {
1339
+ this.building = void 0;
1340
+ }
1341
+ return model;
1342
+ }
1343
+ refresh() {
1344
+ if (!this.store) return;
1345
+ this.store.setState((previous) => Object.freeze(this.build(previous)));
1346
+ this.warnUnread();
1347
+ }
1348
+ };
1349
+ function createGameInstance() {
1350
+ return function(options) {
1351
+ const controller = new Controller(
1352
+ options,
1353
+ options.features
1354
+ );
1355
+ return controller.instance;
1356
+ };
1357
+ }
1358
+
1359
+ export {
1360
+ AmbiguousTargetError,
1361
+ createGameInstance
1362
+ };
1363
+ //# sourceMappingURL=chunk-SP234IY4.js.map