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