@drmxrcy/tcg-lorcana-types 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.
- package/package.json +46 -0
- package/src/abilities/ability-types.ts +766 -0
- package/src/abilities/condition-types.ts +1202 -0
- package/src/abilities/cost-types.ts +344 -0
- package/src/abilities/effect-types/amount-types.ts +115 -0
- package/src/abilities/effect-types/basic-effects.ts +200 -0
- package/src/abilities/effect-types/combined-types.ts +564 -0
- package/src/abilities/effect-types/control-flow.ts +317 -0
- package/src/abilities/effect-types/index.ts +136 -0
- package/src/abilities/effect-types/modifier-effects.ts +248 -0
- package/src/abilities/effect-types/movement-effects.ts +216 -0
- package/src/abilities/effect-types/scry-effects.ts +269 -0
- package/src/abilities/helpers/Abilities.ts +172 -0
- package/src/abilities/helpers/Conditions.ts +266 -0
- package/src/abilities/helpers/Costs.ts +83 -0
- package/src/abilities/helpers/Effects.ts +182 -0
- package/src/abilities/helpers/Targets.ts +193 -0
- package/src/abilities/helpers/Triggers.ts +167 -0
- package/src/abilities/helpers/index.ts +42 -0
- package/src/abilities/index.ts +401 -0
- package/src/abilities/target-types.ts +791 -0
- package/src/abilities/trigger-types.ts +530 -0
- package/src/cards/card-types.ts +502 -0
- package/src/cards/classifications.ts +86 -0
- package/src/cards/deck-validation.ts +71 -0
- package/src/cards/index.ts +77 -0
- package/src/cards/ink-types.ts +55 -0
- package/src/game/index.ts +14 -0
- package/src/game/state-types.ts +258 -0
- package/src/index.ts +16 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Card Types (Section 6)
|
|
3
|
+
*
|
|
4
|
+
* Four card types with distinct behaviors and properties.
|
|
5
|
+
* Provides both a unified LorcanaCardDefinition and discriminated union types
|
|
6
|
+
* for type-safe access to card-type-specific properties.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
ActionAbility,
|
|
11
|
+
ActivatedAbility,
|
|
12
|
+
KeywordAbility,
|
|
13
|
+
ReplacementAbility,
|
|
14
|
+
StaticAbility,
|
|
15
|
+
TriggeredAbility,
|
|
16
|
+
} from "../abilities/ability-types";
|
|
17
|
+
import type { Classification } from "./classifications";
|
|
18
|
+
import type { InkType } from "./ink-types";
|
|
19
|
+
|
|
20
|
+
/** Card Types (Section 6) */
|
|
21
|
+
export const CARD_TYPES = ["character", "action", "item", "location"] as const;
|
|
22
|
+
export type CardType = (typeof CARD_TYPES)[number];
|
|
23
|
+
|
|
24
|
+
/** Action subtypes */
|
|
25
|
+
export type ActionSubtype = "song" | null;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Base interface for all ability definitions in card data
|
|
29
|
+
* Adds card-definition-specific metadata to the comprehensive ability types
|
|
30
|
+
*/
|
|
31
|
+
export interface BaseAbilityDefinition {
|
|
32
|
+
/** Unique identifier for this ability instance */
|
|
33
|
+
id?: string;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Optional name for named abilities (e.g., "I SUMMON THEE", "DISASSEMBLE")
|
|
37
|
+
* Typically appears in ALL CAPS before the ability text
|
|
38
|
+
*/
|
|
39
|
+
name?: string;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Original card text for this ability
|
|
43
|
+
* Used for display and debugging
|
|
44
|
+
*/
|
|
45
|
+
text?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Keyword ability definition
|
|
50
|
+
* @example { type: "keyword", keyword: "Rush", id: "card-001-ability-1", text: "Rush" }
|
|
51
|
+
* @example { type: "keyword", keyword: "Challenger", value: 3, id: "card-002-ability-1", text: "Challenger +3" }
|
|
52
|
+
* @example { type: "keyword", keyword: "Shift", cost: { ink: 5 }, id: "card-003-ability-1", text: "Shift 5" }
|
|
53
|
+
*/
|
|
54
|
+
export type KeywordAbilityDefinition = BaseAbilityDefinition & KeywordAbility;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Triggered ability definition
|
|
58
|
+
* @example {
|
|
59
|
+
* type: "triggered",
|
|
60
|
+
* trigger: { event: "play", timing: "when", on: "SELF" },
|
|
61
|
+
* effect: { type: "draw", amount: 2, target: "CONTROLLER" },
|
|
62
|
+
* id: "card-001-ability-1",
|
|
63
|
+
* text: "When you play this character, draw 2 cards."
|
|
64
|
+
* }
|
|
65
|
+
*/
|
|
66
|
+
export interface TriggeredAbilityDefinition
|
|
67
|
+
extends BaseAbilityDefinition,
|
|
68
|
+
Omit<TriggeredAbility, "type"> {
|
|
69
|
+
type: "triggered";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Activated ability definition
|
|
74
|
+
* @example {
|
|
75
|
+
* type: "activated",
|
|
76
|
+
* name: "I SUMMON THEE",
|
|
77
|
+
* cost: { exert: true },
|
|
78
|
+
* effect: { type: "draw", amount: 1, target: "CONTROLLER" },
|
|
79
|
+
* id: "card-001-ability-1",
|
|
80
|
+
* text: "I SUMMON THEE {E} − Draw a card."
|
|
81
|
+
* }
|
|
82
|
+
*/
|
|
83
|
+
export interface ActivatedAbilityDefinition
|
|
84
|
+
extends BaseAbilityDefinition,
|
|
85
|
+
Omit<ActivatedAbility, "type" | "name"> {
|
|
86
|
+
type: "activated";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Static ability definition
|
|
91
|
+
* @example {
|
|
92
|
+
* type: "static",
|
|
93
|
+
* condition: { type: "during-turn", whose: "your" },
|
|
94
|
+
* effect: { type: "gain-keyword", keyword: "Evasive", target: "SELF" },
|
|
95
|
+
* id: "card-001-ability-1",
|
|
96
|
+
* text: "During your turn, this character gains Evasive."
|
|
97
|
+
* }
|
|
98
|
+
*/
|
|
99
|
+
export interface StaticAbilityDefinition
|
|
100
|
+
extends BaseAbilityDefinition,
|
|
101
|
+
Omit<StaticAbility, "type" | "name"> {
|
|
102
|
+
type: "static";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Action ability definition
|
|
107
|
+
* @example {
|
|
108
|
+
* type: "action",
|
|
109
|
+
* effect: { type: "draw", amount: 2, target: "CONTROLLER" },
|
|
110
|
+
* id: "card-001-ability-1",
|
|
111
|
+
* text: "Draw 2 cards."
|
|
112
|
+
* }
|
|
113
|
+
*/
|
|
114
|
+
export interface ActionAbilityDefinition
|
|
115
|
+
extends BaseAbilityDefinition,
|
|
116
|
+
Omit<ActionAbility, "type"> {
|
|
117
|
+
type: "action";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Replacement ability definition
|
|
122
|
+
* @example {
|
|
123
|
+
* type: "replacement",
|
|
124
|
+
* replaces: "damage-to-self",
|
|
125
|
+
* replacement: "prevent",
|
|
126
|
+
* id: "card-001-ability-1",
|
|
127
|
+
* text: "If this character would be dealt damage, prevent that damage."
|
|
128
|
+
* }
|
|
129
|
+
*/
|
|
130
|
+
export interface ReplacementAbilityDefinition
|
|
131
|
+
extends BaseAbilityDefinition,
|
|
132
|
+
Omit<ReplacementAbility, "type" | "name"> {
|
|
133
|
+
type: "replacement";
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Comprehensive ability definition type
|
|
138
|
+
*
|
|
139
|
+
* Represents any ability that can appear on a Lorcana card.
|
|
140
|
+
* Each variant includes the full ability structure from the comprehensive
|
|
141
|
+
* ability system, plus card-definition-specific metadata (id, name, text).
|
|
142
|
+
*
|
|
143
|
+
* This type bridges the card definition layer (static card data) with
|
|
144
|
+
* the ability execution layer (runtime ability resolution).
|
|
145
|
+
*/
|
|
146
|
+
export type AbilityDefinition =
|
|
147
|
+
| KeywordAbilityDefinition
|
|
148
|
+
| TriggeredAbilityDefinition
|
|
149
|
+
| ActivatedAbilityDefinition
|
|
150
|
+
| StaticAbilityDefinition
|
|
151
|
+
| ActionAbilityDefinition
|
|
152
|
+
| ReplacementAbilityDefinition;
|
|
153
|
+
|
|
154
|
+
// ============================================================================
|
|
155
|
+
// Base Card Properties (shared by all card types)
|
|
156
|
+
// ============================================================================
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Base properties shared by all card types
|
|
160
|
+
*/
|
|
161
|
+
export interface BaseCardProperties {
|
|
162
|
+
/** Unique identifier for the card */
|
|
163
|
+
id: string;
|
|
164
|
+
|
|
165
|
+
/** Card name (Rule 6.2.4) - e.g., "Elsa" */
|
|
166
|
+
name: string;
|
|
167
|
+
|
|
168
|
+
/** Card version (Rule 6.2.5) - e.g., "Ice Queen" */
|
|
169
|
+
version?: string;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Full name (name + version) - e.g., "Elsa - Ice Queen"
|
|
173
|
+
* Used for deck building limits (max 4 copies per full name)
|
|
174
|
+
* When not present, card name is equal to full name
|
|
175
|
+
*/
|
|
176
|
+
fullName?: string;
|
|
177
|
+
|
|
178
|
+
/** Ink type (Rule 6.2.3) - single or dual ink */
|
|
179
|
+
inkType: InkType[];
|
|
180
|
+
|
|
181
|
+
/** Ink cost (Rule 6.2.7) */
|
|
182
|
+
cost: number;
|
|
183
|
+
|
|
184
|
+
/** Inkable - has inkwell symbol (Rule 6.2.8) */
|
|
185
|
+
inkable: boolean;
|
|
186
|
+
|
|
187
|
+
/** Card abilities (includes keywords) */
|
|
188
|
+
abilities?: AbilityDefinition[];
|
|
189
|
+
|
|
190
|
+
/** Rules text - raw ability text as printed on the card */
|
|
191
|
+
text?: string;
|
|
192
|
+
|
|
193
|
+
/** Flavor text (not mechanically relevant) */
|
|
194
|
+
flavorText?: string;
|
|
195
|
+
|
|
196
|
+
/** Set information */
|
|
197
|
+
set: string;
|
|
198
|
+
|
|
199
|
+
/** Card number in set */
|
|
200
|
+
cardNumber?: number;
|
|
201
|
+
|
|
202
|
+
/** Rarity */
|
|
203
|
+
rarity?:
|
|
204
|
+
| "common"
|
|
205
|
+
| "uncommon"
|
|
206
|
+
| "rare"
|
|
207
|
+
| "super_rare"
|
|
208
|
+
| "legendary"
|
|
209
|
+
| "enchanted"
|
|
210
|
+
| "iconic"
|
|
211
|
+
| "promo";
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Special card copy limit rules
|
|
215
|
+
* - "no-limit": Unlimited copies allowed (e.g., Microbots)
|
|
216
|
+
* - number: Custom limit (e.g., 99 for Dalmatian Puppy, 2 for The Glass Slipper)
|
|
217
|
+
* - undefined: Default 4 copies per full name
|
|
218
|
+
*/
|
|
219
|
+
cardCopyLimit?: number | "no-limit";
|
|
220
|
+
|
|
221
|
+
/** Franchise the card belongs to (e.g., "Jungle Book", "Frozen") */
|
|
222
|
+
franchise?: string;
|
|
223
|
+
|
|
224
|
+
/** Whether the card is vanilla (no abilities/rules text) */
|
|
225
|
+
vanilla?: boolean;
|
|
226
|
+
|
|
227
|
+
/** External IDs from various systems */
|
|
228
|
+
externalIds?: {
|
|
229
|
+
ravensburger?: string;
|
|
230
|
+
lorcast?: string;
|
|
231
|
+
tcgPlayer?: number;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
/** Printing references for cards with multiple printings */
|
|
235
|
+
printings?: Array<{
|
|
236
|
+
set: string;
|
|
237
|
+
collectorNumber: number;
|
|
238
|
+
id: string;
|
|
239
|
+
}>;
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Flag indicating that the card logic is not yet fully implemented
|
|
243
|
+
* Used for stubbed cards during development
|
|
244
|
+
*/
|
|
245
|
+
missingImplementation?: boolean;
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Flag indicating that the card lacks comprehensive tests
|
|
249
|
+
* Used for stubbed cards during development
|
|
250
|
+
*/
|
|
251
|
+
missingTests?: boolean;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ============================================================================
|
|
255
|
+
// Discriminated Union Types by Card Type
|
|
256
|
+
// ============================================================================
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Character Card Definition (Rule 6.1.2)
|
|
260
|
+
*
|
|
261
|
+
* Characters have strength, willpower, lore value, and classifications.
|
|
262
|
+
* They can quest, challenge, and be challenged.
|
|
263
|
+
*/
|
|
264
|
+
export interface CharacterCard extends BaseCardProperties {
|
|
265
|
+
cardType: "character";
|
|
266
|
+
|
|
267
|
+
/** Strength (Rule 6.2.9) - damage dealt in challenges */
|
|
268
|
+
strength: number;
|
|
269
|
+
|
|
270
|
+
/** Willpower (Rule 6.2.10) - damage threshold before banishment */
|
|
271
|
+
willpower: number;
|
|
272
|
+
|
|
273
|
+
/** Lore value (Rule 6.2.11) - lore gained when questing */
|
|
274
|
+
lore: number;
|
|
275
|
+
|
|
276
|
+
/** Character classifications (Rule 6.2.6) */
|
|
277
|
+
classifications?: Classification[];
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Action Card Definition (Rule 6.1.3)
|
|
282
|
+
*
|
|
283
|
+
* Actions are one-time effects that are played and then discarded.
|
|
284
|
+
* Songs are a subtype of actions that can be sung by characters.
|
|
285
|
+
*/
|
|
286
|
+
export interface ActionCard extends BaseCardProperties {
|
|
287
|
+
cardType: "action";
|
|
288
|
+
|
|
289
|
+
/** Action subtype (Rule 6.3.3) - "song" for Song cards */
|
|
290
|
+
actionSubtype?: ActionSubtype;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Item Card Definition (Rule 6.1.4)
|
|
295
|
+
*
|
|
296
|
+
* Items are permanent cards that provide ongoing effects.
|
|
297
|
+
* They stay in play until removed.
|
|
298
|
+
*/
|
|
299
|
+
export interface ItemCard extends BaseCardProperties {
|
|
300
|
+
cardType: "item";
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Location Card Definition (Rule 6.5)
|
|
305
|
+
*
|
|
306
|
+
* Locations have a move cost and lore value.
|
|
307
|
+
* Characters can move to locations and quest at them.
|
|
308
|
+
*/
|
|
309
|
+
export interface LocationCard extends BaseCardProperties {
|
|
310
|
+
cardType: "location";
|
|
311
|
+
|
|
312
|
+
/** Move cost (Rule 6.5.5) - ink cost to move a character here */
|
|
313
|
+
moveCost: number;
|
|
314
|
+
|
|
315
|
+
/** Lore value - lore gained when questing at this location */
|
|
316
|
+
lore: number;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Discriminated union of all card types
|
|
321
|
+
* Use this when you need type-safe access to card-type-specific properties.
|
|
322
|
+
*/
|
|
323
|
+
export type LorcanaCard = CharacterCard | ActionCard | ItemCard | LocationCard;
|
|
324
|
+
|
|
325
|
+
// ============================================================================
|
|
326
|
+
// Type Guards for Card Types
|
|
327
|
+
// ============================================================================
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Check if a card is a character
|
|
331
|
+
*/
|
|
332
|
+
export function isCharacterCard(card: LorcanaCard): card is CharacterCard {
|
|
333
|
+
return card.cardType === "character";
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Check if a card is an action
|
|
338
|
+
*/
|
|
339
|
+
export function isActionCard(card: LorcanaCard): card is ActionCard {
|
|
340
|
+
return card.cardType === "action";
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Check if a card is an item
|
|
345
|
+
*/
|
|
346
|
+
export function isItemCard(card: LorcanaCard): card is ItemCard {
|
|
347
|
+
return card.cardType === "item";
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Check if a card is a location
|
|
352
|
+
*/
|
|
353
|
+
export function isLocationCard(card: LorcanaCard): card is LocationCard {
|
|
354
|
+
return card.cardType === "location";
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Lorcana Card Definition (Rule 6.2)
|
|
359
|
+
*
|
|
360
|
+
* Unified type with all properties optional based on card type.
|
|
361
|
+
* Use `LorcanaCard` discriminated union for type-safe access to
|
|
362
|
+
* card-type-specific properties.
|
|
363
|
+
*
|
|
364
|
+
* All card properties including:
|
|
365
|
+
* - id, name, version, fullName
|
|
366
|
+
* - inkType (single or dual)
|
|
367
|
+
* - cost, inkable (inkwell symbol)
|
|
368
|
+
* - cardType
|
|
369
|
+
* - Character-specific: strength, willpower, lore, classifications
|
|
370
|
+
* - Action-specific: actionSubtype (Song)
|
|
371
|
+
* - Location-specific: moveCost
|
|
372
|
+
* - abilities
|
|
373
|
+
*/
|
|
374
|
+
export interface LorcanaCardDefinition {
|
|
375
|
+
/** Unique identifier for the card */
|
|
376
|
+
id: string;
|
|
377
|
+
|
|
378
|
+
/** Card name (Rule 6.2.4) - e.g., "Elsa" */
|
|
379
|
+
name: string;
|
|
380
|
+
|
|
381
|
+
/** Card version (Rule 6.2.5) - e.g., "Ice Queen" */
|
|
382
|
+
version?: string;
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Full name (name + version) - e.g., "Elsa - Ice Queen"
|
|
386
|
+
* Used for deck building limits (max 4 copies per full name)
|
|
387
|
+
* When not present, card name is equal to full name
|
|
388
|
+
*/
|
|
389
|
+
fullName?: string;
|
|
390
|
+
|
|
391
|
+
/** Ink type (Rule 6.2.3) - single or dual ink */
|
|
392
|
+
inkType: InkType[];
|
|
393
|
+
|
|
394
|
+
/** Ink cost (Rule 6.2.7) */
|
|
395
|
+
cost: number;
|
|
396
|
+
|
|
397
|
+
/** Inkable - has inkwell symbol (Rule 6.2.8) */
|
|
398
|
+
inkable: boolean;
|
|
399
|
+
|
|
400
|
+
/** Card type (Rule 6.1) */
|
|
401
|
+
cardType: CardType;
|
|
402
|
+
|
|
403
|
+
// Character-specific properties (Rule 6.1.2)
|
|
404
|
+
|
|
405
|
+
/** Strength (Rule 6.2.9) - damage dealt in challenges */
|
|
406
|
+
strength?: number;
|
|
407
|
+
|
|
408
|
+
/** Willpower (Rule 6.2.10) - damage threshold before banishment */
|
|
409
|
+
willpower?: number;
|
|
410
|
+
|
|
411
|
+
/** Lore value (Rule 6.2.11) - lore gained when questing */
|
|
412
|
+
lore?: number;
|
|
413
|
+
|
|
414
|
+
/** Character classifications (Rule 6.2.6) */
|
|
415
|
+
classifications?: Classification[];
|
|
416
|
+
|
|
417
|
+
// Action-specific properties
|
|
418
|
+
|
|
419
|
+
/** Action subtype (Rule 6.3.3) - "song" for Song cards */
|
|
420
|
+
actionSubtype?: ActionSubtype;
|
|
421
|
+
|
|
422
|
+
// Location-specific properties (Rule 6.5)
|
|
423
|
+
|
|
424
|
+
/** Move cost (Rule 6.5.5) - ink cost to move a character here */
|
|
425
|
+
moveCost?: number;
|
|
426
|
+
|
|
427
|
+
// Abilities
|
|
428
|
+
|
|
429
|
+
/** Card abilities (includes keywords) */
|
|
430
|
+
abilities?: AbilityDefinition[];
|
|
431
|
+
|
|
432
|
+
/** Rules text - raw ability text as printed on the card */
|
|
433
|
+
text?: string;
|
|
434
|
+
|
|
435
|
+
/** Flavor text (not mechanically relevant) */
|
|
436
|
+
flavorText?: string;
|
|
437
|
+
|
|
438
|
+
/** Set information */
|
|
439
|
+
set: string;
|
|
440
|
+
|
|
441
|
+
/** Card number in set */
|
|
442
|
+
cardNumber?: number;
|
|
443
|
+
|
|
444
|
+
/** Rarity */
|
|
445
|
+
rarity?:
|
|
446
|
+
| "common"
|
|
447
|
+
| "uncommon"
|
|
448
|
+
| "rare"
|
|
449
|
+
| "super_rare"
|
|
450
|
+
| "legendary"
|
|
451
|
+
| "enchanted"
|
|
452
|
+
| "iconic"
|
|
453
|
+
| "promo";
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Special card copy limit rules
|
|
457
|
+
* - "no-limit": Unlimited copies allowed (e.g., Microbots)
|
|
458
|
+
* - number: Custom limit (e.g., 99 for Dalmatian Puppy, 2 for The Glass Slipper)
|
|
459
|
+
* - undefined: Default 4 copies per full name
|
|
460
|
+
*/
|
|
461
|
+
cardCopyLimit?: number | "no-limit";
|
|
462
|
+
|
|
463
|
+
/** Franchise the card belongs to (e.g., "Jungle Book", "Frozen") */
|
|
464
|
+
franchise?: string;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Check if a card type is valid
|
|
469
|
+
*/
|
|
470
|
+
export function isCardType(value: unknown): value is CardType {
|
|
471
|
+
return typeof value === "string" && CARD_TYPES.includes(value as CardType);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Get the full name of a card (name + version)
|
|
476
|
+
* Uses the stored fullName or generates it from name and version
|
|
477
|
+
*/
|
|
478
|
+
export function getFullName(card: LorcanaCardDefinition | LorcanaCard): string {
|
|
479
|
+
if (card.fullName) {
|
|
480
|
+
return card.fullName;
|
|
481
|
+
}
|
|
482
|
+
if (card.version) {
|
|
483
|
+
return `${card.name} - ${card.version}`;
|
|
484
|
+
}
|
|
485
|
+
return card.name;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Check if a card has dual ink types (Rule 6.2.3.1)
|
|
490
|
+
*/
|
|
491
|
+
export function isDualInk(card: LorcanaCardDefinition | LorcanaCard): boolean {
|
|
492
|
+
return card.inkType.length === 2;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Get all ink types from a card
|
|
497
|
+
*/
|
|
498
|
+
export function getInkTypes(
|
|
499
|
+
card: LorcanaCardDefinition | LorcanaCard,
|
|
500
|
+
): InkType[] {
|
|
501
|
+
return card.inkType;
|
|
502
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classifications (Rule 6.2.6)
|
|
3
|
+
*
|
|
4
|
+
* Character classifications that appear on character cards.
|
|
5
|
+
* Characters can have multiple classifications.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const CLASSIFICATIONS = [
|
|
9
|
+
"Alien",
|
|
10
|
+
"Ally",
|
|
11
|
+
"Broom",
|
|
12
|
+
"Captain",
|
|
13
|
+
"Deity",
|
|
14
|
+
"Detective",
|
|
15
|
+
"Dragon",
|
|
16
|
+
"Dreamborn",
|
|
17
|
+
"Entangled",
|
|
18
|
+
"Fairy",
|
|
19
|
+
"Floodborn",
|
|
20
|
+
"Gargoyle",
|
|
21
|
+
"Ghost",
|
|
22
|
+
"Hero",
|
|
23
|
+
"Hunny",
|
|
24
|
+
"Hyena",
|
|
25
|
+
"Illusion",
|
|
26
|
+
"Inventor",
|
|
27
|
+
"King",
|
|
28
|
+
"Knight",
|
|
29
|
+
"Madrigal",
|
|
30
|
+
"Mentor",
|
|
31
|
+
"Musketeer",
|
|
32
|
+
"Pirate",
|
|
33
|
+
"Prince",
|
|
34
|
+
"Princess",
|
|
35
|
+
"Puppy",
|
|
36
|
+
"Queen",
|
|
37
|
+
"Racer",
|
|
38
|
+
"Robot",
|
|
39
|
+
"Seven Dwarfs",
|
|
40
|
+
"Sorcerer",
|
|
41
|
+
"Storyborn",
|
|
42
|
+
"Tigger",
|
|
43
|
+
"Titan",
|
|
44
|
+
"Villain",
|
|
45
|
+
"Whisper",
|
|
46
|
+
] as const;
|
|
47
|
+
|
|
48
|
+
export type Classification = (typeof CLASSIFICATIONS)[number];
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Check if a value is a valid classification
|
|
52
|
+
*/
|
|
53
|
+
export function isClassification(value: unknown): value is Classification {
|
|
54
|
+
return (
|
|
55
|
+
typeof value === "string" &&
|
|
56
|
+
CLASSIFICATIONS.includes(value as Classification)
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Check if a classification is Storyborn
|
|
62
|
+
*/
|
|
63
|
+
export function isStoryborn(classification: Classification): boolean {
|
|
64
|
+
return classification === "Storyborn";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Check if a classification is Floodborn
|
|
69
|
+
*/
|
|
70
|
+
export function isFloodborn(classification: Classification): boolean {
|
|
71
|
+
return classification === "Floodborn";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Check if a classification is Dreamborn
|
|
76
|
+
*/
|
|
77
|
+
export function isDreamborn(classification: Classification): boolean {
|
|
78
|
+
return classification === "Dreamborn";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get all classifications
|
|
83
|
+
*/
|
|
84
|
+
export function getAllClassifications(): readonly Classification[] {
|
|
85
|
+
return CLASSIFICATIONS;
|
|
86
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deck Validation Types (Rule 2.1)
|
|
3
|
+
*
|
|
4
|
+
* Deck rules:
|
|
5
|
+
* - Minimum 60 cards (Rule 2.1.1.1)
|
|
6
|
+
* - Maximum 2 ink types (Rule 2.1.1.2)
|
|
7
|
+
* - Maximum 4 copies per full name (Rule 2.1.1.3)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { InkType } from "./ink-types";
|
|
11
|
+
|
|
12
|
+
/** Minimum cards required in a deck */
|
|
13
|
+
export const MIN_DECK_SIZE = 60;
|
|
14
|
+
|
|
15
|
+
/** Maximum different ink types allowed in a deck */
|
|
16
|
+
export const MAX_INK_TYPES = 2;
|
|
17
|
+
|
|
18
|
+
/** Maximum copies of the same card (by full name) allowed */
|
|
19
|
+
export const MAX_COPIES_PER_CARD = 4;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Deck validation error types
|
|
23
|
+
*/
|
|
24
|
+
export type DeckValidationError =
|
|
25
|
+
| TooFewCardsError
|
|
26
|
+
| TooManyInkTypesError
|
|
27
|
+
| TooManyCopiesError;
|
|
28
|
+
|
|
29
|
+
export interface TooFewCardsError {
|
|
30
|
+
type: "TOO_FEW_CARDS";
|
|
31
|
+
count: number;
|
|
32
|
+
minimum: typeof MIN_DECK_SIZE;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface TooManyInkTypesError {
|
|
36
|
+
type: "TOO_MANY_INK_TYPES";
|
|
37
|
+
inkTypes: InkType[];
|
|
38
|
+
maximum: typeof MAX_INK_TYPES;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface TooManyCopiesError {
|
|
42
|
+
type: "TOO_MANY_COPIES";
|
|
43
|
+
fullName: string;
|
|
44
|
+
count: number;
|
|
45
|
+
maximum: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Result of deck validation
|
|
50
|
+
*/
|
|
51
|
+
export interface DeckValidationResult {
|
|
52
|
+
valid: boolean;
|
|
53
|
+
errors: DeckValidationError[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Deck statistics for informational purposes
|
|
58
|
+
*/
|
|
59
|
+
export interface DeckStats {
|
|
60
|
+
totalCards: number;
|
|
61
|
+
inkTypes: InkType[];
|
|
62
|
+
cardCounts: Map<string, number>;
|
|
63
|
+
cardTypeBreakdown: {
|
|
64
|
+
characters: number;
|
|
65
|
+
actions: number;
|
|
66
|
+
items: number;
|
|
67
|
+
locations: number;
|
|
68
|
+
};
|
|
69
|
+
inkableCards: number;
|
|
70
|
+
averageCost: number;
|
|
71
|
+
}
|