@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,427 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import type { Draft } from "immer";
|
|
3
|
+
import type {
|
|
4
|
+
FlowContext,
|
|
5
|
+
FlowDefinition,
|
|
6
|
+
GameSegmentDefinition,
|
|
7
|
+
PhaseDefinition,
|
|
8
|
+
StepDefinition,
|
|
9
|
+
TurnDefinition,
|
|
10
|
+
} from "../flow-definition";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Task 9.1: Write tests for FlowDefinition type
|
|
14
|
+
*
|
|
15
|
+
* Tests verify the structure and behavior of FlowDefinition.
|
|
16
|
+
* Following the user's requirements for flexible turn/phase/step progression.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
type TestGameState = {
|
|
20
|
+
currentPlayer: number;
|
|
21
|
+
players: Array<{ id: string; ready: boolean }>;
|
|
22
|
+
turnCount: number;
|
|
23
|
+
phase: string;
|
|
24
|
+
step?: string;
|
|
25
|
+
phaseCount: number;
|
|
26
|
+
stepCount: number;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
describe("FlowDefinition Type", () => {
|
|
30
|
+
describe("FlowContext API", () => {
|
|
31
|
+
it("should provide rich context beyond just state", () => {
|
|
32
|
+
// Task 9.1: FlowContext should provide:
|
|
33
|
+
// - state access (draft for mutations)
|
|
34
|
+
// - flow control methods (endPhase, endStep, endTurn, endGameSegment)
|
|
35
|
+
// - current flow information (current phase, step, turn, player, game segment)
|
|
36
|
+
|
|
37
|
+
const mockContext: FlowContext<TestGameState> = {
|
|
38
|
+
state: {} as Draft<TestGameState>,
|
|
39
|
+
game: {
|
|
40
|
+
setOTP: () => {},
|
|
41
|
+
getOTP: () => undefined,
|
|
42
|
+
setChoosingFirstPlayer: () => {},
|
|
43
|
+
getChoosingFirstPlayer: () => undefined,
|
|
44
|
+
setPendingMulligan: () => {},
|
|
45
|
+
getPendingMulligan: () => [],
|
|
46
|
+
addPendingMulligan: () => {},
|
|
47
|
+
removePendingMulligan: () => {},
|
|
48
|
+
},
|
|
49
|
+
zones: {
|
|
50
|
+
moveCard: () => {},
|
|
51
|
+
getCardsInZone: () => [],
|
|
52
|
+
shuffleZone: () => {},
|
|
53
|
+
getCardZone: () => undefined,
|
|
54
|
+
drawCards: () => [],
|
|
55
|
+
mulligan: () => {},
|
|
56
|
+
bulkMove: () => [],
|
|
57
|
+
createDeck: () => [],
|
|
58
|
+
},
|
|
59
|
+
cards: {
|
|
60
|
+
getCardMeta: () => ({}),
|
|
61
|
+
updateCardMeta: () => {},
|
|
62
|
+
setCardMeta: () => {},
|
|
63
|
+
getCardOwner: () => undefined,
|
|
64
|
+
queryCards: () => [],
|
|
65
|
+
},
|
|
66
|
+
endGameSegment: () => {},
|
|
67
|
+
endPhase: () => {},
|
|
68
|
+
endStep: () => {},
|
|
69
|
+
endTurn: () => {},
|
|
70
|
+
getCurrentGameSegment: () => "mainGame",
|
|
71
|
+
getCurrentPhase: () => "main",
|
|
72
|
+
getCurrentStep: () => undefined,
|
|
73
|
+
getCurrentPlayer: () => "player-1",
|
|
74
|
+
getTurnNumber: () => 1,
|
|
75
|
+
setCurrentPlayer: () => {},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
expect(mockContext.endGameSegment).toBeDefined();
|
|
79
|
+
expect(mockContext.endPhase).toBeDefined();
|
|
80
|
+
expect(mockContext.endStep).toBeDefined();
|
|
81
|
+
expect(mockContext.endTurn).toBeDefined();
|
|
82
|
+
expect(mockContext.getCurrentPhase()).toBe("main");
|
|
83
|
+
expect(mockContext.getCurrentGameSegment()).toBe("mainGame");
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("TurnDefinition", () => {
|
|
88
|
+
it("should support lifecycle hooks with FlowContext", () => {
|
|
89
|
+
// Task 9.5: Lifecycle hooks receive FlowContext, not just state
|
|
90
|
+
const turnDef: TurnDefinition<TestGameState> = {
|
|
91
|
+
onBegin: (context) => {
|
|
92
|
+
// Can mutate state
|
|
93
|
+
context.state.turnCount += 1;
|
|
94
|
+
// Can access flow information
|
|
95
|
+
expect(context.getCurrentPlayer).toBeDefined();
|
|
96
|
+
},
|
|
97
|
+
onEnd: (context) => {
|
|
98
|
+
context.state.turnCount += 1;
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
expect(turnDef.onBegin).toBeDefined();
|
|
103
|
+
expect(turnDef.onEnd).toBeDefined();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("should support automatic turn end with endIf", () => {
|
|
107
|
+
// Task 9.7: endIf for automatic transitions
|
|
108
|
+
const turnDef: TurnDefinition<TestGameState> = {
|
|
109
|
+
endIf: (context) => {
|
|
110
|
+
// Can check state and return boolean
|
|
111
|
+
return context.state.phaseCount >= 4;
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
expect(turnDef.endIf).toBeDefined();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("should support programmatic turn end", () => {
|
|
119
|
+
// User requirement: "We should be able to end the segment/turn/phase programmatically"
|
|
120
|
+
const turnDef: TurnDefinition<TestGameState> = {
|
|
121
|
+
onBegin: (context) => {
|
|
122
|
+
// Can call endTurn() programmatically from actions
|
|
123
|
+
if (context.state.players.length === 0) {
|
|
124
|
+
context.endTurn();
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
expect(turnDef.onBegin).toBeDefined();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("should define phases that progress sequentially", () => {
|
|
133
|
+
// User requirement: "phases, when the current phase ends, the next phase from the same player will start"
|
|
134
|
+
const turnDef: TurnDefinition<TestGameState> = {
|
|
135
|
+
phases: {
|
|
136
|
+
ready: {
|
|
137
|
+
order: 0,
|
|
138
|
+
next: "draw",
|
|
139
|
+
onEnd: (context) => {
|
|
140
|
+
context.state.phase = "draw";
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
draw: {
|
|
144
|
+
order: 1,
|
|
145
|
+
next: "main",
|
|
146
|
+
},
|
|
147
|
+
main: {
|
|
148
|
+
order: 2,
|
|
149
|
+
next: "end",
|
|
150
|
+
},
|
|
151
|
+
end: {
|
|
152
|
+
order: 3,
|
|
153
|
+
next: undefined, // No next phase, turn ends
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
expect(turnDef.phases).toBeDefined();
|
|
159
|
+
expect(turnDef.phases?.ready.next).toBe("draw");
|
|
160
|
+
expect(turnDef.phases?.end.next).toBeUndefined();
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe("PhaseDefinition", () => {
|
|
165
|
+
it("should support lifecycle hooks", () => {
|
|
166
|
+
const phaseDef: PhaseDefinition<TestGameState> = {
|
|
167
|
+
order: 0,
|
|
168
|
+
onBegin: (context) => {
|
|
169
|
+
context.state.phaseCount += 1;
|
|
170
|
+
},
|
|
171
|
+
onEnd: (context) => {
|
|
172
|
+
context.state.phase = "completed";
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
expect(phaseDef.onBegin).toBeDefined();
|
|
177
|
+
expect(phaseDef.onEnd).toBeDefined();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("should support automatic phase end", () => {
|
|
181
|
+
const phaseDef: PhaseDefinition<TestGameState> = {
|
|
182
|
+
order: 0,
|
|
183
|
+
endIf: (context) => {
|
|
184
|
+
// Phase automatically ends when condition is true
|
|
185
|
+
return context.state.players.every((p) => p.ready);
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
expect(phaseDef.endIf).toBeDefined();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("should support programmatic phase end", () => {
|
|
193
|
+
const phaseDef: PhaseDefinition<TestGameState> = {
|
|
194
|
+
order: 0,
|
|
195
|
+
onBegin: (context) => {
|
|
196
|
+
// Can call endPhase() from within phase
|
|
197
|
+
if (context.state.players.length === 0) {
|
|
198
|
+
context.endPhase();
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
expect(phaseDef.onBegin).toBeDefined();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("should define steps with custom progression", () => {
|
|
207
|
+
// User requirement: "For steps, it's a bit different... combat has different steps"
|
|
208
|
+
const phaseDef: PhaseDefinition<TestGameState> = {
|
|
209
|
+
order: 2,
|
|
210
|
+
steps: {
|
|
211
|
+
declare: {
|
|
212
|
+
order: 0,
|
|
213
|
+
next: "target",
|
|
214
|
+
},
|
|
215
|
+
target: {
|
|
216
|
+
order: 1,
|
|
217
|
+
next: "damage",
|
|
218
|
+
},
|
|
219
|
+
damage: {
|
|
220
|
+
order: 2,
|
|
221
|
+
next: undefined, // Ends the combat phase
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
expect(phaseDef.steps).toBeDefined();
|
|
227
|
+
expect(phaseDef.steps?.declare.next).toBe("target");
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe("StepDefinition", () => {
|
|
232
|
+
it("should support lifecycle hooks", () => {
|
|
233
|
+
const stepDef: StepDefinition<TestGameState> = {
|
|
234
|
+
order: 0,
|
|
235
|
+
onBegin: (context) => {
|
|
236
|
+
context.state.stepCount += 1;
|
|
237
|
+
},
|
|
238
|
+
onEnd: (context) => {
|
|
239
|
+
context.state.step = undefined;
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
expect(stepDef.onBegin).toBeDefined();
|
|
244
|
+
expect(stepDef.onEnd).toBeDefined();
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("should support automatic step end", () => {
|
|
248
|
+
const stepDef: StepDefinition<TestGameState> = {
|
|
249
|
+
order: 0,
|
|
250
|
+
endIf: (context) => {
|
|
251
|
+
return context.state.stepCount >= 3;
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
expect(stepDef.endIf).toBeDefined();
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("should support programmatic step end", () => {
|
|
259
|
+
const stepDef: StepDefinition<TestGameState> = {
|
|
260
|
+
order: 0,
|
|
261
|
+
onBegin: (context) => {
|
|
262
|
+
if (context.state.players.length === 0) {
|
|
263
|
+
context.endStep();
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
expect(stepDef.onBegin).toBeDefined();
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe("FlowDefinition", () => {
|
|
273
|
+
it("should define complete game flow", () => {
|
|
274
|
+
const flow: FlowDefinition<TestGameState> = {
|
|
275
|
+
gameSegments: {
|
|
276
|
+
mainGame: {
|
|
277
|
+
order: 1,
|
|
278
|
+
turn: {
|
|
279
|
+
onBegin: (context) => {
|
|
280
|
+
context.state.currentPlayer =
|
|
281
|
+
(context.state.currentPlayer + 1) %
|
|
282
|
+
context.state.players.length;
|
|
283
|
+
},
|
|
284
|
+
phases: {
|
|
285
|
+
ready: {
|
|
286
|
+
order: 0,
|
|
287
|
+
next: "draw",
|
|
288
|
+
},
|
|
289
|
+
draw: {
|
|
290
|
+
order: 1,
|
|
291
|
+
next: "main",
|
|
292
|
+
},
|
|
293
|
+
main: {
|
|
294
|
+
order: 2,
|
|
295
|
+
next: "end",
|
|
296
|
+
},
|
|
297
|
+
end: {
|
|
298
|
+
order: 3,
|
|
299
|
+
next: undefined,
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
expect(flow.gameSegments).toBeDefined();
|
|
308
|
+
expect(flow.gameSegments.mainGame).toBeDefined();
|
|
309
|
+
expect(flow.gameSegments.mainGame.turn.phases).toBeDefined();
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it("should support default progression behavior", () => {
|
|
313
|
+
// User requirement: "We should have defaults, but we should also be able to customize them"
|
|
314
|
+
const flowWithDefaults: FlowDefinition<TestGameState> = {
|
|
315
|
+
gameSegments: {
|
|
316
|
+
mainGame: {
|
|
317
|
+
order: 1,
|
|
318
|
+
turn: {
|
|
319
|
+
// Uses default: when turn ends, next player starts their turn
|
|
320
|
+
phases: {
|
|
321
|
+
main: {
|
|
322
|
+
order: 0,
|
|
323
|
+
// Uses default: phases progress sequentially by order
|
|
324
|
+
next: undefined,
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
expect(flowWithDefaults.gameSegments.mainGame.turn).toBeDefined();
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it("should support custom progression behavior", () => {
|
|
336
|
+
const flowWithCustom: FlowDefinition<TestGameState> = {
|
|
337
|
+
gameSegments: {
|
|
338
|
+
mainGame: {
|
|
339
|
+
order: 1,
|
|
340
|
+
turn: {
|
|
341
|
+
onEnd: (context) => {
|
|
342
|
+
// Custom logic: maybe skip players, go back to first player, etc.
|
|
343
|
+
context.state.currentPlayer = 0;
|
|
344
|
+
},
|
|
345
|
+
phases: {
|
|
346
|
+
main: {
|
|
347
|
+
order: 0,
|
|
348
|
+
next: "combat",
|
|
349
|
+
steps: {
|
|
350
|
+
declare: {
|
|
351
|
+
order: 0,
|
|
352
|
+
next: "resolve",
|
|
353
|
+
onEnd: (context) => {
|
|
354
|
+
// Custom step transition logic
|
|
355
|
+
if (context.state.stepCount > 5) {
|
|
356
|
+
context.endPhase(); // Skip remaining steps
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
resolve: {
|
|
361
|
+
order: 1,
|
|
362
|
+
next: undefined,
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
combat: {
|
|
367
|
+
order: 1,
|
|
368
|
+
next: undefined,
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
expect(flowWithCustom.gameSegments.mainGame.turn.onEnd).toBeDefined();
|
|
377
|
+
expect(
|
|
378
|
+
flowWithCustom.gameSegments.mainGame.turn.phases?.main.steps,
|
|
379
|
+
).toBeDefined();
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
describe("Type Safety", () => {
|
|
384
|
+
it("should enforce correct generic state type", () => {
|
|
385
|
+
// TypeScript compile-time test
|
|
386
|
+
const flow: FlowDefinition<TestGameState> = {
|
|
387
|
+
gameSegments: {
|
|
388
|
+
mainGame: {
|
|
389
|
+
order: 1,
|
|
390
|
+
turn: {
|
|
391
|
+
onBegin: (context) => {
|
|
392
|
+
// context.state should be Draft<TestGameState>
|
|
393
|
+
context.state.turnCount += 1;
|
|
394
|
+
context.state.currentPlayer = 0;
|
|
395
|
+
// @ts-expect-error - nonexistent property should error
|
|
396
|
+
context.state.nonExistent = true;
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
expect(flow).toBeDefined();
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it("should provide type-safe FlowContext", () => {
|
|
407
|
+
const phaseDef: PhaseDefinition<TestGameState> = {
|
|
408
|
+
order: 0,
|
|
409
|
+
onBegin: (context) => {
|
|
410
|
+
// All FlowContext methods should be type-safe
|
|
411
|
+
const phase: string | undefined = context.getCurrentPhase();
|
|
412
|
+
const player: string = context.getCurrentPlayer();
|
|
413
|
+
const turn: number = context.getTurnNumber();
|
|
414
|
+
|
|
415
|
+
expect(typeof player).toBe("string");
|
|
416
|
+
expect(typeof turn).toBe("number");
|
|
417
|
+
// Phase can be undefined in some contexts
|
|
418
|
+
if (phase) {
|
|
419
|
+
expect(typeof phase).toBe("string");
|
|
420
|
+
}
|
|
421
|
+
},
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
expect(phaseDef).toBeDefined();
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
});
|