@drmxrcy/tcg-core 0.0.0-202602060542

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 (157) hide show
  1. package/README.md +882 -0
  2. package/package.json +58 -0
  3. package/src/__tests__/alpha-clash-engine-definition.test.ts +319 -0
  4. package/src/__tests__/createMockAlphaClashGame.ts +462 -0
  5. package/src/__tests__/createMockGrandArchiveGame.ts +373 -0
  6. package/src/__tests__/createMockGundamGame.ts +379 -0
  7. package/src/__tests__/createMockLorcanaGame.ts +328 -0
  8. package/src/__tests__/createMockOnePieceGame.ts +429 -0
  9. package/src/__tests__/createMockRiftboundGame.ts +462 -0
  10. package/src/__tests__/grand-archive-engine-definition.test.ts +118 -0
  11. package/src/__tests__/gundam-engine-definition.test.ts +110 -0
  12. package/src/__tests__/integration-complete-game.test.ts +508 -0
  13. package/src/__tests__/integration-network-sync.test.ts +469 -0
  14. package/src/__tests__/lorcana-engine-definition.test.ts +100 -0
  15. package/src/__tests__/move-enumeration.test.ts +725 -0
  16. package/src/__tests__/multiplayer-engine.test.ts +555 -0
  17. package/src/__tests__/one-piece-engine-definition.test.ts +114 -0
  18. package/src/__tests__/riftbound-engine-definition.test.ts +124 -0
  19. package/src/actions/action-definition.test.ts +201 -0
  20. package/src/actions/action-definition.ts +122 -0
  21. package/src/actions/action-timing.test.ts +490 -0
  22. package/src/actions/action-timing.ts +257 -0
  23. package/src/cards/card-definition.test.ts +268 -0
  24. package/src/cards/card-definition.ts +27 -0
  25. package/src/cards/card-instance.test.ts +422 -0
  26. package/src/cards/card-instance.ts +49 -0
  27. package/src/cards/computed-properties.test.ts +530 -0
  28. package/src/cards/computed-properties.ts +84 -0
  29. package/src/cards/conditional-modifiers.test.ts +390 -0
  30. package/src/cards/modifiers.test.ts +286 -0
  31. package/src/cards/modifiers.ts +51 -0
  32. package/src/engine/MULTIPLAYER.md +425 -0
  33. package/src/engine/__tests__/rule-engine-flow.test.ts +348 -0
  34. package/src/engine/__tests__/rule-engine-history.test.ts +535 -0
  35. package/src/engine/__tests__/rule-engine-moves.test.ts +488 -0
  36. package/src/engine/__tests__/rule-engine.test.ts +366 -0
  37. package/src/engine/index.ts +14 -0
  38. package/src/engine/multiplayer-engine.example.ts +571 -0
  39. package/src/engine/multiplayer-engine.ts +409 -0
  40. package/src/engine/rule-engine.test.ts +286 -0
  41. package/src/engine/rule-engine.ts +1539 -0
  42. package/src/engine/tracker-system.ts +172 -0
  43. package/src/examples/__tests__/coin-flip-game.test.ts +641 -0
  44. package/src/filtering/card-filter.test.ts +230 -0
  45. package/src/filtering/card-filter.ts +91 -0
  46. package/src/filtering/card-query.test.ts +901 -0
  47. package/src/filtering/card-query.ts +273 -0
  48. package/src/filtering/filter-matching.test.ts +944 -0
  49. package/src/filtering/filter-matching.ts +315 -0
  50. package/src/flow/SERIALIZATION.md +428 -0
  51. package/src/flow/__tests__/flow-definition.test.ts +427 -0
  52. package/src/flow/__tests__/flow-manager.test.ts +756 -0
  53. package/src/flow/__tests__/flow-serialization.test.ts +565 -0
  54. package/src/flow/flow-definition.ts +453 -0
  55. package/src/flow/flow-manager.ts +1044 -0
  56. package/src/flow/index.ts +35 -0
  57. package/src/game-definition/__tests__/game-definition-validation.test.ts +359 -0
  58. package/src/game-definition/__tests__/game-definition.test.ts +291 -0
  59. package/src/game-definition/__tests__/move-definitions.test.ts +328 -0
  60. package/src/game-definition/game-definition.ts +261 -0
  61. package/src/game-definition/index.ts +28 -0
  62. package/src/game-definition/move-definitions.ts +188 -0
  63. package/src/game-definition/validation.ts +183 -0
  64. package/src/history/history-manager.test.ts +497 -0
  65. package/src/history/history-manager.ts +312 -0
  66. package/src/history/history-operations.ts +122 -0
  67. package/src/history/index.ts +9 -0
  68. package/src/history/types.ts +255 -0
  69. package/src/index.ts +32 -0
  70. package/src/logging/index.ts +27 -0
  71. package/src/logging/log-formatter.ts +187 -0
  72. package/src/logging/logger.ts +276 -0
  73. package/src/logging/types.ts +148 -0
  74. package/src/moves/create-move.test.ts +331 -0
  75. package/src/moves/create-move.ts +64 -0
  76. package/src/moves/move-enumeration.ts +228 -0
  77. package/src/moves/move-executor.test.ts +431 -0
  78. package/src/moves/move-executor.ts +195 -0
  79. package/src/moves/move-system.test.ts +380 -0
  80. package/src/moves/move-system.ts +463 -0
  81. package/src/moves/standard-moves.ts +231 -0
  82. package/src/operations/card-operations.test.ts +236 -0
  83. package/src/operations/card-operations.ts +116 -0
  84. package/src/operations/card-registry-impl.test.ts +251 -0
  85. package/src/operations/card-registry-impl.ts +70 -0
  86. package/src/operations/card-registry.test.ts +234 -0
  87. package/src/operations/card-registry.ts +106 -0
  88. package/src/operations/counter-operations.ts +152 -0
  89. package/src/operations/game-operations.test.ts +280 -0
  90. package/src/operations/game-operations.ts +140 -0
  91. package/src/operations/index.ts +24 -0
  92. package/src/operations/operations-impl.test.ts +354 -0
  93. package/src/operations/operations-impl.ts +468 -0
  94. package/src/operations/zone-operations.test.ts +295 -0
  95. package/src/operations/zone-operations.ts +223 -0
  96. package/src/rng/seeded-rng.test.ts +339 -0
  97. package/src/rng/seeded-rng.ts +123 -0
  98. package/src/targeting/index.ts +48 -0
  99. package/src/targeting/target-definition.test.ts +273 -0
  100. package/src/targeting/target-definition.ts +37 -0
  101. package/src/targeting/target-dsl.ts +279 -0
  102. package/src/targeting/target-resolver.ts +486 -0
  103. package/src/targeting/target-validation.test.ts +994 -0
  104. package/src/targeting/target-validation.ts +286 -0
  105. package/src/telemetry/events.ts +202 -0
  106. package/src/telemetry/index.ts +21 -0
  107. package/src/telemetry/telemetry-manager.ts +127 -0
  108. package/src/telemetry/types.ts +68 -0
  109. package/src/testing/__tests__/testing-utilities-integration.test.ts +161 -0
  110. package/src/testing/index.ts +88 -0
  111. package/src/testing/test-assertions.test.ts +341 -0
  112. package/src/testing/test-assertions.ts +256 -0
  113. package/src/testing/test-card-factory.test.ts +228 -0
  114. package/src/testing/test-card-factory.ts +111 -0
  115. package/src/testing/test-context-factory.ts +187 -0
  116. package/src/testing/test-end-assertions.test.ts +262 -0
  117. package/src/testing/test-end-assertions.ts +95 -0
  118. package/src/testing/test-engine-builder.test.ts +389 -0
  119. package/src/testing/test-engine-builder.ts +46 -0
  120. package/src/testing/test-flow-assertions.test.ts +284 -0
  121. package/src/testing/test-flow-assertions.ts +115 -0
  122. package/src/testing/test-player-builder.test.ts +132 -0
  123. package/src/testing/test-player-builder.ts +46 -0
  124. package/src/testing/test-replay-assertions.test.ts +356 -0
  125. package/src/testing/test-replay-assertions.ts +164 -0
  126. package/src/testing/test-rng-helpers.test.ts +260 -0
  127. package/src/testing/test-rng-helpers.ts +190 -0
  128. package/src/testing/test-state-builder.test.ts +373 -0
  129. package/src/testing/test-state-builder.ts +99 -0
  130. package/src/testing/test-zone-factory.test.ts +295 -0
  131. package/src/testing/test-zone-factory.ts +224 -0
  132. package/src/types/branded-utils.ts +54 -0
  133. package/src/types/branded.test.ts +175 -0
  134. package/src/types/branded.ts +33 -0
  135. package/src/types/index.ts +8 -0
  136. package/src/types/state.test.ts +198 -0
  137. package/src/types/state.ts +154 -0
  138. package/src/validation/card-type-guards.test.ts +242 -0
  139. package/src/validation/card-type-guards.ts +179 -0
  140. package/src/validation/index.ts +40 -0
  141. package/src/validation/schema-builders.test.ts +403 -0
  142. package/src/validation/schema-builders.ts +345 -0
  143. package/src/validation/type-guard-builder.test.ts +216 -0
  144. package/src/validation/type-guard-builder.ts +109 -0
  145. package/src/validation/validator-builder.test.ts +375 -0
  146. package/src/validation/validator-builder.ts +273 -0
  147. package/src/zones/index.ts +28 -0
  148. package/src/zones/zone-factory.test.ts +183 -0
  149. package/src/zones/zone-factory.ts +44 -0
  150. package/src/zones/zone-operations.test.ts +800 -0
  151. package/src/zones/zone-operations.ts +306 -0
  152. package/src/zones/zone-state-helpers.test.ts +337 -0
  153. package/src/zones/zone-state-helpers.ts +128 -0
  154. package/src/zones/zone-visibility.test.ts +156 -0
  155. package/src/zones/zone-visibility.ts +36 -0
  156. package/src/zones/zone.test.ts +186 -0
  157. package/src/zones/zone.ts +66 -0
