@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,380 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import type { Draft } from "immer";
3
+ import { produce } from "immer";
4
+ import type { GameMoveDefinition } from "../game-definition/move-definitions";
5
+ import { createMockContext } from "../testing/test-context-factory";
6
+ import type { CardId, PlayerId } from "../types";
7
+ import { createCardId, createPlayerId } from "../types";
8
+ import type {
9
+ MoveCondition,
10
+ MoveContext,
11
+ MoveReducer,
12
+ MoveResult,
13
+ } from "./move-system";
14
+
15
+ describe("Move System with Validation", () => {
16
+ type TestGameState = {
17
+ players: Record<PlayerId, { life: number; mana: number }>;
18
+ currentPlayer: PlayerId;
19
+ cards: Record<CardId, { name: string; cost: number }>;
20
+ };
21
+
22
+ const player1 = createPlayerId("p1");
23
+ const player2 = createPlayerId("p2");
24
+ const card1 = createCardId("card1");
25
+
26
+ const initialState: TestGameState = {
27
+ players: {
28
+ [player1]: { life: 20, mana: 3 },
29
+ [player2]: { life: 20, mana: 3 },
30
+ },
31
+ currentPlayer: player1,
32
+ cards: {
33
+ [card1]: { name: "Lightning Bolt", cost: 1 },
34
+ },
35
+ };
36
+
37
+ describe("GameMoveDefinition", () => {
38
+ it("should define a move with reducer and condition", () => {
39
+ const reducer: MoveReducer<TestGameState> = (draft, context) => {
40
+ draft.players[context.playerId].mana -= 1;
41
+ };
42
+
43
+ const condition: MoveCondition<TestGameState> = (state, context) => {
44
+ return state.players[context.playerId].mana >= 1;
45
+ };
46
+
47
+ const moveDef: GameMoveDefinition<TestGameState> = {
48
+ reducer,
49
+ condition,
50
+ };
51
+
52
+ expect(moveDef.reducer).toBe(reducer);
53
+ expect(moveDef.condition).toBe(condition);
54
+ });
55
+
56
+ it("should define a move without condition (always valid)", () => {
57
+ const reducer: MoveReducer<TestGameState> = (draft, context) => {
58
+ draft.currentPlayer = context.playerId;
59
+ };
60
+
61
+ const moveDef: GameMoveDefinition<TestGameState> = {
62
+ reducer,
63
+ };
64
+
65
+ expect(moveDef.reducer).toBe(reducer);
66
+ expect(moveDef.condition).toBeUndefined();
67
+ });
68
+
69
+ it("should include metadata in move definition", () => {
70
+ const moveDef: GameMoveDefinition<TestGameState> = {
71
+ reducer: (draft: Draft<TestGameState>) => draft,
72
+ metadata: {
73
+ category: "draw",
74
+ description: "Draw a card from your deck",
75
+ },
76
+ };
77
+
78
+ expect(moveDef.metadata?.description).toBe("Draw a card from your deck");
79
+ });
80
+ });
81
+
82
+ describe("MoveReducer", () => {
83
+ it("should update state using Immer draft", () => {
84
+ const reducer: MoveReducer<TestGameState> = (draft, context) => {
85
+ draft.players[context.playerId].mana -= 2;
86
+ };
87
+
88
+ const context: MoveContext = createMockContext({
89
+ playerId: player1,
90
+ params: {},
91
+ });
92
+
93
+ const nextState = produce(initialState, (draft) => {
94
+ reducer(draft, context);
95
+ });
96
+
97
+ expect(nextState.players[player1].mana).toBe(1);
98
+ expect(initialState.players[player1].mana).toBe(3); // Original unchanged
99
+ });
100
+
101
+ it("should access context data in reducer", () => {
102
+ const reducer: MoveReducer<TestGameState> = (draft, context) => {
103
+ if (context.targets?.[0]) {
104
+ const targetId = context.targets[0][0] as PlayerId;
105
+ draft.players[targetId].life -= 3;
106
+ }
107
+ };
108
+
109
+ const context: MoveContext = createMockContext({
110
+ playerId: player1,
111
+ params: {},
112
+ targets: [[player2]],
113
+ });
114
+
115
+ const nextState = produce(initialState, (draft) => {
116
+ reducer(draft, context);
117
+ });
118
+
119
+ expect(nextState.players[player2].life).toBe(17);
120
+ });
121
+
122
+ it("should use source card from context", () => {
123
+ const reducer: MoveReducer<TestGameState> = (draft, context) => {
124
+ if (context.sourceCardId) {
125
+ const card = draft.cards[context.sourceCardId];
126
+ draft.players[context.playerId].mana -= card.cost;
127
+ }
128
+ };
129
+
130
+ const context: MoveContext = createMockContext({
131
+ playerId: player1,
132
+ params: {},
133
+ sourceCardId: card1,
134
+ });
135
+
136
+ const nextState = produce(initialState, (draft) => {
137
+ reducer(draft, context);
138
+ });
139
+
140
+ expect(nextState.players[player1].mana).toBe(2); // 3 - 1 (card cost)
141
+ });
142
+ });
143
+
144
+ describe("MoveCondition", () => {
145
+ it("should validate move is legal before execution", () => {
146
+ const condition: MoveCondition<TestGameState> = (state, context) => {
147
+ return state.players[context.playerId].mana >= 2;
148
+ };
149
+
150
+ expect(
151
+ condition(
152
+ initialState,
153
+ createMockContext({ playerId: player1, params: {} }),
154
+ ),
155
+ ).toBe(true);
156
+
157
+ const lowManaState = produce(initialState, (draft) => {
158
+ draft.players[player1].mana = 1;
159
+ });
160
+ expect(
161
+ condition(
162
+ lowManaState,
163
+ createMockContext({ playerId: player1, params: {} }),
164
+ ),
165
+ ).toBe(false);
166
+ });
167
+
168
+ it("should access context in condition check", () => {
169
+ const condition: MoveCondition<TestGameState> = (state, context) => {
170
+ return state.currentPlayer === context.playerId;
171
+ };
172
+
173
+ expect(
174
+ condition(
175
+ initialState,
176
+ createMockContext({ playerId: player1, params: {} }),
177
+ ),
178
+ ).toBe(true);
179
+ expect(
180
+ condition(
181
+ initialState,
182
+ createMockContext({ playerId: player2, params: {} }),
183
+ ),
184
+ ).toBe(false);
185
+ });
186
+
187
+ it("should validate based on targets", () => {
188
+ const condition: MoveCondition<TestGameState> = (_state, context) => {
189
+ if (!context.targets?.[0]) return false;
190
+ const targetId = context.targets[0][0] as PlayerId;
191
+ return targetId !== context.playerId; // Can't target self
192
+ };
193
+
194
+ expect(
195
+ condition(
196
+ initialState,
197
+ createMockContext({
198
+ playerId: player1,
199
+ params: {},
200
+ targets: [[player2]],
201
+ }),
202
+ ),
203
+ ).toBe(true);
204
+
205
+ expect(
206
+ condition(
207
+ initialState,
208
+ createMockContext({
209
+ playerId: player1,
210
+ params: {},
211
+ targets: [[player1]],
212
+ }),
213
+ ),
214
+ ).toBe(false);
215
+ });
216
+ });
217
+
218
+ describe("MoveResult", () => {
219
+ it("should represent successful move execution", () => {
220
+ const result: MoveResult<TestGameState> = {
221
+ success: true,
222
+ state: initialState,
223
+ };
224
+
225
+ expect(result.success).toBe(true);
226
+ expect(result.state).toBe(initialState);
227
+ expect(result.error).toBeUndefined();
228
+ });
229
+
230
+ it("should represent failed move with error", () => {
231
+ const result: MoveResult<TestGameState> = {
232
+ success: false,
233
+ error: "Not enough mana",
234
+ };
235
+
236
+ expect(result.success).toBe(false);
237
+ expect(result.error).toBe("Not enough mana");
238
+ expect(result.state).toBeUndefined();
239
+ });
240
+
241
+ it("should represent failed move with detailed error info", () => {
242
+ const result: MoveResult<TestGameState> = {
243
+ success: false,
244
+ error: "Invalid target",
245
+ errorCode: "INVALID_TARGET",
246
+ errorContext: { targetId: "card1", reason: "Already destroyed" },
247
+ };
248
+
249
+ expect(result.errorCode).toBe("INVALID_TARGET");
250
+ expect(result.errorContext).toEqual({
251
+ targetId: "card1",
252
+ reason: "Already destroyed",
253
+ });
254
+ });
255
+ });
256
+
257
+ describe("MoveContext", () => {
258
+ it("should provide player ID to move", () => {
259
+ const context: MoveContext = createMockContext({
260
+ playerId: player1,
261
+ params: {},
262
+ });
263
+
264
+ expect(context.playerId).toBe(player1);
265
+ });
266
+
267
+ it("should provide source card ID", () => {
268
+ const context: MoveContext = createMockContext({
269
+ playerId: player1,
270
+ params: {},
271
+ sourceCardId: card1,
272
+ });
273
+
274
+ expect(context.sourceCardId).toBe(card1);
275
+ });
276
+
277
+ it("should provide target selections", () => {
278
+ const context: MoveContext = createMockContext({
279
+ playerId: player1,
280
+ params: {},
281
+ targets: [[card1], [player2]],
282
+ });
283
+
284
+ expect(context.targets).toHaveLength(2);
285
+ expect(context.targets?.[0]).toEqual([card1]);
286
+ expect(context.targets?.[1]).toEqual([player2]);
287
+ });
288
+
289
+ it("should provide additional data", () => {
290
+ const context: MoveContext = createMockContext({
291
+ playerId: player1,
292
+ params: {
293
+ choiceIndex: 2,
294
+ amount: 5,
295
+ },
296
+ });
297
+
298
+ expect(context.params?.choiceIndex).toBe(2);
299
+ expect(context.params?.amount).toBe(5);
300
+ });
301
+
302
+ it("should provide timestamp", () => {
303
+ const now = Date.now();
304
+ const context: MoveContext = createMockContext({
305
+ playerId: player1,
306
+ params: {},
307
+ timestamp: now,
308
+ });
309
+
310
+ expect(context.timestamp).toBe(now);
311
+ });
312
+ });
313
+
314
+ describe("Move Validation Flow", () => {
315
+ it("should execute valid move successfully", () => {
316
+ const moveDef: GameMoveDefinition<TestGameState> = {
317
+ condition: (state: TestGameState, context: MoveContext) =>
318
+ state.players[context.playerId].mana >= 2,
319
+ reducer: (draft: Draft<TestGameState>, context: MoveContext) => {
320
+ draft.players[context.playerId].mana -= 2;
321
+ },
322
+ };
323
+
324
+ const context: MoveContext = createMockContext({
325
+ playerId: player1,
326
+ params: {},
327
+ });
328
+
329
+ // Check condition
330
+ const isValid = moveDef.condition?.(initialState, context);
331
+ expect(isValid).toBe(true);
332
+
333
+ // Execute if valid
334
+ if (isValid) {
335
+ const nextState = produce(initialState, (draft) => {
336
+ moveDef.reducer(draft, context);
337
+ });
338
+
339
+ expect(nextState.players[player1].mana).toBe(1);
340
+ }
341
+ });
342
+
343
+ it("should reject invalid move before execution", () => {
344
+ const moveDef: GameMoveDefinition<TestGameState> = {
345
+ condition: (state: TestGameState, context: MoveContext) =>
346
+ state.players[context.playerId].mana >= 5,
347
+ reducer: (draft: Draft<TestGameState>, context: MoveContext) => {
348
+ draft.players[context.playerId].mana -= 5;
349
+ },
350
+ };
351
+
352
+ const context: MoveContext = createMockContext({
353
+ playerId: player1,
354
+ params: {},
355
+ });
356
+
357
+ const isValid = moveDef.condition?.(initialState, context);
358
+ expect(isValid).toBe(false);
359
+
360
+ // Reducer should NOT be called when invalid
361
+ });
362
+
363
+ it("should handle moves without conditions", () => {
364
+ const moveDef: GameMoveDefinition<TestGameState> = {
365
+ reducer: (_draft: Draft<TestGameState>) => {
366
+ // No-op move
367
+ },
368
+ };
369
+
370
+ const context: MoveContext = createMockContext({
371
+ playerId: player1,
372
+ params: {},
373
+ });
374
+
375
+ // No condition means always valid
376
+ const isValid = moveDef.condition?.(initialState, context) ?? true;
377
+ expect(isValid).toBe(true);
378
+ });
379
+ });
380
+ });