@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,128 @@
|
|
|
1
|
+
import type { CardId, PlayerId } from "../types";
|
|
2
|
+
import type { Zone } from "./zone";
|
|
3
|
+
import { moveCard } from "./zone-operations";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates a record of zones for each player with optional initialization
|
|
7
|
+
* @param players - Array of player IDs
|
|
8
|
+
* @param initialValue - Optional factory function to create initial value for each player
|
|
9
|
+
* @returns Record mapping each player to their value
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* // Create empty arrays for each player
|
|
14
|
+
* const hands = createPlayerZones(players);
|
|
15
|
+
*
|
|
16
|
+
* // Create with custom initial values
|
|
17
|
+
* const decks = createPlayerZones(players, () => []);
|
|
18
|
+
*
|
|
19
|
+
* // Create with complex initial values
|
|
20
|
+
* const zones = createPlayerZones(players, () => ({
|
|
21
|
+
* hand: [],
|
|
22
|
+
* deck: [],
|
|
23
|
+
* graveyard: []
|
|
24
|
+
* }));
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function createPlayerZones<T>(
|
|
28
|
+
players: PlayerId[],
|
|
29
|
+
initialValue?: () => T,
|
|
30
|
+
): Record<PlayerId, T> {
|
|
31
|
+
const zones = {} as Record<PlayerId, T>;
|
|
32
|
+
|
|
33
|
+
for (const playerId of players) {
|
|
34
|
+
zones[playerId] = initialValue ? initialValue() : (undefined as T);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return zones;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Moves a card between zones in a flat zone state object
|
|
42
|
+
* @param state - Object containing zones as properties
|
|
43
|
+
* @param fromZoneKey - Key of the source zone in state
|
|
44
|
+
* @param toZoneKey - Key of the destination zone in state
|
|
45
|
+
* @param cardId - Card to move
|
|
46
|
+
* @param position - Optional position in destination zone
|
|
47
|
+
* @returns New state object with updated zones
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* const state = {
|
|
52
|
+
* hand: createZone(...),
|
|
53
|
+
* deck: createZone(...),
|
|
54
|
+
* };
|
|
55
|
+
*
|
|
56
|
+
* // Move card from hand to deck
|
|
57
|
+
* const newState = moveCardInState(state, "hand", "deck", cardId);
|
|
58
|
+
*
|
|
59
|
+
* // Move to specific position
|
|
60
|
+
* const newState2 = moveCardInState(state, "hand", "deck", cardId, 0);
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export function moveCardInState<
|
|
64
|
+
TState extends Record<string, Zone>,
|
|
65
|
+
TFromKey extends keyof TState,
|
|
66
|
+
TToKey extends keyof TState,
|
|
67
|
+
>(
|
|
68
|
+
state: TState,
|
|
69
|
+
fromZoneKey: TFromKey,
|
|
70
|
+
toZoneKey: TToKey,
|
|
71
|
+
cardId: CardId,
|
|
72
|
+
position?: number,
|
|
73
|
+
): TState {
|
|
74
|
+
const fromZone = state[fromZoneKey];
|
|
75
|
+
const toZone = state[toZoneKey];
|
|
76
|
+
|
|
77
|
+
if (!(fromZone && toZone)) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`Zone not found: ${fromZone ? String(toZoneKey) : String(fromZoneKey)}`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const { fromZone: updatedFrom, toZone: updatedTo } = moveCard(
|
|
84
|
+
fromZone,
|
|
85
|
+
toZone,
|
|
86
|
+
cardId,
|
|
87
|
+
position,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
...state,
|
|
92
|
+
[fromZoneKey]: updatedFrom,
|
|
93
|
+
[toZoneKey]: updatedTo,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Finds which zone contains a card in a flat zone state object
|
|
99
|
+
* @param state - Object containing zones as properties
|
|
100
|
+
* @param cardId - Card to find
|
|
101
|
+
* @returns Key of the zone containing the card, or undefined if not found
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* const state = {
|
|
106
|
+
* hand: createZone(..., [card1, card2]),
|
|
107
|
+
* deck: createZone(..., [card3]),
|
|
108
|
+
* };
|
|
109
|
+
*
|
|
110
|
+
* const zoneName = getCardZone(state, card1);
|
|
111
|
+
* // Returns: "hand"
|
|
112
|
+
*
|
|
113
|
+
* const missing = getCardZone(state, card4);
|
|
114
|
+
* // Returns: undefined
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
export function getCardZone<TState extends Record<string, Zone>>(
|
|
118
|
+
state: TState,
|
|
119
|
+
cardId: CardId,
|
|
120
|
+
): keyof TState | undefined {
|
|
121
|
+
for (const key in state) {
|
|
122
|
+
const zone = state[key];
|
|
123
|
+
if (zone && zone.cards.includes(cardId)) {
|
|
124
|
+
return key;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { createCardId, createPlayerId, createZoneId } from "../types";
|
|
3
|
+
import { createZone } from "./zone-factory";
|
|
4
|
+
import { filterZoneByVisibility } from "./zone-visibility";
|
|
5
|
+
|
|
6
|
+
describe("Zone Visibility", () => {
|
|
7
|
+
describe("filterZoneByVisibility", () => {
|
|
8
|
+
it("should show all cards in public zone to all players", () => {
|
|
9
|
+
const cards = [createCardId("card-1"), createCardId("card-2")];
|
|
10
|
+
const zone = createZone(
|
|
11
|
+
{
|
|
12
|
+
id: createZoneId("play"),
|
|
13
|
+
name: "Play Area",
|
|
14
|
+
visibility: "public",
|
|
15
|
+
ordered: false,
|
|
16
|
+
},
|
|
17
|
+
cards,
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const viewerId = createPlayerId("player-1");
|
|
21
|
+
const filtered = filterZoneByVisibility(zone, viewerId);
|
|
22
|
+
|
|
23
|
+
expect(filtered.cards).toEqual(cards);
|
|
24
|
+
expect(filtered.cards).toHaveLength(2);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should show cards in private zone to owner", () => {
|
|
28
|
+
const ownerId = createPlayerId("player-1");
|
|
29
|
+
const cards = [createCardId("card-1"), createCardId("card-2")];
|
|
30
|
+
const zone = createZone(
|
|
31
|
+
{
|
|
32
|
+
id: createZoneId("hand"),
|
|
33
|
+
name: "Hand",
|
|
34
|
+
visibility: "private",
|
|
35
|
+
ordered: false,
|
|
36
|
+
owner: ownerId,
|
|
37
|
+
},
|
|
38
|
+
cards,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const filtered = filterZoneByVisibility(zone, ownerId);
|
|
42
|
+
|
|
43
|
+
expect(filtered.cards).toEqual(cards);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should hide cards in private zone from non-owner", () => {
|
|
47
|
+
const ownerId = createPlayerId("player-1");
|
|
48
|
+
const viewerId = createPlayerId("player-2");
|
|
49
|
+
const cards = [createCardId("card-1"), createCardId("card-2")];
|
|
50
|
+
const zone = createZone(
|
|
51
|
+
{
|
|
52
|
+
id: createZoneId("hand"),
|
|
53
|
+
name: "Hand",
|
|
54
|
+
visibility: "private",
|
|
55
|
+
ordered: false,
|
|
56
|
+
owner: ownerId,
|
|
57
|
+
},
|
|
58
|
+
cards,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const filtered = filterZoneByVisibility(zone, viewerId);
|
|
62
|
+
|
|
63
|
+
expect(filtered.cards).toHaveLength(0);
|
|
64
|
+
expect(filtered.config).toEqual(zone.config);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("should hide all cards in secret zone from all players", () => {
|
|
68
|
+
const cards = [createCardId("card-1"), createCardId("card-2")];
|
|
69
|
+
const zone = createZone(
|
|
70
|
+
{
|
|
71
|
+
id: createZoneId("deck"),
|
|
72
|
+
name: "Deck",
|
|
73
|
+
visibility: "secret",
|
|
74
|
+
ordered: true,
|
|
75
|
+
faceDown: true,
|
|
76
|
+
},
|
|
77
|
+
cards,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const viewerId = createPlayerId("player-1");
|
|
81
|
+
const filtered = filterZoneByVisibility(zone, viewerId);
|
|
82
|
+
|
|
83
|
+
expect(filtered.cards).toHaveLength(0);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should show card count but hide contents for private zone", () => {
|
|
87
|
+
const ownerId = createPlayerId("player-1");
|
|
88
|
+
const viewerId = createPlayerId("player-2");
|
|
89
|
+
const cards = [
|
|
90
|
+
createCardId("card-1"),
|
|
91
|
+
createCardId("card-2"),
|
|
92
|
+
createCardId("card-3"),
|
|
93
|
+
];
|
|
94
|
+
const zone = createZone(
|
|
95
|
+
{
|
|
96
|
+
id: createZoneId("hand"),
|
|
97
|
+
name: "Hand",
|
|
98
|
+
visibility: "private",
|
|
99
|
+
ordered: false,
|
|
100
|
+
owner: ownerId,
|
|
101
|
+
},
|
|
102
|
+
cards,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const filtered = filterZoneByVisibility(zone, viewerId);
|
|
106
|
+
|
|
107
|
+
// Config should be preserved so count can be determined
|
|
108
|
+
expect(filtered.config).toEqual(zone.config);
|
|
109
|
+
// But cards should be empty
|
|
110
|
+
expect(filtered.cards).toHaveLength(0);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("should show card count but hide contents for secret zone", () => {
|
|
114
|
+
const cards = [
|
|
115
|
+
createCardId("card-1"),
|
|
116
|
+
createCardId("card-2"),
|
|
117
|
+
createCardId("card-3"),
|
|
118
|
+
];
|
|
119
|
+
const zone = createZone(
|
|
120
|
+
{
|
|
121
|
+
id: createZoneId("deck"),
|
|
122
|
+
name: "Deck",
|
|
123
|
+
visibility: "secret",
|
|
124
|
+
ordered: true,
|
|
125
|
+
},
|
|
126
|
+
cards,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const viewerId = createPlayerId("player-1");
|
|
130
|
+
const filtered = filterZoneByVisibility(zone, viewerId);
|
|
131
|
+
|
|
132
|
+
// Config preserved
|
|
133
|
+
expect(filtered.config).toEqual(zone.config);
|
|
134
|
+
// Cards hidden
|
|
135
|
+
expect(filtered.cards).toHaveLength(0);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("should handle zone without owner as public", () => {
|
|
139
|
+
const cards = [createCardId("card-1"), createCardId("card-2")];
|
|
140
|
+
const zone = createZone(
|
|
141
|
+
{
|
|
142
|
+
id: createZoneId("graveyard"),
|
|
143
|
+
name: "Graveyard",
|
|
144
|
+
visibility: "public",
|
|
145
|
+
ordered: true,
|
|
146
|
+
},
|
|
147
|
+
cards,
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const viewerId = createPlayerId("player-1");
|
|
151
|
+
const filtered = filterZoneByVisibility(zone, viewerId);
|
|
152
|
+
|
|
153
|
+
expect(filtered.cards).toEqual(cards);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { produce } from "immer";
|
|
2
|
+
import type { PlayerId } from "../types";
|
|
3
|
+
import type { Zone } from "./zone";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Filters a zone based on visibility rules and the viewing player
|
|
7
|
+
* @param zone - Zone to filter
|
|
8
|
+
* @param viewerId - ID of the player viewing the zone
|
|
9
|
+
* @returns Filtered zone with appropriate cards visible
|
|
10
|
+
*/
|
|
11
|
+
export function filterZoneByVisibility(zone: Zone, viewerId: PlayerId): Zone {
|
|
12
|
+
// Public zones: everyone sees everything
|
|
13
|
+
if (zone.config.visibility === "public") {
|
|
14
|
+
return zone;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Private zones: owner sees everything, others see nothing
|
|
18
|
+
if (zone.config.visibility === "private") {
|
|
19
|
+
if (zone.config.owner === viewerId) {
|
|
20
|
+
return zone;
|
|
21
|
+
}
|
|
22
|
+
// Non-owner sees config but no cards
|
|
23
|
+
return produce(zone, (draft) => {
|
|
24
|
+
draft.cards = [];
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Secret zones: no one sees card details
|
|
29
|
+
if (zone.config.visibility === "secret") {
|
|
30
|
+
return produce(zone, (draft) => {
|
|
31
|
+
draft.cards = [];
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return zone;
|
|
36
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import type { CardId, PlayerId, ZoneId } from "../types";
|
|
3
|
+
import type { CardZoneConfig, Zone, ZoneVisibility } from "./zone";
|
|
4
|
+
|
|
5
|
+
describe("Zone Type Definitions", () => {
|
|
6
|
+
describe("ZoneVisibility", () => {
|
|
7
|
+
it("should support public visibility type", () => {
|
|
8
|
+
const visibility: ZoneVisibility = "public";
|
|
9
|
+
expect(visibility).toBe("public");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("should support private visibility type", () => {
|
|
13
|
+
const visibility: ZoneVisibility = "private";
|
|
14
|
+
expect(visibility).toBe("private");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("should support secret visibility type", () => {
|
|
18
|
+
const visibility: ZoneVisibility = "secret";
|
|
19
|
+
expect(visibility).toBe("secret");
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("ZoneConfig", () => {
|
|
24
|
+
it("should define valid zone configuration structure", () => {
|
|
25
|
+
const config: CardZoneConfig = {
|
|
26
|
+
id: "deck" as ZoneId,
|
|
27
|
+
name: "Deck",
|
|
28
|
+
visibility: "secret",
|
|
29
|
+
ordered: true,
|
|
30
|
+
faceDown: true,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
expect(String(config.id)).toBe("deck");
|
|
34
|
+
expect(config.name).toBe("Deck");
|
|
35
|
+
expect(config.visibility).toBe("secret");
|
|
36
|
+
expect(config.ordered).toBe(true);
|
|
37
|
+
expect(config.faceDown).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should support optional owner property", () => {
|
|
41
|
+
const configWithOwner: CardZoneConfig = {
|
|
42
|
+
id: "hand" as ZoneId,
|
|
43
|
+
name: "Hand",
|
|
44
|
+
visibility: "private",
|
|
45
|
+
ordered: false,
|
|
46
|
+
owner: "player-1" as PlayerId,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
expect(configWithOwner.owner).toBeDefined();
|
|
50
|
+
expect(typeof configWithOwner.owner).toBe("string");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should support optional maxSize property", () => {
|
|
54
|
+
const configWithMaxSize: CardZoneConfig = {
|
|
55
|
+
id: "hand" as ZoneId,
|
|
56
|
+
name: "Hand",
|
|
57
|
+
visibility: "private",
|
|
58
|
+
ordered: false,
|
|
59
|
+
maxSize: 7,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
expect(configWithMaxSize.maxSize).toBe(7);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("should work without optional properties", () => {
|
|
66
|
+
const minimalConfig: CardZoneConfig = {
|
|
67
|
+
id: "play" as ZoneId,
|
|
68
|
+
name: "Play Area",
|
|
69
|
+
visibility: "public",
|
|
70
|
+
ordered: false,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
expect(minimalConfig.owner).toBeUndefined();
|
|
74
|
+
expect(minimalConfig.maxSize).toBeUndefined();
|
|
75
|
+
expect(minimalConfig.faceDown).toBeUndefined();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe("Zone", () => {
|
|
80
|
+
it("should define valid zone structure with config and cards", () => {
|
|
81
|
+
const zone: Zone = {
|
|
82
|
+
config: {
|
|
83
|
+
id: "deck" as ZoneId,
|
|
84
|
+
name: "Deck",
|
|
85
|
+
visibility: "secret",
|
|
86
|
+
ordered: true,
|
|
87
|
+
faceDown: true,
|
|
88
|
+
},
|
|
89
|
+
cards: [],
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
expect(zone.config).toBeDefined();
|
|
93
|
+
expect(zone.cards).toBeInstanceOf(Array);
|
|
94
|
+
expect(zone.cards).toHaveLength(0);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("should support zone with card IDs", () => {
|
|
98
|
+
const cardId1 = "card-1" as CardId;
|
|
99
|
+
const cardId2 = "card-2" as CardId;
|
|
100
|
+
|
|
101
|
+
const zone: Zone = {
|
|
102
|
+
config: {
|
|
103
|
+
id: "hand" as ZoneId,
|
|
104
|
+
name: "Hand",
|
|
105
|
+
visibility: "private",
|
|
106
|
+
ordered: false,
|
|
107
|
+
},
|
|
108
|
+
cards: [cardId1, cardId2],
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
expect(zone.cards).toHaveLength(2);
|
|
112
|
+
expect(zone.cards[0]).toBe(cardId1);
|
|
113
|
+
expect(zone.cards[1]).toBe(cardId2);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("should maintain card order when ordered is true", () => {
|
|
117
|
+
const cardIds = [
|
|
118
|
+
"card-1" as CardId,
|
|
119
|
+
"card-2" as CardId,
|
|
120
|
+
"card-3" as CardId,
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
const orderedZone: Zone = {
|
|
124
|
+
config: {
|
|
125
|
+
id: "deck" as ZoneId,
|
|
126
|
+
name: "Deck",
|
|
127
|
+
visibility: "secret",
|
|
128
|
+
ordered: true,
|
|
129
|
+
},
|
|
130
|
+
cards: cardIds,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
expect(orderedZone.cards[0]).toBe(cardIds[0]);
|
|
134
|
+
expect(orderedZone.cards[1]).toBe(cardIds[1]);
|
|
135
|
+
expect(orderedZone.cards[2]).toBe(cardIds[2]);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe("Zone Type Safety", () => {
|
|
140
|
+
it("should enforce ZoneId type for zone config id", () => {
|
|
141
|
+
const zoneId = "deck" as ZoneId;
|
|
142
|
+
const config: CardZoneConfig = {
|
|
143
|
+
id: zoneId,
|
|
144
|
+
name: "Deck",
|
|
145
|
+
visibility: "secret",
|
|
146
|
+
ordered: true,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const _typeCheck: ZoneId = config.id;
|
|
150
|
+
expect(config.id).toBe(zoneId);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("should enforce CardId array type for zone cards", () => {
|
|
154
|
+
const cardIds: CardId[] = ["card-1" as CardId, "card-2" as CardId];
|
|
155
|
+
|
|
156
|
+
const zone: Zone = {
|
|
157
|
+
config: {
|
|
158
|
+
id: "hand" as ZoneId,
|
|
159
|
+
name: "Hand",
|
|
160
|
+
visibility: "private",
|
|
161
|
+
ordered: false,
|
|
162
|
+
},
|
|
163
|
+
cards: cardIds,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const _typeCheck: CardId[] = zone.cards;
|
|
167
|
+
expect(zone.cards).toEqual(cardIds);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("should enforce PlayerId type for owner", () => {
|
|
171
|
+
const playerId = "player-1" as PlayerId;
|
|
172
|
+
const config: CardZoneConfig = {
|
|
173
|
+
id: "hand" as ZoneId,
|
|
174
|
+
name: "Hand",
|
|
175
|
+
visibility: "private",
|
|
176
|
+
ordered: false,
|
|
177
|
+
owner: playerId,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
if (config.owner) {
|
|
181
|
+
const _typeCheck: PlayerId = config.owner;
|
|
182
|
+
expect(config.owner).toBe(playerId);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { CardId, PlayerId, ZoneId } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Zone visibility types
|
|
5
|
+
*
|
|
6
|
+
* - public: All players can see all cards (e.g., play area, graveyard)
|
|
7
|
+
* - private: Owner can see cards, opponents see count (e.g., hand)
|
|
8
|
+
* - secret: No one can see cards, only count (e.g., deck, face-down cards)
|
|
9
|
+
*/
|
|
10
|
+
export type ZoneVisibility = "public" | "private" | "secret";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Zone configuration defining the properties and rules of a zone
|
|
14
|
+
*/
|
|
15
|
+
export type CardZoneConfig = {
|
|
16
|
+
/**
|
|
17
|
+
* Unique identifier for the zone
|
|
18
|
+
*/
|
|
19
|
+
id: ZoneId;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Human-readable name of the zone
|
|
23
|
+
*/
|
|
24
|
+
name: string;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Visibility rules for the zone
|
|
28
|
+
*/
|
|
29
|
+
visibility: ZoneVisibility;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Whether card order matters in this zone
|
|
33
|
+
* True for decks, false for play areas
|
|
34
|
+
*/
|
|
35
|
+
ordered: boolean;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Optional owner of the zone (undefined for shared zones)
|
|
39
|
+
*/
|
|
40
|
+
owner?: PlayerId;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Whether cards are face-down by default
|
|
44
|
+
*/
|
|
45
|
+
faceDown?: boolean;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Maximum number of cards allowed in the zone
|
|
49
|
+
*/
|
|
50
|
+
maxSize?: number;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A zone containing cards with configuration
|
|
55
|
+
*/
|
|
56
|
+
export type Zone = {
|
|
57
|
+
/**
|
|
58
|
+
* Zone configuration
|
|
59
|
+
*/
|
|
60
|
+
config: CardZoneConfig;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Cards currently in the zone
|
|
64
|
+
*/
|
|
65
|
+
cards: CardId[];
|
|
66
|
+
};
|