@@ -0,0 +1,486 @@
1
+ /**
2
+ * Target Resolution - Interfaces for resolving DSL to actual card selections
3
+ *
4
+ * This module defines the contracts that game engines implement to
5
+ * resolve target DSL specifications into actual card selections.
6
+ *
7
+ * @module targeting/target-resolver
8
+ */
9
+
10
+ import type { CardInstance } from "../cards/card-instance";
11
+ import type { PlayerId } from "../types";
12
+ import type {
13
+ BaseContext,
14
+ TargetCount,
15
+ TargetDSL,
16
+ TargetingUIHint,
17
+ } from "./target-dsl";
18
+
19
+ // ============================================================================
20
+ // Resolution Context
21
+ // ============================================================================
22
+
23
+ /**
24
+ * Context provided during target resolution
25
+ *
26
+ * Contains all information needed to evaluate targeting constraints.
27
+ * Game engines extend this with game-specific context.
28
+ *
29
+ * @typeParam TGameState - The game state type
30
+ * @typeParam TCard - The card instance type
31
+ */
32
+ export interface TargetResolutionContext<
33
+ TGameState,
34
+ TCard extends CardInstance<unknown>,
35
+ > {
36
+ /** Current game state */
37
+ state: TGameState;
38
+
39
+ /** The card that is the source of targeting (if any) */
40
+ sourceCard?: TCard;
41
+
42
+ /** The player performing the targeting action */
43
+ sourcePlayer: PlayerId;
44
+
45
+ /** Previously selected targets (for multi-target validation) */
46
+ previousTargets?: TCard[];
47
+
48
+ /**
49
+ * Game-specific context extensions
50
+ * Games add properties like triggerSource, attacker, defender, etc.
51
+ */
52
+ [key: string]: unknown;
53
+ }
54
+
55
+ // ============================================================================
56
+ // Target Resolver Interface
57
+ // ============================================================================
58
+
59
+ /**
60
+ * Interface for target resolution implementations
61
+ *
62
+ * Game engines implement this to provide targeting logic specific to their rules.
63
+ *
64
+ * @typeParam TGameState - The game state type
65
+ * @typeParam TCard - The card instance type
66
+ * @typeParam TTarget - The target DSL type (game-specific extension)
67
+ */
68
+ export interface TargetResolver<
69
+ TGameState,
70
+ TCard extends CardInstance<unknown>,
71
+ TTarget extends TargetDSL,
72
+ > {
73
+ /**
74
+ * Get all valid targets matching the DSL specification
75
+ *
76
+ * @param target - Target DSL specification
77
+ * @param context - Resolution context
78
+ * @returns Array of cards that are valid targets
79
+ */
80
+ getValidTargets(
81
+ target: TTarget,
82
+ context: TargetResolutionContext<TGameState, TCard>,
83
+ ): TCard[];
84
+
85
+ /**
86
+ * Check if a specific card is a valid target
87
+ *
88
+ * @param card - Card to check
89
+ * @param target - Target DSL specification
90
+ * @param context - Resolution context
91
+ * @returns true if the card is a valid target
92
+ */
93
+ isValidTarget(
94
+ card: TCard,
95
+ target: TTarget,
96
+ context: TargetResolutionContext<TGameState, TCard>,
97
+ ): boolean;
98
+
99
+ /**
100
+ * Validate a target selection
101
+ *
102
+ * @param selectedTargets - Cards selected by the player
103
+ * @param target - Target DSL specification
104
+ * @param context - Resolution context
105
+ * @returns Validation result with error message if invalid
106
+ */
107
+ validateSelection(
108
+ selectedTargets: TCard[],
109
+ target: TTarget,
110
+ context: TargetResolutionContext<TGameState, TCard>,
111
+ ): TargetValidationResult;
112
+
113
+ /**
114
+ * Get UI hints for target selection interface
115
+ *
116
+ * @param target - Target DSL specification
117
+ * @param context - Resolution context
118
+ * @returns UI hints for rendering selection interface
119
+ */
120
+ getTargetingUI(
121
+ target: TTarget,
122
+ context: TargetResolutionContext<TGameState, TCard>,
123
+ ): TargetingUIHint;
124
+ }
125
+
126
+ // ============================================================================
127
+ // Validation Result
128
+ // ============================================================================
129
+
130
+ /**
131
+ * Result of target selection validation
132
+ */
133
+ export interface TargetValidationResult {
134
+ /** Whether the selection is valid */
135
+ valid: boolean;
136
+
137
+ /** Error message if invalid */
138
+ error?: string;
139
+
140
+ /** Specific issues with individual targets */
141
+ issues?: TargetIssue[];
142
+ }
143
+
144
+ /**
145
+ * Issue with a specific target in a selection
146
+ */
147
+ export interface TargetIssue {
148
+ /** Index of the problematic target */
149
+ index: number;
150
+
151
+ /** ID of the problematic card */
152
+ cardId: string;
153
+
154
+ /** Description of the issue */
155
+ reason: string;
156
+ }
157
+
158
+ // ============================================================================
159
+ // Helper Functions
160
+ // ============================================================================
161
+
162
+ /**
163
+ * Create a successful validation result
164
+ */
165
+ export function validSelection(): TargetValidationResult {
166
+ return { valid: true };
167
+ }
168
+
169
+ /**
170
+ * Create a failed validation result
171
+ */
172
+ export function invalidSelection(
173
+ error: string,
174
+ issues?: TargetIssue[],
175
+ ): TargetValidationResult {
176
+ return { valid: false, error, issues };
177
+ }
178
+
179
+ /**
180
+ * Validate target count against a specification
181
+ */
182
+ export function validateTargetCount(
183
+ selectedCount: number,
184
+ targetCount: TargetCount | undefined,
185
+ availableCount: number,
186
+ ): TargetValidationResult {
187
+ // Default to exactly 1 if not specified
188
+ const count = targetCount ?? 1;
189
+
190
+ if (count === "all") {
191
+ // "all" is valid as long as we selected all available
192
+ // (or the player selected what they could)
193
+ return validSelection();
194
+ }
195
+
196
+ if (typeof count === "number") {
197
+ if (selectedCount !== count) {
198
+ // Check if there weren't enough available
199
+ if (availableCount < count) {
200
+ return invalidSelection(
201
+ `Expected ${count} target(s) but only ${availableCount} available`,
202
+ );
203
+ }
204
+ return invalidSelection(
205
+ `Expected exactly ${count} target(s), but got ${selectedCount}`,
206
+ );
207
+ }
208
+ return validSelection();
209
+ }
210
+
211
+ if ("exactly" in count) {
212
+ if (selectedCount !== count.exactly) {
213
+ return invalidSelection(
214
+ `Expected exactly ${count.exactly} target(s), but got ${selectedCount}`,
215
+ );
216
+ }
217
+ return validSelection();
218
+ }
219
+
220
+ if ("upTo" in count) {
221
+ if (selectedCount > count.upTo) {
222
+ return invalidSelection(
223
+ `Expected at most ${count.upTo} target(s), but got ${selectedCount}`,
224
+ );
225
+ }
226
+ return validSelection();
227
+ }
228
+
229
+ if ("atLeast" in count) {
230
+ if (selectedCount < count.atLeast) {
231
+ return invalidSelection(
232
+ `Expected at least ${count.atLeast} target(s), but got ${selectedCount}`,
233
+ );
234
+ }
235
+ return validSelection();
236
+ }
237
+
238
+ if ("between" in count) {
239
+ const [min, max] = count.between;
240
+ if (selectedCount < min) {
241
+ return invalidSelection(
242
+ `Expected at least ${min} target(s), but got ${selectedCount}`,
243
+ );
244
+ }
245
+ if (selectedCount > max) {
246
+ return invalidSelection(
247
+ `Expected at most ${max} target(s), but got ${selectedCount}`,
248
+ );
249
+ }
250
+ return validSelection();
251
+ }
252
+
253
+ return validSelection();
254
+ }
255
+
256
+ // ============================================================================
257
+ // Abstract Base Resolver
258
+ // ============================================================================
259
+
260
+ /**
261
+ * Abstract base class for target resolvers
262
+ *
263
+ * Provides common functionality that game-specific resolvers can extend.
264
+ *
265
+ * @typeParam TGameState - The game state type
266
+ * @typeParam TCard - The card instance type
267
+ * @typeParam TTarget - The target DSL type
268
+ * @typeParam TContext - The context type
269
+ */
270
+ export abstract class BaseTargetResolver<
271
+ TGameState,
272
+ TCard extends CardInstance<unknown>,
273
+ TTarget extends TargetDSL<any, any>,
274
+ TContext extends BaseContext = BaseContext,
275
+ > implements TargetResolver<TGameState, TCard, TTarget>
276
+ {
277
+ /**
278
+ * Get all cards in specified zones
279
+ * Game engines override this to access their zone system
280
+ */
281
+ protected abstract getCardsInZones(
282
+ zones: string[] | undefined,
283
+ context: TargetResolutionContext<TGameState, TCard>,
284
+ ): TCard[];
285
+
286
+ /**
287
+ * Check if a card matches ownership constraints
288
+ */
289
+ protected abstract matchesOwnership(
290
+ card: TCard,
291
+ owner: "you" | "opponent" | "any" | undefined,
292
+ context: TargetResolutionContext<TGameState, TCard>,
293
+ ): boolean;
294
+
295
+ /**
296
+ * Check if a card matches card type constraints
297
+ */
298
+ protected abstract matchesCardType(
299
+ card: TCard,
300
+ cardTypes: string[] | undefined,
301
+ context: TargetResolutionContext<TGameState, TCard>,
302
+ ): boolean;
303
+
304
+ /**
305
+ * Apply game-specific filters to a card
306
+ */
307
+ protected abstract applyFilter(
308
+ card: TCard,
309
+ filter: unknown,
310
+ context: TargetResolutionContext<TGameState, TCard>,
311
+ ): boolean;
312
+
313
+ /**
314
+ * Generate human-readable description of the target
315
+ */
316
+ protected abstract generateDescription(target: TTarget): string;
317
+
318
+ getValidTargets(
319
+ target: TTarget,
320
+ context: TargetResolutionContext<TGameState, TCard>,
321
+ ): TCard[] {
322
+ // Handle self selector
323
+ if (target.selector === "self" && context.sourceCard) {
324
+ return [context.sourceCard];
325
+ }
326
+
327
+ // Get all cards in specified zones
328
+ let candidates = this.getCardsInZones(target.zones, context);
329
+
330
+ // Apply ownership filter
331
+ candidates = candidates.filter((card) =>
332
+ this.matchesOwnership(card, target.owner, context),
333
+ );
334
+
335
+ // Apply card type filter
336
+ candidates = candidates.filter((card) =>
337
+ this.matchesCardType(card, target.cardTypes, context),
338
+ );
339
+
340
+ // Apply game-specific filter
341
+ if (target.filter) {
342
+ candidates = candidates.filter((card) =>
343
+ this.applyFilter(card, target.filter, context),
344
+ );
345
+ }
346
+
347
+ // Handle excludeSelf
348
+ if (target.excludeSelf && context.sourceCard) {
349
+ candidates = candidates.filter(
350
+ (card) => card.id !== context.sourceCard!.id,
351
+ );
352
+ }
353
+
354
+ // Handle different targets requirement
355
+ if (target.requireDifferentTargets && context.previousTargets) {
356
+ const previousIds = new Set(context.previousTargets.map((c) => c.id));
357
+ candidates = candidates.filter((card) => !previousIds.has(card.id));
358
+ }
359
+
360
+ return candidates;
361
+ }
362
+
363
+ isValidTarget(
364
+ card: TCard,
365
+ target: TTarget,
366
+ context: TargetResolutionContext<TGameState, TCard>,
367
+ ): boolean {
368
+ const validTargets = this.getValidTargets(target, context);
369
+ return validTargets.some((t) => t.id === card.id);
370
+ }
371
+
372
+ validateSelection(
373
+ selectedTargets: TCard[],
374
+ target: TTarget,
375
+ context: TargetResolutionContext<TGameState, TCard>,
376
+ ): TargetValidationResult {
377
+ const validTargets = this.getValidTargets(target, context);
378
+
379
+ // Validate count
380
+ const countResult = validateTargetCount(
381
+ selectedTargets.length,
382
+ target.count,
383
+ validTargets.length,
384
+ );
385
+ if (!countResult.valid) {
386
+ return countResult;
387
+ }
388
+
389
+ // Validate each selected target is valid
390
+ const issues: TargetIssue[] = [];
391
+ for (let i = 0; i < selectedTargets.length; i++) {
392
+ const selected = selectedTargets[i];
393
+ if (!selected) {
394
+ issues.push({
395
+ index: i,
396
+ cardId: "undefined",
397
+ reason: "Target is undefined",
398
+ });
399
+ continue;
400
+ }
401
+
402
+ if (!validTargets.some((t) => t.id === selected.id)) {
403
+ issues.push({
404
+ index: i,
405
+ cardId: String(selected.id),
406
+ reason: "Not a valid target",
407
+ });
408
+ }
409
+ }
410
+
411
+ if (issues.length > 0) {
412
+ return invalidSelection("Some selected targets are invalid", issues);
413
+ }
414
+
415
+ // Check for duplicates if required
416
+ if (target.requireDifferentTargets) {
417
+ const ids = selectedTargets.map((t) => t.id);
418
+ const uniqueIds = new Set(ids);
419
+ if (uniqueIds.size !== ids.length) {
420
+ return invalidSelection("All targets must be different cards");
421
+ }
422
+ }
423
+
424
+ return validSelection();
425
+ }
426
+
427
+ getTargetingUI(
428
+ target: TTarget,
429
+ context: TargetResolutionContext<TGameState, TCard>,
430
+ ): TargetingUIHint {
431
+ const validTargets = this.getValidTargets(target, context);
432
+
433
+ // Determine selection type
434
+ let selectionType: TargetingUIHint["selectionType"];
435
+ if (target.selector === "self") {
436
+ selectionType = "none";
437
+ } else if (target.selector === "all" || target.selector === "each") {
438
+ selectionType = "automatic";
439
+ } else if (target.selector === "chosen") {
440
+ const maxCount =
441
+ target.count === "all"
442
+ ? validTargets.length
443
+ : typeof target.count === "number"
444
+ ? target.count
445
+ : target.count && "upTo" in target.count
446
+ ? target.count.upTo
447
+ : 1;
448
+ selectionType = maxCount > 1 ? "multiple" : "single";
449
+ } else {
450
+ selectionType = "single";
451
+ }
452
+
453
+ // Calculate min/max
454
+ let minSelections = 1;
455
+ let maxSelections: number | "unlimited" = 1;
456
+
457
+ if (target.count === "all") {
458
+ minSelections = validTargets.length;
459
+ maxSelections = validTargets.length;
460
+ } else if (typeof target.count === "number") {
461
+ minSelections = target.count;
462
+ maxSelections = target.count;
463
+ } else if (target.count && "upTo" in target.count) {
464
+ minSelections = 0;
465
+ maxSelections = target.count.upTo;
466
+ } else if (target.count && "atLeast" in target.count) {
467
+ minSelections = target.count.atLeast;
468
+ maxSelections = "unlimited";
469
+ } else if (target.count && "between" in target.count) {
470
+ minSelections = target.count.between[0];
471
+ maxSelections = target.count.between[1];
472
+ } else if (target.count && "exactly" in target.count) {
473
+ minSelections = target.count.exactly;
474
+ maxSelections = target.count.exactly;
475
+ }
476
+
477
+ return {
478
+ selectionType,
479
+ minSelections,
480
+ maxSelections,
481
+ prompt: this.generateDescription(target),
482
+ optional: minSelections === 0,
483
+ highlightZones: target.zones || [],
484
+ };
485
+ }
486
+ }