@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,800 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { createCardId, createZoneId } from "../types";
|
|
3
|
+
import { createZone } from "./zone-factory";
|
|
4
|
+
import {
|
|
5
|
+
addCard,
|
|
6
|
+
addCardToBottom,
|
|
7
|
+
addCardToTop,
|
|
8
|
+
clearZone,
|
|
9
|
+
draw,
|
|
10
|
+
findCardInZones,
|
|
11
|
+
getBottomCard,
|
|
12
|
+
getCardsInZone,
|
|
13
|
+
getTopCard,
|
|
14
|
+
getZoneSize,
|
|
15
|
+
isCardInZone,
|
|
16
|
+
mill,
|
|
17
|
+
moveCard,
|
|
18
|
+
peek,
|
|
19
|
+
removeCard,
|
|
20
|
+
reveal,
|
|
21
|
+
search,
|
|
22
|
+
shuffle,
|
|
23
|
+
} from "./zone-operations";
|
|
24
|
+
|
|
25
|
+
describe("Zone Operations", () => {
|
|
26
|
+
describe("addCard", () => {
|
|
27
|
+
it("should add card to zone", () => {
|
|
28
|
+
const zone = createZone({
|
|
29
|
+
id: createZoneId("hand"),
|
|
30
|
+
name: "Hand",
|
|
31
|
+
visibility: "private",
|
|
32
|
+
ordered: false,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const cardId = createCardId("card-1");
|
|
36
|
+
const updated = addCard(zone, cardId);
|
|
37
|
+
|
|
38
|
+
expect(updated.cards).toContain(cardId);
|
|
39
|
+
expect(updated.cards).toHaveLength(1);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should throw error if zone is at maxSize", () => {
|
|
43
|
+
const zone = createZone(
|
|
44
|
+
{
|
|
45
|
+
id: createZoneId("hand"),
|
|
46
|
+
name: "Hand",
|
|
47
|
+
visibility: "private",
|
|
48
|
+
ordered: false,
|
|
49
|
+
maxSize: 1,
|
|
50
|
+
},
|
|
51
|
+
[createCardId("card-1")],
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
expect(() => addCard(zone, createCardId("card-2"))).toThrow(
|
|
55
|
+
"Cannot add card: zone is at maximum size (1)",
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should add card to end of ordered zone", () => {
|
|
60
|
+
const zone = createZone(
|
|
61
|
+
{
|
|
62
|
+
id: createZoneId("deck"),
|
|
63
|
+
name: "Deck",
|
|
64
|
+
visibility: "secret",
|
|
65
|
+
ordered: true,
|
|
66
|
+
},
|
|
67
|
+
[createCardId("card-1"), createCardId("card-2")],
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const newCard = createCardId("card-3");
|
|
71
|
+
const updated = addCard(zone, newCard);
|
|
72
|
+
|
|
73
|
+
expect(updated.cards[2]).toBe(newCard);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should add card at specified position in ordered zone", () => {
|
|
77
|
+
const zone = createZone(
|
|
78
|
+
{
|
|
79
|
+
id: createZoneId("deck"),
|
|
80
|
+
name: "Deck",
|
|
81
|
+
visibility: "secret",
|
|
82
|
+
ordered: true,
|
|
83
|
+
},
|
|
84
|
+
[createCardId("card-1"), createCardId("card-3")],
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const newCard = createCardId("card-2");
|
|
88
|
+
const updated = addCard(zone, newCard, 1);
|
|
89
|
+
|
|
90
|
+
expect(updated.cards[1]).toBe(newCard);
|
|
91
|
+
expect(updated.cards).toHaveLength(3);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("removeCard", () => {
|
|
96
|
+
it("should remove card from zone", () => {
|
|
97
|
+
const cardId = createCardId("card-1");
|
|
98
|
+
const zone = createZone(
|
|
99
|
+
{
|
|
100
|
+
id: createZoneId("hand"),
|
|
101
|
+
name: "Hand",
|
|
102
|
+
visibility: "private",
|
|
103
|
+
ordered: false,
|
|
104
|
+
},
|
|
105
|
+
[cardId, createCardId("card-2")],
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const updated = removeCard(zone, cardId);
|
|
109
|
+
|
|
110
|
+
expect(updated.cards).not.toContain(cardId);
|
|
111
|
+
expect(updated.cards).toHaveLength(1);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("should throw error if card not in zone", () => {
|
|
115
|
+
const zone = createZone({
|
|
116
|
+
id: createZoneId("hand"),
|
|
117
|
+
name: "Hand",
|
|
118
|
+
visibility: "private",
|
|
119
|
+
ordered: false,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
expect(() => removeCard(zone, createCardId("missing"))).toThrow(
|
|
123
|
+
"Card missing not found in zone hand",
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe("moveCard", () => {
|
|
129
|
+
it("should move card from one zone to another", () => {
|
|
130
|
+
const cardId = createCardId("card-1");
|
|
131
|
+
const from = createZone(
|
|
132
|
+
{
|
|
133
|
+
id: createZoneId("hand"),
|
|
134
|
+
name: "Hand",
|
|
135
|
+
visibility: "private",
|
|
136
|
+
ordered: false,
|
|
137
|
+
},
|
|
138
|
+
[cardId],
|
|
139
|
+
);
|
|
140
|
+
const to = createZone({
|
|
141
|
+
id: createZoneId("play"),
|
|
142
|
+
name: "Play",
|
|
143
|
+
visibility: "public",
|
|
144
|
+
ordered: false,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const { fromZone, toZone } = moveCard(from, to, cardId);
|
|
148
|
+
|
|
149
|
+
expect(fromZone.cards).not.toContain(cardId);
|
|
150
|
+
expect(toZone.cards).toContain(cardId);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe("draw", () => {
|
|
155
|
+
it("should draw cards from deck to hand", () => {
|
|
156
|
+
const cards = [
|
|
157
|
+
createCardId("card-1"),
|
|
158
|
+
createCardId("card-2"),
|
|
159
|
+
createCardId("card-3"),
|
|
160
|
+
];
|
|
161
|
+
const deck = createZone(
|
|
162
|
+
{
|
|
163
|
+
id: createZoneId("deck"),
|
|
164
|
+
name: "Deck",
|
|
165
|
+
visibility: "secret",
|
|
166
|
+
ordered: true,
|
|
167
|
+
},
|
|
168
|
+
cards,
|
|
169
|
+
);
|
|
170
|
+
const hand = createZone({
|
|
171
|
+
id: createZoneId("hand"),
|
|
172
|
+
name: "Hand",
|
|
173
|
+
visibility: "private",
|
|
174
|
+
ordered: false,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const result = draw(deck, hand, 2);
|
|
178
|
+
|
|
179
|
+
expect(result.fromZone.cards).toHaveLength(1);
|
|
180
|
+
expect(result.toZone.cards).toHaveLength(2);
|
|
181
|
+
expect(result.drawnCards).toHaveLength(2);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("should draw from top of deck", () => {
|
|
185
|
+
const cards = [
|
|
186
|
+
createCardId("card-1"),
|
|
187
|
+
createCardId("card-2"),
|
|
188
|
+
createCardId("card-3"),
|
|
189
|
+
];
|
|
190
|
+
const deck = createZone(
|
|
191
|
+
{
|
|
192
|
+
id: createZoneId("deck"),
|
|
193
|
+
name: "Deck",
|
|
194
|
+
visibility: "secret",
|
|
195
|
+
ordered: true,
|
|
196
|
+
},
|
|
197
|
+
cards,
|
|
198
|
+
);
|
|
199
|
+
const hand = createZone({
|
|
200
|
+
id: createZoneId("hand"),
|
|
201
|
+
name: "Hand",
|
|
202
|
+
visibility: "private",
|
|
203
|
+
ordered: false,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const result = draw(deck, hand, 1);
|
|
207
|
+
|
|
208
|
+
expect(result.drawnCards[0]).toBe(cards[0]);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("should throw error if not enough cards to draw", () => {
|
|
212
|
+
const deck = createZone(
|
|
213
|
+
{
|
|
214
|
+
id: createZoneId("deck"),
|
|
215
|
+
name: "Deck",
|
|
216
|
+
visibility: "secret",
|
|
217
|
+
ordered: true,
|
|
218
|
+
},
|
|
219
|
+
[createCardId("card-1")],
|
|
220
|
+
);
|
|
221
|
+
const hand = createZone({
|
|
222
|
+
id: createZoneId("hand"),
|
|
223
|
+
name: "Hand",
|
|
224
|
+
visibility: "private",
|
|
225
|
+
ordered: false,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
expect(() => draw(deck, hand, 2)).toThrow(
|
|
229
|
+
"Cannot draw 2 cards: only 1 available in deck",
|
|
230
|
+
);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe("shuffle", () => {
|
|
235
|
+
it("should shuffle zone with seeded RNG", () => {
|
|
236
|
+
const cards = [
|
|
237
|
+
createCardId("card-1"),
|
|
238
|
+
createCardId("card-2"),
|
|
239
|
+
createCardId("card-3"),
|
|
240
|
+
createCardId("card-4"),
|
|
241
|
+
createCardId("card-5"),
|
|
242
|
+
];
|
|
243
|
+
const zone = createZone(
|
|
244
|
+
{
|
|
245
|
+
id: createZoneId("deck"),
|
|
246
|
+
name: "Deck",
|
|
247
|
+
visibility: "secret",
|
|
248
|
+
ordered: true,
|
|
249
|
+
},
|
|
250
|
+
cards,
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
const shuffled = shuffle(zone, "test-seed");
|
|
254
|
+
|
|
255
|
+
// Should have same cards
|
|
256
|
+
expect(shuffled.cards).toHaveLength(cards.length);
|
|
257
|
+
for (const card of cards) {
|
|
258
|
+
expect(shuffled.cards).toContain(card);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Order should be different (with high probability)
|
|
262
|
+
const orderChanged = shuffled.cards.some(
|
|
263
|
+
(card, index) => card !== cards[index],
|
|
264
|
+
);
|
|
265
|
+
expect(orderChanged).toBe(true);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("should produce same shuffle with same seed", () => {
|
|
269
|
+
const cards = [
|
|
270
|
+
createCardId("card-1"),
|
|
271
|
+
createCardId("card-2"),
|
|
272
|
+
createCardId("card-3"),
|
|
273
|
+
];
|
|
274
|
+
const zone1 = createZone(
|
|
275
|
+
{
|
|
276
|
+
id: createZoneId("deck"),
|
|
277
|
+
name: "Deck",
|
|
278
|
+
visibility: "secret",
|
|
279
|
+
ordered: true,
|
|
280
|
+
},
|
|
281
|
+
cards,
|
|
282
|
+
);
|
|
283
|
+
const zone2 = createZone(
|
|
284
|
+
{
|
|
285
|
+
id: createZoneId("deck"),
|
|
286
|
+
name: "Deck",
|
|
287
|
+
visibility: "secret",
|
|
288
|
+
ordered: true,
|
|
289
|
+
},
|
|
290
|
+
cards,
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
const shuffled1 = shuffle(zone1, "same-seed");
|
|
294
|
+
const shuffled2 = shuffle(zone2, "same-seed");
|
|
295
|
+
|
|
296
|
+
expect(shuffled1.cards).toEqual(shuffled2.cards);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
describe("search", () => {
|
|
301
|
+
it("should find cards matching filter", () => {
|
|
302
|
+
const cards = [
|
|
303
|
+
createCardId("card-1"),
|
|
304
|
+
createCardId("card-2"),
|
|
305
|
+
createCardId("card-3"),
|
|
306
|
+
];
|
|
307
|
+
const zone = createZone(
|
|
308
|
+
{
|
|
309
|
+
id: createZoneId("deck"),
|
|
310
|
+
name: "Deck",
|
|
311
|
+
visibility: "secret",
|
|
312
|
+
ordered: true,
|
|
313
|
+
},
|
|
314
|
+
cards,
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
const filter = (cardId: string) => cardId === "card-2";
|
|
318
|
+
const found = search(zone, filter);
|
|
319
|
+
|
|
320
|
+
expect(found).toHaveLength(1);
|
|
321
|
+
expect(found[0]).toBe(cards[1]);
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
describe("peek", () => {
|
|
326
|
+
it("should return top N cards without removing them", () => {
|
|
327
|
+
const cards = [
|
|
328
|
+
createCardId("card-1"),
|
|
329
|
+
createCardId("card-2"),
|
|
330
|
+
createCardId("card-3"),
|
|
331
|
+
];
|
|
332
|
+
const zone = createZone(
|
|
333
|
+
{
|
|
334
|
+
id: createZoneId("deck"),
|
|
335
|
+
name: "Deck",
|
|
336
|
+
visibility: "secret",
|
|
337
|
+
ordered: true,
|
|
338
|
+
},
|
|
339
|
+
cards,
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
const peeked = peek(zone, 2);
|
|
343
|
+
|
|
344
|
+
expect(peeked).toHaveLength(2);
|
|
345
|
+
expect(peeked[0]).toBe(cards[0]);
|
|
346
|
+
expect(peeked[1]).toBe(cards[1]);
|
|
347
|
+
expect(zone.cards).toHaveLength(3); // Original unchanged
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
describe("mill", () => {
|
|
352
|
+
it("should move cards from deck to graveyard", () => {
|
|
353
|
+
const cards = [
|
|
354
|
+
createCardId("card-1"),
|
|
355
|
+
createCardId("card-2"),
|
|
356
|
+
createCardId("card-3"),
|
|
357
|
+
];
|
|
358
|
+
const deck = createZone(
|
|
359
|
+
{
|
|
360
|
+
id: createZoneId("deck"),
|
|
361
|
+
name: "Deck",
|
|
362
|
+
visibility: "secret",
|
|
363
|
+
ordered: true,
|
|
364
|
+
},
|
|
365
|
+
cards,
|
|
366
|
+
);
|
|
367
|
+
const graveyard = createZone({
|
|
368
|
+
id: createZoneId("graveyard"),
|
|
369
|
+
name: "Graveyard",
|
|
370
|
+
visibility: "public",
|
|
371
|
+
ordered: true,
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
const result = mill(deck, graveyard, 2);
|
|
375
|
+
|
|
376
|
+
expect(result.fromZone.cards).toHaveLength(1);
|
|
377
|
+
expect(result.toZone.cards).toHaveLength(2);
|
|
378
|
+
expect(result.milledCards).toHaveLength(2);
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
describe("reveal", () => {
|
|
383
|
+
it("should mark cards as revealed", () => {
|
|
384
|
+
const cards = [createCardId("card-1"), createCardId("card-2")];
|
|
385
|
+
|
|
386
|
+
const revealed = reveal(cards);
|
|
387
|
+
|
|
388
|
+
expect(revealed).toHaveLength(2);
|
|
389
|
+
expect(revealed).toContain(cards[0]);
|
|
390
|
+
expect(revealed).toContain(cards[1]);
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
describe("Zone Queries", () => {
|
|
395
|
+
describe("getZoneSize", () => {
|
|
396
|
+
it("should return number of cards in zone", () => {
|
|
397
|
+
const zone = createZone(
|
|
398
|
+
{
|
|
399
|
+
id: createZoneId("hand"),
|
|
400
|
+
name: "Hand",
|
|
401
|
+
visibility: "private",
|
|
402
|
+
ordered: false,
|
|
403
|
+
},
|
|
404
|
+
[createCardId("card-1"), createCardId("card-2")],
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
expect(getZoneSize(zone)).toBe(2);
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
describe("getCardsInZone", () => {
|
|
412
|
+
it("should return all cards in zone", () => {
|
|
413
|
+
const cards = [createCardId("card-1"), createCardId("card-2")];
|
|
414
|
+
const zone = createZone(
|
|
415
|
+
{
|
|
416
|
+
id: createZoneId("hand"),
|
|
417
|
+
name: "Hand",
|
|
418
|
+
visibility: "private",
|
|
419
|
+
ordered: false,
|
|
420
|
+
},
|
|
421
|
+
cards,
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
expect(getCardsInZone(zone)).toEqual(cards);
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
describe("getTopCard", () => {
|
|
429
|
+
it("should return first card in ordered zone", () => {
|
|
430
|
+
const cards = [createCardId("card-1"), createCardId("card-2")];
|
|
431
|
+
const zone = createZone(
|
|
432
|
+
{
|
|
433
|
+
id: createZoneId("deck"),
|
|
434
|
+
name: "Deck",
|
|
435
|
+
visibility: "secret",
|
|
436
|
+
ordered: true,
|
|
437
|
+
},
|
|
438
|
+
cards,
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
expect(getTopCard(zone)).toBe(cards[0]);
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it("should return undefined for empty zone", () => {
|
|
445
|
+
const zone = createZone({
|
|
446
|
+
id: createZoneId("deck"),
|
|
447
|
+
name: "Deck",
|
|
448
|
+
visibility: "secret",
|
|
449
|
+
ordered: true,
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
expect(getTopCard(zone)).toBeUndefined();
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
describe("getBottomCard", () => {
|
|
457
|
+
it("should return last card in ordered zone", () => {
|
|
458
|
+
const cards = [createCardId("card-1"), createCardId("card-2")];
|
|
459
|
+
const zone = createZone(
|
|
460
|
+
{
|
|
461
|
+
id: createZoneId("deck"),
|
|
462
|
+
name: "Deck",
|
|
463
|
+
visibility: "secret",
|
|
464
|
+
ordered: true,
|
|
465
|
+
},
|
|
466
|
+
cards,
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
expect(getBottomCard(zone)).toBe(cards[1]);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it("should return undefined for empty zone", () => {
|
|
473
|
+
const zone = createZone({
|
|
474
|
+
id: createZoneId("deck"),
|
|
475
|
+
name: "Deck",
|
|
476
|
+
visibility: "secret",
|
|
477
|
+
ordered: true,
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
expect(getBottomCard(zone)).toBeUndefined();
|
|
481
|
+
});
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
describe("isCardInZone", () => {
|
|
486
|
+
it("should return true when card is in zone", () => {
|
|
487
|
+
const cardId = createCardId("card-1");
|
|
488
|
+
const zone = createZone(
|
|
489
|
+
{
|
|
490
|
+
id: createZoneId("hand"),
|
|
491
|
+
name: "Hand",
|
|
492
|
+
visibility: "private",
|
|
493
|
+
ordered: false,
|
|
494
|
+
},
|
|
495
|
+
[cardId, createCardId("card-2")],
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
expect(isCardInZone(zone, cardId)).toBe(true);
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
it("should return false when card is not in zone", () => {
|
|
502
|
+
const zone = createZone(
|
|
503
|
+
{
|
|
504
|
+
id: createZoneId("hand"),
|
|
505
|
+
name: "Hand",
|
|
506
|
+
visibility: "private",
|
|
507
|
+
ordered: false,
|
|
508
|
+
},
|
|
509
|
+
[createCardId("card-1"), createCardId("card-2")],
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
expect(isCardInZone(zone, createCardId("card-3"))).toBe(false);
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it("should return false for empty zone", () => {
|
|
516
|
+
const zone = createZone({
|
|
517
|
+
id: createZoneId("hand"),
|
|
518
|
+
name: "Hand",
|
|
519
|
+
visibility: "private",
|
|
520
|
+
ordered: false,
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
expect(isCardInZone(zone, createCardId("card-1"))).toBe(false);
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
describe("addCardToTop", () => {
|
|
528
|
+
it("should add card to top of ordered zone", () => {
|
|
529
|
+
const zone = createZone(
|
|
530
|
+
{
|
|
531
|
+
id: createZoneId("deck"),
|
|
532
|
+
name: "Deck",
|
|
533
|
+
visibility: "secret",
|
|
534
|
+
ordered: true,
|
|
535
|
+
},
|
|
536
|
+
[createCardId("card-2"), createCardId("card-3")],
|
|
537
|
+
);
|
|
538
|
+
|
|
539
|
+
const newCard = createCardId("card-1");
|
|
540
|
+
const updated = addCardToTop(zone, newCard);
|
|
541
|
+
|
|
542
|
+
expect(updated.cards[0]).toBe(newCard);
|
|
543
|
+
expect(updated.cards).toHaveLength(3);
|
|
544
|
+
expect(updated.cards[1]).toBe(createCardId("card-2"));
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
it("should add card to empty zone", () => {
|
|
548
|
+
const zone = createZone({
|
|
549
|
+
id: createZoneId("deck"),
|
|
550
|
+
name: "Deck",
|
|
551
|
+
visibility: "secret",
|
|
552
|
+
ordered: true,
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
const newCard = createCardId("card-1");
|
|
556
|
+
const updated = addCardToTop(zone, newCard);
|
|
557
|
+
|
|
558
|
+
expect(updated.cards[0]).toBe(newCard);
|
|
559
|
+
expect(updated.cards).toHaveLength(1);
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
it("should throw error if zone is at maxSize", () => {
|
|
563
|
+
const zone = createZone(
|
|
564
|
+
{
|
|
565
|
+
id: createZoneId("hand"),
|
|
566
|
+
name: "Hand",
|
|
567
|
+
visibility: "private",
|
|
568
|
+
ordered: false,
|
|
569
|
+
maxSize: 1,
|
|
570
|
+
},
|
|
571
|
+
[createCardId("card-1")],
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
expect(() => addCardToTop(zone, createCardId("card-2"))).toThrow(
|
|
575
|
+
"Cannot add card: zone is at maximum size (1)",
|
|
576
|
+
);
|
|
577
|
+
});
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
describe("addCardToBottom", () => {
|
|
581
|
+
it("should add card to bottom of ordered zone", () => {
|
|
582
|
+
const zone = createZone(
|
|
583
|
+
{
|
|
584
|
+
id: createZoneId("deck"),
|
|
585
|
+
name: "Deck",
|
|
586
|
+
visibility: "secret",
|
|
587
|
+
ordered: true,
|
|
588
|
+
},
|
|
589
|
+
[createCardId("card-1"), createCardId("card-2")],
|
|
590
|
+
);
|
|
591
|
+
|
|
592
|
+
const newCard = createCardId("card-3");
|
|
593
|
+
const updated = addCardToBottom(zone, newCard);
|
|
594
|
+
|
|
595
|
+
expect(updated.cards[2]).toBe(newCard);
|
|
596
|
+
expect(updated.cards).toHaveLength(3);
|
|
597
|
+
expect(updated.cards[0]).toBe(createCardId("card-1"));
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
it("should add card to empty zone", () => {
|
|
601
|
+
const zone = createZone({
|
|
602
|
+
id: createZoneId("deck"),
|
|
603
|
+
name: "Deck",
|
|
604
|
+
visibility: "secret",
|
|
605
|
+
ordered: true,
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
const newCard = createCardId("card-1");
|
|
609
|
+
const updated = addCardToBottom(zone, newCard);
|
|
610
|
+
|
|
611
|
+
expect(updated.cards[0]).toBe(newCard);
|
|
612
|
+
expect(updated.cards).toHaveLength(1);
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
it("should throw error if zone is at maxSize", () => {
|
|
616
|
+
const zone = createZone(
|
|
617
|
+
{
|
|
618
|
+
id: createZoneId("hand"),
|
|
619
|
+
name: "Hand",
|
|
620
|
+
visibility: "private",
|
|
621
|
+
ordered: false,
|
|
622
|
+
maxSize: 1,
|
|
623
|
+
},
|
|
624
|
+
[createCardId("card-1")],
|
|
625
|
+
);
|
|
626
|
+
|
|
627
|
+
expect(() => addCardToBottom(zone, createCardId("card-2"))).toThrow(
|
|
628
|
+
"Cannot add card: zone is at maximum size (1)",
|
|
629
|
+
);
|
|
630
|
+
});
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
describe("clearZone", () => {
|
|
634
|
+
it("should remove all cards from zone", () => {
|
|
635
|
+
const zone = createZone(
|
|
636
|
+
{
|
|
637
|
+
id: createZoneId("hand"),
|
|
638
|
+
name: "Hand",
|
|
639
|
+
visibility: "private",
|
|
640
|
+
ordered: false,
|
|
641
|
+
},
|
|
642
|
+
[
|
|
643
|
+
createCardId("card-1"),
|
|
644
|
+
createCardId("card-2"),
|
|
645
|
+
createCardId("card-3"),
|
|
646
|
+
],
|
|
647
|
+
);
|
|
648
|
+
|
|
649
|
+
const updated = clearZone(zone);
|
|
650
|
+
|
|
651
|
+
expect(updated.cards).toHaveLength(0);
|
|
652
|
+
expect(updated.cards).toEqual([]);
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
it("should handle empty zone", () => {
|
|
656
|
+
const zone = createZone({
|
|
657
|
+
id: createZoneId("hand"),
|
|
658
|
+
name: "Hand",
|
|
659
|
+
visibility: "private",
|
|
660
|
+
ordered: false,
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
const updated = clearZone(zone);
|
|
664
|
+
|
|
665
|
+
expect(updated.cards).toHaveLength(0);
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
it("should preserve zone config", () => {
|
|
669
|
+
const zone = createZone(
|
|
670
|
+
{
|
|
671
|
+
id: createZoneId("hand"),
|
|
672
|
+
name: "Hand",
|
|
673
|
+
visibility: "private",
|
|
674
|
+
ordered: false,
|
|
675
|
+
maxSize: 10,
|
|
676
|
+
},
|
|
677
|
+
[createCardId("card-1")],
|
|
678
|
+
);
|
|
679
|
+
|
|
680
|
+
const updated = clearZone(zone);
|
|
681
|
+
|
|
682
|
+
expect(updated.config).toEqual(zone.config);
|
|
683
|
+
expect(updated.config.maxSize).toBe(10);
|
|
684
|
+
});
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
describe("findCardInZones", () => {
|
|
688
|
+
it("should find card in first zone", () => {
|
|
689
|
+
const cardId = createCardId("card-1");
|
|
690
|
+
const zone1 = createZone(
|
|
691
|
+
{
|
|
692
|
+
id: createZoneId("hand"),
|
|
693
|
+
name: "Hand",
|
|
694
|
+
visibility: "private",
|
|
695
|
+
ordered: false,
|
|
696
|
+
},
|
|
697
|
+
[cardId, createCardId("card-2")],
|
|
698
|
+
);
|
|
699
|
+
const zone2 = createZone(
|
|
700
|
+
{
|
|
701
|
+
id: createZoneId("deck"),
|
|
702
|
+
name: "Deck",
|
|
703
|
+
visibility: "secret",
|
|
704
|
+
ordered: true,
|
|
705
|
+
},
|
|
706
|
+
[createCardId("card-3")],
|
|
707
|
+
);
|
|
708
|
+
|
|
709
|
+
const found = findCardInZones(cardId, [zone1, zone2]);
|
|
710
|
+
|
|
711
|
+
expect(found).toBeDefined();
|
|
712
|
+
expect(found?.config.id).toBe(createZoneId("hand"));
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
it("should find card in later zone", () => {
|
|
716
|
+
const cardId = createCardId("card-3");
|
|
717
|
+
const zone1 = createZone(
|
|
718
|
+
{
|
|
719
|
+
id: createZoneId("hand"),
|
|
720
|
+
name: "Hand",
|
|
721
|
+
visibility: "private",
|
|
722
|
+
ordered: false,
|
|
723
|
+
},
|
|
724
|
+
[createCardId("card-1")],
|
|
725
|
+
);
|
|
726
|
+
const zone2 = createZone(
|
|
727
|
+
{
|
|
728
|
+
id: createZoneId("deck"),
|
|
729
|
+
name: "Deck",
|
|
730
|
+
visibility: "secret",
|
|
731
|
+
ordered: true,
|
|
732
|
+
},
|
|
733
|
+
[createCardId("card-2"), cardId],
|
|
734
|
+
);
|
|
735
|
+
|
|
736
|
+
const found = findCardInZones(cardId, [zone1, zone2]);
|
|
737
|
+
|
|
738
|
+
expect(found).toBeDefined();
|
|
739
|
+
expect(found?.config.id).toBe(createZoneId("deck"));
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
it("should return undefined when card not in any zone", () => {
|
|
743
|
+
const zone1 = createZone(
|
|
744
|
+
{
|
|
745
|
+
id: createZoneId("hand"),
|
|
746
|
+
name: "Hand",
|
|
747
|
+
visibility: "private",
|
|
748
|
+
ordered: false,
|
|
749
|
+
},
|
|
750
|
+
[createCardId("card-1")],
|
|
751
|
+
);
|
|
752
|
+
const zone2 = createZone(
|
|
753
|
+
{
|
|
754
|
+
id: createZoneId("deck"),
|
|
755
|
+
name: "Deck",
|
|
756
|
+
visibility: "secret",
|
|
757
|
+
ordered: true,
|
|
758
|
+
},
|
|
759
|
+
[createCardId("card-2")],
|
|
760
|
+
);
|
|
761
|
+
|
|
762
|
+
const found = findCardInZones(createCardId("card-3"), [zone1, zone2]);
|
|
763
|
+
|
|
764
|
+
expect(found).toBeUndefined();
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
it("should return undefined for empty zones array", () => {
|
|
768
|
+
const found = findCardInZones(createCardId("card-1"), []);
|
|
769
|
+
|
|
770
|
+
expect(found).toBeUndefined();
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
it("should return first zone if card exists in multiple zones", () => {
|
|
774
|
+
const cardId = createCardId("card-1");
|
|
775
|
+
const zone1 = createZone(
|
|
776
|
+
{
|
|
777
|
+
id: createZoneId("hand"),
|
|
778
|
+
name: "Hand",
|
|
779
|
+
visibility: "private",
|
|
780
|
+
ordered: false,
|
|
781
|
+
},
|
|
782
|
+
[cardId],
|
|
783
|
+
);
|
|
784
|
+
const zone2 = createZone(
|
|
785
|
+
{
|
|
786
|
+
id: createZoneId("deck"),
|
|
787
|
+
name: "Deck",
|
|
788
|
+
visibility: "secret",
|
|
789
|
+
ordered: true,
|
|
790
|
+
},
|
|
791
|
+
[cardId],
|
|
792
|
+
);
|
|
793
|
+
|
|
794
|
+
const found = findCardInZones(cardId, [zone1, zone2]);
|
|
795
|
+
|
|
796
|
+
expect(found).toBeDefined();
|
|
797
|
+
expect(found?.config.id).toBe(createZoneId("hand"));
|
|
798
|
+
});
|
|
799
|
+
});
|
|
800
|
+
});
|