@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.
- package/README.md +882 -0
- package/package.json +58 -0
- package/src/__tests__/alpha-clash-engine-definition.test.ts +319 -0
- package/src/__tests__/createMockAlphaClashGame.ts +462 -0
- package/src/__tests__/createMockGrandArchiveGame.ts +373 -0
- package/src/__tests__/createMockGundamGame.ts +379 -0
- package/src/__tests__/createMockLorcanaGame.ts +328 -0
- package/src/__tests__/createMockOnePieceGame.ts +429 -0
- package/src/__tests__/createMockRiftboundGame.ts +462 -0
- package/src/__tests__/grand-archive-engine-definition.test.ts +118 -0
- package/src/__tests__/gundam-engine-definition.test.ts +110 -0
- package/src/__tests__/integration-complete-game.test.ts +508 -0
- package/src/__tests__/integration-network-sync.test.ts +469 -0
- package/src/__tests__/lorcana-engine-definition.test.ts +100 -0
- package/src/__tests__/move-enumeration.test.ts +725 -0
- package/src/__tests__/multiplayer-engine.test.ts +555 -0
- package/src/__tests__/one-piece-engine-definition.test.ts +114 -0
- package/src/__tests__/riftbound-engine-definition.test.ts +124 -0
- package/src/actions/action-definition.test.ts +201 -0
- package/src/actions/action-definition.ts +122 -0
- package/src/actions/action-timing.test.ts +490 -0
- package/src/actions/action-timing.ts +257 -0
- package/src/cards/card-definition.test.ts +268 -0
- package/src/cards/card-definition.ts +27 -0
- package/src/cards/card-instance.test.ts +422 -0
- package/src/cards/card-instance.ts +49 -0
- package/src/cards/computed-properties.test.ts +530 -0
- package/src/cards/computed-properties.ts +84 -0
- package/src/cards/conditional-modifiers.test.ts +390 -0
- package/src/cards/modifiers.test.ts +286 -0
- package/src/cards/modifiers.ts +51 -0
- package/src/engine/MULTIPLAYER.md +425 -0
- package/src/engine/__tests__/rule-engine-flow.test.ts +348 -0
- package/src/engine/__tests__/rule-engine-history.test.ts +535 -0
- package/src/engine/__tests__/rule-engine-moves.test.ts +488 -0
- package/src/engine/__tests__/rule-engine.test.ts +366 -0
- package/src/engine/index.ts +14 -0
- package/src/engine/multiplayer-engine.example.ts +571 -0
- package/src/engine/multiplayer-engine.ts +409 -0
- package/src/engine/rule-engine.test.ts +286 -0
- package/src/engine/rule-engine.ts +1539 -0
- package/src/engine/tracker-system.ts +172 -0
- package/src/examples/__tests__/coin-flip-game.test.ts +641 -0
- package/src/filtering/card-filter.test.ts +230 -0
- package/src/filtering/card-filter.ts +91 -0
- package/src/filtering/card-query.test.ts +901 -0
- package/src/filtering/card-query.ts +273 -0
- package/src/filtering/filter-matching.test.ts +944 -0
- package/src/filtering/filter-matching.ts +315 -0
- package/src/flow/SERIALIZATION.md +428 -0
- package/src/flow/__tests__/flow-definition.test.ts +427 -0
- package/src/flow/__tests__/flow-manager.test.ts +756 -0
- package/src/flow/__tests__/flow-serialization.test.ts +565 -0
- package/src/flow/flow-definition.ts +453 -0
- package/src/flow/flow-manager.ts +1044 -0
- package/src/flow/index.ts +35 -0
- package/src/game-definition/__tests__/game-definition-validation.test.ts +359 -0
- package/src/game-definition/__tests__/game-definition.test.ts +291 -0
- package/src/game-definition/__tests__/move-definitions.test.ts +328 -0
- package/src/game-definition/game-definition.ts +261 -0
- package/src/game-definition/index.ts +28 -0
- package/src/game-definition/move-definitions.ts +188 -0
- package/src/game-definition/validation.ts +183 -0
- package/src/history/history-manager.test.ts +497 -0
- package/src/history/history-manager.ts +312 -0
- package/src/history/history-operations.ts +122 -0
- package/src/history/index.ts +9 -0
- package/src/history/types.ts +255 -0
- package/src/index.ts +32 -0
- package/src/logging/index.ts +27 -0
- package/src/logging/log-formatter.ts +187 -0
- package/src/logging/logger.ts +276 -0
- package/src/logging/types.ts +148 -0
- package/src/moves/create-move.test.ts +331 -0
- package/src/moves/create-move.ts +64 -0
- package/src/moves/move-enumeration.ts +228 -0
- package/src/moves/move-executor.test.ts +431 -0
- package/src/moves/move-executor.ts +195 -0
- package/src/moves/move-system.test.ts +380 -0
- package/src/moves/move-system.ts +463 -0
- package/src/moves/standard-moves.ts +231 -0
- package/src/operations/card-operations.test.ts +236 -0
- package/src/operations/card-operations.ts +116 -0
- package/src/operations/card-registry-impl.test.ts +251 -0
- package/src/operations/card-registry-impl.ts +70 -0
- package/src/operations/card-registry.test.ts +234 -0
- package/src/operations/card-registry.ts +106 -0
- package/src/operations/counter-operations.ts +152 -0
- package/src/operations/game-operations.test.ts +280 -0
- package/src/operations/game-operations.ts +140 -0
- package/src/operations/index.ts +24 -0
- package/src/operations/operations-impl.test.ts +354 -0
- package/src/operations/operations-impl.ts +468 -0
- package/src/operations/zone-operations.test.ts +295 -0
- package/src/operations/zone-operations.ts +223 -0
- package/src/rng/seeded-rng.test.ts +339 -0
- package/src/rng/seeded-rng.ts +123 -0
- package/src/targeting/index.ts +48 -0
- package/src/targeting/target-definition.test.ts +273 -0
- package/src/targeting/target-definition.ts +37 -0
- package/src/targeting/target-dsl.ts +279 -0
- package/src/targeting/target-resolver.ts +486 -0
- package/src/targeting/target-validation.test.ts +994 -0
- package/src/targeting/target-validation.ts +286 -0
- package/src/telemetry/events.ts +202 -0
- package/src/telemetry/index.ts +21 -0
- package/src/telemetry/telemetry-manager.ts +127 -0
- package/src/telemetry/types.ts +68 -0
- package/src/testing/__tests__/testing-utilities-integration.test.ts +161 -0
- package/src/testing/index.ts +88 -0
- package/src/testing/test-assertions.test.ts +341 -0
- package/src/testing/test-assertions.ts +256 -0
- package/src/testing/test-card-factory.test.ts +228 -0
- package/src/testing/test-card-factory.ts +111 -0
- package/src/testing/test-context-factory.ts +187 -0
- package/src/testing/test-end-assertions.test.ts +262 -0
- package/src/testing/test-end-assertions.ts +95 -0
- package/src/testing/test-engine-builder.test.ts +389 -0
- package/src/testing/test-engine-builder.ts +46 -0
- package/src/testing/test-flow-assertions.test.ts +284 -0
- package/src/testing/test-flow-assertions.ts +115 -0
- package/src/testing/test-player-builder.test.ts +132 -0
- package/src/testing/test-player-builder.ts +46 -0
- package/src/testing/test-replay-assertions.test.ts +356 -0
- package/src/testing/test-replay-assertions.ts +164 -0
- package/src/testing/test-rng-helpers.test.ts +260 -0
- package/src/testing/test-rng-helpers.ts +190 -0
- package/src/testing/test-state-builder.test.ts +373 -0
- package/src/testing/test-state-builder.ts +99 -0
- package/src/testing/test-zone-factory.test.ts +295 -0
- package/src/testing/test-zone-factory.ts +224 -0
- package/src/types/branded-utils.ts +54 -0
- package/src/types/branded.test.ts +175 -0
- package/src/types/branded.ts +33 -0
- package/src/types/index.ts +8 -0
- package/src/types/state.test.ts +198 -0
- package/src/types/state.ts +154 -0
- package/src/validation/card-type-guards.test.ts +242 -0
- package/src/validation/card-type-guards.ts +179 -0
- package/src/validation/index.ts +40 -0
- package/src/validation/schema-builders.test.ts +403 -0
- package/src/validation/schema-builders.ts +345 -0
- package/src/validation/type-guard-builder.test.ts +216 -0
- package/src/validation/type-guard-builder.ts +109 -0
- package/src/validation/validator-builder.test.ts +375 -0
- package/src/validation/validator-builder.ts +273 -0
- package/src/zones/index.ts +28 -0
- package/src/zones/zone-factory.test.ts +183 -0
- package/src/zones/zone-factory.ts +44 -0
- package/src/zones/zone-operations.test.ts +800 -0
- package/src/zones/zone-operations.ts +306 -0
- package/src/zones/zone-state-helpers.test.ts +337 -0
- package/src/zones/zone-state-helpers.ts +128 -0
- package/src/zones/zone-visibility.test.ts +156 -0
- package/src/zones/zone-visibility.ts +36 -0
- package/src/zones/zone.test.ts +186 -0
- package/src/zones/zone.ts +66 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { produce } from "immer";
|
|
3
|
+
import type { MoveContext } from "../../moves/move-system";
|
|
4
|
+
import { createMockContext } from "../../testing/test-context-factory";
|
|
5
|
+
import type {
|
|
6
|
+
GameMoveDefinition,
|
|
7
|
+
GameMoveDefinitions,
|
|
8
|
+
} from "../move-definitions";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Test suite for GameMoveDefinitions type system
|
|
12
|
+
*
|
|
13
|
+
* Task 10.5, 10.6: Write tests for moves mapping
|
|
14
|
+
*
|
|
15
|
+
* Validates:
|
|
16
|
+
* - GameMoveDefinition structure
|
|
17
|
+
* - GameMoveDefinitions exhaustive mapping
|
|
18
|
+
* - Type safety for move reducers
|
|
19
|
+
* - Conditional move execution
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
type CounterState = {
|
|
23
|
+
count: number;
|
|
24
|
+
player: string;
|
|
25
|
+
locked: boolean;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type CounterMoves = {
|
|
29
|
+
increment: { amount: number };
|
|
30
|
+
decrement: { amount: number };
|
|
31
|
+
reset: Record<string, never>;
|
|
32
|
+
setPlayer: { playerId: string };
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
describe("GameMoveDefinitions - Type System", () => {
|
|
36
|
+
describe("GameMoveDefinition structure", () => {
|
|
37
|
+
it("should create a valid GameMoveDefinition with reducer only", () => {
|
|
38
|
+
const incrementMove: GameMoveDefinition<CounterState> = {
|
|
39
|
+
reducer: (draft, context) => {
|
|
40
|
+
const amount = (context.params?.amount as number) || 1;
|
|
41
|
+
draft.count += amount;
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
expect(incrementMove.reducer).toBeFunction();
|
|
46
|
+
expect(incrementMove.condition).toBeUndefined();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("should create a GameMoveDefinition with condition and reducer", () => {
|
|
50
|
+
const incrementMove: GameMoveDefinition<CounterState> = {
|
|
51
|
+
condition: (state) => !state.locked,
|
|
52
|
+
reducer: (draft, context) => {
|
|
53
|
+
const amount = (context.params?.amount as number) || 1;
|
|
54
|
+
draft.count += amount;
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
expect(incrementMove.condition).toBeFunction();
|
|
59
|
+
expect(incrementMove.reducer).toBeFunction();
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("GameMoveDefinitions exhaustive mapping", () => {
|
|
64
|
+
it("should map all moves in TMoves type", () => {
|
|
65
|
+
const moves: GameMoveDefinitions<CounterState, CounterMoves> = {
|
|
66
|
+
increment: {
|
|
67
|
+
reducer: (draft, context) => {
|
|
68
|
+
const amount = (context.params?.amount as number) || 1;
|
|
69
|
+
draft.count += amount;
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
decrement: {
|
|
73
|
+
reducer: (draft, context) => {
|
|
74
|
+
const amount = (context.params?.amount as number) || 1;
|
|
75
|
+
draft.count -= amount;
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
reset: {
|
|
79
|
+
reducer: (draft) => {
|
|
80
|
+
draft.count = 0;
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
setPlayer: {
|
|
84
|
+
reducer: (draft, context) => {
|
|
85
|
+
draft.player = (context.params?.playerId as string) || "";
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
expect(Object.keys(moves)).toEqual([
|
|
91
|
+
"increment",
|
|
92
|
+
"decrement",
|
|
93
|
+
"reset",
|
|
94
|
+
"setPlayer",
|
|
95
|
+
]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should enforce that all move names have definitions", () => {
|
|
99
|
+
// TypeScript will error if any move is missing
|
|
100
|
+
const moves: GameMoveDefinitions<CounterState, CounterMoves> = {
|
|
101
|
+
increment: {
|
|
102
|
+
reducer: (draft) => {
|
|
103
|
+
draft.count += 1;
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
decrement: {
|
|
107
|
+
reducer: (draft) => {
|
|
108
|
+
draft.count -= 1;
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
reset: {
|
|
112
|
+
reducer: (draft) => {
|
|
113
|
+
draft.count = 0;
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
setPlayer: {
|
|
117
|
+
reducer: (draft) => {
|
|
118
|
+
draft.player = "test";
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// All moves should be present
|
|
124
|
+
expect(moves.increment).toBeDefined();
|
|
125
|
+
expect(moves.decrement).toBeDefined();
|
|
126
|
+
expect(moves.reset).toBeDefined();
|
|
127
|
+
expect(moves.setPlayer).toBeDefined();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe("move reducer execution", () => {
|
|
132
|
+
it("should execute reducer with Immer draft", () => {
|
|
133
|
+
const incrementMove: GameMoveDefinition<CounterState> = {
|
|
134
|
+
reducer: (draft, context) => {
|
|
135
|
+
const amount = (context.params?.amount as number) || 1;
|
|
136
|
+
draft.count += amount;
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const initialState: CounterState = {
|
|
141
|
+
count: 5,
|
|
142
|
+
player: "p1",
|
|
143
|
+
locked: false,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const context: MoveContext = createMockContext({
|
|
147
|
+
playerId: "p1" as any,
|
|
148
|
+
params: { amount: 3 },
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const newState = produce(initialState, (draft) => {
|
|
152
|
+
incrementMove.reducer(draft, context);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
expect(newState.count).toBe(8);
|
|
156
|
+
expect(initialState.count).toBe(5); // Original unchanged
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("should support complex state modifications", () => {
|
|
160
|
+
const complexMove: GameMoveDefinition<CounterState> = {
|
|
161
|
+
reducer: (draft, context) => {
|
|
162
|
+
draft.count += 1;
|
|
163
|
+
draft.player = context.playerId;
|
|
164
|
+
draft.locked = true;
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const initialState: CounterState = {
|
|
169
|
+
count: 0,
|
|
170
|
+
player: "",
|
|
171
|
+
locked: false,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const context: MoveContext = createMockContext({
|
|
175
|
+
playerId: "p1" as any,
|
|
176
|
+
params: {},
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const newState = produce(initialState, (draft) => {
|
|
180
|
+
complexMove.reducer(draft, context);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
expect(newState).toEqual({
|
|
184
|
+
count: 1,
|
|
185
|
+
player: "p1",
|
|
186
|
+
locked: true,
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe("move conditions", () => {
|
|
192
|
+
it("should evaluate condition before executing move", () => {
|
|
193
|
+
const lockedMove: GameMoveDefinition<CounterState> = {
|
|
194
|
+
condition: (state) => !state.locked,
|
|
195
|
+
reducer: (draft) => {
|
|
196
|
+
draft.count += 1;
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const unlockedState: CounterState = {
|
|
201
|
+
count: 0,
|
|
202
|
+
player: "p1",
|
|
203
|
+
locked: false,
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const lockedState: CounterState = {
|
|
207
|
+
count: 0,
|
|
208
|
+
player: "p1",
|
|
209
|
+
locked: true,
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const context: MoveContext = createMockContext({
|
|
213
|
+
playerId: "p1" as any,
|
|
214
|
+
params: {},
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Should pass condition
|
|
218
|
+
expect(lockedMove.condition?.(unlockedState, context)).toBe(true);
|
|
219
|
+
|
|
220
|
+
// Should fail condition
|
|
221
|
+
expect(lockedMove.condition?.(lockedState, context)).toBe(false);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("should support complex conditions", () => {
|
|
225
|
+
const conditionalMove: GameMoveDefinition<CounterState> = {
|
|
226
|
+
condition: (state, context) => {
|
|
227
|
+
return (
|
|
228
|
+
!state.locked &&
|
|
229
|
+
state.count < 10 &&
|
|
230
|
+
state.player === context.playerId
|
|
231
|
+
);
|
|
232
|
+
},
|
|
233
|
+
reducer: (draft) => {
|
|
234
|
+
draft.count += 1;
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const validState: CounterState = {
|
|
239
|
+
count: 5,
|
|
240
|
+
player: "p1",
|
|
241
|
+
locked: false,
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const context: MoveContext = createMockContext({
|
|
245
|
+
playerId: "p1" as any,
|
|
246
|
+
params: {},
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
expect(conditionalMove.condition?.(validState, context)).toBe(true);
|
|
250
|
+
|
|
251
|
+
// Wrong player
|
|
252
|
+
expect(
|
|
253
|
+
conditionalMove.condition?.(
|
|
254
|
+
validState,
|
|
255
|
+
createMockContext({
|
|
256
|
+
playerId: "p2" as any,
|
|
257
|
+
params: {},
|
|
258
|
+
}),
|
|
259
|
+
),
|
|
260
|
+
).toBe(false);
|
|
261
|
+
|
|
262
|
+
// Locked
|
|
263
|
+
expect(
|
|
264
|
+
conditionalMove.condition?.({ ...validState, locked: true }, context),
|
|
265
|
+
).toBe(false);
|
|
266
|
+
|
|
267
|
+
// Count too high
|
|
268
|
+
expect(
|
|
269
|
+
conditionalMove.condition?.({ ...validState, count: 10 }, context),
|
|
270
|
+
).toBe(false);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
describe("type safety", () => {
|
|
275
|
+
it("should preserve state type through reducer", () => {
|
|
276
|
+
const typedMove: GameMoveDefinition<CounterState> = {
|
|
277
|
+
reducer: (draft) => {
|
|
278
|
+
// TypeScript should know draft is CounterState
|
|
279
|
+
draft.count += 1;
|
|
280
|
+
draft.player = "updated";
|
|
281
|
+
draft.locked = true;
|
|
282
|
+
|
|
283
|
+
// These should cause TypeScript errors:
|
|
284
|
+
// draft.nonExistent = true;
|
|
285
|
+
// draft.count = "string";
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
expect(typedMove.reducer).toBeFunction();
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("should preserve state type through condition", () => {
|
|
293
|
+
const typedMove: GameMoveDefinition<CounterState> = {
|
|
294
|
+
condition: (state) => {
|
|
295
|
+
// TypeScript should know state is CounterState
|
|
296
|
+
return state.count < 10 && !state.locked;
|
|
297
|
+
|
|
298
|
+
// This should cause TypeScript error:
|
|
299
|
+
// return state.nonExistent;
|
|
300
|
+
},
|
|
301
|
+
reducer: (draft) => {
|
|
302
|
+
draft.count += 1;
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
expect(typedMove.condition).toBeFunction();
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
describe("metadata support", () => {
|
|
311
|
+
it("should support optional metadata", () => {
|
|
312
|
+
const metadataMove: GameMoveDefinition<CounterState> = {
|
|
313
|
+
reducer: (draft) => {
|
|
314
|
+
draft.count += 1;
|
|
315
|
+
},
|
|
316
|
+
metadata: {
|
|
317
|
+
category: "counter",
|
|
318
|
+
tags: ["increment", "basic"],
|
|
319
|
+
priority: 1,
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
expect(metadataMove.metadata?.category).toBe("counter");
|
|
324
|
+
expect(metadataMove.metadata?.tags).toEqual(["increment", "basic"]);
|
|
325
|
+
expect(metadataMove.metadata?.priority).toBe(1);
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
});
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import type { FlowDefinition } from "../flow";
|
|
2
|
+
import type { TelemetryHooks } from "../telemetry";
|
|
3
|
+
import type { CardZoneConfig } from "../zones";
|
|
4
|
+
import type { GameMoveDefinitions } from "./move-definitions";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Player information for game setup
|
|
8
|
+
*/
|
|
9
|
+
export type Player = {
|
|
10
|
+
/** Unique player identifier */
|
|
11
|
+
id: string;
|
|
12
|
+
/** Optional player name for display */
|
|
13
|
+
name?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Game end result
|
|
18
|
+
*
|
|
19
|
+
* Returned by endIf when the game has ended.
|
|
20
|
+
*/
|
|
21
|
+
export type GameEndResult = {
|
|
22
|
+
/** Winner identifier (player ID or special value like 'draw') */
|
|
23
|
+
winner: string;
|
|
24
|
+
/** Reason for game end (for display/logging) */
|
|
25
|
+
reason: string;
|
|
26
|
+
/** Additional metadata about the game end */
|
|
27
|
+
metadata?: Record<string, unknown>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* GameDefinition Type System
|
|
32
|
+
*
|
|
33
|
+
* Task 10.2: Implement GameDefinition<TState, TMoves> type
|
|
34
|
+
*
|
|
35
|
+
* The core declarative game definition with full type safety.
|
|
36
|
+
* Generic over:
|
|
37
|
+
* - TState: Game state shape (game-specific logic state)
|
|
38
|
+
* - TMoves: Available moves (record of move names to move arg types)
|
|
39
|
+
* - TCardDefinition: Static card definition type
|
|
40
|
+
* - TCardMeta: Dynamic card metadata type
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* type MyGameState = {
|
|
45
|
+
* players: Player[];
|
|
46
|
+
* currentPlayer: number;
|
|
47
|
+
* };
|
|
48
|
+
*
|
|
49
|
+
* type MyMoves = {
|
|
50
|
+
* playCard: { cardId: string };
|
|
51
|
+
* pass: {};
|
|
52
|
+
* };
|
|
53
|
+
*
|
|
54
|
+
* type MyCardDef = {
|
|
55
|
+
* id: string;
|
|
56
|
+
* name: string;
|
|
57
|
+
* cost: number;
|
|
58
|
+
* };
|
|
59
|
+
*
|
|
60
|
+
* type MyCardMeta = {
|
|
61
|
+
* damage?: number;
|
|
62
|
+
* tapped?: boolean;
|
|
63
|
+
* };
|
|
64
|
+
*
|
|
65
|
+
* const game: GameDefinition<MyGameState, MyMoves, MyCardDef, MyCardMeta> = {
|
|
66
|
+
* name: 'My Card Game',
|
|
67
|
+
* zones: {
|
|
68
|
+
* hand: { id: 'hand', name: 'Hand', visibility: 'private', ordered: false },
|
|
69
|
+
* deck: { id: 'deck', name: 'Deck', visibility: 'secret', ordered: true },
|
|
70
|
+
* },
|
|
71
|
+
* setup: (players) => ({
|
|
72
|
+
* players,
|
|
73
|
+
* currentPlayer: 0,
|
|
74
|
+
* }),
|
|
75
|
+
* moves: {
|
|
76
|
+
* playCard: {
|
|
77
|
+
* condition: (state, context) => true,
|
|
78
|
+
* reducer: (draft, context) => { ... }
|
|
79
|
+
* },
|
|
80
|
+
* pass: {
|
|
81
|
+
* reducer: (draft) => { ... }
|
|
82
|
+
* }
|
|
83
|
+
* }
|
|
84
|
+
* };
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export type GameDefinition<
|
|
88
|
+
TState,
|
|
89
|
+
TMoves extends Record<string, any>,
|
|
90
|
+
TCardDefinition = any,
|
|
91
|
+
TCardMeta = any,
|
|
92
|
+
> = {
|
|
93
|
+
/**
|
|
94
|
+
* Game name for identification and display
|
|
95
|
+
*
|
|
96
|
+
* Task 10.2: Required field
|
|
97
|
+
*/
|
|
98
|
+
name: string;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Zone configuration (optional, but recommended for card games)
|
|
102
|
+
*
|
|
103
|
+
* Defines all zones used in the game.
|
|
104
|
+
* The framework will manage card locations and zone state internally.
|
|
105
|
+
*
|
|
106
|
+
* If zones are not provided, games must manage their own zone/card logic.
|
|
107
|
+
* This field enables the framework's internal zone management system.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```typescript
|
|
111
|
+
* zones: {
|
|
112
|
+
* hand: { id: 'hand', name: 'Hand', visibility: 'private', ordered: false },
|
|
113
|
+
* deck: { id: 'deck', name: 'Deck', visibility: 'secret', ordered: true },
|
|
114
|
+
* play: { id: 'play', name: 'Play Area', visibility: 'public', ordered: false },
|
|
115
|
+
* graveyard: { id: 'graveyard', name: 'Graveyard', visibility: 'public', ordered: false },
|
|
116
|
+
* }
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
zones?: Record<string, CardZoneConfig>;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Card definitions (optional)
|
|
123
|
+
*
|
|
124
|
+
* Map of card definition ID -> card data.
|
|
125
|
+
* Can be loaded dynamically or provided upfront.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```typescript
|
|
129
|
+
* cards: {
|
|
130
|
+
* 'pikachu': { id: 'pikachu', name: 'Pikachu', hp: 60, type: 'electric' },
|
|
131
|
+
* 'charizard': { id: 'charizard', name: 'Charizard', hp: 150, type: 'fire' },
|
|
132
|
+
* }
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
cards?: Record<string, TCardDefinition>;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Setup function - creates initial game state
|
|
139
|
+
*
|
|
140
|
+
* Task 10.4: Setup function signature
|
|
141
|
+
*
|
|
142
|
+
* Must be pure and deterministic:
|
|
143
|
+
* - Same players -> same initial state
|
|
144
|
+
* - No side effects
|
|
145
|
+
* - No randomness (use RNG in moves instead)
|
|
146
|
+
*
|
|
147
|
+
* @param players - Array of players in the game
|
|
148
|
+
* @returns Initial game state
|
|
149
|
+
*/
|
|
150
|
+
setup: (players: Player[]) => TState;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Moves definition - exhaustive mapping of move names to move definitions
|
|
154
|
+
*
|
|
155
|
+
* Task 10.6: GameMoveDefinitions type with exhaustive mapping
|
|
156
|
+
*
|
|
157
|
+
* Each key in TMoves must have a corresponding GameMoveDefinition.
|
|
158
|
+
* Type system enforces this at compile time.
|
|
159
|
+
*
|
|
160
|
+
* Moves receive context with zones, cards operations API, and card registry.
|
|
161
|
+
*/
|
|
162
|
+
moves: GameMoveDefinitions<TState, TMoves, TCardMeta, TCardDefinition>;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Flow definition (optional) - XState-based turn/phase/step orchestration
|
|
166
|
+
*
|
|
167
|
+
* Task 10.8: Flow configuration validation
|
|
168
|
+
*
|
|
169
|
+
* If omitted, game has no built-in flow control.
|
|
170
|
+
* Games can still progress through moves, but no automatic phase transitions.
|
|
171
|
+
*/
|
|
172
|
+
flow?: FlowDefinition<TState>;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Tracker configuration (optional)
|
|
176
|
+
*
|
|
177
|
+
* Defines boolean flags that auto-reset at turn/phase boundaries.
|
|
178
|
+
* Eliminates boilerplate for "hasDrawnThisTurn", "hasPlayedResourceThisTurn", etc.
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```typescript
|
|
182
|
+
* trackers: {
|
|
183
|
+
* perTurn: ['hasDrawn', 'hasPlayedResource', 'hasAttacked'],
|
|
184
|
+
* perPhase: {
|
|
185
|
+
* main: ['hasPlayedCard']
|
|
186
|
+
* },
|
|
187
|
+
* perPlayer: true // Default: true
|
|
188
|
+
* }
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
trackers?: {
|
|
192
|
+
/** Trackers that reset at the end of each turn */
|
|
193
|
+
perTurn?: string[];
|
|
194
|
+
/** Trackers that reset at the end of specific phases */
|
|
195
|
+
perPhase?: Record<string, string[]>;
|
|
196
|
+
/** Whether trackers are per-player or global (default: true) */
|
|
197
|
+
perPlayer?: boolean;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Game end condition (optional)
|
|
202
|
+
*
|
|
203
|
+
* Task 10.10: EndIf evaluation logic
|
|
204
|
+
*
|
|
205
|
+
* Checked after every move execution.
|
|
206
|
+
* If returns a GameEndResult, the game ends.
|
|
207
|
+
* If returns undefined, game continues.
|
|
208
|
+
*
|
|
209
|
+
* @param state - Current game state
|
|
210
|
+
* @returns GameEndResult if game ended, undefined otherwise
|
|
211
|
+
*/
|
|
212
|
+
endIf?: (state: TState) => GameEndResult | undefined;
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Player view filter (optional)
|
|
216
|
+
*
|
|
217
|
+
* Task 10.12: PlayerView function signature
|
|
218
|
+
*
|
|
219
|
+
* Filters game state to hide private information from a player.
|
|
220
|
+
* If omitted, all players see complete state.
|
|
221
|
+
*
|
|
222
|
+
* Must be pure and deterministic:
|
|
223
|
+
* - Same state + playerId -> same filtered state
|
|
224
|
+
* - No side effects
|
|
225
|
+
*
|
|
226
|
+
* @param state - Complete game state
|
|
227
|
+
* @param playerId - Player requesting the view
|
|
228
|
+
* @returns Filtered state for this player
|
|
229
|
+
*/
|
|
230
|
+
playerView?: (state: TState, playerId: string) => TState;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Telemetry hooks (optional)
|
|
234
|
+
*
|
|
235
|
+
* Callbacks for tracking game events and player actions.
|
|
236
|
+
* Hooks registered here are automatically subscribed when the engine starts.
|
|
237
|
+
*
|
|
238
|
+
* Use cases:
|
|
239
|
+
* - Analytics tracking
|
|
240
|
+
* - Error reporting
|
|
241
|
+
* - Custom logging
|
|
242
|
+
* - Performance monitoring
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* ```typescript
|
|
246
|
+
* telemetryHooks: {
|
|
247
|
+
* onPlayerAction: (event) => {
|
|
248
|
+
* analytics.track('game.move', {
|
|
249
|
+
* moveId: event.moveId,
|
|
250
|
+
* playerId: event.playerId,
|
|
251
|
+
* duration: event.duration
|
|
252
|
+
* });
|
|
253
|
+
* },
|
|
254
|
+
* onEngineError: (event) => {
|
|
255
|
+
* errorReporter.captureException(event.error, event.context);
|
|
256
|
+
* }
|
|
257
|
+
* }
|
|
258
|
+
* ```
|
|
259
|
+
*/
|
|
260
|
+
telemetryHooks?: TelemetryHooks;
|
|
261
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Game Definition Module
|
|
3
|
+
*
|
|
4
|
+
* Task 10: GameDefinition Type System
|
|
5
|
+
*
|
|
6
|
+
* Provides declarative game definition pattern with full type safety.
|
|
7
|
+
* Exports:
|
|
8
|
+
* - GameDefinition type (core definition)
|
|
9
|
+
* - MoveDefinition and MoveDefinitions types
|
|
10
|
+
* - Validation utilities
|
|
11
|
+
* - Supporting types (Player, GameEndResult, FlowDefinition)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export type {
|
|
15
|
+
GameDefinition,
|
|
16
|
+
GameEndResult,
|
|
17
|
+
Player,
|
|
18
|
+
} from "./game-definition";
|
|
19
|
+
|
|
20
|
+
export type {
|
|
21
|
+
GameMoveDefinition,
|
|
22
|
+
GameMoveDefinitions,
|
|
23
|
+
} from "./move-definitions";
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
type GameDefinitionValidationResult,
|
|
27
|
+
validateGameDefinition,
|
|
28
|
+
} from "./validation";
|