@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,366 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import type { GameDefinition } from "../../game-definition/game-definition";
|
|
3
|
+
import type { GameMoveDefinitions } from "../../game-definition/move-definitions";
|
|
4
|
+
import { createPlayerId } from "../../types";
|
|
5
|
+
import { RuleEngine } from "../rule-engine";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Task 11: Rule Engine Core Tests
|
|
9
|
+
*
|
|
10
|
+
* Tests verify the RuleEngine integrates all systems:
|
|
11
|
+
* - GameDefinition initialization
|
|
12
|
+
* - State management
|
|
13
|
+
* - Move execution with validation
|
|
14
|
+
* - Player views
|
|
15
|
+
* - History tracking
|
|
16
|
+
* - Patch generation
|
|
17
|
+
* - RNG integration
|
|
18
|
+
* - Flow orchestration
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
// Test game state
|
|
22
|
+
type TestGameState = {
|
|
23
|
+
players: Array<{ id: string; name: string; score: number; hand: string[] }>;
|
|
24
|
+
currentPlayerIndex: number;
|
|
25
|
+
deck: string[];
|
|
26
|
+
phase: "setup" | "draw" | "play" | "ended";
|
|
27
|
+
turnNumber: number;
|
|
28
|
+
winner?: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Test moves
|
|
32
|
+
type TestMoves = {
|
|
33
|
+
drawCard: Record<string, never>;
|
|
34
|
+
playCard: { cardId: string };
|
|
35
|
+
nextPhase: Record<string, never>;
|
|
36
|
+
endGame: { winnerId: string };
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
describe("RuleEngine - Constructor", () => {
|
|
40
|
+
describe("Task 11.1, 11.2: Constructor and Initialization", () => {
|
|
41
|
+
it("should initialize with GameDefinition", () => {
|
|
42
|
+
const moves: GameMoveDefinitions<TestGameState, TestMoves> = {
|
|
43
|
+
drawCard: {
|
|
44
|
+
reducer: (draft) => {
|
|
45
|
+
const player = draft.players[draft.currentPlayerIndex];
|
|
46
|
+
if (player && draft.deck.length > 0) {
|
|
47
|
+
const card = draft.deck.pop();
|
|
48
|
+
if (card) {
|
|
49
|
+
player.hand.push(card);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
playCard: {
|
|
55
|
+
condition: (state, context) => {
|
|
56
|
+
const player = state.players[state.currentPlayerIndex];
|
|
57
|
+
const cardId = context.params?.cardId as string;
|
|
58
|
+
return player?.hand.includes(cardId) ?? false;
|
|
59
|
+
},
|
|
60
|
+
reducer: (draft, context) => {
|
|
61
|
+
const player = draft.players[draft.currentPlayerIndex];
|
|
62
|
+
const cardId = context.params?.cardId as string;
|
|
63
|
+
if (player && cardId) {
|
|
64
|
+
const index = player.hand.indexOf(cardId);
|
|
65
|
+
if (index >= 0) {
|
|
66
|
+
player.hand.splice(index, 1);
|
|
67
|
+
player.score += 1;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
nextPhase: {
|
|
73
|
+
reducer: (draft) => {
|
|
74
|
+
if (draft.phase === "setup") draft.phase = "draw";
|
|
75
|
+
else if (draft.phase === "draw") draft.phase = "play";
|
|
76
|
+
else if (draft.phase === "play") draft.phase = "ended";
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
endGame: {
|
|
80
|
+
reducer: (draft, context) => {
|
|
81
|
+
draft.phase = "ended";
|
|
82
|
+
draft.winner = context.params?.winnerId as string;
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const gameDef: GameDefinition<TestGameState, TestMoves> = {
|
|
88
|
+
name: "Test Card Game",
|
|
89
|
+
setup: (players) => ({
|
|
90
|
+
players: players.map((p) => ({
|
|
91
|
+
id: p.id,
|
|
92
|
+
name: p.name || "Player",
|
|
93
|
+
score: 0,
|
|
94
|
+
hand: [],
|
|
95
|
+
})),
|
|
96
|
+
currentPlayerIndex: 0,
|
|
97
|
+
deck: ["card1", "card2", "card3", "card4"],
|
|
98
|
+
phase: "setup",
|
|
99
|
+
turnNumber: 1,
|
|
100
|
+
}),
|
|
101
|
+
moves,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const players = [
|
|
105
|
+
{ id: createPlayerId("p1"), name: "Alice" },
|
|
106
|
+
{ id: createPlayerId("p2"), name: "Bob" },
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
const engine = new RuleEngine(gameDef, players);
|
|
110
|
+
|
|
111
|
+
expect(engine).toBeDefined();
|
|
112
|
+
expect(engine.getState()).toBeDefined();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("should call setup function to initialize state", () => {
|
|
116
|
+
const moves: GameMoveDefinitions<TestGameState, TestMoves> = {
|
|
117
|
+
drawCard: { reducer: () => {} },
|
|
118
|
+
playCard: { reducer: () => {} },
|
|
119
|
+
nextPhase: { reducer: () => {} },
|
|
120
|
+
endGame: { reducer: () => {} },
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const gameDef: GameDefinition<TestGameState, TestMoves> = {
|
|
124
|
+
name: "Test Game",
|
|
125
|
+
setup: (players) => ({
|
|
126
|
+
players: players.map((p) => ({
|
|
127
|
+
id: p.id,
|
|
128
|
+
name: p.name || "Player",
|
|
129
|
+
score: 0,
|
|
130
|
+
hand: [],
|
|
131
|
+
})),
|
|
132
|
+
currentPlayerIndex: 0,
|
|
133
|
+
deck: ["a", "b", "c"],
|
|
134
|
+
phase: "setup",
|
|
135
|
+
turnNumber: 1,
|
|
136
|
+
}),
|
|
137
|
+
moves,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const players = [
|
|
141
|
+
{ id: createPlayerId("p1"), name: "Alice" },
|
|
142
|
+
{ id: createPlayerId("p2"), name: "Bob" },
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
const engine = new RuleEngine(gameDef, players);
|
|
146
|
+
const state = engine.getState();
|
|
147
|
+
|
|
148
|
+
expect(state.players).toHaveLength(2);
|
|
149
|
+
expect(state.players[0]?.id).toBe(createPlayerId("p1"));
|
|
150
|
+
expect(state.deck).toEqual(["a", "b", "c"]);
|
|
151
|
+
expect(state.phase).toBe("setup");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("should accept optional RNG seed", () => {
|
|
155
|
+
const moves: GameMoveDefinitions<TestGameState, TestMoves> = {
|
|
156
|
+
drawCard: { reducer: () => {} },
|
|
157
|
+
playCard: { reducer: () => {} },
|
|
158
|
+
nextPhase: { reducer: () => {} },
|
|
159
|
+
endGame: { reducer: () => {} },
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const gameDef: GameDefinition<TestGameState, TestMoves> = {
|
|
163
|
+
name: "Test Game",
|
|
164
|
+
setup: (players) => ({
|
|
165
|
+
players: players.map((p) => ({
|
|
166
|
+
id: p.id,
|
|
167
|
+
name: p.name || "Player",
|
|
168
|
+
score: 0,
|
|
169
|
+
hand: [],
|
|
170
|
+
})),
|
|
171
|
+
currentPlayerIndex: 0,
|
|
172
|
+
deck: [],
|
|
173
|
+
phase: "setup",
|
|
174
|
+
turnNumber: 1,
|
|
175
|
+
}),
|
|
176
|
+
moves,
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const players = [
|
|
180
|
+
{ id: createPlayerId("p1"), name: "Alice" },
|
|
181
|
+
{ id: createPlayerId("p2"), name: "Bob" },
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
const engine = new RuleEngine(gameDef, players, {
|
|
185
|
+
seed: "test-seed-123",
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
expect(engine).toBeDefined();
|
|
189
|
+
expect(engine.getState()).toBeDefined();
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe("RuleEngine - State Access", () => {
|
|
195
|
+
describe("Task 11.3, 11.4: getState Method", () => {
|
|
196
|
+
it("should return current game state", () => {
|
|
197
|
+
const moves: GameMoveDefinitions<TestGameState, TestMoves> = {
|
|
198
|
+
drawCard: { reducer: () => {} },
|
|
199
|
+
playCard: { reducer: () => {} },
|
|
200
|
+
nextPhase: { reducer: () => {} },
|
|
201
|
+
endGame: { reducer: () => {} },
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const gameDef: GameDefinition<TestGameState, TestMoves> = {
|
|
205
|
+
name: "Test Game",
|
|
206
|
+
setup: (players) => ({
|
|
207
|
+
players: players.map((p) => ({
|
|
208
|
+
id: p.id,
|
|
209
|
+
name: p.name || "Player",
|
|
210
|
+
score: 0,
|
|
211
|
+
hand: [],
|
|
212
|
+
})),
|
|
213
|
+
currentPlayerIndex: 0,
|
|
214
|
+
deck: ["card1", "card2"],
|
|
215
|
+
phase: "setup",
|
|
216
|
+
turnNumber: 1,
|
|
217
|
+
}),
|
|
218
|
+
moves,
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const players = [
|
|
222
|
+
{ id: createPlayerId("p1"), name: "Alice" },
|
|
223
|
+
{ id: createPlayerId("p2"), name: "Bob" },
|
|
224
|
+
];
|
|
225
|
+
|
|
226
|
+
const engine = new RuleEngine(gameDef, players);
|
|
227
|
+
const state = engine.getState();
|
|
228
|
+
|
|
229
|
+
expect(state.players).toHaveLength(2);
|
|
230
|
+
expect(state.deck).toEqual(["card1", "card2"]);
|
|
231
|
+
expect(state.phase).toBe("setup");
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("should return immutable state (modifications don't affect engine)", () => {
|
|
235
|
+
const moves: GameMoveDefinitions<TestGameState, TestMoves> = {
|
|
236
|
+
drawCard: { reducer: () => {} },
|
|
237
|
+
playCard: { reducer: () => {} },
|
|
238
|
+
nextPhase: { reducer: () => {} },
|
|
239
|
+
endGame: { reducer: () => {} },
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const gameDef: GameDefinition<TestGameState, TestMoves> = {
|
|
243
|
+
name: "Test Game",
|
|
244
|
+
setup: (players) => ({
|
|
245
|
+
players: players.map((p) => ({
|
|
246
|
+
id: p.id,
|
|
247
|
+
name: p.name || "Player",
|
|
248
|
+
score: 42,
|
|
249
|
+
hand: [],
|
|
250
|
+
})),
|
|
251
|
+
currentPlayerIndex: 0,
|
|
252
|
+
deck: [],
|
|
253
|
+
phase: "setup",
|
|
254
|
+
turnNumber: 1,
|
|
255
|
+
}),
|
|
256
|
+
moves,
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const players = [
|
|
260
|
+
{ id: createPlayerId("p1"), name: "Alice" },
|
|
261
|
+
{ id: createPlayerId("p2"), name: "Bob" },
|
|
262
|
+
];
|
|
263
|
+
|
|
264
|
+
const engine = new RuleEngine(gameDef, players);
|
|
265
|
+
const state1 = engine.getState();
|
|
266
|
+
|
|
267
|
+
// Try to mutate the returned state
|
|
268
|
+
state1.players[0]!.score = 999;
|
|
269
|
+
state1.deck.push("hacked-card");
|
|
270
|
+
|
|
271
|
+
// Engine state should be unchanged
|
|
272
|
+
const state2 = engine.getState();
|
|
273
|
+
expect(state2.players[0]?.score).toBe(42);
|
|
274
|
+
expect(state2.deck).toEqual([]);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
describe("Task 11.5, 11.6: getPlayerView Method", () => {
|
|
279
|
+
it("should return full state when no playerView defined", () => {
|
|
280
|
+
const moves: GameMoveDefinitions<TestGameState, TestMoves> = {
|
|
281
|
+
drawCard: { reducer: () => {} },
|
|
282
|
+
playCard: { reducer: () => {} },
|
|
283
|
+
nextPhase: { reducer: () => {} },
|
|
284
|
+
endGame: { reducer: () => {} },
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const gameDef: GameDefinition<TestGameState, TestMoves> = {
|
|
288
|
+
name: "Test Game",
|
|
289
|
+
setup: (players) => ({
|
|
290
|
+
players: players.map((p) => ({
|
|
291
|
+
id: p.id,
|
|
292
|
+
name: p.name || "Player",
|
|
293
|
+
score: 0,
|
|
294
|
+
hand: [],
|
|
295
|
+
})),
|
|
296
|
+
currentPlayerIndex: 0,
|
|
297
|
+
deck: ["secret1", "secret2"],
|
|
298
|
+
phase: "setup",
|
|
299
|
+
turnNumber: 1,
|
|
300
|
+
}),
|
|
301
|
+
moves,
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const players = [
|
|
305
|
+
{ id: createPlayerId("p1"), name: "Alice" },
|
|
306
|
+
{ id: createPlayerId("p2"), name: "Bob" },
|
|
307
|
+
];
|
|
308
|
+
|
|
309
|
+
const engine = new RuleEngine(gameDef, players);
|
|
310
|
+
const view = engine.getPlayerView(createPlayerId("p1"));
|
|
311
|
+
|
|
312
|
+
expect(view.deck).toEqual(["secret1", "secret2"]);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("should filter state using playerView function", () => {
|
|
316
|
+
const moves: GameMoveDefinitions<TestGameState, TestMoves> = {
|
|
317
|
+
drawCard: { reducer: () => {} },
|
|
318
|
+
playCard: { reducer: () => {} },
|
|
319
|
+
nextPhase: { reducer: () => {} },
|
|
320
|
+
endGame: { reducer: () => {} },
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
const gameDef: GameDefinition<TestGameState, TestMoves> = {
|
|
324
|
+
name: "Test Game",
|
|
325
|
+
setup: (players) => ({
|
|
326
|
+
players: players.map((p, i) => ({
|
|
327
|
+
id: p.id,
|
|
328
|
+
name: p.name || "Player",
|
|
329
|
+
score: i * 10,
|
|
330
|
+
hand: [`hand-${i}-1`, `hand-${i}-2`],
|
|
331
|
+
})),
|
|
332
|
+
currentPlayerIndex: 0,
|
|
333
|
+
deck: ["secret1", "secret2"],
|
|
334
|
+
phase: "setup",
|
|
335
|
+
turnNumber: 1,
|
|
336
|
+
}),
|
|
337
|
+
moves,
|
|
338
|
+
playerView: (state, playerId) => ({
|
|
339
|
+
...state,
|
|
340
|
+
// Hide other players' hands
|
|
341
|
+
players: state.players.map((p) => ({
|
|
342
|
+
...p,
|
|
343
|
+
hand: p.id === playerId ? p.hand : [],
|
|
344
|
+
})),
|
|
345
|
+
// Hide deck contents
|
|
346
|
+
deck: [],
|
|
347
|
+
}),
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
const players = [
|
|
351
|
+
{ id: createPlayerId("p1"), name: "Alice" },
|
|
352
|
+
{ id: createPlayerId("p2"), name: "Bob" },
|
|
353
|
+
];
|
|
354
|
+
|
|
355
|
+
const engine = new RuleEngine(gameDef, players);
|
|
356
|
+
const p1View = engine.getPlayerView(createPlayerId("p1"));
|
|
357
|
+
|
|
358
|
+
// P1 should see their own hand
|
|
359
|
+
expect(p1View.players[0]?.hand).toEqual(["hand-0-1", "hand-0-2"]);
|
|
360
|
+
// But not P2's hand
|
|
361
|
+
expect(p1View.players[1]?.hand).toEqual([]);
|
|
362
|
+
// Deck should be hidden
|
|
363
|
+
expect(p1View.deck).toEqual([]);
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export {
|
|
2
|
+
type ClientState,
|
|
3
|
+
MultiplayerEngine,
|
|
4
|
+
type MultiplayerEngineOptions,
|
|
5
|
+
type MultiplayerMode,
|
|
6
|
+
type PatchBroadcast,
|
|
7
|
+
} from "./multiplayer-engine";
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
type MoveExecutionResult,
|
|
11
|
+
type ReplayHistoryEntry,
|
|
12
|
+
RuleEngine,
|
|
13
|
+
type RuleEngineOptions,
|
|
14
|
+
} from "./rule-engine";
|