@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.
- package/README.md +882 -0
- package/package.json +58 -0
- package/src/__tests__/alpha-clash-engine-definition.test.ts +319 -0
- package/src/__tests__/createMockAlphaClashGame.ts +462 -0
- package/src/__tests__/createMockGrandArchiveGame.ts +373 -0
- package/src/__tests__/createMockGundamGame.ts +379 -0
- package/src/__tests__/createMockLorcanaGame.ts +328 -0
- package/src/__tests__/createMockOnePieceGame.ts +429 -0
- package/src/__tests__/createMockRiftboundGame.ts +462 -0
- package/src/__tests__/grand-archive-engine-definition.test.ts +118 -0
- package/src/__tests__/gundam-engine-definition.test.ts +110 -0
- package/src/__tests__/integration-complete-game.test.ts +508 -0
- package/src/__tests__/integration-network-sync.test.ts +469 -0
- package/src/__tests__/lorcana-engine-definition.test.ts +100 -0
- package/src/__tests__/move-enumeration.test.ts +725 -0
- package/src/__tests__/multiplayer-engine.test.ts +555 -0
- package/src/__tests__/one-piece-engine-definition.test.ts +114 -0
- package/src/__tests__/riftbound-engine-definition.test.ts +124 -0
- package/src/actions/action-definition.test.ts +201 -0
- package/src/actions/action-definition.ts +122 -0
- package/src/actions/action-timing.test.ts +490 -0
- package/src/actions/action-timing.ts +257 -0
- package/src/cards/card-definition.test.ts +268 -0
- package/src/cards/card-definition.ts +27 -0
- package/src/cards/card-instance.test.ts +422 -0
- package/src/cards/card-instance.ts +49 -0
- package/src/cards/computed-properties.test.ts +530 -0
- package/src/cards/computed-properties.ts +84 -0
- package/src/cards/conditional-modifiers.test.ts +390 -0
- package/src/cards/modifiers.test.ts +286 -0
- package/src/cards/modifiers.ts +51 -0
- package/src/engine/MULTIPLAYER.md +425 -0
- package/src/engine/__tests__/rule-engine-flow.test.ts +348 -0
- package/src/engine/__tests__/rule-engine-history.test.ts +535 -0
- package/src/engine/__tests__/rule-engine-moves.test.ts +488 -0
- package/src/engine/__tests__/rule-engine.test.ts +366 -0
- package/src/engine/index.ts +14 -0
- package/src/engine/multiplayer-engine.example.ts +571 -0
- package/src/engine/multiplayer-engine.ts +409 -0
- package/src/engine/rule-engine.test.ts +286 -0
- package/src/engine/rule-engine.ts +1539 -0
- package/src/engine/tracker-system.ts +172 -0
- package/src/examples/__tests__/coin-flip-game.test.ts +641 -0
- package/src/filtering/card-filter.test.ts +230 -0
- package/src/filtering/card-filter.ts +91 -0
- package/src/filtering/card-query.test.ts +901 -0
- package/src/filtering/card-query.ts +273 -0
- package/src/filtering/filter-matching.test.ts +944 -0
- package/src/filtering/filter-matching.ts +315 -0
- package/src/flow/SERIALIZATION.md +428 -0
- package/src/flow/__tests__/flow-definition.test.ts +427 -0
- package/src/flow/__tests__/flow-manager.test.ts +756 -0
- package/src/flow/__tests__/flow-serialization.test.ts +565 -0
- package/src/flow/flow-definition.ts +453 -0
- package/src/flow/flow-manager.ts +1044 -0
- package/src/flow/index.ts +35 -0
- package/src/game-definition/__tests__/game-definition-validation.test.ts +359 -0
- package/src/game-definition/__tests__/game-definition.test.ts +291 -0
- package/src/game-definition/__tests__/move-definitions.test.ts +328 -0
- package/src/game-definition/game-definition.ts +261 -0
- package/src/game-definition/index.ts +28 -0
- package/src/game-definition/move-definitions.ts +188 -0
- package/src/game-definition/validation.ts +183 -0
- package/src/history/history-manager.test.ts +497 -0
- package/src/history/history-manager.ts +312 -0
- package/src/history/history-operations.ts +122 -0
- package/src/history/index.ts +9 -0
- package/src/history/types.ts +255 -0
- package/src/index.ts +32 -0
- package/src/logging/index.ts +27 -0
- package/src/logging/log-formatter.ts +187 -0
- package/src/logging/logger.ts +276 -0
- package/src/logging/types.ts +148 -0
- package/src/moves/create-move.test.ts +331 -0
- package/src/moves/create-move.ts +64 -0
- package/src/moves/move-enumeration.ts +228 -0
- package/src/moves/move-executor.test.ts +431 -0
- package/src/moves/move-executor.ts +195 -0
- package/src/moves/move-system.test.ts +380 -0
- package/src/moves/move-system.ts +463 -0
- package/src/moves/standard-moves.ts +231 -0
- package/src/operations/card-operations.test.ts +236 -0
- package/src/operations/card-operations.ts +116 -0
- package/src/operations/card-registry-impl.test.ts +251 -0
- package/src/operations/card-registry-impl.ts +70 -0
- package/src/operations/card-registry.test.ts +234 -0
- package/src/operations/card-registry.ts +106 -0
- package/src/operations/counter-operations.ts +152 -0
- package/src/operations/game-operations.test.ts +280 -0
- package/src/operations/game-operations.ts +140 -0
- package/src/operations/index.ts +24 -0
- package/src/operations/operations-impl.test.ts +354 -0
- package/src/operations/operations-impl.ts +468 -0
- package/src/operations/zone-operations.test.ts +295 -0
- package/src/operations/zone-operations.ts +223 -0
- package/src/rng/seeded-rng.test.ts +339 -0
- package/src/rng/seeded-rng.ts +123 -0
- package/src/targeting/index.ts +48 -0
- package/src/targeting/target-definition.test.ts +273 -0
- package/src/targeting/target-definition.ts +37 -0
- package/src/targeting/target-dsl.ts +279 -0
- package/src/targeting/target-resolver.ts +486 -0
- package/src/targeting/target-validation.test.ts +994 -0
- package/src/targeting/target-validation.ts +286 -0
- package/src/telemetry/events.ts +202 -0
- package/src/telemetry/index.ts +21 -0
- package/src/telemetry/telemetry-manager.ts +127 -0
- package/src/telemetry/types.ts +68 -0
- package/src/testing/__tests__/testing-utilities-integration.test.ts +161 -0
- package/src/testing/index.ts +88 -0
- package/src/testing/test-assertions.test.ts +341 -0
- package/src/testing/test-assertions.ts +256 -0
- package/src/testing/test-card-factory.test.ts +228 -0
- package/src/testing/test-card-factory.ts +111 -0
- package/src/testing/test-context-factory.ts +187 -0
- package/src/testing/test-end-assertions.test.ts +262 -0
- package/src/testing/test-end-assertions.ts +95 -0
- package/src/testing/test-engine-builder.test.ts +389 -0
- package/src/testing/test-engine-builder.ts +46 -0
- package/src/testing/test-flow-assertions.test.ts +284 -0
- package/src/testing/test-flow-assertions.ts +115 -0
- package/src/testing/test-player-builder.test.ts +132 -0
- package/src/testing/test-player-builder.ts +46 -0
- package/src/testing/test-replay-assertions.test.ts +356 -0
- package/src/testing/test-replay-assertions.ts +164 -0
- package/src/testing/test-rng-helpers.test.ts +260 -0
- package/src/testing/test-rng-helpers.ts +190 -0
- package/src/testing/test-state-builder.test.ts +373 -0
- package/src/testing/test-state-builder.ts +99 -0
- package/src/testing/test-zone-factory.test.ts +295 -0
- package/src/testing/test-zone-factory.ts +224 -0
- package/src/types/branded-utils.ts +54 -0
- package/src/types/branded.test.ts +175 -0
- package/src/types/branded.ts +33 -0
- package/src/types/index.ts +8 -0
- package/src/types/state.test.ts +198 -0
- package/src/types/state.ts +154 -0
- package/src/validation/card-type-guards.test.ts +242 -0
- package/src/validation/card-type-guards.ts +179 -0
- package/src/validation/index.ts +40 -0
- package/src/validation/schema-builders.test.ts +403 -0
- package/src/validation/schema-builders.ts +345 -0
- package/src/validation/type-guard-builder.test.ts +216 -0
- package/src/validation/type-guard-builder.ts +109 -0
- package/src/validation/validator-builder.test.ts +375 -0
- package/src/validation/validator-builder.ts +273 -0
- package/src/zones/index.ts +28 -0
- package/src/zones/zone-factory.test.ts +183 -0
- package/src/zones/zone-factory.ts +44 -0
- package/src/zones/zone-operations.test.ts +800 -0
- package/src/zones/zone-operations.ts +306 -0
- package/src/zones/zone-state-helpers.test.ts +337 -0
- package/src/zones/zone-state-helpers.ts +128 -0
- package/src/zones/zone-visibility.test.ts +156 -0
- package/src/zones/zone-visibility.ts +36 -0
- package/src/zones/zone.test.ts +186 -0
- package/src/zones/zone.ts +66 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import type { CardDefinition } from "../cards/card-definition";
|
|
3
|
+
import { isCardOfType } from "./card-type-guards";
|
|
4
|
+
|
|
5
|
+
describe("isCardOfType", () => {
|
|
6
|
+
describe("basic usage", () => {
|
|
7
|
+
it("should create a type guard for card type", () => {
|
|
8
|
+
const isCreature = isCardOfType("creature");
|
|
9
|
+
|
|
10
|
+
const creature: CardDefinition = {
|
|
11
|
+
id: "dragon-1",
|
|
12
|
+
name: "Dragon",
|
|
13
|
+
type: "creature",
|
|
14
|
+
basePower: 5,
|
|
15
|
+
baseToughness: 5,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const instant: CardDefinition = {
|
|
19
|
+
id: "bolt-1",
|
|
20
|
+
name: "Lightning Bolt",
|
|
21
|
+
type: "instant",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
expect(isCreature(creature)).toBe(true);
|
|
25
|
+
expect(isCreature(instant)).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should work with multiple card types", () => {
|
|
29
|
+
const isCreature = isCardOfType("creature");
|
|
30
|
+
const isInstant = isCardOfType("instant");
|
|
31
|
+
const isSorcery = isCardOfType("sorcery");
|
|
32
|
+
|
|
33
|
+
const creature: CardDefinition = {
|
|
34
|
+
id: "creature-1",
|
|
35
|
+
name: "Creature",
|
|
36
|
+
type: "creature",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const instant: CardDefinition = {
|
|
40
|
+
id: "instant-1",
|
|
41
|
+
name: "Instant",
|
|
42
|
+
type: "instant",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const sorcery: CardDefinition = {
|
|
46
|
+
id: "sorcery-1",
|
|
47
|
+
name: "Sorcery",
|
|
48
|
+
type: "sorcery",
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
expect(isCreature(creature)).toBe(true);
|
|
52
|
+
expect(isCreature(instant)).toBe(false);
|
|
53
|
+
expect(isCreature(sorcery)).toBe(false);
|
|
54
|
+
|
|
55
|
+
expect(isInstant(instant)).toBe(true);
|
|
56
|
+
expect(isInstant(creature)).toBe(false);
|
|
57
|
+
expect(isInstant(sorcery)).toBe(false);
|
|
58
|
+
|
|
59
|
+
expect(isSorcery(sorcery)).toBe(true);
|
|
60
|
+
expect(isSorcery(creature)).toBe(false);
|
|
61
|
+
expect(isSorcery(instant)).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("filtering arrays", () => {
|
|
66
|
+
it("should filter card arrays by type", () => {
|
|
67
|
+
const cards: CardDefinition[] = [
|
|
68
|
+
{ id: "1", name: "Dragon", type: "creature" },
|
|
69
|
+
{ id: "2", name: "Bolt", type: "instant" },
|
|
70
|
+
{ id: "3", name: "Goblin", type: "creature" },
|
|
71
|
+
{ id: "4", name: "Wrath", type: "sorcery" },
|
|
72
|
+
{ id: "5", name: "Angel", type: "creature" },
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
const isCreature = isCardOfType("creature");
|
|
76
|
+
const creatures = cards.filter(isCreature);
|
|
77
|
+
|
|
78
|
+
expect(creatures).toHaveLength(3);
|
|
79
|
+
expect(creatures.map((c) => c.name)).toEqual([
|
|
80
|
+
"Dragon",
|
|
81
|
+
"Goblin",
|
|
82
|
+
"Angel",
|
|
83
|
+
]);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should work with Array.some and Array.every", () => {
|
|
87
|
+
const cards: CardDefinition[] = [
|
|
88
|
+
{ id: "1", name: "Dragon", type: "creature" },
|
|
89
|
+
{ id: "2", name: "Goblin", type: "creature" },
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
const allCreatures: CardDefinition[] = [
|
|
93
|
+
{ id: "3", name: "Angel", type: "creature" },
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
const isCreature = isCardOfType("creature");
|
|
97
|
+
|
|
98
|
+
expect(cards.some(isCreature)).toBe(true);
|
|
99
|
+
expect(cards.every(isCreature)).toBe(true);
|
|
100
|
+
expect(allCreatures.every(isCreature)).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe("type narrowing", () => {
|
|
105
|
+
it("should narrow types in conditional blocks", () => {
|
|
106
|
+
const card: CardDefinition = {
|
|
107
|
+
id: "dragon-1",
|
|
108
|
+
name: "Dragon",
|
|
109
|
+
type: "creature",
|
|
110
|
+
basePower: 5,
|
|
111
|
+
baseToughness: 5,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const isCreature = isCardOfType("creature");
|
|
115
|
+
|
|
116
|
+
if (isCreature(card)) {
|
|
117
|
+
// TypeScript should know card.type is "creature"
|
|
118
|
+
expect(card.type).toBe("creature");
|
|
119
|
+
expect(card.name).toBe("Dragon");
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("game-specific types", () => {
|
|
125
|
+
it("should work with Gundam-specific card types", () => {
|
|
126
|
+
type GundamCard = CardDefinition & {
|
|
127
|
+
type: "unit" | "command" | "character" | "base";
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const isUnit = isCardOfType<GundamCard>("unit");
|
|
131
|
+
const isCommand = isCardOfType<GundamCard>("command");
|
|
132
|
+
|
|
133
|
+
const unit: GundamCard = {
|
|
134
|
+
id: "gundam-1",
|
|
135
|
+
name: "RX-78-2 Gundam",
|
|
136
|
+
type: "unit",
|
|
137
|
+
basePower: 3,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const command: GundamCard = {
|
|
141
|
+
id: "command-1",
|
|
142
|
+
name: "All-Out Attack",
|
|
143
|
+
type: "command",
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
expect(isUnit(unit)).toBe(true);
|
|
147
|
+
expect(isUnit(command)).toBe(false);
|
|
148
|
+
expect(isCommand(command)).toBe(true);
|
|
149
|
+
expect(isCommand(unit)).toBe(false);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("should work with Lorcana-specific card types", () => {
|
|
153
|
+
type LorcanaCard = CardDefinition & {
|
|
154
|
+
type: "character" | "action" | "item" | "location";
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const isCharacter = isCardOfType<LorcanaCard>("character");
|
|
158
|
+
const isAction = isCardOfType<LorcanaCard>("action");
|
|
159
|
+
|
|
160
|
+
const character: LorcanaCard = {
|
|
161
|
+
id: "mickey-1",
|
|
162
|
+
name: "Mickey Mouse - Brave Little Tailor",
|
|
163
|
+
type: "character",
|
|
164
|
+
basePower: 4,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const action: LorcanaCard = {
|
|
168
|
+
id: "action-1",
|
|
169
|
+
name: "Be Prepared",
|
|
170
|
+
type: "action",
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
expect(isCharacter(character)).toBe(true);
|
|
174
|
+
expect(isCharacter(action)).toBe(false);
|
|
175
|
+
expect(isAction(action)).toBe(true);
|
|
176
|
+
expect(isAction(character)).toBe(false);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe("edge cases", () => {
|
|
181
|
+
it("should handle cards with missing type field", () => {
|
|
182
|
+
const isCreature = isCardOfType("creature");
|
|
183
|
+
const cardWithoutType = { id: "1", name: "Card" } as CardDefinition;
|
|
184
|
+
|
|
185
|
+
expect(isCreature(cardWithoutType)).toBe(false);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("should handle empty strings", () => {
|
|
189
|
+
const isEmpty = isCardOfType("");
|
|
190
|
+
const emptyCard: CardDefinition = {
|
|
191
|
+
id: "1",
|
|
192
|
+
name: "Card",
|
|
193
|
+
type: "",
|
|
194
|
+
};
|
|
195
|
+
const normalCard: CardDefinition = {
|
|
196
|
+
id: "2",
|
|
197
|
+
name: "Card",
|
|
198
|
+
type: "creature",
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
expect(isEmpty(emptyCard)).toBe(true);
|
|
202
|
+
expect(isEmpty(normalCard)).toBe(false);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("should be case-sensitive", () => {
|
|
206
|
+
const isCreature = isCardOfType("creature");
|
|
207
|
+
const uppercaseCard: CardDefinition = {
|
|
208
|
+
id: "1",
|
|
209
|
+
name: "Card",
|
|
210
|
+
type: "Creature", // uppercase C
|
|
211
|
+
};
|
|
212
|
+
const lowercaseCard: CardDefinition = {
|
|
213
|
+
id: "2",
|
|
214
|
+
name: "Card",
|
|
215
|
+
type: "creature",
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
expect(isCreature(uppercaseCard)).toBe(false);
|
|
219
|
+
expect(isCreature(lowercaseCard)).toBe(true);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe("performance", () => {
|
|
224
|
+
it("should be efficient for large arrays", () => {
|
|
225
|
+
const cards: CardDefinition[] = Array.from({ length: 10000 }, (_, i) => ({
|
|
226
|
+
id: `card-${i}`,
|
|
227
|
+
name: `Card ${i}`,
|
|
228
|
+
type: i % 3 === 0 ? "creature" : i % 3 === 1 ? "instant" : "sorcery",
|
|
229
|
+
}));
|
|
230
|
+
|
|
231
|
+
const isCreature = isCardOfType("creature");
|
|
232
|
+
|
|
233
|
+
const startTime = performance.now();
|
|
234
|
+
const creatures = cards.filter(isCreature);
|
|
235
|
+
const endTime = performance.now();
|
|
236
|
+
|
|
237
|
+
expect(creatures.length).toBeGreaterThan(0);
|
|
238
|
+
// Should complete in reasonable time (< 1000ms for 10k cards, higher threshold for CI parallel execution)
|
|
239
|
+
expect(endTime - startTime).toBeLessThan(1000);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
});
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Card-specific type guard utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides convenient type guards for filtering and narrowing card types
|
|
5
|
+
* in a type-safe manner.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { CardDefinition } from "../cards/card-definition";
|
|
9
|
+
import { createTypeGuard } from "./type-guard-builder";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates a type guard for checking if a card is of a specific type
|
|
13
|
+
*
|
|
14
|
+
* This is a specialized version of createTypeGuard optimized for card definitions.
|
|
15
|
+
* It provides better ergonomics for the common use case of filtering cards by type.
|
|
16
|
+
*
|
|
17
|
+
* @template T - The card type (extends CardDefinition)
|
|
18
|
+
* @param cardType - The type value to check against
|
|
19
|
+
* @returns A type guard function that checks if a card matches the type
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* // Basic usage
|
|
24
|
+
* const isCreature = isCardOfType("creature");
|
|
25
|
+
* const creature: CardDefinition = { id: "1", name: "Dragon", type: "creature" };
|
|
26
|
+
* console.log(isCreature(creature)); // true
|
|
27
|
+
*
|
|
28
|
+
* // Filtering arrays
|
|
29
|
+
* const cards: CardDefinition[] = [...];
|
|
30
|
+
* const creatures = cards.filter(isCardOfType("creature"));
|
|
31
|
+
*
|
|
32
|
+
* // Type narrowing in conditionals
|
|
33
|
+
* if (isCardOfType("creature")(card)) {
|
|
34
|
+
* // TypeScript knows card.type is "creature" here
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* // Game-specific types
|
|
41
|
+
* type GundamCard = CardDefinition & {
|
|
42
|
+
* type: "unit" | "command" | "character" | "base";
|
|
43
|
+
* };
|
|
44
|
+
*
|
|
45
|
+
* const isUnit = isCardOfType<GundamCard>("unit");
|
|
46
|
+
* const gundamCard: GundamCard = { id: "1", name: "Gundam", type: "unit" };
|
|
47
|
+
* console.log(isUnit(gundamCard)); // true
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export function isCardOfType<T extends CardDefinition = CardDefinition>(
|
|
51
|
+
cardType: T["type"],
|
|
52
|
+
): (card: T) => card is T & { type: typeof cardType } {
|
|
53
|
+
return createTypeGuard<T, "type", T["type"]>("type", cardType);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Creates a type guard for checking if a card has a specific field value
|
|
58
|
+
*
|
|
59
|
+
* This is a more generic version that works with any card field, not just type.
|
|
60
|
+
* Useful for filtering by other properties like rarity, set, or custom fields.
|
|
61
|
+
*
|
|
62
|
+
* @template T - The card type (extends CardDefinition)
|
|
63
|
+
* @template K - The key of the field to check
|
|
64
|
+
* @template V - The value type to check against
|
|
65
|
+
*
|
|
66
|
+
* @param field - The field name to check
|
|
67
|
+
* @param value - The value to compare against
|
|
68
|
+
* @returns A type guard function
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```typescript
|
|
72
|
+
* type ExtendedCard = CardDefinition & { rarity: "common" | "rare" | "mythic" };
|
|
73
|
+
*
|
|
74
|
+
* const isRare = isCardWithField<ExtendedCard, "rarity", "rare">("rarity", "rare");
|
|
75
|
+
* const rareCard: ExtendedCard = {
|
|
76
|
+
* id: "1",
|
|
77
|
+
* name: "Rare Dragon",
|
|
78
|
+
* type: "creature",
|
|
79
|
+
* rarity: "rare"
|
|
80
|
+
* };
|
|
81
|
+
*
|
|
82
|
+
* console.log(isRare(rareCard)); // true
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export function isCardWithField<
|
|
86
|
+
T extends CardDefinition,
|
|
87
|
+
K extends keyof T,
|
|
88
|
+
V extends T[K],
|
|
89
|
+
>(field: K, value: V): (card: T) => card is T & Record<K, V> {
|
|
90
|
+
return createTypeGuard<T, K, V>(field, value);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Combines multiple type guards with AND logic
|
|
95
|
+
*
|
|
96
|
+
* Returns a type guard that passes only if all provided type guards pass.
|
|
97
|
+
* Useful for filtering cards that match multiple criteria.
|
|
98
|
+
*
|
|
99
|
+
* @template T - The object type to guard
|
|
100
|
+
* @param guards - Array of type guard functions to combine
|
|
101
|
+
* @returns A combined type guard that checks all conditions
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* type Card = CardDefinition & {
|
|
106
|
+
* type: string;
|
|
107
|
+
* rarity: string;
|
|
108
|
+
* };
|
|
109
|
+
*
|
|
110
|
+
* const isCreature = isCardOfType<Card>("creature");
|
|
111
|
+
* const isRare = isCardWithField<Card, "rarity", "rare">("rarity", "rare");
|
|
112
|
+
* const isRareCreature = combineTypeGuards([isCreature, isRare]);
|
|
113
|
+
*
|
|
114
|
+
* const cards: Card[] = [...];
|
|
115
|
+
* const rareCreatures = cards.filter(isRareCreature);
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
export function combineTypeGuards<T>(
|
|
119
|
+
guards: Array<(obj: T) => boolean>,
|
|
120
|
+
): (obj: T) => obj is T {
|
|
121
|
+
return (obj: T): obj is T => {
|
|
122
|
+
return guards.every((guard) => guard(obj));
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Combines multiple type guards with OR logic
|
|
128
|
+
*
|
|
129
|
+
* Returns a type guard that passes if any of the provided type guards pass.
|
|
130
|
+
* Useful for filtering cards that match any of several criteria.
|
|
131
|
+
*
|
|
132
|
+
* @template T - The object type to guard
|
|
133
|
+
* @param guards - Array of type guard functions to combine
|
|
134
|
+
* @returns A combined type guard that checks any condition
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```typescript
|
|
138
|
+
* const isCreature = isCardOfType("creature");
|
|
139
|
+
* const isInstant = isCardOfType("instant");
|
|
140
|
+
* const isSpell = combineTypeGuardsOr([isCreature, isInstant]);
|
|
141
|
+
*
|
|
142
|
+
* const cards: CardDefinition[] = [...];
|
|
143
|
+
* const spells = cards.filter(isSpell);
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
export function combineTypeGuardsOr<T>(
|
|
147
|
+
guards: Array<(obj: T) => boolean>,
|
|
148
|
+
): (obj: T) => obj is T {
|
|
149
|
+
return (obj: T): obj is T => {
|
|
150
|
+
return guards.some((guard) => guard(obj));
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Negates a type guard
|
|
156
|
+
*
|
|
157
|
+
* Returns a type guard that passes when the provided type guard fails.
|
|
158
|
+
* Useful for filtering cards that don't match a specific criterion.
|
|
159
|
+
*
|
|
160
|
+
* @template T - The object type to guard
|
|
161
|
+
* @param guard - The type guard to negate
|
|
162
|
+
* @returns A negated type guard
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```typescript
|
|
166
|
+
* const isCreature = isCardOfType("creature");
|
|
167
|
+
* const isNotCreature = negateTypeGuard(isCreature);
|
|
168
|
+
*
|
|
169
|
+
* const cards: CardDefinition[] = [...];
|
|
170
|
+
* const nonCreatures = cards.filter(isNotCreature);
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
export function negateTypeGuard<T>(
|
|
174
|
+
guard: (obj: T) => boolean,
|
|
175
|
+
): (obj: T) => obj is T {
|
|
176
|
+
return (obj: T): obj is T => {
|
|
177
|
+
return !guard(obj);
|
|
178
|
+
};
|
|
179
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation utilities for @drmxrcy/tcg-core
|
|
3
|
+
*
|
|
4
|
+
* This module provides type guards, validators, and schema builders
|
|
5
|
+
* for runtime validation of cards, moves, and game states.
|
|
6
|
+
*
|
|
7
|
+
* @module validation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Card-specific type guards
|
|
11
|
+
export {
|
|
12
|
+
combineTypeGuards,
|
|
13
|
+
combineTypeGuardsOr,
|
|
14
|
+
isCardOfType,
|
|
15
|
+
isCardWithField,
|
|
16
|
+
negateTypeGuard,
|
|
17
|
+
} from "./card-type-guards";
|
|
18
|
+
// Zod schema builders
|
|
19
|
+
export {
|
|
20
|
+
composeSchemas,
|
|
21
|
+
createArraySchema,
|
|
22
|
+
createCardSchema,
|
|
23
|
+
createDiscriminatedUnion,
|
|
24
|
+
createMultiRefinedSchema,
|
|
25
|
+
createOptionalSchema,
|
|
26
|
+
createRecordSchema,
|
|
27
|
+
createRefinedSchema,
|
|
28
|
+
createStrictSchema,
|
|
29
|
+
extendSchema,
|
|
30
|
+
mergeSchemas,
|
|
31
|
+
} from "./schema-builders";
|
|
32
|
+
// Type guard builder
|
|
33
|
+
export { createTypeGuard } from "./type-guard-builder";
|
|
34
|
+
// Validator builder
|
|
35
|
+
export {
|
|
36
|
+
createValidator,
|
|
37
|
+
type ValidationResult,
|
|
38
|
+
type Validator,
|
|
39
|
+
ValidatorBuilder,
|
|
40
|
+
} from "./validator-builder";
|