@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,427 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import type { Draft } from "immer";
3
+ import type {
4
+ FlowContext,
5
+ FlowDefinition,
6
+ GameSegmentDefinition,
7
+ PhaseDefinition,
8
+ StepDefinition,
9
+ TurnDefinition,
10
+ } from "../flow-definition";
11
+
12
+ /**
13
+ * Task 9.1: Write tests for FlowDefinition type
14
+ *
15
+ * Tests verify the structure and behavior of FlowDefinition.
16
+ * Following the user's requirements for flexible turn/phase/step progression.
17
+ */
18
+
19
+ type TestGameState = {
20
+ currentPlayer: number;
21
+ players: Array<{ id: string; ready: boolean }>;
22
+ turnCount: number;
23
+ phase: string;
24
+ step?: string;
25
+ phaseCount: number;
26
+ stepCount: number;
27
+ };
28
+
29
+ describe("FlowDefinition Type", () => {
30
+ describe("FlowContext API", () => {
31
+ it("should provide rich context beyond just state", () => {
32
+ // Task 9.1: FlowContext should provide:
33
+ // - state access (draft for mutations)
34
+ // - flow control methods (endPhase, endStep, endTurn, endGameSegment)
35
+ // - current flow information (current phase, step, turn, player, game segment)
36
+
37
+ const mockContext: FlowContext<TestGameState> = {
38
+ state: {} as Draft<TestGameState>,
39
+ game: {
40
+ setOTP: () => {},
41
+ getOTP: () => undefined,
42
+ setChoosingFirstPlayer: () => {},
43
+ getChoosingFirstPlayer: () => undefined,
44
+ setPendingMulligan: () => {},
45
+ getPendingMulligan: () => [],
46
+ addPendingMulligan: () => {},
47
+ removePendingMulligan: () => {},
48
+ },
49
+ zones: {
50
+ moveCard: () => {},
51
+ getCardsInZone: () => [],
52
+ shuffleZone: () => {},
53
+ getCardZone: () => undefined,
54
+ drawCards: () => [],
55
+ mulligan: () => {},
56
+ bulkMove: () => [],
57
+ createDeck: () => [],
58
+ },
59
+ cards: {
60
+ getCardMeta: () => ({}),
61
+ updateCardMeta: () => {},
62
+ setCardMeta: () => {},
63
+ getCardOwner: () => undefined,
64
+ queryCards: () => [],
65
+ },
66
+ endGameSegment: () => {},
67
+ endPhase: () => {},
68
+ endStep: () => {},
69
+ endTurn: () => {},
70
+ getCurrentGameSegment: () => "mainGame",
71
+ getCurrentPhase: () => "main",
72
+ getCurrentStep: () => undefined,
73
+ getCurrentPlayer: () => "player-1",
74
+ getTurnNumber: () => 1,
75
+ setCurrentPlayer: () => {},
76
+ };
77
+
78
+ expect(mockContext.endGameSegment).toBeDefined();
79
+ expect(mockContext.endPhase).toBeDefined();
80
+ expect(mockContext.endStep).toBeDefined();
81
+ expect(mockContext.endTurn).toBeDefined();
82
+ expect(mockContext.getCurrentPhase()).toBe("main");
83
+ expect(mockContext.getCurrentGameSegment()).toBe("mainGame");
84
+ });
85
+ });
86
+
87
+ describe("TurnDefinition", () => {
88
+ it("should support lifecycle hooks with FlowContext", () => {
89
+ // Task 9.5: Lifecycle hooks receive FlowContext, not just state
90
+ const turnDef: TurnDefinition<TestGameState> = {
91
+ onBegin: (context) => {
92
+ // Can mutate state
93
+ context.state.turnCount += 1;
94
+ // Can access flow information
95
+ expect(context.getCurrentPlayer).toBeDefined();
96
+ },
97
+ onEnd: (context) => {
98
+ context.state.turnCount += 1;
99
+ },
100
+ };
101
+
102
+ expect(turnDef.onBegin).toBeDefined();
103
+ expect(turnDef.onEnd).toBeDefined();
104
+ });
105
+
106
+ it("should support automatic turn end with endIf", () => {
107
+ // Task 9.7: endIf for automatic transitions
108
+ const turnDef: TurnDefinition<TestGameState> = {
109
+ endIf: (context) => {
110
+ // Can check state and return boolean
111
+ return context.state.phaseCount >= 4;
112
+ },
113
+ };
114
+
115
+ expect(turnDef.endIf).toBeDefined();
116
+ });
117
+
118
+ it("should support programmatic turn end", () => {
119
+ // User requirement: "We should be able to end the segment/turn/phase programmatically"
120
+ const turnDef: TurnDefinition<TestGameState> = {
121
+ onBegin: (context) => {
122
+ // Can call endTurn() programmatically from actions
123
+ if (context.state.players.length === 0) {
124
+ context.endTurn();
125
+ }
126
+ },
127
+ };
128
+
129
+ expect(turnDef.onBegin).toBeDefined();
130
+ });
131
+
132
+ it("should define phases that progress sequentially", () => {
133
+ // User requirement: "phases, when the current phase ends, the next phase from the same player will start"
134
+ const turnDef: TurnDefinition<TestGameState> = {
135
+ phases: {
136
+ ready: {
137
+ order: 0,
138
+ next: "draw",
139
+ onEnd: (context) => {
140
+ context.state.phase = "draw";
141
+ },
142
+ },
143
+ draw: {
144
+ order: 1,
145
+ next: "main",
146
+ },
147
+ main: {
148
+ order: 2,
149
+ next: "end",
150
+ },
151
+ end: {
152
+ order: 3,
153
+ next: undefined, // No next phase, turn ends
154
+ },
155
+ },
156
+ };
157
+
158
+ expect(turnDef.phases).toBeDefined();
159
+ expect(turnDef.phases?.ready.next).toBe("draw");
160
+ expect(turnDef.phases?.end.next).toBeUndefined();
161
+ });
162
+ });
163
+
164
+ describe("PhaseDefinition", () => {
165
+ it("should support lifecycle hooks", () => {
166
+ const phaseDef: PhaseDefinition<TestGameState> = {
167
+ order: 0,
168
+ onBegin: (context) => {
169
+ context.state.phaseCount += 1;
170
+ },
171
+ onEnd: (context) => {
172
+ context.state.phase = "completed";
173
+ },
174
+ };
175
+
176
+ expect(phaseDef.onBegin).toBeDefined();
177
+ expect(phaseDef.onEnd).toBeDefined();
178
+ });
179
+
180
+ it("should support automatic phase end", () => {
181
+ const phaseDef: PhaseDefinition<TestGameState> = {
182
+ order: 0,
183
+ endIf: (context) => {
184
+ // Phase automatically ends when condition is true
185
+ return context.state.players.every((p) => p.ready);
186
+ },
187
+ };
188
+
189
+ expect(phaseDef.endIf).toBeDefined();
190
+ });
191
+
192
+ it("should support programmatic phase end", () => {
193
+ const phaseDef: PhaseDefinition<TestGameState> = {
194
+ order: 0,
195
+ onBegin: (context) => {
196
+ // Can call endPhase() from within phase
197
+ if (context.state.players.length === 0) {
198
+ context.endPhase();
199
+ }
200
+ },
201
+ };
202
+
203
+ expect(phaseDef.onBegin).toBeDefined();
204
+ });
205
+
206
+ it("should define steps with custom progression", () => {
207
+ // User requirement: "For steps, it's a bit different... combat has different steps"
208
+ const phaseDef: PhaseDefinition<TestGameState> = {
209
+ order: 2,
210
+ steps: {
211
+ declare: {
212
+ order: 0,
213
+ next: "target",
214
+ },
215
+ target: {
216
+ order: 1,
217
+ next: "damage",
218
+ },
219
+ damage: {
220
+ order: 2,
221
+ next: undefined, // Ends the combat phase
222
+ },
223
+ },
224
+ };
225
+
226
+ expect(phaseDef.steps).toBeDefined();
227
+ expect(phaseDef.steps?.declare.next).toBe("target");
228
+ });
229
+ });
230
+
231
+ describe("StepDefinition", () => {
232
+ it("should support lifecycle hooks", () => {
233
+ const stepDef: StepDefinition<TestGameState> = {
234
+ order: 0,
235
+ onBegin: (context) => {
236
+ context.state.stepCount += 1;
237
+ },
238
+ onEnd: (context) => {
239
+ context.state.step = undefined;
240
+ },
241
+ };
242
+
243
+ expect(stepDef.onBegin).toBeDefined();
244
+ expect(stepDef.onEnd).toBeDefined();
245
+ });
246
+
247
+ it("should support automatic step end", () => {
248
+ const stepDef: StepDefinition<TestGameState> = {
249
+ order: 0,
250
+ endIf: (context) => {
251
+ return context.state.stepCount >= 3;
252
+ },
253
+ };
254
+
255
+ expect(stepDef.endIf).toBeDefined();
256
+ });
257
+
258
+ it("should support programmatic step end", () => {
259
+ const stepDef: StepDefinition<TestGameState> = {
260
+ order: 0,
261
+ onBegin: (context) => {
262
+ if (context.state.players.length === 0) {
263
+ context.endStep();
264
+ }
265
+ },
266
+ };
267
+
268
+ expect(stepDef.onBegin).toBeDefined();
269
+ });
270
+ });
271
+
272
+ describe("FlowDefinition", () => {
273
+ it("should define complete game flow", () => {
274
+ const flow: FlowDefinition<TestGameState> = {
275
+ gameSegments: {
276
+ mainGame: {
277
+ order: 1,
278
+ turn: {
279
+ onBegin: (context) => {
280
+ context.state.currentPlayer =
281
+ (context.state.currentPlayer + 1) %
282
+ context.state.players.length;
283
+ },
284
+ phases: {
285
+ ready: {
286
+ order: 0,
287
+ next: "draw",
288
+ },
289
+ draw: {
290
+ order: 1,
291
+ next: "main",
292
+ },
293
+ main: {
294
+ order: 2,
295
+ next: "end",
296
+ },
297
+ end: {
298
+ order: 3,
299
+ next: undefined,
300
+ },
301
+ },
302
+ },
303
+ },
304
+ },
305
+ };
306
+
307
+ expect(flow.gameSegments).toBeDefined();
308
+ expect(flow.gameSegments.mainGame).toBeDefined();
309
+ expect(flow.gameSegments.mainGame.turn.phases).toBeDefined();
310
+ });
311
+
312
+ it("should support default progression behavior", () => {
313
+ // User requirement: "We should have defaults, but we should also be able to customize them"
314
+ const flowWithDefaults: FlowDefinition<TestGameState> = {
315
+ gameSegments: {
316
+ mainGame: {
317
+ order: 1,
318
+ turn: {
319
+ // Uses default: when turn ends, next player starts their turn
320
+ phases: {
321
+ main: {
322
+ order: 0,
323
+ // Uses default: phases progress sequentially by order
324
+ next: undefined,
325
+ },
326
+ },
327
+ },
328
+ },
329
+ },
330
+ };
331
+
332
+ expect(flowWithDefaults.gameSegments.mainGame.turn).toBeDefined();
333
+ });
334
+
335
+ it("should support custom progression behavior", () => {
336
+ const flowWithCustom: FlowDefinition<TestGameState> = {
337
+ gameSegments: {
338
+ mainGame: {
339
+ order: 1,
340
+ turn: {
341
+ onEnd: (context) => {
342
+ // Custom logic: maybe skip players, go back to first player, etc.
343
+ context.state.currentPlayer = 0;
344
+ },
345
+ phases: {
346
+ main: {
347
+ order: 0,
348
+ next: "combat",
349
+ steps: {
350
+ declare: {
351
+ order: 0,
352
+ next: "resolve",
353
+ onEnd: (context) => {
354
+ // Custom step transition logic
355
+ if (context.state.stepCount > 5) {
356
+ context.endPhase(); // Skip remaining steps
357
+ }
358
+ },
359
+ },
360
+ resolve: {
361
+ order: 1,
362
+ next: undefined,
363
+ },
364
+ },
365
+ },
366
+ combat: {
367
+ order: 1,
368
+ next: undefined,
369
+ },
370
+ },
371
+ },
372
+ },
373
+ },
374
+ };
375
+
376
+ expect(flowWithCustom.gameSegments.mainGame.turn.onEnd).toBeDefined();
377
+ expect(
378
+ flowWithCustom.gameSegments.mainGame.turn.phases?.main.steps,
379
+ ).toBeDefined();
380
+ });
381
+ });
382
+
383
+ describe("Type Safety", () => {
384
+ it("should enforce correct generic state type", () => {
385
+ // TypeScript compile-time test
386
+ const flow: FlowDefinition<TestGameState> = {
387
+ gameSegments: {
388
+ mainGame: {
389
+ order: 1,
390
+ turn: {
391
+ onBegin: (context) => {
392
+ // context.state should be Draft<TestGameState>
393
+ context.state.turnCount += 1;
394
+ context.state.currentPlayer = 0;
395
+ // @ts-expect-error - nonexistent property should error
396
+ context.state.nonExistent = true;
397
+ },
398
+ },
399
+ },
400
+ },
401
+ };
402
+
403
+ expect(flow).toBeDefined();
404
+ });
405
+
406
+ it("should provide type-safe FlowContext", () => {
407
+ const phaseDef: PhaseDefinition<TestGameState> = {
408
+ order: 0,
409
+ onBegin: (context) => {
410
+ // All FlowContext methods should be type-safe
411
+ const phase: string | undefined = context.getCurrentPhase();
412
+ const player: string = context.getCurrentPlayer();
413
+ const turn: number = context.getTurnNumber();
414
+
415
+ expect(typeof player).toBe("string");
416
+ expect(typeof turn).toBe("number");
417
+ // Phase can be undefined in some contexts
418
+ if (phase) {
419
+ expect(typeof phase).toBe("string");
420
+ }
421
+ },
422
+ };
423
+
424
+ expect(phaseDef).toBeDefined();
425
+ });
426
+ });
427
+ });