@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,273 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { createPlayerId, createZoneId } from "../types";
3
+ import type { TargetDefinition, TargetRestriction } from "./target-definition";
4
+
5
+ describe("Target Definition Types", () => {
6
+ describe("TargetDefinition", () => {
7
+ it("should define target with filter and count", () => {
8
+ const target: TargetDefinition = {
9
+ filter: { zone: createZoneId("play") },
10
+ count: 1,
11
+ };
12
+
13
+ expect(target.filter).toBeDefined();
14
+ expect(target.count).toBe(1);
15
+ });
16
+
17
+ it("should support optional targets with min/max count", () => {
18
+ const target: TargetDefinition = {
19
+ filter: { type: "creature" },
20
+ count: { min: 0, max: 3 },
21
+ };
22
+
23
+ expect(target.count).toEqual({ min: 0, max: 3 });
24
+ });
25
+
26
+ it("should support required targets with exact count", () => {
27
+ const target: TargetDefinition = {
28
+ filter: { zone: createZoneId("hand") },
29
+ count: 2,
30
+ };
31
+
32
+ expect(target.count).toBe(2);
33
+ });
34
+
35
+ it("should support targeting restrictions", () => {
36
+ const target: TargetDefinition = {
37
+ filter: { type: "creature" },
38
+ count: 1,
39
+ restrictions: ["not-self"],
40
+ };
41
+
42
+ expect(target.restrictions).toContain("not-self");
43
+ });
44
+
45
+ it("should support multiple restrictions", () => {
46
+ const target: TargetDefinition = {
47
+ filter: { zone: createZoneId("play") },
48
+ count: 2,
49
+ restrictions: ["not-self", "different-targets"],
50
+ };
51
+
52
+ expect(target.restrictions).toHaveLength(2);
53
+ });
54
+
55
+ it("should work without restrictions", () => {
56
+ const target: TargetDefinition = {
57
+ filter: { type: "land" },
58
+ count: 1,
59
+ };
60
+
61
+ expect(target.restrictions).toBeUndefined();
62
+ });
63
+ });
64
+
65
+ describe("TargetRestriction", () => {
66
+ it("should support 'not-self' restriction", () => {
67
+ const restriction: TargetRestriction = "not-self";
68
+ expect(restriction).toBe("not-self");
69
+ });
70
+
71
+ it("should support 'not-controller' restriction", () => {
72
+ const restriction: TargetRestriction = "not-controller";
73
+ expect(restriction).toBe("not-controller");
74
+ });
75
+
76
+ it("should support 'not-owner' restriction", () => {
77
+ const restriction: TargetRestriction = "not-owner";
78
+ expect(restriction).toBe("not-owner");
79
+ });
80
+
81
+ it("should support 'different-targets' restriction", () => {
82
+ const restriction: TargetRestriction = "different-targets";
83
+ expect(restriction).toBe("different-targets");
84
+ });
85
+ });
86
+
87
+ describe("Target Count Types", () => {
88
+ it("should support exact count as number", () => {
89
+ const count: number | { min: number; max: number } = 1;
90
+ expect(count).toBe(1);
91
+ });
92
+
93
+ it("should support range with min and max", () => {
94
+ const count: number | { min: number; max: number } = {
95
+ min: 1,
96
+ max: 3,
97
+ };
98
+ expect(count).toEqual({ min: 1, max: 3 });
99
+ });
100
+
101
+ it("should support optional targets with min 0", () => {
102
+ const count: number | { min: number; max: number } = {
103
+ min: 0,
104
+ max: 5,
105
+ };
106
+ expect(count.min).toBe(0);
107
+ });
108
+
109
+ it("should support unbounded max targets", () => {
110
+ const count: number | { min: number; max: number } = {
111
+ min: 1,
112
+ max: Number.POSITIVE_INFINITY,
113
+ };
114
+ expect(count.max).toBe(Number.POSITIVE_INFINITY);
115
+ });
116
+ });
117
+
118
+ describe("Multi-Target Definitions", () => {
119
+ it("should support multiple target groups", () => {
120
+ const targets: TargetDefinition[] = [
121
+ {
122
+ filter: { type: "creature" },
123
+ count: 1,
124
+ },
125
+ {
126
+ filter: { type: "player" },
127
+ count: 1,
128
+ },
129
+ ];
130
+
131
+ expect(targets).toHaveLength(2);
132
+ expect(targets[0].filter.type).toBe("creature");
133
+ expect(targets[1].filter.type).toBe("player");
134
+ });
135
+
136
+ it("should support optional and required targets together", () => {
137
+ const targets: TargetDefinition[] = [
138
+ {
139
+ filter: { type: "creature" },
140
+ count: 1,
141
+ },
142
+ {
143
+ filter: { type: "land" },
144
+ count: { min: 0, max: 2 },
145
+ },
146
+ ];
147
+
148
+ expect(typeof targets[0].count).toBe("number");
149
+ expect(typeof targets[1].count).toBe("object");
150
+ });
151
+ });
152
+
153
+ describe("Complex Target Definitions", () => {
154
+ it("should support targets with complex filters", () => {
155
+ const playZone = createZoneId("play");
156
+ const target: TargetDefinition = {
157
+ filter: {
158
+ zone: playZone,
159
+ type: "creature",
160
+ properties: {
161
+ basePower: { gte: 3 },
162
+ },
163
+ tapped: false,
164
+ },
165
+ count: { min: 1, max: 3 },
166
+ restrictions: ["not-self"],
167
+ };
168
+
169
+ expect(target.filter.zone).toBe(playZone);
170
+ expect(target.filter.type).toBe("creature");
171
+ expect(target.filter.properties?.basePower).toEqual({ gte: 3 });
172
+ expect(target.filter.tapped).toBe(false);
173
+ expect(target.count).toEqual({ min: 1, max: 3 });
174
+ expect(target.restrictions).toContain("not-self");
175
+ });
176
+
177
+ it("should support targets with composite filters", () => {
178
+ const target: TargetDefinition = {
179
+ filter: {
180
+ or: [{ type: "creature" }, { type: "artifact" }],
181
+ },
182
+ count: 2,
183
+ restrictions: ["different-targets"],
184
+ };
185
+
186
+ expect(target.filter.or).toBeDefined();
187
+ expect(target.filter.or).toHaveLength(2);
188
+ expect(target.restrictions).toContain("different-targets");
189
+ });
190
+ });
191
+
192
+ describe("Real-World Examples", () => {
193
+ it("should model Lightning Bolt (1 target, any creature or player)", () => {
194
+ const target: TargetDefinition = {
195
+ filter: {
196
+ or: [{ type: "creature" }, { type: "player" }],
197
+ },
198
+ count: 1,
199
+ };
200
+
201
+ expect(target.count).toBe(1);
202
+ expect(target.filter.or).toHaveLength(2);
203
+ });
204
+
205
+ it("should model Wrath of God (no targets)", () => {
206
+ const targets: TargetDefinition[] = [];
207
+
208
+ expect(targets).toHaveLength(0);
209
+ });
210
+
211
+ it("should model Fireball (1 required target, additional optional)", () => {
212
+ const targets: TargetDefinition[] = [
213
+ {
214
+ filter: {
215
+ or: [{ type: "creature" }, { type: "player" }],
216
+ },
217
+ count: 1,
218
+ },
219
+ {
220
+ filter: {
221
+ or: [{ type: "creature" }, { type: "player" }],
222
+ },
223
+ count: { min: 0, max: Number.POSITIVE_INFINITY },
224
+ restrictions: ["different-targets"],
225
+ },
226
+ ];
227
+
228
+ expect(targets).toHaveLength(2);
229
+ expect(targets[0].count).toBe(1);
230
+ expect(targets[1].count).toEqual({
231
+ min: 0,
232
+ max: Number.POSITIVE_INFINITY,
233
+ });
234
+ });
235
+
236
+ it("should model Pump spell (target creature you control)", () => {
237
+ const player = createPlayerId("player-1");
238
+ const target: TargetDefinition = {
239
+ filter: {
240
+ type: "creature",
241
+ controller: player,
242
+ },
243
+ count: 1,
244
+ };
245
+
246
+ expect(target.filter.controller).toBe(player);
247
+ });
248
+
249
+ it("should model Fight spell (2 creatures, can't target same)", () => {
250
+ const target: TargetDefinition = {
251
+ filter: { type: "creature" },
252
+ count: 2,
253
+ restrictions: ["different-targets"],
254
+ };
255
+
256
+ expect(target.count).toBe(2);
257
+ expect(target.restrictions).toContain("different-targets");
258
+ });
259
+
260
+ it("should model Act of Treason (opponent's creature)", () => {
261
+ const _player = createPlayerId("player-1");
262
+ const target: TargetDefinition = {
263
+ filter: {
264
+ type: "creature",
265
+ },
266
+ count: 1,
267
+ restrictions: ["not-controller"],
268
+ };
269
+
270
+ expect(target.restrictions).toContain("not-controller");
271
+ });
272
+ });
273
+ });
@@ -0,0 +1,37 @@
1
+ import type { CardFilter } from "../filtering/card-filter";
2
+
3
+ /**
4
+ * Target restriction types
5
+ * Defines additional constraints on target selection beyond the filter
6
+ */
7
+ export type TargetRestriction =
8
+ | "not-self" // Cannot target the source card itself
9
+ | "not-controller" // Cannot target cards controlled by the move's controller
10
+ | "not-owner" // Cannot target cards owned by the move's player
11
+ | "different-targets"; // All selected targets must be different cards
12
+
13
+ /**
14
+ * Target count specification
15
+ * Can be an exact number or a range (min, max)
16
+ */
17
+ export type TargetCount =
18
+ | number // Exact count (required)
19
+ | {
20
+ min: number; // Minimum number of targets (0 = optional)
21
+ max: number; // Maximum number of targets (can be Infinity)
22
+ };
23
+
24
+ /**
25
+ * Target definition for a move
26
+ * Specifies what can be targeted and how many targets are needed
27
+ */
28
+ export type TargetDefinition = {
29
+ /** Filter defining valid target cards/entities */
30
+ filter: CardFilter;
31
+
32
+ /** Number of targets required/allowed */
33
+ count: TargetCount;
34
+
35
+ /** Optional restrictions on target selection */
36
+ restrictions?: TargetRestriction[];
37
+ };
@@ -0,0 +1,279 @@
1
+ /**
2
+ * Unified Target DSL - Core Types
3
+ *
4
+ * Game-agnostic targeting primitives that TCG engines can extend.
5
+ * This module provides the foundational DSL structure for expressing
6
+ * card and player targeting in a declarative, composable way.
7
+ *
8
+ * @module targeting/target-dsl
9
+ */
10
+
11
+ import type { CardFilter } from "../filtering/card-filter";
12
+
13
+ // ============================================================================
14
+ // Selector: HOW targets are selected
15
+ // ============================================================================
16
+
17
+ /**
18
+ * Selector scope determines how targets are selected from valid options
19
+ *
20
+ * @example
21
+ * - "self": Target the source card itself
22
+ * - "chosen": Player actively selects from valid targets
23
+ * - "all": All matching cards are automatically targeted
24
+ * - "each": Semantic alias for "all" (used in effect descriptions)
25
+ * - "any": Single target, typically random or first-match
26
+ * - "random": Explicitly random selection
27
+ */
28
+ export type SelectorScope =
29
+ | "self" // This card (the source of the ability)
30
+ | "chosen" // Player chooses from valid options
31
+ | "all" // All matching cards
32
+ | "each" // Each matching (semantic alias for effects)
33
+ | "any" // Any single matching card
34
+ | "random"; // Random selection from valid options
35
+
36
+ // ============================================================================
37
+ // Owner Scope: WHOSE cards can be selected
38
+ // ============================================================================
39
+
40
+ /**
41
+ * Owner scope determines which players' cards are valid targets
42
+ */
43
+ export type OwnerScope =
44
+ | "you" // Controller of source card
45
+ | "opponent" // Opponent(s)
46
+ | "any"; // Any player's cards
47
+
48
+ // ============================================================================
49
+ // Target Count: HOW MANY to select
50
+ // ============================================================================
51
+
52
+ /**
53
+ * Target count specification with various semantics
54
+ *
55
+ * @example
56
+ * - `1`: Exactly 1 target (required)
57
+ * - `{ exactly: 2 }`: Exactly 2 targets
58
+ * - `{ upTo: 3 }`: 0 to 3 targets (player chooses how many)
59
+ * - `{ atLeast: 1 }`: 1 or more targets
60
+ * - `{ between: [2, 4] }`: 2 to 4 targets
61
+ * - `"all"`: All matching targets
62
+ */
63
+ export type TargetCount =
64
+ | number // Exact count (required)
65
+ | { exactly: number } // Explicit exact count
66
+ | { upTo: number } // 0 to N (optional up to max)
67
+ | { atLeast: number } // N or more (minimum required)
68
+ | { between: [number, number] } // Range [min, max]
69
+ | "all"; // All matching
70
+
71
+ // ============================================================================
72
+ // Context References: Contextual card references
73
+ // ============================================================================
74
+
75
+ /**
76
+ * Base context for targeting - game engines extend this
77
+ *
78
+ * Provides references to cards based on the current game context,
79
+ * such as the trigger source, combat participants, etc.
80
+ */
81
+ export interface BaseContext {
82
+ /** Reference the source card itself */
83
+ self?: boolean;
84
+ }
85
+
86
+ // ============================================================================
87
+ // Core Target DSL Structure
88
+ // ============================================================================
89
+
90
+ /**
91
+ * Core Target DSL - The main targeting structure
92
+ *
93
+ * This generic type defines how targets are selected. Game-specific
94
+ * engines extend the filter and context type parameters.
95
+ *
96
+ * @typeParam TFilter - Type of filters (extends CardFilter or game-specific)
97
+ * @typeParam TContext - Type of context references
98
+ *
99
+ * @example Basic character targeting
100
+ * ```typescript
101
+ * const target: TargetDSL = {
102
+ * selector: "chosen",
103
+ * count: 1,
104
+ * owner: "opponent",
105
+ * zones: ["play"],
106
+ * cardTypes: ["character"]
107
+ * };
108
+ * ```
109
+ *
110
+ * @example Targeting with filters
111
+ * ```typescript
112
+ * const target: TargetDSL = {
113
+ * selector: "all",
114
+ * owner: "any",
115
+ * zones: ["play"],
116
+ * filter: { type: "creature", tapped: true }
117
+ * };
118
+ * ```
119
+ */
120
+ export interface TargetDSL<
121
+ TFilter = CardFilter,
122
+ TContext extends BaseContext = BaseContext,
123
+ > {
124
+ /** How targets are selected (chosen, all, self, etc.) */
125
+ selector: SelectorScope;
126
+
127
+ /** How many targets to select */
128
+ count?: TargetCount;
129
+
130
+ /** Whose cards can be targeted */
131
+ owner?: OwnerScope;
132
+
133
+ /** Which zones to search for targets */
134
+ zones?: string[];
135
+
136
+ /** Card type restriction (game-specific type names) */
137
+ cardTypes?: string[];
138
+
139
+ /** Additional filter criteria (merged with base filter) */
140
+ filter?: TFilter;
141
+
142
+ /** Context references (self, trigger-source, etc.) */
143
+ context?: TContext;
144
+
145
+ /** Exclude the source card from valid targets */
146
+ excludeSelf?: boolean;
147
+
148
+ /** All selected targets must be different cards */
149
+ requireDifferentTargets?: boolean;
150
+ }
151
+
152
+ // ============================================================================
153
+ // UI Hint Types
154
+ // ============================================================================
155
+
156
+ /**
157
+ * UI hints for target selection interfaces
158
+ *
159
+ * Games can use these to generate appropriate selection UI
160
+ */
161
+ export interface TargetingUIHint {
162
+ /** Type of selection UI to show */
163
+ selectionType: "single" | "multiple" | "automatic" | "none";
164
+
165
+ /** Minimum number of selections required */
166
+ minSelections: number;
167
+
168
+ /** Maximum number of selections allowed */
169
+ maxSelections: number | "unlimited";
170
+
171
+ /** Human-readable prompt for the player */
172
+ prompt: string;
173
+
174
+ /** Whether selection is optional (can select 0) */
175
+ optional: boolean;
176
+
177
+ /** Zone(s) to highlight in the UI */
178
+ highlightZones: string[];
179
+ }
180
+
181
+ // ============================================================================
182
+ // Player Targeting
183
+ // ============================================================================
184
+
185
+ /**
186
+ * Player target specification (separate from card targeting)
187
+ *
188
+ * Used when effects target players rather than cards.
189
+ *
190
+ * @example
191
+ * - "controller": The player who controls the source card
192
+ * - "opponent": A single opponent
193
+ * - "each-player": All players
194
+ * - "each-opponent": All opponents
195
+ * - "chosen-player": Player selects which player to target
196
+ */
197
+ export type PlayerTargetDSL =
198
+ | "controller" // Player who controls the source
199
+ | "opponent" // Single opponent (or active opponent in 1v1)
200
+ | "each-player" // All players including controller
201
+ | "each-opponent" // All opponents
202
+ | "chosen-player"; // Controller chooses which player
203
+
204
+ // ============================================================================
205
+ // Type Utilities
206
+ // ============================================================================
207
+
208
+ /**
209
+ * Extract the minimum count from a TargetCount specification
210
+ */
211
+ export function getMinCount(count: TargetCount | undefined): number {
212
+ if (count === undefined) return 1;
213
+ if (count === "all") return 0;
214
+ if (typeof count === "number") return count;
215
+ if ("exactly" in count) return count.exactly;
216
+ if ("upTo" in count) return 0;
217
+ if ("atLeast" in count) return count.atLeast;
218
+ if ("between" in count) return count.between[0];
219
+ return 0;
220
+ }
221
+
222
+ /**
223
+ * Extract the maximum count from a TargetCount specification
224
+ */
225
+ export function getMaxCount(
226
+ count: TargetCount | undefined,
227
+ ): number | "unlimited" {
228
+ if (count === undefined) return 1;
229
+ if (count === "all") return "unlimited";
230
+ if (typeof count === "number") return count;
231
+ if ("exactly" in count) return count.exactly;
232
+ if ("upTo" in count) return count.upTo;
233
+ if ("atLeast" in count) return "unlimited";
234
+ if ("between" in count) return count.between[1];
235
+ return 1;
236
+ }
237
+
238
+ /**
239
+ * Check if a target count is optional (allows selecting 0)
240
+ */
241
+ export function isOptionalCount(count: TargetCount | undefined): boolean {
242
+ return getMinCount(count) === 0;
243
+ }
244
+
245
+ /**
246
+ * Check if a selector requires player interaction
247
+ */
248
+ export function requiresPlayerChoice(selector: SelectorScope): boolean {
249
+ return selector === "chosen";
250
+ }
251
+
252
+ /**
253
+ * Check if a selector targets multiple cards
254
+ */
255
+ export function isMultipleTargetSelector(selector: SelectorScope): boolean {
256
+ return selector === "all" || selector === "each";
257
+ }
258
+
259
+ // ============================================================================
260
+ // Default Values
261
+ // ============================================================================
262
+
263
+ /**
264
+ * Default target DSL for a single chosen card
265
+ */
266
+ export const DEFAULT_SINGLE_TARGET: Partial<TargetDSL> = {
267
+ selector: "chosen",
268
+ count: 1,
269
+ owner: "any",
270
+ };
271
+
272
+ /**
273
+ * Default target DSL for self-targeting
274
+ */
275
+ export const DEFAULT_SELF_TARGET: Partial<TargetDSL> = {
276
+ selector: "self",
277
+ count: 1,
278
+ context: { self: true },
279
+ };