@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,366 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import type { GameDefinition } from "../../game-definition/game-definition";
3
+ import type { GameMoveDefinitions } from "../../game-definition/move-definitions";
4
+ import { createPlayerId } from "../../types";
5
+ import { RuleEngine } from "../rule-engine";
6
+
7
+ /**
8
+ * Task 11: Rule Engine Core Tests
9
+ *
10
+ * Tests verify the RuleEngine integrates all systems:
11
+ * - GameDefinition initialization
12
+ * - State management
13
+ * - Move execution with validation
14
+ * - Player views
15
+ * - History tracking
16
+ * - Patch generation
17
+ * - RNG integration
18
+ * - Flow orchestration
19
+ */
20
+
21
+ // Test game state
22
+ type TestGameState = {
23
+ players: Array<{ id: string; name: string; score: number; hand: string[] }>;
24
+ currentPlayerIndex: number;
25
+ deck: string[];
26
+ phase: "setup" | "draw" | "play" | "ended";
27
+ turnNumber: number;
28
+ winner?: string;
29
+ };
30
+
31
+ // Test moves
32
+ type TestMoves = {
33
+ drawCard: Record<string, never>;
34
+ playCard: { cardId: string };
35
+ nextPhase: Record<string, never>;
36
+ endGame: { winnerId: string };
37
+ };
38
+
39
+ describe("RuleEngine - Constructor", () => {
40
+ describe("Task 11.1, 11.2: Constructor and Initialization", () => {
41
+ it("should initialize with GameDefinition", () => {
42
+ const moves: GameMoveDefinitions<TestGameState, TestMoves> = {
43
+ drawCard: {
44
+ reducer: (draft) => {
45
+ const player = draft.players[draft.currentPlayerIndex];
46
+ if (player && draft.deck.length > 0) {
47
+ const card = draft.deck.pop();
48
+ if (card) {
49
+ player.hand.push(card);
50
+ }
51
+ }
52
+ },
53
+ },
54
+ playCard: {
55
+ condition: (state, context) => {
56
+ const player = state.players[state.currentPlayerIndex];
57
+ const cardId = context.params?.cardId as string;
58
+ return player?.hand.includes(cardId) ?? false;
59
+ },
60
+ reducer: (draft, context) => {
61
+ const player = draft.players[draft.currentPlayerIndex];
62
+ const cardId = context.params?.cardId as string;
63
+ if (player && cardId) {
64
+ const index = player.hand.indexOf(cardId);
65
+ if (index >= 0) {
66
+ player.hand.splice(index, 1);
67
+ player.score += 1;
68
+ }
69
+ }
70
+ },
71
+ },
72
+ nextPhase: {
73
+ reducer: (draft) => {
74
+ if (draft.phase === "setup") draft.phase = "draw";
75
+ else if (draft.phase === "draw") draft.phase = "play";
76
+ else if (draft.phase === "play") draft.phase = "ended";
77
+ },
78
+ },
79
+ endGame: {
80
+ reducer: (draft, context) => {
81
+ draft.phase = "ended";
82
+ draft.winner = context.params?.winnerId as string;
83
+ },
84
+ },
85
+ };
86
+
87
+ const gameDef: GameDefinition<TestGameState, TestMoves> = {
88
+ name: "Test Card Game",
89
+ setup: (players) => ({
90
+ players: players.map((p) => ({
91
+ id: p.id,
92
+ name: p.name || "Player",
93
+ score: 0,
94
+ hand: [],
95
+ })),
96
+ currentPlayerIndex: 0,
97
+ deck: ["card1", "card2", "card3", "card4"],
98
+ phase: "setup",
99
+ turnNumber: 1,
100
+ }),
101
+ moves,
102
+ };
103
+
104
+ const players = [
105
+ { id: createPlayerId("p1"), name: "Alice" },
106
+ { id: createPlayerId("p2"), name: "Bob" },
107
+ ];
108
+
109
+ const engine = new RuleEngine(gameDef, players);
110
+
111
+ expect(engine).toBeDefined();
112
+ expect(engine.getState()).toBeDefined();
113
+ });
114
+
115
+ it("should call setup function to initialize state", () => {
116
+ const moves: GameMoveDefinitions<TestGameState, TestMoves> = {
117
+ drawCard: { reducer: () => {} },
118
+ playCard: { reducer: () => {} },
119
+ nextPhase: { reducer: () => {} },
120
+ endGame: { reducer: () => {} },
121
+ };
122
+
123
+ const gameDef: GameDefinition<TestGameState, TestMoves> = {
124
+ name: "Test Game",
125
+ setup: (players) => ({
126
+ players: players.map((p) => ({
127
+ id: p.id,
128
+ name: p.name || "Player",
129
+ score: 0,
130
+ hand: [],
131
+ })),
132
+ currentPlayerIndex: 0,
133
+ deck: ["a", "b", "c"],
134
+ phase: "setup",
135
+ turnNumber: 1,
136
+ }),
137
+ moves,
138
+ };
139
+
140
+ const players = [
141
+ { id: createPlayerId("p1"), name: "Alice" },
142
+ { id: createPlayerId("p2"), name: "Bob" },
143
+ ];
144
+
145
+ const engine = new RuleEngine(gameDef, players);
146
+ const state = engine.getState();
147
+
148
+ expect(state.players).toHaveLength(2);
149
+ expect(state.players[0]?.id).toBe(createPlayerId("p1"));
150
+ expect(state.deck).toEqual(["a", "b", "c"]);
151
+ expect(state.phase).toBe("setup");
152
+ });
153
+
154
+ it("should accept optional RNG seed", () => {
155
+ const moves: GameMoveDefinitions<TestGameState, TestMoves> = {
156
+ drawCard: { reducer: () => {} },
157
+ playCard: { reducer: () => {} },
158
+ nextPhase: { reducer: () => {} },
159
+ endGame: { reducer: () => {} },
160
+ };
161
+
162
+ const gameDef: GameDefinition<TestGameState, TestMoves> = {
163
+ name: "Test Game",
164
+ setup: (players) => ({
165
+ players: players.map((p) => ({
166
+ id: p.id,
167
+ name: p.name || "Player",
168
+ score: 0,
169
+ hand: [],
170
+ })),
171
+ currentPlayerIndex: 0,
172
+ deck: [],
173
+ phase: "setup",
174
+ turnNumber: 1,
175
+ }),
176
+ moves,
177
+ };
178
+
179
+ const players = [
180
+ { id: createPlayerId("p1"), name: "Alice" },
181
+ { id: createPlayerId("p2"), name: "Bob" },
182
+ ];
183
+
184
+ const engine = new RuleEngine(gameDef, players, {
185
+ seed: "test-seed-123",
186
+ });
187
+
188
+ expect(engine).toBeDefined();
189
+ expect(engine.getState()).toBeDefined();
190
+ });
191
+ });
192
+ });
193
+
194
+ describe("RuleEngine - State Access", () => {
195
+ describe("Task 11.3, 11.4: getState Method", () => {
196
+ it("should return current game state", () => {
197
+ const moves: GameMoveDefinitions<TestGameState, TestMoves> = {
198
+ drawCard: { reducer: () => {} },
199
+ playCard: { reducer: () => {} },
200
+ nextPhase: { reducer: () => {} },
201
+ endGame: { reducer: () => {} },
202
+ };
203
+
204
+ const gameDef: GameDefinition<TestGameState, TestMoves> = {
205
+ name: "Test Game",
206
+ setup: (players) => ({
207
+ players: players.map((p) => ({
208
+ id: p.id,
209
+ name: p.name || "Player",
210
+ score: 0,
211
+ hand: [],
212
+ })),
213
+ currentPlayerIndex: 0,
214
+ deck: ["card1", "card2"],
215
+ phase: "setup",
216
+ turnNumber: 1,
217
+ }),
218
+ moves,
219
+ };
220
+
221
+ const players = [
222
+ { id: createPlayerId("p1"), name: "Alice" },
223
+ { id: createPlayerId("p2"), name: "Bob" },
224
+ ];
225
+
226
+ const engine = new RuleEngine(gameDef, players);
227
+ const state = engine.getState();
228
+
229
+ expect(state.players).toHaveLength(2);
230
+ expect(state.deck).toEqual(["card1", "card2"]);
231
+ expect(state.phase).toBe("setup");
232
+ });
233
+
234
+ it("should return immutable state (modifications don't affect engine)", () => {
235
+ const moves: GameMoveDefinitions<TestGameState, TestMoves> = {
236
+ drawCard: { reducer: () => {} },
237
+ playCard: { reducer: () => {} },
238
+ nextPhase: { reducer: () => {} },
239
+ endGame: { reducer: () => {} },
240
+ };
241
+
242
+ const gameDef: GameDefinition<TestGameState, TestMoves> = {
243
+ name: "Test Game",
244
+ setup: (players) => ({
245
+ players: players.map((p) => ({
246
+ id: p.id,
247
+ name: p.name || "Player",
248
+ score: 42,
249
+ hand: [],
250
+ })),
251
+ currentPlayerIndex: 0,
252
+ deck: [],
253
+ phase: "setup",
254
+ turnNumber: 1,
255
+ }),
256
+ moves,
257
+ };
258
+
259
+ const players = [
260
+ { id: createPlayerId("p1"), name: "Alice" },
261
+ { id: createPlayerId("p2"), name: "Bob" },
262
+ ];
263
+
264
+ const engine = new RuleEngine(gameDef, players);
265
+ const state1 = engine.getState();
266
+
267
+ // Try to mutate the returned state
268
+ state1.players[0]!.score = 999;
269
+ state1.deck.push("hacked-card");
270
+
271
+ // Engine state should be unchanged
272
+ const state2 = engine.getState();
273
+ expect(state2.players[0]?.score).toBe(42);
274
+ expect(state2.deck).toEqual([]);
275
+ });
276
+ });
277
+
278
+ describe("Task 11.5, 11.6: getPlayerView Method", () => {
279
+ it("should return full state when no playerView defined", () => {
280
+ const moves: GameMoveDefinitions<TestGameState, TestMoves> = {
281
+ drawCard: { reducer: () => {} },
282
+ playCard: { reducer: () => {} },
283
+ nextPhase: { reducer: () => {} },
284
+ endGame: { reducer: () => {} },
285
+ };
286
+
287
+ const gameDef: GameDefinition<TestGameState, TestMoves> = {
288
+ name: "Test Game",
289
+ setup: (players) => ({
290
+ players: players.map((p) => ({
291
+ id: p.id,
292
+ name: p.name || "Player",
293
+ score: 0,
294
+ hand: [],
295
+ })),
296
+ currentPlayerIndex: 0,
297
+ deck: ["secret1", "secret2"],
298
+ phase: "setup",
299
+ turnNumber: 1,
300
+ }),
301
+ moves,
302
+ };
303
+
304
+ const players = [
305
+ { id: createPlayerId("p1"), name: "Alice" },
306
+ { id: createPlayerId("p2"), name: "Bob" },
307
+ ];
308
+
309
+ const engine = new RuleEngine(gameDef, players);
310
+ const view = engine.getPlayerView(createPlayerId("p1"));
311
+
312
+ expect(view.deck).toEqual(["secret1", "secret2"]);
313
+ });
314
+
315
+ it("should filter state using playerView function", () => {
316
+ const moves: GameMoveDefinitions<TestGameState, TestMoves> = {
317
+ drawCard: { reducer: () => {} },
318
+ playCard: { reducer: () => {} },
319
+ nextPhase: { reducer: () => {} },
320
+ endGame: { reducer: () => {} },
321
+ };
322
+
323
+ const gameDef: GameDefinition<TestGameState, TestMoves> = {
324
+ name: "Test Game",
325
+ setup: (players) => ({
326
+ players: players.map((p, i) => ({
327
+ id: p.id,
328
+ name: p.name || "Player",
329
+ score: i * 10,
330
+ hand: [`hand-${i}-1`, `hand-${i}-2`],
331
+ })),
332
+ currentPlayerIndex: 0,
333
+ deck: ["secret1", "secret2"],
334
+ phase: "setup",
335
+ turnNumber: 1,
336
+ }),
337
+ moves,
338
+ playerView: (state, playerId) => ({
339
+ ...state,
340
+ // Hide other players' hands
341
+ players: state.players.map((p) => ({
342
+ ...p,
343
+ hand: p.id === playerId ? p.hand : [],
344
+ })),
345
+ // Hide deck contents
346
+ deck: [],
347
+ }),
348
+ };
349
+
350
+ const players = [
351
+ { id: createPlayerId("p1"), name: "Alice" },
352
+ { id: createPlayerId("p2"), name: "Bob" },
353
+ ];
354
+
355
+ const engine = new RuleEngine(gameDef, players);
356
+ const p1View = engine.getPlayerView(createPlayerId("p1"));
357
+
358
+ // P1 should see their own hand
359
+ expect(p1View.players[0]?.hand).toEqual(["hand-0-1", "hand-0-2"]);
360
+ // But not P2's hand
361
+ expect(p1View.players[1]?.hand).toEqual([]);
362
+ // Deck should be hidden
363
+ expect(p1View.deck).toEqual([]);
364
+ });
365
+ });
366
+ });
@@ -0,0 +1,14 @@
1
+ export {
2
+ type ClientState,
3
+ MultiplayerEngine,
4
+ type MultiplayerEngineOptions,
5
+ type MultiplayerMode,
6
+ type PatchBroadcast,
7
+ } from "./multiplayer-engine";
8
+
9
+ export {
10
+ type MoveExecutionResult,
11
+ type ReplayHistoryEntry,
12
+ RuleEngine,
13
+ type RuleEngineOptions,
14
+ } from "./rule-engine";