@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,354 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import type { CardId, PlayerId, ZoneId } from "../types";
3
+ import type { InternalState } from "../types/state";
4
+ import type { CardZoneConfig } from "../zones";
5
+ import { createCardOperations, createZoneOperations } from "./operations-impl";
6
+
7
+ describe("Operations Implementation", () => {
8
+ type TestCardDef = { id: string; name: string };
9
+ type TestCardMeta = {
10
+ damage?: number;
11
+ exerted?: boolean;
12
+ counters?: number;
13
+ };
14
+
15
+ const createTestInternalState = (): InternalState<
16
+ TestCardDef,
17
+ TestCardMeta
18
+ > => {
19
+ const handZone: CardZoneConfig = {
20
+ id: "hand" as ZoneId,
21
+ name: "Hand",
22
+ visibility: "private",
23
+ ordered: false,
24
+ };
25
+
26
+ const deckZone: CardZoneConfig = {
27
+ id: "deck" as ZoneId,
28
+ name: "Deck",
29
+ visibility: "secret",
30
+ ordered: true,
31
+ };
32
+
33
+ return {
34
+ zones: {
35
+ hand: {
36
+ config: handZone,
37
+ cardIds: ["card-1", "card-2"] as unknown as CardId[],
38
+ },
39
+ deck: {
40
+ config: deckZone,
41
+ cardIds: ["card-3", "card-4", "card-5"] as unknown as CardId[],
42
+ },
43
+ play: {
44
+ config: {
45
+ id: "play" as ZoneId,
46
+ name: "Play",
47
+ visibility: "public",
48
+ ordered: false,
49
+ },
50
+ cardIds: [],
51
+ },
52
+ },
53
+ cards: {
54
+ "card-1": {
55
+ definitionId: "monster-1",
56
+ owner: "player-1" as unknown as PlayerId,
57
+ controller: "player-1" as unknown as PlayerId,
58
+ zone: "hand" as ZoneId,
59
+ },
60
+ "card-2": {
61
+ definitionId: "monster-2",
62
+ owner: "player-1" as unknown as PlayerId,
63
+ controller: "player-1" as unknown as PlayerId,
64
+ zone: "hand" as ZoneId,
65
+ },
66
+ "card-3": {
67
+ definitionId: "spell-1",
68
+ owner: "player-1" as unknown as PlayerId,
69
+ controller: "player-1" as unknown as PlayerId,
70
+ zone: "deck" as ZoneId,
71
+ position: 0,
72
+ },
73
+ "card-4": {
74
+ definitionId: "spell-2",
75
+ owner: "player-1" as unknown as PlayerId,
76
+ controller: "player-1" as unknown as PlayerId,
77
+ zone: "deck" as ZoneId,
78
+ position: 1,
79
+ },
80
+ "card-5": {
81
+ definitionId: "spell-3",
82
+ owner: "player-1" as unknown as PlayerId,
83
+ controller: "player-1" as unknown as PlayerId,
84
+ zone: "deck" as ZoneId,
85
+ position: 2,
86
+ },
87
+ },
88
+ cardMetas: {
89
+ "card-1": { damage: 0, exerted: false },
90
+ "card-2": { damage: 3, exerted: true, counters: 2 },
91
+ },
92
+ };
93
+ };
94
+
95
+ describe("ZoneOperations Implementation", () => {
96
+ describe("moveCard", () => {
97
+ it("should move card from hand to play", () => {
98
+ const state = createTestInternalState();
99
+ const ops = createZoneOperations(state);
100
+
101
+ ops.moveCard({
102
+ cardId: "card-1" as CardId,
103
+ targetZoneId: "play" as ZoneId,
104
+ });
105
+
106
+ expect(state.zones.hand.cardIds).not.toContain("card-1");
107
+ expect(state.zones.play.cardIds).toContain("card-1");
108
+ expect(state.cards["card-1"].zone).toBe("play" as ZoneId);
109
+ });
110
+
111
+ it("should move card to top of deck", () => {
112
+ const state = createTestInternalState();
113
+ const ops = createZoneOperations(state);
114
+
115
+ ops.moveCard({
116
+ cardId: "card-1" as CardId,
117
+ targetZoneId: "deck" as ZoneId,
118
+ position: "top",
119
+ });
120
+
121
+ expect(state.zones.deck.cardIds[0]).toBe("card-1" as unknown as CardId);
122
+ expect(state.cards["card-1"].position).toBe(0);
123
+ });
124
+
125
+ it("should move card to bottom of deck", () => {
126
+ const state = createTestInternalState();
127
+ const ops = createZoneOperations(state);
128
+
129
+ ops.moveCard({
130
+ cardId: "card-1" as CardId,
131
+ targetZoneId: "deck" as ZoneId,
132
+ position: "bottom",
133
+ });
134
+
135
+ const lastIndex = state.zones.deck.cardIds.length - 1;
136
+ expect(state.zones.deck.cardIds[lastIndex]).toBe(
137
+ "card-1" as unknown as CardId,
138
+ );
139
+ });
140
+
141
+ it("should update positions in ordered zones", () => {
142
+ const state = createTestInternalState();
143
+ const ops = createZoneOperations(state);
144
+
145
+ ops.moveCard({
146
+ cardId: "card-1" as CardId,
147
+ targetZoneId: "deck" as ZoneId,
148
+ position: 1,
149
+ });
150
+
151
+ expect(state.cards["card-1"].position).toBe(1);
152
+ expect(state.cards["card-4"].position).toBe(2); // Shifted down
153
+ });
154
+ });
155
+
156
+ describe("getCardsInZone", () => {
157
+ it("should return all cards in a zone", () => {
158
+ const state = createTestInternalState();
159
+ const ops = createZoneOperations(state);
160
+
161
+ const handCards = ops.getCardsInZone("hand" as ZoneId);
162
+
163
+ expect(handCards).toHaveLength(2);
164
+ expect(handCards).toContain("card-1");
165
+ expect(handCards).toContain("card-2");
166
+ });
167
+
168
+ it("should return empty array for empty zone", () => {
169
+ const state = createTestInternalState();
170
+ const ops = createZoneOperations(state);
171
+
172
+ const playCards = ops.getCardsInZone("play" as ZoneId);
173
+
174
+ expect(playCards).toHaveLength(0);
175
+ });
176
+
177
+ it("should filter by owner", () => {
178
+ const state = createTestInternalState();
179
+ // Add card for different player
180
+ state.zones.hand.cardIds.push("card-6" as CardId);
181
+ state.cards["card-6"] = {
182
+ definitionId: "monster-3",
183
+ owner: "player-2" as unknown as PlayerId,
184
+ controller: "player-2" as unknown as PlayerId,
185
+ zone: "hand" as ZoneId,
186
+ };
187
+
188
+ const ops = createZoneOperations(state);
189
+
190
+ const player1Cards = ops.getCardsInZone(
191
+ "hand" as ZoneId,
192
+ "player-1" as unknown as PlayerId,
193
+ );
194
+
195
+ expect(player1Cards).toHaveLength(2);
196
+ expect(player1Cards).not.toContain("card-6");
197
+ });
198
+ });
199
+
200
+ describe("shuffleZone", () => {
201
+ it("should shuffle cards in a zone", () => {
202
+ const state = createTestInternalState();
203
+ const ops = createZoneOperations(state);
204
+
205
+ const beforeShuffle = [...state.zones.deck.cardIds];
206
+ ops.shuffleZone("deck" as ZoneId);
207
+ const afterShuffle = state.zones.deck.cardIds;
208
+
209
+ // Should have same cards
210
+ expect(afterShuffle).toHaveLength(beforeShuffle.length);
211
+ for (const card of beforeShuffle) {
212
+ expect(afterShuffle).toContain(card);
213
+ }
214
+
215
+ // Should update positions
216
+ for (let i = 0; i < afterShuffle.length; i++) {
217
+ const cardId = afterShuffle[i] as string;
218
+ expect(state.cards[cardId].position).toBe(i);
219
+ }
220
+ });
221
+ });
222
+
223
+ describe("getCardZone", () => {
224
+ it("should return zone containing a card", () => {
225
+ const state = createTestInternalState();
226
+ const ops = createZoneOperations(state);
227
+
228
+ const zone = ops.getCardZone("card-3" as CardId);
229
+
230
+ expect(zone).toBe("deck" as ZoneId);
231
+ });
232
+
233
+ it("should return undefined for nonexistent card", () => {
234
+ const state = createTestInternalState();
235
+ const ops = createZoneOperations(state);
236
+
237
+ const zone = ops.getCardZone("nonexistent" as CardId);
238
+
239
+ expect(zone).toBeUndefined();
240
+ });
241
+ });
242
+ });
243
+
244
+ describe("CardOperations Implementation", () => {
245
+ describe("getCardMeta", () => {
246
+ it("should return card metadata", () => {
247
+ const state = createTestInternalState();
248
+ const ops = createCardOperations(state);
249
+
250
+ const meta = ops.getCardMeta("card-2" as CardId);
251
+
252
+ expect(meta.damage).toBe(3);
253
+ expect(meta.exerted).toBe(true);
254
+ expect(meta.counters).toBe(2);
255
+ });
256
+
257
+ it("should return empty object for card without metadata", () => {
258
+ const state = createTestInternalState();
259
+ const ops = createCardOperations(state);
260
+
261
+ const meta = ops.getCardMeta("card-3" as CardId);
262
+
263
+ expect(meta).toEqual({});
264
+ });
265
+ });
266
+
267
+ describe("updateCardMeta", () => {
268
+ it("should merge metadata", () => {
269
+ const state = createTestInternalState();
270
+ const ops = createCardOperations(state);
271
+
272
+ ops.updateCardMeta("card-1" as CardId, { damage: 5 });
273
+
274
+ expect(state.cardMetas["card-1"].damage).toBe(5);
275
+ expect(state.cardMetas["card-1"].exerted).toBe(false); // Preserved
276
+ });
277
+
278
+ it("should create metadata for new card", () => {
279
+ const state = createTestInternalState();
280
+ const ops = createCardOperations(state);
281
+
282
+ ops.updateCardMeta("card-3" as CardId, { damage: 2 });
283
+
284
+ expect(state.cardMetas["card-3"].damage).toBe(2);
285
+ });
286
+ });
287
+
288
+ describe("setCardMeta", () => {
289
+ it("should replace metadata completely", () => {
290
+ const state = createTestInternalState();
291
+ const ops = createCardOperations(state);
292
+
293
+ ops.setCardMeta("card-2" as CardId, { damage: 10 });
294
+
295
+ expect(state.cardMetas["card-2"].damage).toBe(10);
296
+ expect(state.cardMetas["card-2"].exerted).toBeUndefined();
297
+ expect(state.cardMetas["card-2"].counters).toBeUndefined();
298
+ });
299
+ });
300
+
301
+ describe("getCardOwner", () => {
302
+ it("should return card owner", () => {
303
+ const state = createTestInternalState();
304
+ const ops = createCardOperations(state);
305
+
306
+ const owner = ops.getCardOwner("card-1" as CardId);
307
+
308
+ expect(owner).toBe("player-1" as unknown as PlayerId);
309
+ });
310
+
311
+ it("should return undefined for nonexistent card", () => {
312
+ const state = createTestInternalState();
313
+ const ops = createCardOperations(state);
314
+
315
+ const owner = ops.getCardOwner("nonexistent" as CardId);
316
+
317
+ expect(owner).toBeUndefined();
318
+ });
319
+ });
320
+
321
+ describe("queryCards", () => {
322
+ it("should find cards by predicate", () => {
323
+ const state = createTestInternalState();
324
+ const ops = createCardOperations(state);
325
+
326
+ const exerted = ops.queryCards((cardId, meta) => meta.exerted === true);
327
+
328
+ expect(exerted).toHaveLength(1);
329
+ expect(exerted).toContain("card-2");
330
+ });
331
+
332
+ it("should support complex predicates", () => {
333
+ const state = createTestInternalState();
334
+ const ops = createCardOperations(state);
335
+
336
+ const results = ops.queryCards(
337
+ (cardId, meta) => (meta.damage ?? 0) > 0 && (meta.counters ?? 0) >= 2,
338
+ );
339
+
340
+ expect(results).toHaveLength(1);
341
+ expect(results).toContain("card-2");
342
+ });
343
+
344
+ it("should return empty array when no matches", () => {
345
+ const state = createTestInternalState();
346
+ const ops = createCardOperations(state);
347
+
348
+ const results = ops.queryCards((cardId, meta) => meta.counters === 999);
349
+
350
+ expect(results).toHaveLength(0);
351
+ });
352
+ });
353
+ });
354
+ });