@drmxrcy/tcg-lorcana 0.0.0-202602060544

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 (100) hide show
  1. package/README.md +160 -0
  2. package/package.json +45 -0
  3. package/src/__tests__/integration/move-enumeration.test.ts +256 -0
  4. package/src/__tests__/rules/section-01-concepts.test.ts +426 -0
  5. package/src/__tests__/rules/section-03-gameplay.test.ts +298 -0
  6. package/src/__tests__/rules/section-04-turn-structure.test.ts +708 -0
  7. package/src/__tests__/rules/section-05-cards.test.ts +158 -0
  8. package/src/__tests__/rules/section-06-card-types.test.ts +342 -0
  9. package/src/__tests__/rules/section-07-abilities.test.ts +333 -0
  10. package/src/__tests__/rules/section-08-zones.test.ts +231 -0
  11. package/src/__tests__/rules/section-09-damage.test.ts +148 -0
  12. package/src/__tests__/rules/section-10-keywords.test.ts +469 -0
  13. package/src/__tests__/spec-01-foundation-types.test.ts +534 -0
  14. package/src/__tests__/spec-02-zones-card-states.test.ts +295 -0
  15. package/src/card-utils.ts +302 -0
  16. package/src/cards/README.md +296 -0
  17. package/src/cards/abilities/index.ts +175 -0
  18. package/src/cards/index.ts +10 -0
  19. package/src/deck-validation.ts +175 -0
  20. package/src/engine/lorcana-engine.ts +625 -0
  21. package/src/game-definition/__tests__/core-zone-integration.test.ts +553 -0
  22. package/src/game-definition/__tests__/zone-operations.test.ts +362 -0
  23. package/src/game-definition/__tests__/zones.test.ts +176 -0
  24. package/src/game-definition/definition.ts +45 -0
  25. package/src/game-definition/flow/turn-flow.ts +216 -0
  26. package/src/game-definition/index.ts +31 -0
  27. package/src/game-definition/moves/abilities/activate-ability.ts +51 -0
  28. package/src/game-definition/moves/core/__tests__/move-parameter-enumeration.test.ts +316 -0
  29. package/src/game-definition/moves/core/challenge.test.ts +545 -0
  30. package/src/game-definition/moves/core/challenge.ts +81 -0
  31. package/src/game-definition/moves/core/play-card.ts +83 -0
  32. package/src/game-definition/moves/core/quest.test.ts +448 -0
  33. package/src/game-definition/moves/core/quest.ts +49 -0
  34. package/src/game-definition/moves/debug/manual-exert.ts +36 -0
  35. package/src/game-definition/moves/effects/resolve-bag.ts +35 -0
  36. package/src/game-definition/moves/effects/resolve-effect.ts +34 -0
  37. package/src/game-definition/moves/index.ts +85 -0
  38. package/src/game-definition/moves/locations/move-character-to-location.ts +42 -0
  39. package/src/game-definition/moves/resources/put-card-into-inkwell.test.ts +462 -0
  40. package/src/game-definition/moves/resources/put-card-into-inkwell.ts +51 -0
  41. package/src/game-definition/moves/setup/alter-hand.test.ts +395 -0
  42. package/src/game-definition/moves/setup/alter-hand.ts +210 -0
  43. package/src/game-definition/moves/setup/choose-first-player.test.ts +450 -0
  44. package/src/game-definition/moves/setup/choose-first-player.ts +105 -0
  45. package/src/game-definition/moves/setup/draw-cards.ts +37 -0
  46. package/src/game-definition/moves/songs/sing-together.ts +47 -0
  47. package/src/game-definition/moves/songs/sing.ts +56 -0
  48. package/src/game-definition/moves/standard/concede.test.ts +189 -0
  49. package/src/game-definition/moves/standard/concede.ts +72 -0
  50. package/src/game-definition/moves/standard/pass-turn.ts +49 -0
  51. package/src/game-definition/setup/game-setup.ts +19 -0
  52. package/src/game-definition/trackers/tracker-config.ts +23 -0
  53. package/src/game-definition/win-conditions/lore-victory.ts +26 -0
  54. package/src/game-definition/zone-operations.ts +405 -0
  55. package/src/game-definition/zones/zone-configs.ts +59 -0
  56. package/src/game-definition/zones.ts +283 -0
  57. package/src/index.ts +189 -0
  58. package/src/operations/index.ts +7 -0
  59. package/src/operations/lorcana-operations.ts +288 -0
  60. package/src/queries/README.md +56 -0
  61. package/src/resolvers/__tests__/condition-resolver.test.ts +301 -0
  62. package/src/resolvers/condition-registry.ts +70 -0
  63. package/src/resolvers/condition-resolver.ts +85 -0
  64. package/src/resolvers/conditions/basic.ts +81 -0
  65. package/src/resolvers/conditions/card-state.ts +12 -0
  66. package/src/resolvers/conditions/comparison.ts +102 -0
  67. package/src/resolvers/conditions/existence.ts +219 -0
  68. package/src/resolvers/conditions/history.ts +68 -0
  69. package/src/resolvers/conditions/index.ts +15 -0
  70. package/src/resolvers/conditions/logical.ts +55 -0
  71. package/src/resolvers/conditions/resolution.ts +41 -0
  72. package/src/resolvers/conditions/revealed.ts +42 -0
  73. package/src/resolvers/conditions/zone.ts +84 -0
  74. package/src/setup.test.ts +18 -0
  75. package/src/targeting/__tests__/filter-resolver.test.ts +294 -0
  76. package/src/targeting/__tests__/real-cards-targeting.test.ts +303 -0
  77. package/src/targeting/__tests__/targeting-dsl.test.ts +386 -0
  78. package/src/targeting/enum-expansion.ts +387 -0
  79. package/src/targeting/filter-registry.ts +322 -0
  80. package/src/targeting/filter-resolver.ts +145 -0
  81. package/src/targeting/index.ts +91 -0
  82. package/src/targeting/lorcana-target-dsl.ts +495 -0
  83. package/src/targeting/targeting-ui.ts +407 -0
  84. package/src/testing/index.ts +14 -0
  85. package/src/testing/lorcana-test-engine.ts +813 -0
  86. package/src/types/README.md +303 -0
  87. package/src/types/__tests__/lorcana-state.test.ts +168 -0
  88. package/src/types/__tests__/move-enumeration.test.ts +179 -0
  89. package/src/types/branded-types.ts +106 -0
  90. package/src/types/game-state.ts +184 -0
  91. package/src/types/index.ts +87 -0
  92. package/src/types/keywords.ts +187 -0
  93. package/src/types/lorcana-state.ts +260 -0
  94. package/src/types/move-enumeration.ts +126 -0
  95. package/src/types/move-params.ts +216 -0
  96. package/src/validators/index.ts +7 -0
  97. package/src/validators/move-validators.ts +374 -0
  98. package/src/zones/card-state.ts +234 -0
  99. package/src/zones/index.ts +42 -0
  100. package/src/zones/zone-config.ts +150 -0
