@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.
Files changed (157) hide show
  1. package/README.md +882 -0
  2. package/package.json +58 -0
  3. package/src/__tests__/alpha-clash-engine-definition.test.ts +319 -0
  4. package/src/__tests__/createMockAlphaClashGame.ts +462 -0
  5. package/src/__tests__/createMockGrandArchiveGame.ts +373 -0
  6. package/src/__tests__/createMockGundamGame.ts +379 -0
  7. package/src/__tests__/createMockLorcanaGame.ts +328 -0
  8. package/src/__tests__/createMockOnePieceGame.ts +429 -0
  9. package/src/__tests__/createMockRiftboundGame.ts +462 -0
  10. package/src/__tests__/grand-archive-engine-definition.test.ts +118 -0
  11. package/src/__tests__/gundam-engine-definition.test.ts +110 -0
  12. package/src/__tests__/integration-complete-game.test.ts +508 -0
  13. package/src/__tests__/integration-network-sync.test.ts +469 -0
  14. package/src/__tests__/lorcana-engine-definition.test.ts +100 -0
  15. package/src/__tests__/move-enumeration.test.ts +725 -0
  16. package/src/__tests__/multiplayer-engine.test.ts +555 -0
  17. package/src/__tests__/one-piece-engine-definition.test.ts +114 -0
  18. package/src/__tests__/riftbound-engine-definition.test.ts +124 -0
  19. package/src/actions/action-definition.test.ts +201 -0
  20. package/src/actions/action-definition.ts +122 -0
  21. package/src/actions/action-timing.test.ts +490 -0
  22. package/src/actions/action-timing.ts +257 -0
  23. package/src/cards/card-definition.test.ts +268 -0
  24. package/src/cards/card-definition.ts +27 -0
  25. package/src/cards/card-instance.test.ts +422 -0
  26. package/src/cards/card-instance.ts +49 -0
  27. package/src/cards/computed-properties.test.ts +530 -0
  28. package/src/cards/computed-properties.ts +84 -0
  29. package/src/cards/conditional-modifiers.test.ts +390 -0
  30. package/src/cards/modifiers.test.ts +286 -0
  31. package/src/cards/modifiers.ts +51 -0
  32. package/src/engine/MULTIPLAYER.md +425 -0
  33. package/src/engine/__tests__/rule-engine-flow.test.ts +348 -0
  34. package/src/engine/__tests__/rule-engine-history.test.ts +535 -0
  35. package/src/engine/__tests__/rule-engine-moves.test.ts +488 -0
  36. package/src/engine/__tests__/rule-engine.test.ts +366 -0
  37. package/src/engine/index.ts +14 -0
  38. package/src/engine/multiplayer-engine.example.ts +571 -0
  39. package/src/engine/multiplayer-engine.ts +409 -0
  40. package/src/engine/rule-engine.test.ts +286 -0
  41. package/src/engine/rule-engine.ts +1539 -0
  42. package/src/engine/tracker-system.ts +172 -0
  43. package/src/examples/__tests__/coin-flip-game.test.ts +641 -0
  44. package/src/filtering/card-filter.test.ts +230 -0
  45. package/src/filtering/card-filter.ts +91 -0
  46. package/src/filtering/card-query.test.ts +901 -0
  47. package/src/filtering/card-query.ts +273 -0
  48. package/src/filtering/filter-matching.test.ts +944 -0
  49. package/src/filtering/filter-matching.ts +315 -0
  50. package/src/flow/SERIALIZATION.md +428 -0
  51. package/src/flow/__tests__/flow-definition.test.ts +427 -0
  52. package/src/flow/__tests__/flow-manager.test.ts +756 -0
  53. package/src/flow/__tests__/flow-serialization.test.ts +565 -0
  54. package/src/flow/flow-definition.ts +453 -0
  55. package/src/flow/flow-manager.ts +1044 -0
  56. package/src/flow/index.ts +35 -0
  57. package/src/game-definition/__tests__/game-definition-validation.test.ts +359 -0
  58. package/src/game-definition/__tests__/game-definition.test.ts +291 -0
  59. package/src/game-definition/__tests__/move-definitions.test.ts +328 -0
  60. package/src/game-definition/game-definition.ts +261 -0
  61. package/src/game-definition/index.ts +28 -0
  62. package/src/game-definition/move-definitions.ts +188 -0
  63. package/src/game-definition/validation.ts +183 -0
  64. package/src/history/history-manager.test.ts +497 -0
  65. package/src/history/history-manager.ts +312 -0
  66. package/src/history/history-operations.ts +122 -0
  67. package/src/history/index.ts +9 -0
  68. package/src/history/types.ts +255 -0
  69. package/src/index.ts +32 -0
  70. package/src/logging/index.ts +27 -0
  71. package/src/logging/log-formatter.ts +187 -0
  72. package/src/logging/logger.ts +276 -0
  73. package/src/logging/types.ts +148 -0
  74. package/src/moves/create-move.test.ts +331 -0
  75. package/src/moves/create-move.ts +64 -0
  76. package/src/moves/move-enumeration.ts +228 -0
  77. package/src/moves/move-executor.test.ts +431 -0
  78. package/src/moves/move-executor.ts +195 -0
  79. package/src/moves/move-system.test.ts +380 -0
  80. package/src/moves/move-system.ts +463 -0
  81. package/src/moves/standard-moves.ts +231 -0
  82. package/src/operations/card-operations.test.ts +236 -0
  83. package/src/operations/card-operations.ts +116 -0
  84. package/src/operations/card-registry-impl.test.ts +251 -0
  85. package/src/operations/card-registry-impl.ts +70 -0
  86. package/src/operations/card-registry.test.ts +234 -0
  87. package/src/operations/card-registry.ts +106 -0
  88. package/src/operations/counter-operations.ts +152 -0
  89. package/src/operations/game-operations.test.ts +280 -0
  90. package/src/operations/game-operations.ts +140 -0
  91. package/src/operations/index.ts +24 -0
  92. package/src/operations/operations-impl.test.ts +354 -0
  93. package/src/operations/operations-impl.ts +468 -0
  94. package/src/operations/zone-operations.test.ts +295 -0
  95. package/src/operations/zone-operations.ts +223 -0
  96. package/src/rng/seeded-rng.test.ts +339 -0
  97. package/src/rng/seeded-rng.ts +123 -0
  98. package/src/targeting/index.ts +48 -0
  99. package/src/targeting/target-definition.test.ts +273 -0
  100. package/src/targeting/target-definition.ts +37 -0
  101. package/src/targeting/target-dsl.ts +279 -0
  102. package/src/targeting/target-resolver.ts +486 -0
  103. package/src/targeting/target-validation.test.ts +994 -0
  104. package/src/targeting/target-validation.ts +286 -0
  105. package/src/telemetry/events.ts +202 -0
  106. package/src/telemetry/index.ts +21 -0
  107. package/src/telemetry/telemetry-manager.ts +127 -0
  108. package/src/telemetry/types.ts +68 -0
  109. package/src/testing/__tests__/testing-utilities-integration.test.ts +161 -0
  110. package/src/testing/index.ts +88 -0
  111. package/src/testing/test-assertions.test.ts +341 -0
  112. package/src/testing/test-assertions.ts +256 -0
  113. package/src/testing/test-card-factory.test.ts +228 -0
  114. package/src/testing/test-card-factory.ts +111 -0
  115. package/src/testing/test-context-factory.ts +187 -0
  116. package/src/testing/test-end-assertions.test.ts +262 -0
  117. package/src/testing/test-end-assertions.ts +95 -0
  118. package/src/testing/test-engine-builder.test.ts +389 -0
  119. package/src/testing/test-engine-builder.ts +46 -0
  120. package/src/testing/test-flow-assertions.test.ts +284 -0
  121. package/src/testing/test-flow-assertions.ts +115 -0
  122. package/src/testing/test-player-builder.test.ts +132 -0
  123. package/src/testing/test-player-builder.ts +46 -0
  124. package/src/testing/test-replay-assertions.test.ts +356 -0
  125. package/src/testing/test-replay-assertions.ts +164 -0
  126. package/src/testing/test-rng-helpers.test.ts +260 -0
  127. package/src/testing/test-rng-helpers.ts +190 -0
  128. package/src/testing/test-state-builder.test.ts +373 -0
  129. package/src/testing/test-state-builder.ts +99 -0
  130. package/src/testing/test-zone-factory.test.ts +295 -0
  131. package/src/testing/test-zone-factory.ts +224 -0
  132. package/src/types/branded-utils.ts +54 -0
  133. package/src/types/branded.test.ts +175 -0
  134. package/src/types/branded.ts +33 -0
  135. package/src/types/index.ts +8 -0
  136. package/src/types/state.test.ts +198 -0
  137. package/src/types/state.ts +154 -0
  138. package/src/validation/card-type-guards.test.ts +242 -0
  139. package/src/validation/card-type-guards.ts +179 -0
  140. package/src/validation/index.ts +40 -0
  141. package/src/validation/schema-builders.test.ts +403 -0
  142. package/src/validation/schema-builders.ts +345 -0
  143. package/src/validation/type-guard-builder.test.ts +216 -0
  144. package/src/validation/type-guard-builder.ts +109 -0
  145. package/src/validation/validator-builder.test.ts +375 -0
  146. package/src/validation/validator-builder.ts +273 -0
  147. package/src/zones/index.ts +28 -0
  148. package/src/zones/zone-factory.test.ts +183 -0
  149. package/src/zones/zone-factory.ts +44 -0
  150. package/src/zones/zone-operations.test.ts +800 -0
  151. package/src/zones/zone-operations.ts +306 -0
  152. package/src/zones/zone-state-helpers.test.ts +337 -0
  153. package/src/zones/zone-state-helpers.ts +128 -0
  154. package/src/zones/zone-visibility.test.ts +156 -0
  155. package/src/zones/zone-visibility.ts +36 -0
  156. package/src/zones/zone.test.ts +186 -0
  157. package/src/zones/zone.ts +66 -0
