@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,331 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { produce } from "immer";
3
+ import { createMockContext } from "../testing/test-context-factory";
4
+ import type { CardId, PlayerId } from "../types";
5
+ import { createCardId, createPlayerId } from "../types";
6
+ import { createMove } from "./create-move";
7
+ import type { MoveContext } from "./move-system";
8
+
9
+ describe("createMove Helper", () => {
10
+ /**
11
+ * Test game state structure
12
+ */
13
+ type TestGameState = {
14
+ players: Record<PlayerId, { health: number; mana: number }>;
15
+ cards: Record<CardId, { name: string; damage: number }>;
16
+ };
17
+
18
+ /**
19
+ * Test move parameter types - using a record type like real game implementations
20
+ */
21
+ type TestMoveParams = {
22
+ // Move with specific params
23
+ quest: { cardId: CardId };
24
+ // Move with multiple params
25
+ challenge: { attackerId: CardId; defenderId: CardId };
26
+ // Move with optional params
27
+ playCard: { cardId: CardId; alternativeCost?: number };
28
+ // Move with no params
29
+ pass: Record<string, never>;
30
+ };
31
+
32
+ const player1 = createPlayerId("p1");
33
+ const player2 = createPlayerId("p2");
34
+ const card1 = createCardId("card1");
35
+ const card2 = createCardId("card2");
36
+
37
+ const initialState: TestGameState = {
38
+ players: {
39
+ [player1]: { health: 20, mana: 5 },
40
+ [player2]: { health: 20, mana: 5 },
41
+ },
42
+ cards: {
43
+ [card1]: { name: "Character A", damage: 0 },
44
+ [card2]: { name: "Character B", damage: 0 },
45
+ },
46
+ };
47
+
48
+ describe("Type Narrowing", () => {
49
+ it("should narrow params for single-param move", () => {
50
+ // Create a move with narrowed params using createMove
51
+ const questMove = createMove<
52
+ TestGameState,
53
+ TestMoveParams,
54
+ "quest" // ✅ Narrows to TestMoveParams["quest"]
55
+ >({
56
+ condition: (state, context) => {
57
+ // ✅ context.params is { cardId: CardId }
58
+ const { cardId } = context.params;
59
+ return !!state.cards[cardId];
60
+ },
61
+ reducer: (draft, context) => {
62
+ // ✅ context.params is { cardId: CardId }
63
+ const { cardId } = context.params;
64
+ const card = draft.cards[cardId];
65
+ if (card) {
66
+ card.damage += 1;
67
+ }
68
+ },
69
+ });
70
+
71
+ // Verify the move works correctly
72
+ const context: MoveContext<TestMoveParams["quest"]> = createMockContext({
73
+ playerId: player1,
74
+ params: { cardId: card1 },
75
+ });
76
+
77
+ // Test condition
78
+ expect(questMove.condition?.(initialState, context)).toBe(true);
79
+
80
+ // Test reducer
81
+ const nextState = produce(initialState, (draft) => {
82
+ questMove.reducer(draft, context);
83
+ });
84
+
85
+ expect(nextState.cards[card1].damage).toBe(1);
86
+ });
87
+
88
+ it("should narrow params for multi-param move", () => {
89
+ // Create a move with multiple params
90
+ const challengeMove = createMove<
91
+ TestGameState,
92
+ TestMoveParams,
93
+ "challenge" // ✅ Narrows to TestMoveParams["challenge"]
94
+ >({
95
+ condition: (state, context) => {
96
+ // ✅ context.params is { attackerId: CardId; defenderId: CardId }
97
+ const { attackerId, defenderId } = context.params;
98
+ return !!state.cards[attackerId] && !!state.cards[defenderId];
99
+ },
100
+ reducer: (draft, context) => {
101
+ // ✅ context.params is { attackerId: CardId; defenderId: CardId }
102
+ const { attackerId, defenderId } = context.params;
103
+ const attacker = draft.cards[attackerId];
104
+ const defender = draft.cards[defenderId];
105
+ if (attacker && defender) {
106
+ defender.damage += 3;
107
+ }
108
+ },
109
+ });
110
+
111
+ const context: MoveContext<TestMoveParams["challenge"]> =
112
+ createMockContext({
113
+ playerId: player1,
114
+ params: { attackerId: card1, defenderId: card2 },
115
+ });
116
+
117
+ expect(challengeMove.condition?.(initialState, context)).toBe(true);
118
+
119
+ const nextState = produce(initialState, (draft) => {
120
+ challengeMove.reducer(draft, context);
121
+ });
122
+
123
+ expect(nextState.cards[card2].damage).toBe(3);
124
+ });
125
+
126
+ it("should narrow params for move with optional fields", () => {
127
+ // Create a move with optional params
128
+ const playCardMove = createMove<
129
+ TestGameState,
130
+ TestMoveParams,
131
+ "playCard" // ✅ Narrows to TestMoveParams["playCard"]
132
+ >({
133
+ reducer: (draft, context) => {
134
+ // ✅ context.params is { cardId: CardId; alternativeCost?: number }
135
+ const { cardId, alternativeCost } = context.params;
136
+ const player = draft.players[context.playerId];
137
+ const cost = alternativeCost ?? 3;
138
+ player.mana -= cost;
139
+
140
+ // Use cardId (verify it's available)
141
+ const card = draft.cards[cardId];
142
+ if (card) {
143
+ card.damage = 0; // Reset damage on play
144
+ }
145
+ },
146
+ });
147
+
148
+ // Test with alternativeCost
149
+ const contextWithAlt: MoveContext<TestMoveParams["playCard"]> =
150
+ createMockContext({
151
+ playerId: player1,
152
+ params: { cardId: card1, alternativeCost: 2 },
153
+ });
154
+
155
+ const nextState1 = produce(initialState, (draft) => {
156
+ playCardMove.reducer(draft, contextWithAlt);
157
+ });
158
+
159
+ expect(nextState1.players[player1].mana).toBe(3); // 5 - 2
160
+
161
+ // Test without alternativeCost
162
+ const contextWithoutAlt: MoveContext<TestMoveParams["playCard"]> =
163
+ createMockContext({
164
+ playerId: player1,
165
+ params: { cardId: card1 }, // alternativeCost is optional
166
+ });
167
+
168
+ const nextState2 = produce(initialState, (draft) => {
169
+ playCardMove.reducer(draft, contextWithoutAlt);
170
+ });
171
+
172
+ expect(nextState2.players[player1].mana).toBe(2); // 5 - 3 (default)
173
+ });
174
+
175
+ it("should narrow params for no-param move", () => {
176
+ // Create a move with no params
177
+ const passMove = createMove<
178
+ TestGameState,
179
+ TestMoveParams,
180
+ "pass" // ✅ Narrows to TestMoveParams["pass"] (empty object)
181
+ >({
182
+ reducer: (draft, context) => {
183
+ // ✅ context.params is {} (empty object)
184
+ const player = draft.players[context.playerId];
185
+ player.mana = 10; // Reset mana on pass
186
+ },
187
+ });
188
+
189
+ const context: MoveContext<TestMoveParams["pass"]> = createMockContext({
190
+ playerId: player1,
191
+ params: {},
192
+ });
193
+
194
+ const nextState = produce(initialState, (draft) => {
195
+ passMove.reducer(draft, context);
196
+ });
197
+
198
+ expect(nextState.players[player1].mana).toBe(10);
199
+ });
200
+ });
201
+
202
+ describe("Runtime Behavior", () => {
203
+ it("should return the definition unchanged", () => {
204
+ const definition = {
205
+ condition: (state: TestGameState) => state.players[player1].mana >= 1,
206
+ reducer: (draft: any) => {
207
+ draft.players[player1].mana -= 1;
208
+ },
209
+ };
210
+
211
+ const result = createMove<TestGameState, TestMoveParams, "quest">(
212
+ definition,
213
+ );
214
+
215
+ // Runtime behavior: returns the same object
216
+ expect(result).toBe(definition);
217
+ expect(result.condition).toBe(definition.condition);
218
+ expect(result.reducer).toBe(definition.reducer);
219
+ });
220
+
221
+ it("should work with move definitions without conditions", () => {
222
+ const definition = {
223
+ reducer: (draft: any) => {
224
+ draft.players[player1].health -= 1;
225
+ },
226
+ };
227
+
228
+ const result = createMove<TestGameState, TestMoveParams, "pass">(
229
+ definition,
230
+ );
231
+
232
+ expect(result).toBe(definition);
233
+ expect(result.condition).toBeUndefined();
234
+ });
235
+
236
+ it("should work with move definitions with metadata", () => {
237
+ const definition = {
238
+ reducer: (draft: any) => {
239
+ draft.players[player1].health += 5;
240
+ },
241
+ metadata: {
242
+ category: "healing",
243
+ tags: ["buff"],
244
+ },
245
+ };
246
+
247
+ const result = createMove<TestGameState, TestMoveParams, "pass">(
248
+ definition,
249
+ );
250
+
251
+ expect(result.metadata?.category).toBe("healing");
252
+ expect(result.metadata?.tags).toEqual(["buff"]);
253
+ });
254
+ });
255
+
256
+ describe("Integration with GameMoveDefinitions", () => {
257
+ it("should work correctly when aggregated into move map", () => {
258
+ // Create individual moves using createMove
259
+ const quest = createMove<TestGameState, TestMoveParams, "quest">({
260
+ reducer: (draft, context) => {
261
+ const { cardId } = context.params;
262
+ draft.cards[cardId].damage += 1;
263
+ },
264
+ });
265
+
266
+ const challenge = createMove<TestGameState, TestMoveParams, "challenge">({
267
+ reducer: (draft, context) => {
268
+ const { attackerId, defenderId } = context.params;
269
+ draft.cards[defenderId].damage += 2;
270
+ },
271
+ });
272
+
273
+ const pass = createMove<TestGameState, TestMoveParams, "pass">({
274
+ reducer: (draft, context) => {
275
+ draft.players[context.playerId].mana = 10;
276
+ },
277
+ });
278
+
279
+ // Aggregate into a move map (simulating GameMoveDefinitions)
280
+ const moves = {
281
+ quest,
282
+ challenge,
283
+ pass,
284
+ };
285
+
286
+ // Verify all moves are accessible and work correctly
287
+ expect(moves.quest).toBe(quest);
288
+ expect(moves.challenge).toBe(challenge);
289
+ expect(moves.pass).toBe(pass);
290
+
291
+ // Test executing quest move
292
+ const questContext: MoveContext<TestMoveParams["quest"]> =
293
+ createMockContext({
294
+ playerId: player1,
295
+ params: { cardId: card1 },
296
+ });
297
+
298
+ const state1 = produce(initialState, (draft) => {
299
+ moves.quest.reducer(draft, questContext);
300
+ });
301
+
302
+ expect(state1.cards[card1].damage).toBe(1);
303
+
304
+ // Test executing challenge move
305
+ const challengeContext: MoveContext<TestMoveParams["challenge"]> =
306
+ createMockContext({
307
+ playerId: player1,
308
+ params: { attackerId: card1, defenderId: card2 },
309
+ });
310
+
311
+ const state2 = produce(state1, (draft) => {
312
+ moves.challenge.reducer(draft, challengeContext);
313
+ });
314
+
315
+ expect(state2.cards[card2].damage).toBe(2);
316
+
317
+ // Test executing pass move
318
+ const passContext: MoveContext<TestMoveParams["pass"]> =
319
+ createMockContext({
320
+ playerId: player1,
321
+ params: {},
322
+ });
323
+
324
+ const state3 = produce(state2, (draft) => {
325
+ moves.pass.reducer(draft, passContext);
326
+ });
327
+
328
+ expect(state3.players[player1].mana).toBe(10);
329
+ });
330
+ });
331
+ });
@@ -0,0 +1,64 @@
1
+ import type { GameMoveDefinition } from "../game-definition/move-definitions";
2
+
3
+ /**
4
+ * Create a type-safe move definition with proper parameter narrowing
5
+ *
6
+ * This helper ensures that move parameters are correctly narrowed to the specific
7
+ * move's parameter type, avoiding TypeScript's limitations with module boundary
8
+ * type inference.
9
+ *
10
+ * @template TState - Game state type
11
+ * @template TMoves - Record of move names to parameter types
12
+ * @template K - Specific move name (keyof TMoves)
13
+ * @template TCardMeta - Card metadata type
14
+ * @template TCardDefinition - Card definition type
15
+ *
16
+ * @param definition - The move definition (reducer, condition, metadata)
17
+ * @returns The same move definition with proper type narrowing
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * type GameMoves = {
22
+ * quest: { cardId: string };
23
+ * playCard: { cardId: string; cost: number };
24
+ * };
25
+ *
26
+ * export const quest = createMove<GameState, GameMoves, "quest", CardMeta>({
27
+ * condition: (state, context) => {
28
+ * const { cardId } = context.params; // ✅ Typed as { cardId: string }
29
+ * return true;
30
+ * },
31
+ * reducer: (draft, context) => {
32
+ * const { cardId } = context.params; // ✅ Typed as { cardId: string }
33
+ * // Implementation...
34
+ * }
35
+ * });
36
+ * ```
37
+ *
38
+ * **Why this is needed**:
39
+ * Without this helper, when you export a move with an explicit type annotation:
40
+ * ```typescript
41
+ * export const quest: MoveDefinition<GameState, GameMoves, CardMeta> = {...}
42
+ * ```
43
+ * TypeScript sees `context.params` as the full `GameMoves` type union, not just
44
+ * the narrowed `GameMoves["quest"]` type.
45
+ *
46
+ * This helper uses TypeScript's generic inference to properly narrow the type
47
+ * at the definition site, avoiding module boundary issues.
48
+ */
49
+ export function createMove<
50
+ TState,
51
+ TMoves extends Record<string, any>,
52
+ K extends keyof TMoves,
53
+ TCardMeta = any,
54
+ TCardDefinition = any,
55
+ >(
56
+ definition: GameMoveDefinition<
57
+ TState,
58
+ TMoves[K], // ✅ Narrow to specific move's params
59
+ TCardMeta,
60
+ TCardDefinition
61
+ >,
62
+ ): GameMoveDefinition<TState, TMoves[K], TCardMeta, TCardDefinition> {
63
+ return definition;
64
+ }
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Move Enumeration System
3
+ *
4
+ * Provides types and interfaces for enumerating all possible moves
5
+ * with their valid parameters. This enables AI agents and UI components
6
+ * to discover available actions at any game state.
7
+ *
8
+ * @packageDocumentation
9
+ */
10
+
11
+ import type { CardOperations } from "../operations/card-operations";
12
+ import type { CardRegistry } from "../operations/card-registry";
13
+ import type { CounterOperations } from "../operations/counter-operations";
14
+ import type { GameOperations } from "../operations/game-operations";
15
+ import type { ZoneOperations } from "../operations/zone-operations";
16
+ import type { SeededRNG } from "../rng/seeded-rng";
17
+ import type { CardId, PlayerId } from "../types";
18
+
19
+ /**
20
+ * Enumerated Move Result
21
+ *
22
+ * Represents a single valid move with all its parameter values.
23
+ * Returned by RuleEngine.enumerateMoves() for each possible move + parameter combination.
24
+ *
25
+ * @template TParams - Move-specific parameter type
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const moves = engine.enumerateMoves(playerId, { validOnly: true });
30
+ * for (const move of moves) {
31
+ * console.log(`${move.moveId}:`, move.params);
32
+ * if (move.isValid) {
33
+ * // Execute the move
34
+ * engine.executeMove(move.moveId, {
35
+ * playerId: move.playerId,
36
+ * params: move.params
37
+ * });
38
+ * }
39
+ * }
40
+ * ```
41
+ */
42
+ export type EnumeratedMove<TParams = unknown> = {
43
+ /** Move identifier */
44
+ moveId: string;
45
+
46
+ /** Player who can execute this move */
47
+ playerId: PlayerId;
48
+
49
+ /** Fully populated parameters for this move */
50
+ params: TParams;
51
+
52
+ /** Optional source card for this move */
53
+ sourceCardId?: CardId;
54
+
55
+ /** Optional targets for this move */
56
+ targets?: string[][];
57
+
58
+ /** Whether this move is currently valid (passed condition check) */
59
+ isValid: boolean;
60
+
61
+ /** If not valid, reason for failure */
62
+ validationError?: {
63
+ reason: string;
64
+ errorCode: string;
65
+ context?: Record<string, unknown>;
66
+ };
67
+
68
+ /** Optional metadata for UI/AI consumption */
69
+ metadata?: {
70
+ displayName?: string;
71
+ description?: string;
72
+ category?: string;
73
+ priority?: number;
74
+ [key: string]: unknown;
75
+ };
76
+ };
77
+
78
+ /**
79
+ * Enumeration Context
80
+ *
81
+ * Provided to enumerator functions, contains all information needed
82
+ * to discover valid parameters. Similar to MoveContext but focused on
83
+ * read-only operations for parameter discovery.
84
+ *
85
+ * @template TCardMeta - Card metadata type
86
+ * @template TCardDefinition - Card definition type
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * const enumerator: MoveEnumerator<GameState, PlayCardParams> = (state, context) => {
91
+ * // Get all cards in player's hand
92
+ * const handCards = context.zones.getCardsInZone('hand', context.playerId);
93
+ *
94
+ * // Generate parameter combination for each card
95
+ * return handCards.map(cardId => ({ cardId }));
96
+ * };
97
+ * ```
98
+ */
99
+ export type MoveEnumerationContext<
100
+ TCardMeta = unknown,
101
+ TCardDefinition = unknown,
102
+ > = {
103
+ /** Player to enumerate moves for */
104
+ playerId: PlayerId;
105
+
106
+ /** Zone operations for querying card locations */
107
+ zones: ZoneOperations;
108
+
109
+ /** Card operations for querying card state */
110
+ cards: CardOperations<TCardMeta>;
111
+
112
+ /** Game operations for game-level state */
113
+ game: GameOperations;
114
+
115
+ /** Counter operations for querying card counters/flags */
116
+ counters: CounterOperations;
117
+
118
+ /** Card registry for static card definitions */
119
+ registry?: CardRegistry<TCardDefinition>;
120
+
121
+ /** Flow state (turn, phase, segment) */
122
+ flow?: {
123
+ currentPhase?: string;
124
+ currentSegment?: string;
125
+ turn: number;
126
+ currentPlayer?: PlayerId;
127
+ isFirstTurn: boolean;
128
+ };
129
+
130
+ /** RNG for deterministic enumeration if needed */
131
+ rng: SeededRNG;
132
+ };
133
+
134
+ /**
135
+ * Move Enumerator Function
136
+ *
137
+ * Game-provided function that generates all possible parameter combinations
138
+ * for a given move. Returns an array of parameter objects.
139
+ *
140
+ * Each parameter object returned will be validated against the move's condition
141
+ * and included in the enumeration results.
142
+ *
143
+ * @template TGameState - Game state type
144
+ * @template TParams - Move-specific parameter type
145
+ * @template TCardMeta - Card metadata type
146
+ * @template TCardDefinition - Card definition type
147
+ *
148
+ * @param state - Current game state (readonly)
149
+ * @param context - Enumeration context with player, operations, etc.
150
+ * @returns Array of possible parameter combinations
151
+ *
152
+ * @example
153
+ * ```typescript
154
+ * // Simple card play enumerator
155
+ * const playCardEnumerator: MoveEnumerator<GameState, PlayCardParams> = (state, context) => {
156
+ * const handCards = context.zones.getCardsInZone('hand', context.playerId);
157
+ * return handCards.map(cardId => ({ cardId }));
158
+ * };
159
+ *
160
+ * // Attack enumerator with multiple targets
161
+ * const attackEnumerator: MoveEnumerator<GameState, AttackParams> = (state, context) => {
162
+ * const results: AttackParams[] = [];
163
+ * const attackers = context.zones.getCardsInZone('field', context.playerId);
164
+ *
165
+ * for (const attackerId of attackers) {
166
+ * const targets = getValidTargets(state, attackerId);
167
+ * for (const targetId of targets) {
168
+ * results.push({ attackerId, targetId });
169
+ * }
170
+ * }
171
+ *
172
+ * return results;
173
+ * };
174
+ * ```
175
+ */
176
+ export type MoveEnumerator<
177
+ TGameState,
178
+ TParams = unknown,
179
+ TCardMeta = unknown,
180
+ TCardDefinition = unknown,
181
+ > = (
182
+ state: TGameState,
183
+ context: MoveEnumerationContext<TCardMeta, TCardDefinition>,
184
+ ) => TParams[];
185
+
186
+ /**
187
+ * Move Enumeration Options
188
+ *
189
+ * Configuration for enumeration behavior.
190
+ *
191
+ * @example
192
+ * ```typescript
193
+ * // Get only valid moves
194
+ * const validMoves = engine.enumerateMoves(playerId, {
195
+ * validOnly: true
196
+ * });
197
+ *
198
+ * // Get all moves with metadata
199
+ * const allMoves = engine.enumerateMoves(playerId, {
200
+ * validOnly: false,
201
+ * includeMetadata: true
202
+ * });
203
+ *
204
+ * // Enumerate specific moves only
205
+ * const attackMoves = engine.enumerateMoves(playerId, {
206
+ * moveIds: ['attack', 'special-attack'],
207
+ * validOnly: true
208
+ * });
209
+ *
210
+ * // Limit results per move
211
+ * const limitedMoves = engine.enumerateMoves(playerId, {
212
+ * maxPerMove: 10 // Max 10 parameter combinations per move
213
+ * });
214
+ * ```
215
+ */
216
+ export type MoveEnumerationOptions = {
217
+ /** Only return valid moves (passed condition check). Default: false */
218
+ validOnly?: boolean;
219
+
220
+ /** Include metadata in results. Default: false */
221
+ includeMetadata?: boolean;
222
+
223
+ /** Filter to specific move IDs. Default: all moves */
224
+ moveIds?: string[];
225
+
226
+ /** Maximum number of results per move (optional limit) */
227
+ maxPerMove?: number;
228
+ };