@@ -0,0 +1,495 @@
1
+ import type { BaseContext, PlayerTargetDSL, TargetDSL } from "@drmxrcy/tcg-core";
2
+ import type { KeywordType } from "../types/keywords";
3
+
4
+ /** Lorcana zone IDs */
5
+ export type LorcanaZoneId = "deck" | "hand" | "play" | "discard" | "inkwell";
6
+
7
+ // ============================================================================
8
+ // Lorcana-Specific Filters
9
+ // ============================================================================
10
+
11
+ // --- State Filters ---
12
+
13
+ /**
14
+ * Filter for damaged cards (have damage counters)
15
+ */
16
+ export interface DamagedFilter {
17
+ type: "damaged";
18
+ }
19
+
20
+ /**
21
+ * Filter for undamaged cards (no damage counters)
22
+ */
23
+ export interface UndamagedFilter {
24
+ type: "undamaged";
25
+ }
26
+
27
+ /**
28
+ * Filter for exerted cards
29
+ */
30
+ export interface ExertedFilter {
31
+ type: "exerted";
32
+ }
33
+
34
+ /**
35
+ * Filter for ready (non-exerted) cards
36
+ */
37
+ export interface ReadyFilter {
38
+ type: "ready";
39
+ }
40
+
41
+ /**
42
+ * Filter for dry cards (freshly played, can't be used)
43
+ */
44
+ export interface DryFilter {
45
+ type: "dry";
46
+ }
47
+
48
+ // --- Property Filters ---
49
+
50
+ /**
51
+ * Filter for cards with a specific keyword
52
+ */
53
+ export interface HasKeywordFilter {
54
+ type: "has-keyword";
55
+ keyword: KeywordType;
56
+ }
57
+
58
+ /**
59
+ * Filter for cards with a specific classification (Hero, Villain, etc.)
60
+ */
61
+ export interface HasClassificationFilter {
62
+ type: "has-classification";
63
+ classification: string;
64
+ }
65
+
66
+ /**
67
+ * Filter for inkable/non-inkable cards
68
+ */
69
+ export interface InkableFilter {
70
+ type: "inkable";
71
+ value: boolean;
72
+ }
73
+
74
+ // --- Numeric Comparison Filters ---
75
+
76
+ export type ComparisonOperator = "eq" | "ne" | "gt" | "gte" | "lt" | "lte";
77
+
78
+ export interface NumericComparison {
79
+ operator: ComparisonOperator;
80
+ value: number;
81
+ }
82
+
83
+ export interface RelativeComparison {
84
+ operator: ComparisonOperator;
85
+ value: "target"; // Compares against the target of the ability (parent context)
86
+ }
87
+
88
+ /**
89
+ * Filter by strength value
90
+ */
91
+ export type StrengthFilter =
92
+ | {
93
+ type: "strength";
94
+ comparison: ComparisonOperator;
95
+ value: number;
96
+ ignoreBonuses?: boolean;
97
+ compareWithParentsTarget?: never;
98
+ }
99
+ | {
100
+ type: "strength";
101
+ comparison: ComparisonOperator;
102
+ value: "target";
103
+ ignoreBonuses?: boolean;
104
+ compareWithParentsTarget: true;
105
+ };
106
+
107
+ /**
108
+ * Filter by willpower value
109
+ */
110
+ export type WillpowerFilter =
111
+ | {
112
+ type: "willpower";
113
+ comparison: ComparisonOperator;
114
+ value: number;
115
+ compareWithParentsTarget?: never;
116
+ }
117
+ | {
118
+ type: "willpower";
119
+ comparison: ComparisonOperator;
120
+ value: "target";
121
+ compareWithParentsTarget: true;
122
+ };
123
+
124
+ /**
125
+ * Filter by ink cost
126
+ */
127
+ export type CostFilter =
128
+ | {
129
+ type: "cost";
130
+ comparison: ComparisonOperator;
131
+ value: number;
132
+ compareWithParentsTarget?: never;
133
+ }
134
+ | {
135
+ type: "cost";
136
+ comparison: ComparisonOperator;
137
+ value: "target";
138
+ compareWithParentsTarget: true;
139
+ };
140
+
141
+ /**
142
+ * Filter by lore value
143
+ */
144
+ export type LoreValueFilter =
145
+ | {
146
+ type: "lore-value";
147
+ comparison: ComparisonOperator;
148
+ value: number;
149
+ compareWithParentsTarget?: never;
150
+ }
151
+ | {
152
+ type: "lore-value";
153
+ comparison: ComparisonOperator;
154
+ value: "target";
155
+ compareWithParentsTarget: true;
156
+ };
157
+
158
+ // --- Location Filters ---
159
+
160
+ /**
161
+ * Filter for characters at a location
162
+ */
163
+ export interface AtLocationFilter {
164
+ type: "at-location";
165
+ /** Specific location name, or undefined for any location */
166
+ location?: string;
167
+ }
168
+
169
+ /**
170
+ * Filter by move cost (for locations)
171
+ */
172
+ export interface MoveCostFilter {
173
+ type: "move-cost";
174
+ comparison: ComparisonOperator;
175
+ value: number;
176
+ }
177
+
178
+ // --- Name Filters ---
179
+
180
+ /**
181
+ * Filter by card name
182
+ */
183
+ export type NameFilter =
184
+ | { type: "name"; equals: string }
185
+ | { type: "name"; contains: string };
186
+
187
+ // --- Card Type Filters ---
188
+
189
+ /**
190
+ * Filter by card type (character, item, location, action)
191
+ */
192
+ export interface CardTypeFilter {
193
+ type: "card-type";
194
+ value: LorcanaCardType;
195
+ }
196
+
197
+ // --- Combined Lorcana Filter Type ---
198
+
199
+ /**
200
+ * All Lorcana-specific filters
201
+ */
202
+ export type LorcanaFilter =
203
+ // State filters
204
+ | DamagedFilter
205
+ | UndamagedFilter
206
+ | ExertedFilter
207
+ | ReadyFilter
208
+ | DryFilter
209
+ // Property filters
210
+ | HasKeywordFilter
211
+ | HasClassificationFilter
212
+ | InkableFilter
213
+ // Numeric filters
214
+ | StrengthFilter
215
+ | WillpowerFilter
216
+ | CostFilter
217
+ | LoreValueFilter
218
+ // Location filters
219
+ | AtLocationFilter
220
+ | MoveCostFilter
221
+ // Name filter
222
+ | NameFilter
223
+ // Card Type filter
224
+ | CardTypeFilter
225
+ // Composite filters
226
+ | { type: "and"; filters: LorcanaFilter[] }
227
+ | { type: "or"; filters: LorcanaFilter[] }
228
+ | { type: "not"; filter: LorcanaFilter };
229
+
230
+ // ============================================================================
231
+ // Lorcana Context References
232
+ // ============================================================================
233
+
234
+ /**
235
+ * Context references for Lorcana abilities
236
+ *
237
+ * These allow effects to reference cards based on the current context
238
+ * rather than requiring explicit targeting.
239
+ */
240
+ export interface LorcanaContext extends BaseContext {
241
+ /** Reference the source card itself */
242
+ self?: boolean;
243
+
244
+ /** Reference the card that triggered this ability */
245
+ triggerSource?: boolean;
246
+
247
+ /** Reference the attacker in a challenge */
248
+ attacker?: boolean;
249
+
250
+ /** Reference the defender in a challenge */
251
+ defender?: boolean;
252
+
253
+ /** Reference the previously selected target in an effect chain */
254
+ previousTarget?: boolean;
255
+
256
+ /** Reference the singer in a song */
257
+ singer?: boolean;
258
+
259
+ /** Reference the song being sung */
260
+ song?: boolean;
261
+
262
+ /**
263
+ * Resolution context for resolution conditions
264
+ * Used for checking if we are currently resolving a specific mechanic (e.g. Bodyguard)
265
+ */
266
+ resolutionContext?: "bodyguard" | "shift" | string;
267
+
268
+ /** Reference cards revealed by an effect (e.g. "Look at the top card of your deck") */
269
+ revealedCards?: string[];
270
+
271
+ /**
272
+ * Recursion depth tracking for condition evaluation
273
+ * Prevents infinite loops in recursive condition checks
274
+ * @internal
275
+ */
276
+ recursionDepth?: number;
277
+ }
278
+
279
+ // ============================================================================
280
+ // Lorcana Card Types
281
+ // ============================================================================
282
+
283
+ /**
284
+ * Lorcana card types for targeting
285
+ */
286
+ export type LorcanaCardType = "character" | "item" | "location" | "action";
287
+
288
+ // ============================================================================
289
+ // Lorcana Target DSL
290
+ // ============================================================================
291
+
292
+ /**
293
+ * Lorcana card target - extends core DSL with Lorcana-specific features
294
+ *
295
+ * @example Target a chosen opposing damaged character
296
+ * ```typescript
297
+ * const target: LorcanaCardTarget = {
298
+ * selector: "chosen",
299
+ * count: 1,
300
+ * owner: "opponent",
301
+ * cardType: "character",
302
+ * zones: ["play"],
303
+ * filters: [{ type: "damaged" }]
304
+ * };
305
+ * ```
306
+ *
307
+ * @example Target all your characters with Evasive
308
+ * ```typescript
309
+ * const target: LorcanaCardTarget = {
310
+ * selector: "all",
311
+ * owner: "you",
312
+ * cardType: "character",
313
+ * zones: ["play"],
314
+ * filters: [{ type: "has-keyword", keyword: "Evasive" }]
315
+ * };
316
+ * ```
317
+ */
318
+ export interface LorcanaCardTarget
319
+ extends TargetDSL<LorcanaFilter, LorcanaContext> {
320
+ /** Lorcana card type constraint */
321
+ cardType?: LorcanaCardType;
322
+
323
+ /** Override zones with Lorcana-specific zone IDs */
324
+ zones?: LorcanaZoneId[];
325
+
326
+ /** Lorcana-specific filters */
327
+ filters?: LorcanaFilter[];
328
+ }
329
+
330
+ // ============================================================================
331
+ // Convenience Type Aliases
332
+ // ============================================================================
333
+
334
+ /**
335
+ * Character target (card type constrained)
336
+ */
337
+ export type CharacterTarget = LorcanaCardTarget & { cardType: "character" };
338
+
339
+ /**
340
+ * Item target (card type constrained)
341
+ */
342
+ export type ItemTarget = LorcanaCardTarget & { cardType: "item" };
343
+
344
+ /**
345
+ * Location target (card type constrained)
346
+ */
347
+ export type LocationTarget = LorcanaCardTarget & { cardType: "location" };
348
+
349
+ // ============================================================================
350
+ // Player Targeting (re-export with Lorcana context)
351
+ // ============================================================================
352
+
353
+ /**
354
+ * Lorcana player target
355
+ *
356
+ * Uses the core PlayerTargetDSL with Lorcana terminology
357
+ */
358
+ export type LorcanaPlayerTarget = PlayerTargetDSL;
359
+
360
+ // ============================================================================
361
+ // Type Guards
362
+ // ============================================================================
363
+
364
+ /**
365
+ * Check if a target is a DSL object (vs enum string)
366
+ */
367
+ export function isDSLTarget(
368
+ target: LorcanaTarget,
369
+ ): target is LorcanaCardTarget {
370
+ return typeof target === "object" && target !== null;
371
+ }
372
+
373
+ /**
374
+ * Check if a filter is a state filter
375
+ */
376
+ export function isStateFilter(
377
+ filter: LorcanaFilter,
378
+ ): filter is
379
+ | DamagedFilter
380
+ | UndamagedFilter
381
+ | ExertedFilter
382
+ | ReadyFilter
383
+ | DryFilter {
384
+ return (
385
+ filter.type === "damaged" ||
386
+ filter.type === "undamaged" ||
387
+ filter.type === "exerted" ||
388
+ filter.type === "ready" ||
389
+ filter.type === "dry"
390
+ );
391
+ }
392
+
393
+ /**
394
+ * Check if a filter is a numeric comparison filter
395
+ */
396
+ export function isNumericFilter(
397
+ filter: LorcanaFilter,
398
+ ): filter is
399
+ | StrengthFilter
400
+ | WillpowerFilter
401
+ | CostFilter
402
+ | LoreValueFilter
403
+ | MoveCostFilter {
404
+ return (
405
+ filter.type === "strength" ||
406
+ filter.type === "willpower" ||
407
+ filter.type === "cost" ||
408
+ filter.type === "lore-value" ||
409
+ filter.type === "move-cost"
410
+ );
411
+ }
412
+
413
+ // ============================================================================
414
+ // Enum Shortcuts (defined here, expanded in enum-expansion.ts)
415
+ // ============================================================================
416
+
417
+ /**
418
+ * Character target enum shortcuts
419
+ *
420
+ * These provide syntactic sugar for common targeting patterns.
421
+ * Use these for simple cases, use LorcanaCardTarget for complex cases.
422
+ */
423
+ export type CharacterTargetEnum =
424
+ // Self-referential
425
+ | "SELF"
426
+ | "THIS_CHARACTER"
427
+
428
+ // Chosen (requires player choice)
429
+ | "CHOSEN_CHARACTER"
430
+ | "CHOSEN_OPPOSING_CHARACTER"
431
+ | "CHOSEN_CHARACTER_OF_YOURS"
432
+ | "ANOTHER_CHOSEN_CHARACTER"
433
+ | "ANOTHER_CHOSEN_CHARACTER_OF_YOURS"
434
+
435
+ // All/Each (affects multiple)
436
+ | "ALL_CHARACTERS"
437
+ | "ALL_OPPOSING_CHARACTERS"
438
+ | "YOUR_CHARACTERS"
439
+ | "YOUR_OTHER_CHARACTERS"
440
+ | "EACH_CHARACTER"
441
+ | "EACH_OPPOSING_CHARACTER"
442
+
443
+ // Damaged variants
444
+ | "CHOSEN_DAMAGED_CHARACTER"
445
+ | "CHOSEN_OPPOSING_DAMAGED_CHARACTER"
446
+ | "ALL_OPPOSING_DAMAGED_CHARACTERS";
447
+
448
+ /**
449
+ * Item target enum shortcuts
450
+ */
451
+ export type ItemTargetEnum =
452
+ | "CHOSEN_ITEM"
453
+ | "CHOSEN_OPPOSING_ITEM"
454
+ | "YOUR_ITEMS"
455
+ | "ALL_ITEMS"
456
+ | "ALL_OPPOSING_ITEMS"
457
+ | "THIS_ITEM";
458
+
459
+ /**
460
+ * Location target enum shortcuts
461
+ */
462
+ export type LocationTargetEnum =
463
+ | "CHOSEN_LOCATION"
464
+ | "CHOSEN_OPPOSING_LOCATION"
465
+ | "YOUR_LOCATIONS"
466
+ | "ALL_OPPOSING_LOCATIONS"
467
+ | "THIS_LOCATION";
468
+
469
+ // ============================================================================
470
+ // Union Types (enum OR DSL)
471
+ // ============================================================================
472
+
473
+ /**
474
+ * Character target: either an enum shortcut or full DSL
475
+ */
476
+ export type LorcanaCharacterTarget = CharacterTargetEnum | CharacterTarget;
477
+
478
+ /**
479
+ * Item target: either an enum shortcut or full DSL
480
+ */
481
+ export type LorcanaItemTarget = ItemTargetEnum | ItemTarget;
482
+
483
+ /**
484
+ * Location target: either an enum shortcut or full DSL
485
+ */
486
+ export type LorcanaLocationTarget = LocationTargetEnum | LocationTarget;
487
+
488
+ /**
489
+ * Any card target
490
+ */
491
+ export type LorcanaTarget =
492
+ | LorcanaCharacterTarget
493
+ | LorcanaItemTarget
494
+ | LorcanaLocationTarget
495
+ | LorcanaCardTarget;