@@ -0,0 +1,175 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import type { CardId, GameId, PlayerId, ZoneId } from "./branded";
3
+ import {
4
+ createCardId,
5
+ createGameId,
6
+ createPlayerId,
7
+ createZoneId,
8
+ } from "./branded-utils";
9
+
10
+ describe("Branded Types", () => {
11
+ describe("createCardId", () => {
12
+ it("should create a CardId from a string literal", () => {
13
+ const cardId = createCardId("card-123");
14
+ expect(typeof cardId).toBe("string");
15
+ // Runtime value should match the input
16
+ expect(String(cardId)).toBe("card-123");
17
+ // Type assertion to verify it's properly typed
18
+ const _typeCheck: CardId = cardId;
19
+ });
20
+
21
+ it("should generate unique IDs when called without arguments", () => {
22
+ const id1 = createCardId();
23
+ const id2 = createCardId();
24
+ expect(id1).not.toBe(id2);
25
+ });
26
+
27
+ it("should generate IDs with proper format", () => {
28
+ const cardId = createCardId();
29
+ expect(typeof cardId).toBe("string");
30
+ expect(cardId.length).toBeGreaterThan(0);
31
+ });
32
+ });
33
+
34
+ describe("createPlayerId", () => {
35
+ it("should create a PlayerId from a string literal", () => {
36
+ const playerId = createPlayerId("player-456");
37
+ expect(typeof playerId).toBe("string");
38
+ // Runtime value should match the input
39
+ expect(String(playerId)).toBe("player-456");
40
+ // Type assertion to verify it's properly typed
41
+ const _typeCheck: PlayerId = playerId;
42
+ });
43
+
44
+ it("should generate unique IDs when called without arguments", () => {
45
+ const id1 = createPlayerId();
46
+ const id2 = createPlayerId();
47
+ expect(id1).not.toBe(id2);
48
+ });
49
+
50
+ it("should generate IDs with proper format", () => {
51
+ const playerId = createPlayerId();
52
+ expect(typeof playerId).toBe("string");
53
+ expect(playerId.length).toBeGreaterThan(0);
54
+ });
55
+ });
56
+
57
+ describe("createGameId", () => {
58
+ it("should create a GameId from a string literal", () => {
59
+ const gameId = createGameId("game-789");
60
+ expect(typeof gameId).toBe("string");
61
+ // Runtime value should match the input
62
+ expect(String(gameId)).toBe("game-789");
63
+ // Type assertion to verify it's properly typed
64
+ const _typeCheck: GameId = gameId;
65
+ });
66
+
67
+ it("should generate unique IDs when called without arguments", () => {
68
+ const id1 = createGameId();
69
+ const id2 = createGameId();
70
+ expect(id1).not.toBe(id2);
71
+ });
72
+
73
+ it("should generate IDs with proper format", () => {
74
+ const gameId = createGameId();
75
+ expect(typeof gameId).toBe("string");
76
+ expect(gameId.length).toBeGreaterThan(0);
77
+ });
78
+ });
79
+
80
+ describe("createZoneId", () => {
81
+ it("should create a ZoneId from a string literal", () => {
82
+ const zoneId = createZoneId("zone-abc");
83
+ expect(typeof zoneId).toBe("string");
84
+ // Runtime value should match the input
85
+ expect(String(zoneId)).toBe("zone-abc");
86
+ // Type assertion to verify it's properly typed
87
+ const _typeCheck: ZoneId = zoneId;
88
+ });
89
+
90
+ it("should generate unique IDs when called without arguments", () => {
91
+ const id1 = createZoneId();
92
+ const id2 = createZoneId();
93
+ expect(id1).not.toBe(id2);
94
+ });
95
+
96
+ it("should generate IDs with proper format", () => {
97
+ const zoneId = createZoneId();
98
+ expect(typeof zoneId).toBe("string");
99
+ expect(zoneId.length).toBeGreaterThan(0);
100
+ });
101
+ });
102
+
103
+ describe("Type Safety", () => {
104
+ it("should prevent mixing different ID types at compile time", () => {
105
+ const cardId = createCardId("id-1");
106
+ const playerId = createPlayerId("id-2");
107
+
108
+ // This test verifies type safety at compile time
109
+ // The following would fail TypeScript compilation:
110
+ // const wrongAssignment1: CardId = playerId;
111
+ // const wrongAssignment2: PlayerId = cardId;
112
+
113
+ // But we can verify runtime values are still just strings
114
+ expect(typeof cardId).toBe("string");
115
+ expect(typeof playerId).toBe("string");
116
+ });
117
+
118
+ it("should allow assignment of branded types to their brand type", () => {
119
+ const cardId: CardId = createCardId("card-123");
120
+ const acceptsCardId = (id: CardId): CardId => id;
121
+
122
+ expect(acceptsCardId(cardId)).toBe(cardId);
123
+ });
124
+
125
+ it("should work with arrays and collections", () => {
126
+ const cardIds: CardId[] = [
127
+ createCardId("1"),
128
+ createCardId("2"),
129
+ createCardId("3"),
130
+ ];
131
+
132
+ expect(cardIds).toHaveLength(3);
133
+ expect(cardIds.every((id) => typeof id === "string")).toBe(true);
134
+ });
135
+
136
+ it("should work with Set and Map", () => {
137
+ const cardIdSet = new Set<CardId>([createCardId("1"), createCardId("2")]);
138
+ const cardIdMap = new Map<CardId, string>([
139
+ [createCardId("1"), "Card One"],
140
+ [createCardId("2"), "Card Two"],
141
+ ]);
142
+
143
+ expect(cardIdSet.size).toBe(2);
144
+ expect(cardIdMap.size).toBe(2);
145
+ });
146
+ });
147
+
148
+ describe("ID Generation Consistency", () => {
149
+ it("should generate IDs of consistent length", () => {
150
+ const ids = [
151
+ createCardId(),
152
+ createPlayerId(),
153
+ createGameId(),
154
+ createZoneId(),
155
+ ];
156
+
157
+ const lengths = ids.map((id) => id.length);
158
+ expect(new Set(lengths).size).toBe(1); // All same length
159
+ });
160
+
161
+ it("should generate URL-safe IDs", () => {
162
+ const ids = [
163
+ createCardId(),
164
+ createPlayerId(),
165
+ createGameId(),
166
+ createZoneId(),
167
+ ];
168
+
169
+ for (const id of ids) {
170
+ // nanoid generates URL-safe characters: A-Za-z0-9_-
171
+ expect(id).toMatch(/^[A-Za-z0-9_-]+$/);
172
+ }
173
+ });
174
+ });
175
+ });
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Branded Types
3
+ *
4
+ * Branded types provide compile-time type safety for primitive values
5
+ * to prevent mixing different ID types and other domain values.
6
+ */
7
+
8
+ declare const brand: unique symbol;
9
+
10
+ /**
11
+ * Base branded type that adds a compile-time brand to a value
12
+ */
13
+ export type Brand<T, TBrand> = T & { readonly [brand]: TBrand };
14
+
15
+ /**
16
+ * Card identifier - unique ID for a card instance in the game
17
+ */
18
+ export type CardId = Brand<string, "CardId">;
19
+
20
+ /**
21
+ * Player identifier - unique ID for a player in the game
22
+ */
23
+ export type PlayerId = Brand<string, "PlayerId">;
24
+
25
+ /**
26
+ * Game identifier - unique ID for a game session
27
+ */
28
+ export type GameId = Brand<string, "GameId">;
29
+
30
+ /**
31
+ * Zone identifier - unique ID for a zone in the game
32
+ */
33
+ export type ZoneId = Brand<string, "ZoneId">;
@@ -0,0 +1,8 @@
1
+ export type { Brand, CardId, GameId, PlayerId, ZoneId } from "./branded";
2
+ export {
3
+ createCardId,
4
+ createGameId,
5
+ createPlayerId,
6
+ createZoneId,
7
+ } from "./branded-utils";
8
+ export type { IState } from "./state";
@@ -0,0 +1,198 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import type { CardZoneConfig } from "../zones";
3
+ import type { CardId, PlayerId, ZoneId } from "./index";
4
+ import type { InternalState, IState } from "./state";
5
+
6
+ describe("InternalState", () => {
7
+ it("should allow defining zones with configuration and card lists", () => {
8
+ // Test that InternalState can hold zone data
9
+ type TestCardDef = { id: string; name: string };
10
+ type TestCardMeta = { damage?: number };
11
+
12
+ const zoneConfig: CardZoneConfig = {
13
+ id: "hand" as unknown as ZoneId,
14
+ name: "Hand",
15
+ visibility: "private",
16
+ ordered: false,
17
+ };
18
+
19
+ const internalState: InternalState<TestCardDef, TestCardMeta> = {
20
+ zones: {
21
+ hand: {
22
+ config: zoneConfig,
23
+ cardIds: ["card-1", "card-2"] as unknown as CardId[],
24
+ },
25
+ },
26
+ cards: {},
27
+ cardMetas: {},
28
+ };
29
+
30
+ expect(internalState.zones.hand.cardIds).toHaveLength(2);
31
+ expect(internalState.zones.hand.config.visibility).toBe("private");
32
+ });
33
+
34
+ it("should allow defining card instances with owner and zone", () => {
35
+ type TestCardDef = { id: string; name: string };
36
+ type TestCardMeta = { damage?: number };
37
+
38
+ const internalState: InternalState<TestCardDef, TestCardMeta> = {
39
+ zones: {},
40
+ cards: {
41
+ "card-1": {
42
+ definitionId: "pikachu",
43
+ owner: "player-1" as unknown as PlayerId,
44
+ controller: "player-1" as unknown as PlayerId,
45
+ zone: "hand" as unknown as ZoneId,
46
+ },
47
+ },
48
+ cardMetas: {},
49
+ };
50
+
51
+ expect(internalState.cards["card-1"].definitionId).toBe("pikachu");
52
+ expect(internalState.cards["card-1"].owner).toBe(
53
+ "player-1" as unknown as PlayerId,
54
+ );
55
+ expect(internalState.cards["card-1"].zone).toBe(
56
+ "hand" as unknown as ZoneId,
57
+ );
58
+ });
59
+
60
+ it("should allow defining card metadata for dynamic properties", () => {
61
+ type TestCardDef = { id: string };
62
+ type TestCardMeta = {
63
+ damage?: number;
64
+ exerted?: boolean;
65
+ effects?: string[];
66
+ };
67
+
68
+ const internalState: InternalState<TestCardDef, TestCardMeta> = {
69
+ zones: {},
70
+ cards: {},
71
+ cardMetas: {
72
+ "card-1": {
73
+ damage: 5,
74
+ exerted: true,
75
+ effects: ["poisoned"],
76
+ },
77
+ },
78
+ };
79
+
80
+ expect(internalState.cardMetas["card-1"].damage).toBe(5);
81
+ expect(internalState.cardMetas["card-1"].exerted).toBe(true);
82
+ });
83
+
84
+ it("should allow position tracking for ordered zones", () => {
85
+ type TestCardDef = { id: string };
86
+ type TestCardMeta = Record<string, never>;
87
+
88
+ const internalState: InternalState<TestCardDef, TestCardMeta> = {
89
+ zones: {},
90
+ cards: {
91
+ "card-1": {
92
+ definitionId: "card-def-1",
93
+ owner: "player-1" as unknown as PlayerId,
94
+ controller: "player-1" as unknown as PlayerId,
95
+ zone: "deck" as unknown as ZoneId,
96
+ position: 0, // Top of deck
97
+ },
98
+ "card-2": {
99
+ definitionId: "card-def-2",
100
+ owner: "player-1" as unknown as PlayerId,
101
+ controller: "player-1" as unknown as PlayerId,
102
+ zone: "deck" as unknown as ZoneId,
103
+ position: 1,
104
+ },
105
+ },
106
+ cardMetas: {},
107
+ };
108
+
109
+ expect(internalState.cards["card-1"].position).toBe(0);
110
+ expect(internalState.cards["card-2"].position).toBe(1);
111
+ });
112
+ });
113
+
114
+ describe("IState", () => {
115
+ it("should wrap external game state with internal framework state", () => {
116
+ type GameState = {
117
+ turnCount: number;
118
+ currentPlayer: string;
119
+ };
120
+
121
+ type TestCardDef = { id: string };
122
+ type TestCardMeta = { damage?: number };
123
+
124
+ const state: IState<GameState, TestCardDef, TestCardMeta> = {
125
+ internal: {
126
+ zones: {},
127
+ cards: {},
128
+ cardMetas: {},
129
+ },
130
+ external: {
131
+ turnCount: 1,
132
+ currentPlayer: "player-1",
133
+ },
134
+ };
135
+
136
+ // Games can access their state
137
+ expect(state.external.turnCount).toBe(1);
138
+ expect(state.external.currentPlayer).toBe("player-1");
139
+
140
+ // Framework manages internal state
141
+ expect(state.internal.zones).toEqual({});
142
+ expect(state.internal.cards).toEqual({});
143
+ });
144
+
145
+ it("should allow complex external state while framework manages infrastructure", () => {
146
+ type GameState = {
147
+ players: Array<{ id: string; score: number }>;
148
+ effects: Array<{ type: string; duration: number }>;
149
+ };
150
+
151
+ type TestCardDef = { id: string; name: string };
152
+ type TestCardMeta = { counters?: number };
153
+
154
+ const state: IState<GameState, TestCardDef, TestCardMeta> = {
155
+ internal: {
156
+ zones: {
157
+ hand: {
158
+ config: {
159
+ id: "hand" as unknown as ZoneId,
160
+ name: "Hand",
161
+ visibility: "private",
162
+ ordered: false,
163
+ },
164
+ cardIds: ["card-1"] as unknown as CardId[],
165
+ },
166
+ },
167
+ cards: {
168
+ "card-1": {
169
+ definitionId: "monster-1",
170
+ owner: "player-1" as unknown as PlayerId,
171
+ controller: "player-1" as unknown as PlayerId,
172
+ zone: "hand" as unknown as ZoneId,
173
+ },
174
+ },
175
+ cardMetas: {
176
+ "card-1": {
177
+ counters: 3,
178
+ },
179
+ },
180
+ },
181
+ external: {
182
+ players: [
183
+ { id: "player-1", score: 100 },
184
+ { id: "player-2", score: 85 },
185
+ ],
186
+ effects: [{ type: "global-buff", duration: 2 }],
187
+ },
188
+ };
189
+
190
+ // External game logic
191
+ expect(state.external.players).toHaveLength(2);
192
+ expect(state.external.effects[0].type).toBe("global-buff");
193
+
194
+ // Internal framework management
195
+ expect(state.internal.zones.hand.cardIds).toContain("card-1");
196
+ expect(state.internal.cardMetas["card-1"].counters).toBe(3);
197
+ });
198
+ });
@@ -0,0 +1,154 @@
1
+ import type { CardZoneConfig } from "../zones";
2
+ import type { CardId, PlayerId, ZoneId } from "./index";
3
+
4
+ /**
5
+ * Internal State - Managed by the Framework
6
+ *
7
+ * This state contains infrastructure concerns that the framework handles:
8
+ * - Zone management (which card is in which zone)
9
+ * - Card instance tracking (instance ID, owner, location)
10
+ * - Card metadata (dynamic properties, counters, effects)
11
+ *
12
+ * Games cannot directly modify internal state. They must use the operations API
13
+ * provided in move context.
14
+ *
15
+ * @template TCardDefinition - Static card definition type (game-specific)
16
+ * @template TCardMeta - Dynamic card metadata type (game-specific)
17
+ */
18
+ export type InternalState<TCardDefinition = any, TCardMeta = any> = {
19
+ /**
20
+ * Zone registry - Maps zone ID to zone data
21
+ *
22
+ * Each zone contains:
23
+ * - config: Zone configuration (visibility, ordering, etc.)
24
+ * - cardIds: Array of card instance IDs in this zone
25
+ *
26
+ * The framework maintains this mapping and ensures consistency.
27
+ */
28
+ zones: {
29
+ [zoneId: string]: {
30
+ config: CardZoneConfig;
31
+ cardIds: CardId[];
32
+ };
33
+ };
34
+
35
+ /**
36
+ * Card instance registry - Maps card instance ID to card data
37
+ *
38
+ * Each card instance contains:
39
+ * - definitionId: Reference to the static card definition
40
+ * - owner: Player who owns this card (never changes)
41
+ * - controller: Player currently controlling this card (can change via effects)
42
+ * - zone: Current zone containing this card
43
+ * - position: Optional position in zone (for ordered zones like decks)
44
+ *
45
+ * Card instances are created during game setup or through game actions.
46
+ * Note: Field names align with CardInstanceBase from cards/card-instance.ts
47
+ */
48
+ cards: {
49
+ [cardId: string]: {
50
+ /** Reference to card definition (static properties) */
51
+ definitionId: string;
52
+ /** Player who owns this card (never changes) */
53
+ owner: PlayerId;
54
+ /** Player currently controlling this card (can change via effects) */
55
+ controller: PlayerId;
56
+ /** Current zone containing this card */
57
+ zone: ZoneId;
58
+ /** Position in zone (for ordered zones) */
59
+ position?: number;
60
+ };
61
+ };
62
+
63
+ /**
64
+ * Card metadata registry - Maps card instance ID to dynamic metadata
65
+ *
66
+ * Stores mutable, game-specific card properties:
67
+ * - Damage/counters
68
+ * - Status effects (e.g., poisoned, stunned)
69
+ * - Gained abilities
70
+ * - Temporary modifications
71
+ *
72
+ * Metadata type is generic to allow game-specific structures.
73
+ */
74
+ cardMetas: {
75
+ [cardId: string]: TCardMeta;
76
+ };
77
+
78
+ /**
79
+ * On The Play (OTP) - Player who goes first
80
+ *
81
+ * Universal TCG concept. The OTP player typically goes first
82
+ * and may have different rules (e.g., no draw on first turn).
83
+ */
84
+ otp?: PlayerId;
85
+
86
+ /**
87
+ * Choosing First Player - Player designated to choose who goes first
88
+ *
89
+ * In TCGs like Lorcana, one player is randomly selected to choose
90
+ * who will be the starting player (OTP). This field tracks which
91
+ * player has that privilege.
92
+ *
93
+ * Typically set during game initialization and cleared after OTP is chosen.
94
+ */
95
+ choosingFirstPlayer?: PlayerId;
96
+
97
+ /**
98
+ * Players pending mulligan decision
99
+ *
100
+ * Tracks which players still need to decide whether to mulligan.
101
+ * Typically initialized with all players at game start.
102
+ */
103
+ pendingMulligan?: PlayerId[];
104
+ };
105
+
106
+ /**
107
+ * Complete Game State
108
+ *
109
+ * Separates framework-managed state (internal) from game-specific state (external).
110
+ *
111
+ * - internal: Zone/card management handled by framework
112
+ * - external: Game-specific state defined by game developers
113
+ *
114
+ * Moves receive operations API to modify internal state.
115
+ * Moves receive Immer draft of external state for direct modification.
116
+ *
117
+ * @template TState - Game-specific state type
118
+ * @template TCardDefinition - Static card definition type
119
+ * @template TCardMeta - Dynamic card metadata type
120
+ *
121
+ * @example
122
+ * ```typescript
123
+ * type MyGameState = { turnCount: number; currentPlayer: string };
124
+ * type MyCardDef = { id: string; name: string; cost: number };
125
+ * type MyCardMeta = { damage?: number; effects?: string[] };
126
+ *
127
+ * const state: IState<MyGameState, MyCardDef, MyCardMeta> = {
128
+ * internal: {
129
+ * zones: { ... },
130
+ * cards: { ... },
131
+ * cardMetas: { ... }
132
+ * },
133
+ * external: {
134
+ * turnCount: 1,
135
+ * currentPlayer: "player-1"
136
+ * }
137
+ * };
138
+ * ```
139
+ */
140
+ export type IState<TState, TCardDefinition = any, TCardMeta = any> = {
141
+ /**
142
+ * Framework-managed state
143
+ * Contains zone/card infrastructure
144
+ * Modified only through operations API
145
+ */
146
+ internal: InternalState<TCardDefinition, TCardMeta>;
147
+
148
+ /**
149
+ * Game-specific state
150
+ * Contains game logic state
151
+ * Modified directly via Immer draft in move reducers
152
+ */
153
+ external: TState;
154
+ };