@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,339 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { SeededRNG } from "./seeded-rng";
3
+
4
+ describe("SeededRNG", () => {
5
+ describe("Interface", () => {
6
+ it("should create RNG instance with default seed", () => {
7
+ const rng = new SeededRNG();
8
+
9
+ expect(rng).toBeDefined();
10
+ expect(typeof rng.getSeed()).toBe("string");
11
+ });
12
+
13
+ it("should create RNG instance with provided seed", () => {
14
+ const seed = "test-seed-123";
15
+ const rng = new SeededRNG(seed);
16
+
17
+ expect(rng.getSeed()).toBe(seed);
18
+ });
19
+
20
+ it("should have all required methods", () => {
21
+ const rng = new SeededRNG();
22
+
23
+ expect(typeof rng.getSeed).toBe("function");
24
+ expect(typeof rng.setSeed).toBe("function");
25
+ expect(typeof rng.random).toBe("function");
26
+ expect(typeof rng.randomInt).toBe("function");
27
+ expect(typeof rng.pick).toBe("function");
28
+ expect(typeof rng.shuffle).toBe("function");
29
+ expect(typeof rng.rollDice).toBe("function");
30
+ expect(typeof rng.flipCoin).toBe("function");
31
+ expect(typeof rng.createChild).toBe("function");
32
+ });
33
+ });
34
+
35
+ describe("Seed Management", () => {
36
+ it("should get current seed", () => {
37
+ const seed = "my-seed";
38
+ const rng = new SeededRNG(seed);
39
+
40
+ expect(rng.getSeed()).toBe(seed);
41
+ });
42
+
43
+ it("should set new seed", () => {
44
+ const rng = new SeededRNG("initial-seed");
45
+ const newSeed = "new-seed";
46
+
47
+ rng.setSeed(newSeed);
48
+
49
+ expect(rng.getSeed()).toBe(newSeed);
50
+ });
51
+
52
+ it("should reset generator when seed changes", () => {
53
+ const rng = new SeededRNG("seed1");
54
+ const value1 = rng.random();
55
+
56
+ rng.setSeed("seed1");
57
+ const value2 = rng.random();
58
+
59
+ expect(value1).toBe(value2);
60
+ });
61
+ });
62
+
63
+ describe("Random Number Generation", () => {
64
+ it("should generate random float between 0 and 1", () => {
65
+ const rng = new SeededRNG("test");
66
+
67
+ for (let i = 0; i < 100; i++) {
68
+ const value = rng.random();
69
+ expect(value).toBeGreaterThanOrEqual(0);
70
+ expect(value).toBeLessThan(1);
71
+ }
72
+ });
73
+
74
+ it("should generate random integer in range [min, max]", () => {
75
+ const rng = new SeededRNG("test");
76
+
77
+ for (let i = 0; i < 100; i++) {
78
+ const value = rng.randomInt(1, 10);
79
+ expect(value).toBeGreaterThanOrEqual(1);
80
+ expect(value).toBeLessThanOrEqual(10);
81
+ expect(Number.isInteger(value)).toBe(true);
82
+ }
83
+ });
84
+
85
+ it("should generate random integer with single argument [0, max]", () => {
86
+ const rng = new SeededRNG("test");
87
+
88
+ for (let i = 0; i < 100; i++) {
89
+ const value = rng.randomInt(5);
90
+ expect(value).toBeGreaterThanOrEqual(0);
91
+ expect(value).toBeLessThanOrEqual(5);
92
+ expect(Number.isInteger(value)).toBe(true);
93
+ }
94
+ });
95
+
96
+ it("should handle min === max in randomInt", () => {
97
+ const rng = new SeededRNG("test");
98
+ const value = rng.randomInt(5, 5);
99
+
100
+ expect(value).toBe(5);
101
+ });
102
+ });
103
+
104
+ describe("Array Operations", () => {
105
+ it("should pick random element from array", () => {
106
+ const rng = new SeededRNG("test");
107
+ const array = ["a", "b", "c", "d", "e"];
108
+
109
+ const picked = rng.pick(array);
110
+
111
+ expect(array).toContain(picked);
112
+ });
113
+
114
+ it("should not mutate original array when picking", () => {
115
+ const rng = new SeededRNG("test");
116
+ const array = ["a", "b", "c"];
117
+ const originalArray = [...array];
118
+
119
+ rng.pick(array);
120
+
121
+ expect(array).toEqual(originalArray);
122
+ });
123
+
124
+ it("should shuffle array", () => {
125
+ const rng = new SeededRNG("test");
126
+ const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
127
+
128
+ const shuffled = rng.shuffle(array);
129
+
130
+ expect(shuffled).toHaveLength(array.length);
131
+ expect(shuffled.every((item) => array.includes(item))).toBe(true);
132
+ expect(array.every((item) => shuffled.includes(item))).toBe(true);
133
+ });
134
+
135
+ it("should not mutate original array when shuffling", () => {
136
+ const rng = new SeededRNG("test");
137
+ const array = [1, 2, 3, 4, 5];
138
+ const originalArray = [...array];
139
+
140
+ rng.shuffle(array);
141
+
142
+ expect(array).toEqual(originalArray);
143
+ });
144
+
145
+ it("should handle single element array", () => {
146
+ const rng = new SeededRNG("test");
147
+ const array = [42];
148
+
149
+ const picked = rng.pick(array);
150
+ const shuffled = rng.shuffle(array);
151
+
152
+ expect(picked).toBe(42);
153
+ expect(shuffled).toEqual([42]);
154
+ });
155
+
156
+ it("should handle empty array for pick", () => {
157
+ const rng = new SeededRNG("test");
158
+ const array: number[] = [];
159
+
160
+ const picked = rng.pick(array);
161
+
162
+ expect(picked).toBeUndefined();
163
+ });
164
+
165
+ it("should handle empty array for shuffle", () => {
166
+ const rng = new SeededRNG("test");
167
+ const array: number[] = [];
168
+
169
+ const shuffled = rng.shuffle(array);
170
+
171
+ expect(shuffled).toEqual([]);
172
+ });
173
+ });
174
+
175
+ describe("Dice and Coin", () => {
176
+ it("should roll dice with specified sides", () => {
177
+ const rng = new SeededRNG("test");
178
+
179
+ for (let i = 0; i < 100; i++) {
180
+ const roll = rng.rollDice(6);
181
+ expect(roll).toBeGreaterThanOrEqual(1);
182
+ expect(roll).toBeLessThanOrEqual(6);
183
+ expect(Number.isInteger(roll)).toBe(true);
184
+ }
185
+ });
186
+
187
+ it("should roll multiple dice", () => {
188
+ const rng = new SeededRNG("test");
189
+
190
+ for (let i = 0; i < 50; i++) {
191
+ const rolls = rng.rollDice(6, 3);
192
+ expect(Array.isArray(rolls)).toBe(true);
193
+ expect(rolls).toHaveLength(3);
194
+ if (Array.isArray(rolls)) {
195
+ for (const roll of rolls) {
196
+ expect(roll).toBeGreaterThanOrEqual(1);
197
+ expect(roll).toBeLessThanOrEqual(6);
198
+ expect(Number.isInteger(roll)).toBe(true);
199
+ }
200
+ }
201
+ }
202
+ });
203
+
204
+ it("should flip coin returning boolean", () => {
205
+ const rng = new SeededRNG("test");
206
+ let heads = 0;
207
+ let tails = 0;
208
+
209
+ for (let i = 0; i < 100; i++) {
210
+ const flip = rng.flipCoin();
211
+ expect(typeof flip).toBe("boolean");
212
+ if (flip) heads++;
213
+ else tails++;
214
+ }
215
+
216
+ // Both outcomes should occur (probability check)
217
+ expect(heads).toBeGreaterThan(0);
218
+ expect(tails).toBeGreaterThan(0);
219
+ });
220
+
221
+ it("should flip coin with bias", () => {
222
+ const rng = new SeededRNG("test");
223
+ let heads = 0;
224
+
225
+ // 90% bias towards heads
226
+ for (let i = 0; i < 1000; i++) {
227
+ if (rng.flipCoin(0.9)) heads++;
228
+ }
229
+
230
+ // Should be roughly 900 heads out of 1000
231
+ expect(heads).toBeGreaterThan(850);
232
+ expect(heads).toBeLessThan(950);
233
+ });
234
+ });
235
+
236
+ describe("Child RNG Creation", () => {
237
+ it("should create child RNG with derived seed", () => {
238
+ const parent = new SeededRNG("parent-seed");
239
+ const child = parent.createChild("operation-1");
240
+
241
+ expect(child).toBeInstanceOf(SeededRNG);
242
+ expect(child.getSeed()).not.toBe(parent.getSeed());
243
+ });
244
+
245
+ it("should create independent child RNG", () => {
246
+ const parent = new SeededRNG("parent-seed");
247
+ const child = parent.createChild("child");
248
+
249
+ // Generate some values in child
250
+ child.random();
251
+ child.random();
252
+
253
+ // Parent should not be affected
254
+ parent.setSeed("parent-seed");
255
+ const parentValue = parent.random();
256
+
257
+ parent.setSeed("parent-seed");
258
+ const resetValue = parent.random();
259
+
260
+ expect(parentValue).toBe(resetValue);
261
+ });
262
+
263
+ it("should create deterministic child RNGs", () => {
264
+ const parent1 = new SeededRNG("seed");
265
+ const child1 = parent1.createChild("operation");
266
+
267
+ const parent2 = new SeededRNG("seed");
268
+ const child2 = parent2.createChild("operation");
269
+
270
+ expect(child1.random()).toBe(child2.random());
271
+ });
272
+ });
273
+
274
+ describe("Deterministic Behavior", () => {
275
+ it("should produce same sequence with same seed", () => {
276
+ const rng1 = new SeededRNG("deterministic-seed");
277
+ const sequence1 = Array.from({ length: 10 }, () => rng1.random());
278
+
279
+ const rng2 = new SeededRNG("deterministic-seed");
280
+ const sequence2 = Array.from({ length: 10 }, () => rng2.random());
281
+
282
+ expect(sequence1).toEqual(sequence2);
283
+ });
284
+
285
+ it("should produce same shuffle with same seed", () => {
286
+ const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
287
+
288
+ const rng1 = new SeededRNG("shuffle-seed");
289
+ const shuffled1 = rng1.shuffle(array);
290
+
291
+ const rng2 = new SeededRNG("shuffle-seed");
292
+ const shuffled2 = rng2.shuffle(array);
293
+
294
+ expect(shuffled1).toEqual(shuffled2);
295
+ });
296
+
297
+ it("should produce same dice rolls with same seed", () => {
298
+ const rng1 = new SeededRNG("dice-seed");
299
+ const rolls1 = Array.from({ length: 10 }, () => rng1.rollDice(20));
300
+
301
+ const rng2 = new SeededRNG("dice-seed");
302
+ const rolls2 = Array.from({ length: 10 }, () => rng2.rollDice(20));
303
+
304
+ expect(rolls1).toEqual(rolls2);
305
+ });
306
+
307
+ it("should produce different sequences with different seeds", () => {
308
+ const rng1 = new SeededRNG("seed1");
309
+ const sequence1 = Array.from({ length: 10 }, () => rng1.random());
310
+
311
+ const rng2 = new SeededRNG("seed2");
312
+ const sequence2 = Array.from({ length: 10 }, () => rng2.random());
313
+
314
+ expect(sequence1).not.toEqual(sequence2);
315
+ });
316
+
317
+ it("should maintain determinism across all operations", () => {
318
+ const rng1 = new SeededRNG("complex-seed");
319
+ const results1 = {
320
+ random: rng1.random(),
321
+ int: rng1.randomInt(1, 100),
322
+ pick: rng1.pick(["a", "b", "c", "d"]),
323
+ dice: rng1.rollDice(6),
324
+ coin: rng1.flipCoin(),
325
+ };
326
+
327
+ const rng2 = new SeededRNG("complex-seed");
328
+ const results2 = {
329
+ random: rng2.random(),
330
+ int: rng2.randomInt(1, 100),
331
+ pick: rng2.pick(["a", "b", "c", "d"]),
332
+ dice: rng2.rollDice(6),
333
+ coin: rng2.flipCoin(),
334
+ };
335
+
336
+ expect(results1).toEqual(results2);
337
+ });
338
+ });
339
+ });
@@ -0,0 +1,123 @@
1
+ import { nanoid } from "nanoid";
2
+ import seedrandom from "seedrandom";
3
+
4
+ /**
5
+ * Seeded random number generator for deterministic randomness
6
+ * Wraps seedrandom library to provide game-specific RNG operations
7
+ */
8
+ export class SeededRNG {
9
+ private seed: string;
10
+ private prng: seedrandom.PRNG;
11
+
12
+ constructor(seed?: string) {
13
+ this.seed = seed ?? nanoid();
14
+ this.prng = seedrandom(this.seed);
15
+ }
16
+
17
+ /**
18
+ * Get the current seed
19
+ */
20
+ getSeed(): string {
21
+ return this.seed;
22
+ }
23
+
24
+ /**
25
+ * Set a new seed and reset the generator
26
+ */
27
+ setSeed(newSeed: string): void {
28
+ this.seed = newSeed;
29
+ this.prng = seedrandom(this.seed);
30
+ }
31
+
32
+ /**
33
+ * Generate a random float in range [0, 1)
34
+ */
35
+ random(): number {
36
+ return this.prng();
37
+ }
38
+
39
+ /**
40
+ * Generate a random integer in range [min, max] (inclusive)
41
+ * If only one argument is provided, range is [0, max]
42
+ */
43
+ randomInt(min: number, max?: number): number {
44
+ const actualMin = max === undefined ? 0 : min;
45
+ const actualMax = max === undefined ? min : max;
46
+
47
+ if (actualMin === actualMax) {
48
+ return actualMin;
49
+ }
50
+
51
+ return Math.floor(this.random() * (actualMax - actualMin + 1)) + actualMin;
52
+ }
53
+
54
+ /**
55
+ * Pick a random element from an array
56
+ * Returns undefined if array is empty
57
+ */
58
+ pick<T>(array: readonly T[]): T | undefined {
59
+ if (array.length === 0) {
60
+ return undefined;
61
+ }
62
+
63
+ if (array.length === 1) {
64
+ return array[0];
65
+ }
66
+
67
+ const index = this.randomInt(0, array.length - 1);
68
+ return array[index];
69
+ }
70
+
71
+ /**
72
+ * Shuffle an array using Fisher-Yates algorithm
73
+ * Returns a new array, does not mutate the original
74
+ */
75
+ shuffle<T>(array: readonly T[]): T[] {
76
+ if (array.length <= 1) {
77
+ return [...array];
78
+ }
79
+
80
+ const result = [...array];
81
+
82
+ for (let i = result.length - 1; i > 0; i--) {
83
+ const j = this.randomInt(0, i);
84
+ const itemI = result[i];
85
+ const itemJ = result[j];
86
+ if (itemI !== undefined && itemJ !== undefined) {
87
+ result[i] = itemJ;
88
+ result[j] = itemI;
89
+ }
90
+ }
91
+
92
+ return result;
93
+ }
94
+
95
+ /**
96
+ * Roll dice with specified number of sides
97
+ * Returns a single number for one die, or an array for multiple dice
98
+ */
99
+ rollDice(sides: number, count?: number): number | number[] {
100
+ if (count === undefined || count === 1) {
101
+ return this.randomInt(1, sides);
102
+ }
103
+
104
+ return Array.from({ length: count }, () => this.randomInt(1, sides));
105
+ }
106
+
107
+ /**
108
+ * Flip a coin
109
+ * @param bias - Probability of returning true (default: 0.5)
110
+ */
111
+ flipCoin(bias = 0.5): boolean {
112
+ return this.random() < bias;
113
+ }
114
+
115
+ /**
116
+ * Create a child RNG with a derived seed
117
+ * Useful for creating independent RNG instances for sub-operations
118
+ */
119
+ createChild(namespace: string): SeededRNG {
120
+ const childSeed = `${this.seed}:${namespace}`;
121
+ return new SeededRNG(childSeed);
122
+ }
123
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Targeting Module
3
+ *
4
+ * Provides DSL and utilities for expressing and resolving card/player targets
5
+ * in a game-agnostic way. Game engines extend these types for game-specific
6
+ * targeting needs.
7
+ *
8
+ * @module targeting
9
+ */
10
+
11
+ // Core DSL types
12
+ export {
13
+ type BaseContext,
14
+ DEFAULT_SELF_TARGET,
15
+ DEFAULT_SINGLE_TARGET,
16
+ getMaxCount,
17
+ getMinCount,
18
+ isMultipleTargetSelector,
19
+ isOptionalCount,
20
+ type OwnerScope,
21
+ type PlayerTargetDSL,
22
+ requiresPlayerChoice,
23
+ type SelectorScope,
24
+ type TargetCount,
25
+ type TargetDSL,
26
+ type TargetingUIHint,
27
+ } from "./target-dsl";
28
+ // Target resolution
29
+ export {
30
+ BaseTargetResolver,
31
+ invalidSelection,
32
+ type TargetIssue,
33
+ type TargetResolutionContext,
34
+ type TargetResolver,
35
+ type TargetValidationResult,
36
+ validateTargetCount,
37
+ validSelection,
38
+ } from "./target-resolver";
39
+
40
+ // Target validation utilities
41
+ export {
42
+ enumerateTargetCombinations,
43
+ getLegalTargets,
44
+ isLegalTarget,
45
+ type TargetContext,
46
+ type ValidationResult,
47
+ validateTargetSelection,
48
+ } from "./target-validation";