@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,42 @@
1
+ import type { RevealedMatchesNamedCondition } from "@drmxrcy/tcg-lorcana-types";
2
+ import { conditionRegistry } from "../condition-registry";
3
+
4
+ conditionRegistry.register<RevealedMatchesNamedCondition>(
5
+ "revealed-matches-named",
6
+ {
7
+ complexity: 40,
8
+ evaluate: (_condition, _sourceCard, { state, context, registry }) => {
9
+ const namedCard = state.external.namedCard;
10
+ if (!namedCard) return false;
11
+
12
+ // Check revealed cards in context
13
+ if (!context?.revealedCards || context.revealedCards.length === 0) {
14
+ return false;
15
+ }
16
+
17
+ // Usually checks the "top card" or just "the revealed card"
18
+ // We iterate all revealed cards in context. If any matches, we return true?
19
+ // Or stricter: "If IT matches". Usually implies a single card was revealed.
20
+ // We'll check if ANY of the revealed cards matches the name.
21
+
22
+ return context.revealedCards.some((cardId) => {
23
+ // We need lookup definition from registry using the card ID?
24
+ // context.revealedCards are IDs?
25
+ // Wait, context.revealedCards in DSL is string[]. Assuming IDs.
26
+
27
+ // We need to look up the card in state to get definitionId?
28
+ // Or if it's from deck, it might not be in state.cards yet?
29
+ // In Lorcana engine, usually cards in deck are fully instantiated or just definitions?
30
+ // If they are IDs, they should be in state.cards (even if in deck).
31
+
32
+ const card = state.internal.cards[cardId];
33
+ if (!card) return false;
34
+
35
+ const def = registry.getCard(card.definitionId);
36
+ if (!def) return false;
37
+
38
+ return def.name === namedCard || def.fullName === namedCard;
39
+ });
40
+ },
41
+ },
42
+ );
@@ -0,0 +1,84 @@
1
+ import type { CardInstance, PlayerId } from "@drmxrcy/tcg-core";
2
+ import type {
3
+ AtLocationCondition,
4
+ HasCharacterHereCondition,
5
+ HasNamedLocationCondition,
6
+ ZoneCondition,
7
+ } from "@drmxrcy/tcg-lorcana-types";
8
+ import type { LorcanaCardMeta } from "../../types/game-state";
9
+ import { conditionRegistry } from "../condition-registry";
10
+
11
+ conditionRegistry.register<ZoneCondition>("zone", {
12
+ complexity: 20,
13
+ evaluate: (condition, sourceCard, { state, registry }) => {
14
+ // Determine target controller
15
+ const targetControllerId =
16
+ condition.controller === "you"
17
+ ? sourceCard.controller
18
+ : (Object.keys(state.external.loreScores).find(
19
+ (id) => id !== sourceCard.controller,
20
+ ) as PlayerId);
21
+
22
+ if (!targetControllerId) return false;
23
+
24
+ // Filter cards
25
+ const matchingCards = Object.values(state.internal.cards).filter((c) => {
26
+ if (c.controller !== targetControllerId) return false;
27
+ if (c.zone !== condition.zone) return false;
28
+
29
+ // Registry check
30
+ if (condition.cardType || condition.cardName) {
31
+ const def = registry.getCard(c.definitionId);
32
+ if (!def) return false;
33
+ if (condition.cardType && def.cardType !== condition.cardType)
34
+ return false;
35
+ if (condition.cardName && def.name !== condition.cardName) return false;
36
+ }
37
+
38
+ return true;
39
+ });
40
+
41
+ if (condition.hasCards !== undefined) {
42
+ return matchingCards.length > 0 === condition.hasCards;
43
+ }
44
+
45
+ return matchingCards.length > 0;
46
+ },
47
+ });
48
+
49
+ conditionRegistry.register<AtLocationCondition>("at-location", {
50
+ complexity: 20,
51
+ evaluate: (_condition, sourceCard) => {
52
+ // Check if sourceCard has attached location
53
+ return !!sourceCard.atLocationId;
54
+ },
55
+ });
56
+
57
+ conditionRegistry.register<HasCharacterHereCondition>("has-character-here", {
58
+ complexity: 30,
59
+ evaluate: (_condition, sourceCard, { state }) => {
60
+ // Check all cards in play to see if their 'atLocationId' matches sourceCard.id
61
+ return Object.values(state.internal.cards).some((c) => {
62
+ const card = c as CardInstance<LorcanaCardMeta>;
63
+ return card.zone === "play" && card.atLocationId === sourceCard.id;
64
+ });
65
+ },
66
+ });
67
+
68
+ conditionRegistry.register<HasNamedLocationCondition>("has-named-location", {
69
+ complexity: 40,
70
+ evaluate: (condition, sourceCard, { state, registry }) => {
71
+ return Object.values(state.internal.cards).some((c) => {
72
+ if (c.zone !== "play") return false;
73
+ const def = registry.getCard(c.definitionId);
74
+ if (def?.cardType !== "location") return false;
75
+ if (condition.name && def.name !== condition.name) return false;
76
+
77
+ if (condition.controller === "you")
78
+ return c.controller === sourceCard.controller;
79
+ if (condition.controller === "opponent")
80
+ return c.controller !== sourceCard.controller;
81
+ return true;
82
+ });
83
+ },
84
+ });
@@ -0,0 +1,18 @@
1
+ import { describe, expect, it } from "bun:test";
2
+
3
+ describe("@drmxrcy/tcg-lorcana Package Setup", () => {
4
+ it("package is properly configured", () => {
5
+ // Basic smoke test to verify package structure
6
+ expect(true).toBe(true);
7
+ });
8
+
9
+ it(
10
+ "can import from @drmxrcy/tcg-core",
11
+ async () => {
12
+ const core = await import("@drmxrcy/tcg-core");
13
+ expect(core).toBeDefined();
14
+ expect(core.RuleEngine).toBeDefined();
15
+ },
16
+ { timeout: 30000 },
17
+ );
18
+ });
@@ -0,0 +1,294 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import type { CardRegistry } from "@drmxrcy/tcg-core";
3
+ import type { LorcanaCardDefinition } from "@drmxrcy/tcg-lorcana-types";
4
+ import { createDefaultCardMeta } from "../../types/game-state";
5
+ import {
6
+ createTargetFiltersPredicate,
7
+ matchesLorcanaFilter,
8
+ sortFilters,
9
+ } from "../filter-resolver";
10
+ import type { LorcanaFilter } from "../lorcana-target-dsl";
11
+
12
+ // Mock Registry
13
+ const mockRegistry: CardRegistry<LorcanaCardDefinition> = {
14
+ getCard: (id: string) => {
15
+ return MOCK_DEFINITIONS[id];
16
+ },
17
+ getAllCards: () => Object.values(MOCK_DEFINITIONS),
18
+ hasCard: (id: string) => !!MOCK_DEFINITIONS[id],
19
+ queryCards: () => [],
20
+ getCardCount: () => 0,
21
+ };
22
+
23
+ const MOCK_DEFINITIONS: Record<string, LorcanaCardDefinition> = {
24
+ "char-1": {
25
+ id: "char-1",
26
+ name: "Mickey Mouse",
27
+ set: "set1",
28
+ cardType: "character",
29
+ cost: 3,
30
+ inkable: true,
31
+ inkType: ["ruby"],
32
+ strength: 3,
33
+ willpower: 3,
34
+ lore: 1,
35
+ abilities: [
36
+ { type: "keyword", keyword: "Evasive", id: "k1", text: "Evasive" },
37
+ ],
38
+ },
39
+ "char-2": {
40
+ id: "char-2",
41
+ name: "Donald Duck",
42
+ set: "set1",
43
+ cardType: "character",
44
+ cost: 5,
45
+ inkable: false,
46
+ inkType: ["sapphire"],
47
+ strength: 5,
48
+ willpower: 6,
49
+ lore: 2,
50
+ classifications: ["Hero"],
51
+ },
52
+ "loc-1": {
53
+ id: "loc-1",
54
+ name: "The Library",
55
+ set: "set1",
56
+ cardType: "location",
57
+ cost: 2,
58
+ inkable: true,
59
+ inkType: ["amethyst"],
60
+ moveCost: 1,
61
+ lore: 1,
62
+ },
63
+ };
64
+
65
+ const mockState: any = {}; // Simple mock state
66
+
67
+ describe("Filter Resolver", () => {
68
+ describe("Ranking", () => {
69
+ it("should sort filters by rank", () => {
70
+ const filters: LorcanaFilter[] = [
71
+ { type: "cost", comparison: "eq", value: 3 },
72
+ { type: "ready" },
73
+ { type: "has-keyword", keyword: "Evasive" },
74
+ ];
75
+
76
+ const sorted = sortFilters(filters);
77
+ expect(sorted[0].type).toBe("ready"); // Cheap
78
+ expect(sorted[1].type).toBe("has-keyword"); // Property
79
+ expect(sorted[2].type).toBe("cost"); // Numeric
80
+ });
81
+ });
82
+
83
+ describe("Matching", () => {
84
+ const char1Instance = {
85
+ definitionId: "char-1",
86
+ ...createDefaultCardMeta(),
87
+ state: "ready",
88
+ damage: 0,
89
+ } as any;
90
+
91
+ const char1Exerted = {
92
+ definitionId: "char-1",
93
+ ...createDefaultCardMeta(),
94
+ state: "exerted",
95
+ damage: 2,
96
+ } as any;
97
+
98
+ it("should match state filters", () => {
99
+ expect(
100
+ matchesLorcanaFilter(
101
+ char1Instance,
102
+ { type: "ready" },
103
+ mockState,
104
+ mockRegistry,
105
+ ),
106
+ ).toBe(true);
107
+ expect(
108
+ matchesLorcanaFilter(
109
+ char1Exerted,
110
+ { type: "ready" },
111
+ mockState,
112
+ mockRegistry,
113
+ ),
114
+ ).toBe(false);
115
+
116
+ expect(
117
+ matchesLorcanaFilter(
118
+ char1Exerted,
119
+ { type: "exerted" },
120
+ mockState,
121
+ mockRegistry,
122
+ ),
123
+ ).toBe(true);
124
+
125
+ expect(
126
+ matchesLorcanaFilter(
127
+ char1Exerted,
128
+ { type: "damaged" },
129
+ mockState,
130
+ mockRegistry,
131
+ ),
132
+ ).toBe(true);
133
+ expect(
134
+ matchesLorcanaFilter(
135
+ char1Instance,
136
+ { type: "damaged" },
137
+ mockState,
138
+ mockRegistry,
139
+ ),
140
+ ).toBe(false);
141
+
142
+ expect(
143
+ matchesLorcanaFilter(
144
+ char1Instance,
145
+ { type: "undamaged" },
146
+ mockState,
147
+ mockRegistry,
148
+ ),
149
+ ).toBe(true);
150
+ });
151
+
152
+ it("should match property filters", () => {
153
+ expect(
154
+ matchesLorcanaFilter(
155
+ char1Instance,
156
+ { type: "has-keyword", keyword: "Evasive" },
157
+ mockState,
158
+ mockRegistry,
159
+ ),
160
+ ).toBe(true);
161
+ expect(
162
+ matchesLorcanaFilter(
163
+ char1Instance,
164
+ { type: "has-keyword", keyword: "Rush" },
165
+ mockState,
166
+ mockRegistry,
167
+ ),
168
+ ).toBe(false);
169
+
170
+ const char2Instance = {
171
+ definitionId: "char-2",
172
+ ...createDefaultCardMeta(),
173
+ } as any;
174
+ expect(
175
+ matchesLorcanaFilter(
176
+ char2Instance,
177
+ { type: "has-classification", classification: "Hero" },
178
+ mockState,
179
+ mockRegistry,
180
+ ),
181
+ ).toBe(true);
182
+ expect(
183
+ matchesLorcanaFilter(
184
+ char1Instance,
185
+ { type: "has-classification", classification: "Hero" },
186
+ mockState,
187
+ mockRegistry,
188
+ ),
189
+ ).toBe(false);
190
+
191
+ expect(
192
+ matchesLorcanaFilter(
193
+ char1Instance,
194
+ { type: "name", equals: "Mickey Mouse" },
195
+ mockState,
196
+ mockRegistry,
197
+ ),
198
+ ).toBe(true);
199
+ expect(
200
+ matchesLorcanaFilter(
201
+ char1Instance,
202
+ { type: "name", contains: "Mickey" },
203
+ mockState,
204
+ mockRegistry,
205
+ ),
206
+ ).toBe(true);
207
+ });
208
+
209
+ it("should match numeric filters", () => {
210
+ expect(
211
+ matchesLorcanaFilter(
212
+ char1Instance,
213
+ { type: "strength", comparison: "eq", value: 3 },
214
+ mockState,
215
+ mockRegistry,
216
+ ),
217
+ ).toBe(true);
218
+ expect(
219
+ matchesLorcanaFilter(
220
+ char1Instance,
221
+ { type: "strength", comparison: "gt", value: 2 },
222
+ mockState,
223
+ mockRegistry,
224
+ ),
225
+ ).toBe(true);
226
+ expect(
227
+ matchesLorcanaFilter(
228
+ char1Instance,
229
+ { type: "strength", comparison: "lt", value: 4 },
230
+ mockState,
231
+ mockRegistry,
232
+ ),
233
+ ).toBe(true);
234
+
235
+ expect(
236
+ matchesLorcanaFilter(
237
+ char1Instance,
238
+ { type: "cost", comparison: "lte", value: 3 },
239
+ mockState,
240
+ mockRegistry,
241
+ ),
242
+ ).toBe(true);
243
+ });
244
+
245
+ it("should match composite filters", () => {
246
+ const filter: LorcanaFilter = {
247
+ type: "and",
248
+ filters: [
249
+ { type: "ready" },
250
+ { type: "strength", comparison: "eq", value: 3 },
251
+ ],
252
+ };
253
+ expect(
254
+ matchesLorcanaFilter(char1Instance, filter, mockState, mockRegistry),
255
+ ).toBe(true);
256
+
257
+ const orFilter: LorcanaFilter = {
258
+ type: "or",
259
+ filters: [
260
+ { type: "exerted" }, // false
261
+ { type: "strength", comparison: "eq", value: 3 }, // true
262
+ ],
263
+ };
264
+ expect(
265
+ matchesLorcanaFilter(char1Instance, orFilter, mockState, mockRegistry),
266
+ ).toBe(true);
267
+ });
268
+ });
269
+
270
+ describe("Predicate Creation", () => {
271
+ it("should create working predicate", () => {
272
+ const predicate = createTargetFiltersPredicate(
273
+ [{ type: "ready" }, { type: "cost", comparison: "eq", value: 3 }],
274
+ mockState,
275
+ mockRegistry,
276
+ );
277
+
278
+ const char1Instance = {
279
+ definitionId: "char-1",
280
+ ...createDefaultCardMeta(),
281
+ state: "ready",
282
+ } as any;
283
+
284
+ const char1Exerted = {
285
+ definitionId: "char-1",
286
+ ...createDefaultCardMeta(),
287
+ state: "exerted",
288
+ } as any;
289
+
290
+ expect(predicate(char1Instance)).toBe(true);
291
+ expect(predicate(char1Exerted)).toBe(false);
292
+ });
293
+ });
294
+ });