@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,462 @@
1
+ import type { FlowDefinition } from "../flow";
2
+ import type { GameDefinition, GameMoveDefinitions } from "../game-definition";
3
+ import { standardMoves } from "../moves/standard-moves";
4
+ import type { CardId, PlayerId, ZoneId } from "../types";
5
+ import type { CardZoneConfig } from "../zones";
6
+
7
+ // Mock Alpha Clash game state - MASSIVELY SIMPLIFIED!
8
+ // The engine now handles: phase, turn, currentPlayer, setupStep, mulliganOffered
9
+ type TestGameState = {
10
+ contenderHealth: Record<string, number>;
11
+ resourcesAvailable: Record<string, number>;
12
+ clashInProgress: boolean;
13
+ };
14
+
15
+ type TestMoves = {
16
+ // Setup moves
17
+ placeContender: { playerId: PlayerId };
18
+ drawInitialHand: { playerId: PlayerId };
19
+ decideMulligan: { playerId: PlayerId; keepHand: boolean };
20
+ chooseFirstPlayer: { playerId: PlayerId };
21
+ transitionToPlay: Record<string, never>;
22
+ // Regular game moves
23
+ drawCard: { playerId: PlayerId };
24
+ playResource: { playerId: PlayerId; cardId: CardId };
25
+ playClashCard: { playerId: PlayerId; cardId: CardId };
26
+ playAction: { playerId: PlayerId; cardId: CardId };
27
+ setTrap: { playerId: PlayerId; cardId: CardId };
28
+ initiateClash: { playerId: PlayerId; attackerIds: CardId[] };
29
+ declareObstructors: {
30
+ playerId: PlayerId;
31
+ obstructorAssignments: Record<string, string>;
32
+ };
33
+ playClashBuff: { playerId: PlayerId; cardId: CardId };
34
+ // Standard moves provided by engine
35
+ pass: { playerId: PlayerId };
36
+ concede: { playerId: PlayerId };
37
+ };
38
+
39
+ // Alpha Clash move definitions
40
+ const alphaClashMoves: GameMoveDefinitions<TestGameState, TestMoves> = {
41
+ // Setup moves - now using engine utilities!
42
+ placeContender: {
43
+ reducer: (draft, context) => {
44
+ // NO MORE: if (!zones) checks - zones is guaranteed by engine!
45
+ const { zones } = context;
46
+ const playerId = context.params.playerId as PlayerId;
47
+ const deckCards = zones.getCardsInZone("deck" as ZoneId, playerId);
48
+
49
+ // Move first card (assumed to be Contender) to Contender zone
50
+ if (deckCards.length > 0) {
51
+ const contenderCardId = deckCards[0];
52
+ if (contenderCardId) {
53
+ zones.moveCard({
54
+ cardId: contenderCardId,
55
+ targetZoneId: "contender" as ZoneId,
56
+ position: "bottom",
57
+ });
58
+ }
59
+ }
60
+
61
+ // NO MORE: draft.setupStep - engine handles this!
62
+ },
63
+ },
64
+
65
+ drawInitialHand: {
66
+ reducer: (draft, context) => {
67
+ const { zones } = context;
68
+ const playerId = context.params.playerId as PlayerId;
69
+
70
+ // BEFORE: Manual shuffle + loop to draw cards (11 lines)
71
+ // AFTER: Use engine's high-level drawCards utility (3 lines!)
72
+ zones.shuffleZone("deck" as ZoneId, playerId);
73
+ zones.drawCards({
74
+ from: "deck" as ZoneId,
75
+ to: "hand" as ZoneId,
76
+ count: 8, // Alpha Clash standard starting hand size
77
+ playerId,
78
+ });
79
+
80
+ // NO MORE: draft.setupStep, draft.mulliganOffered - engine handles this!
81
+ },
82
+ },
83
+
84
+ decideMulligan: {
85
+ reducer: (draft, context) => {
86
+ const { zones } = context;
87
+ const playerId = context.params.playerId as PlayerId;
88
+ const keepHand = context.params.keepHand;
89
+
90
+ if (!keepHand) {
91
+ // BEFORE: Manual loop to return cards, shuffle, redraw (25 lines)
92
+ // AFTER: Use engine's mulligan utility (1 line!)
93
+ zones.mulligan({
94
+ hand: "hand" as ZoneId,
95
+ deck: "deck" as ZoneId,
96
+ drawCount: 8,
97
+ playerId,
98
+ });
99
+ }
100
+
101
+ // NO MORE: draft.mulliganOffered - engine handles this with trackers!
102
+ },
103
+ },
104
+
105
+ chooseFirstPlayer: {
106
+ reducer: (draft, context) => {
107
+ // NO MORE: draft.currentPlayer - engine handles this via flow!
108
+ // NO MORE: draft.firstPlayerChosen - engine provides context.flow.isFirstTurn!
109
+ // In a full implementation, we'd signal to the engine who goes first
110
+ // For now, this is just a placeholder
111
+ },
112
+ },
113
+
114
+ transitionToPlay: {
115
+ reducer: (draft, context) => {
116
+ // NO MORE: draft.setupStep, draft.phase, draft.turn - engine handles ALL of this!
117
+ // The game is ready to start - no manual state management needed!
118
+ },
119
+ },
120
+
121
+ // Regular game moves (enhanced with engine features)
122
+ drawCard: {
123
+ condition: (state, context) => {
124
+ // Use engine's flow context to check if it's the first turn
125
+ const isFirstTurn = context.flow?.isFirstTurn ?? false;
126
+ const isFirstPlayer = context.flow?.currentPlayer === context.playerId;
127
+
128
+ // First player skips draw on first turn (rule 103.7a)
129
+ if (isFirstTurn && isFirstPlayer) {
130
+ return false;
131
+ }
132
+
133
+ return true;
134
+ },
135
+ reducer: (draft, context) => {
136
+ const playerId = context.params.playerId as PlayerId;
137
+
138
+ // Use engine's drawCards utility
139
+ context.zones.drawCards({
140
+ from: "deck" as ZoneId,
141
+ to: "hand" as ZoneId,
142
+ count: 1,
143
+ playerId,
144
+ });
145
+ },
146
+ },
147
+
148
+ playResource: {
149
+ condition: (state, context) => {
150
+ const playerId = context.params.playerId;
151
+
152
+ // BEFORE: state.hasPlayedResourceThisTurn[playerId]
153
+ // AFTER: Use engine's tracker system!
154
+ return !context.trackers?.check(
155
+ "hasPlayedResource",
156
+ playerId as PlayerId,
157
+ );
158
+ },
159
+ reducer: (draft, context) => {
160
+ const playerId = context.params.playerId as PlayerId;
161
+ const cardId = context.params.cardId as CardId;
162
+
163
+ // Move card to resource zone
164
+ context.zones.moveCard({
165
+ cardId,
166
+ targetZoneId: "resource" as ZoneId,
167
+ });
168
+
169
+ // Increment resources
170
+ draft.resourcesAvailable[playerId] =
171
+ (draft.resourcesAvailable[playerId] || 0) + 1;
172
+
173
+ // BEFORE: draft.hasPlayedResourceThisTurn[playerId] = true
174
+ // AFTER: Use engine's tracker system!
175
+ context.trackers?.mark("hasPlayedResource", playerId);
176
+ },
177
+ },
178
+
179
+ playClashCard: {
180
+ reducer: (draft, context) => {
181
+ const cardId = context.params.cardId as CardId;
182
+
183
+ // Move card to clash zone
184
+ context.zones.moveCard({
185
+ cardId,
186
+ targetZoneId: "clash" as ZoneId,
187
+ });
188
+ },
189
+ },
190
+
191
+ playAction: {
192
+ reducer: (draft, context) => {
193
+ const cardId = context.params.cardId as CardId;
194
+
195
+ // Play action, then discard
196
+ context.zones.moveCard({
197
+ cardId,
198
+ targetZoneId: "discard" as ZoneId,
199
+ });
200
+ },
201
+ },
202
+
203
+ setTrap: {
204
+ reducer: (draft, context) => {
205
+ const cardId = context.params.cardId as CardId;
206
+
207
+ // Move card to accessory zone (face-down)
208
+ context.zones.moveCard({
209
+ cardId,
210
+ targetZoneId: "accessory" as ZoneId,
211
+ });
212
+ },
213
+ },
214
+
215
+ initiateClash: {
216
+ reducer: (draft, context) => {
217
+ draft.clashInProgress = true;
218
+ },
219
+ },
220
+
221
+ declareObstructors: {
222
+ reducer: (draft, context) => {
223
+ // Handle obstructor assignments
224
+ },
225
+ },
226
+
227
+ playClashBuff: {
228
+ reducer: (draft, context) => {
229
+ const cardId = context.params.cardId as CardId;
230
+
231
+ // Play buff during clash
232
+ context.zones.moveCard({
233
+ cardId,
234
+ targetZoneId: "standby" as ZoneId,
235
+ });
236
+ },
237
+ },
238
+
239
+ // Standard moves - using engine's standard moves library!
240
+ pass: standardMoves<TestGameState>({
241
+ include: ["pass"],
242
+ }).pass!,
243
+
244
+ concede: standardMoves<TestGameState>({
245
+ include: ["concede"],
246
+ }).concede!,
247
+ };
248
+
249
+ // Alpha Clash zones configuration (unchanged)
250
+ const alphaClashZones: Record<string, CardZoneConfig> = {
251
+ deck: {
252
+ id: "deck" as ZoneId,
253
+ name: "zones.deck",
254
+ visibility: "private",
255
+ ordered: true,
256
+ owner: undefined,
257
+ faceDown: true,
258
+ maxSize: 50,
259
+ },
260
+ hand: {
261
+ id: "hand" as ZoneId,
262
+ name: "zones.hand",
263
+ visibility: "private",
264
+ ordered: false,
265
+ owner: undefined,
266
+ faceDown: false,
267
+ maxSize: undefined,
268
+ },
269
+ contender: {
270
+ id: "contender" as ZoneId,
271
+ name: "zones.contender",
272
+ visibility: "public",
273
+ ordered: false,
274
+ owner: undefined,
275
+ faceDown: false,
276
+ maxSize: 1,
277
+ },
278
+ clash: {
279
+ id: "clash" as ZoneId,
280
+ name: "zones.clash",
281
+ visibility: "public",
282
+ ordered: false,
283
+ owner: undefined,
284
+ faceDown: false,
285
+ maxSize: undefined,
286
+ },
287
+ clashground: {
288
+ id: "clashground" as ZoneId,
289
+ name: "zones.clashground",
290
+ visibility: "public",
291
+ ordered: false,
292
+ owner: undefined,
293
+ faceDown: false,
294
+ maxSize: 1,
295
+ },
296
+ accessory: {
297
+ id: "accessory" as ZoneId,
298
+ name: "zones.accessory",
299
+ visibility: "secret",
300
+ ordered: false,
301
+ owner: undefined,
302
+ faceDown: true,
303
+ maxSize: undefined,
304
+ },
305
+ resource: {
306
+ id: "resource" as ZoneId,
307
+ name: "zones.resource",
308
+ visibility: "public",
309
+ ordered: false,
310
+ owner: undefined,
311
+ faceDown: false,
312
+ maxSize: undefined,
313
+ },
314
+ discard: {
315
+ id: "discard" as ZoneId,
316
+ name: "zones.discard",
317
+ visibility: "public",
318
+ ordered: false,
319
+ owner: undefined,
320
+ faceDown: false,
321
+ maxSize: undefined,
322
+ },
323
+ oblivion: {
324
+ id: "oblivion" as ZoneId,
325
+ name: "zones.oblivion",
326
+ visibility: "public",
327
+ ordered: false,
328
+ owner: undefined,
329
+ faceDown: false,
330
+ maxSize: undefined,
331
+ },
332
+ standby: {
333
+ id: "standby" as ZoneId,
334
+ name: "zones.standby",
335
+ visibility: "public",
336
+ ordered: true,
337
+ owner: undefined,
338
+ faceDown: false,
339
+ maxSize: undefined,
340
+ },
341
+ };
342
+
343
+ // Alpha Clash flow definition (unchanged)
344
+ const alphaClashFlow: FlowDefinition<TestGameState> = {
345
+ turn: {
346
+ initialPhase: "startOfTurn",
347
+ onBegin: (_context) => {
348
+ // Turn begins - handle start of turn effects
349
+ },
350
+ onEnd: (_context) => {
351
+ // Turn cleanup
352
+ },
353
+ phases: {
354
+ startOfTurn: {
355
+ order: 0,
356
+ next: "expansion",
357
+ onBegin: (_context) => {
358
+ // Trigger start of turn effects
359
+ },
360
+ endIf: () => true,
361
+ },
362
+ expansion: {
363
+ order: 1,
364
+ next: "primary",
365
+ steps: {
366
+ readyStep: {
367
+ order: 1,
368
+ next: "drawStep",
369
+ onBegin: (_context) => {
370
+ // Ready all engaged cards
371
+ },
372
+ endIf: () => true,
373
+ },
374
+ drawStep: {
375
+ order: 2,
376
+ next: "resourceStep",
377
+ onBegin: (_context) => {
378
+ // Draw a card (handled by drawCard move)
379
+ },
380
+ endIf: () => true,
381
+ },
382
+ resourceStep: {
383
+ order: 3,
384
+ onBegin: (_context) => {
385
+ // Player may play one resource
386
+ },
387
+ },
388
+ },
389
+ },
390
+ primary: {
391
+ order: 2,
392
+ next: "endOfTurn",
393
+ onBegin: (_context) => {
394
+ // Primary phase - player can play cards, initiate clashes
395
+ },
396
+ },
397
+ endOfTurn: {
398
+ order: 3,
399
+ next: "startOfTurn",
400
+ onBegin: (_context) => {
401
+ // End of turn effects
402
+ },
403
+ endIf: (_context) => {
404
+ return true;
405
+ },
406
+ },
407
+ },
408
+ },
409
+ };
410
+
411
+ /**
412
+ * Create minimal Alpha Clash game definition for testing
413
+ *
414
+ * REFACTORED to showcase new engine features:
415
+ * ✨ 100+ lines of boilerplate ELIMINATED!
416
+ * ✅ No manual phase/turn/player tracking
417
+ * ✅ High-level zone utilities (drawCards, mulligan)
418
+ * ✅ Tracker system for per-turn flags
419
+ * ✅ Standard moves library (pass, concede)
420
+ * ✅ Flow context access (isFirstTurn, currentPlayer)
421
+ * ✅ No redundant zone checks
422
+ */
423
+ export function createMockAlphaClashGame(): GameDefinition<
424
+ TestGameState,
425
+ TestMoves
426
+ > {
427
+ return {
428
+ name: "Test Alpha Clash Game",
429
+ zones: alphaClashZones,
430
+ flow: alphaClashFlow,
431
+ moves: alphaClashMoves,
432
+
433
+ // Configure engine's tracker system for per-turn flags
434
+ trackers: {
435
+ perTurn: ["hasPlayedResource"], // Auto-resets at turn end
436
+ perPlayer: true, // Track separately for each player
437
+ },
438
+
439
+ /**
440
+ * Setup function - MASSIVELY SIMPLIFIED!
441
+ *
442
+ * BEFORE: 60+ lines tracking phase, turn, currentPlayer, setupStep, firstPlayerChosen, mulliganOffered
443
+ * AFTER: 15 lines - just initialize game-specific data!
444
+ */
445
+ setup: (players) => {
446
+ const playerIds = players.map((p) => p.id);
447
+ const contenderHealth: Record<string, number> = {};
448
+ const resourcesAvailable: Record<string, number> = {};
449
+
450
+ for (const playerId of playerIds) {
451
+ contenderHealth[playerId] = 20; // Default Contender starting health
452
+ resourcesAvailable[playerId] = 0;
453
+ }
454
+
455
+ return {
456
+ contenderHealth,
457
+ resourcesAvailable,
458
+ clashInProgress: false,
459
+ };
460
+ },
461
+ };
462
+ }