@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,188 @@
|
|
|
1
|
+
import type { Draft } from "immer";
|
|
2
|
+
import type { MoveContext, NormalizeParams } from "../moves/move-system";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Game Move Definition
|
|
6
|
+
*
|
|
7
|
+
* Task 10.5, 10.6: Implement GameMoveDefinition with reducer and optional condition
|
|
8
|
+
*
|
|
9
|
+
* Declarative definition of a single move/action in the game.
|
|
10
|
+
* Each move has:
|
|
11
|
+
* - A reducer function (required) - executes the move with typed parameters
|
|
12
|
+
* - An optional condition function - validates if move is legal
|
|
13
|
+
* - Optional metadata - for categorization, UI, etc.
|
|
14
|
+
*
|
|
15
|
+
* @template TState - Game state type
|
|
16
|
+
* @template TParams - Move-specific parameter type (from TMoves[MoveName])
|
|
17
|
+
* @template TCardMeta - Card metadata type (for zone/card operations)
|
|
18
|
+
* @template TCardDefinition - Card definition type (for registry access)
|
|
19
|
+
*/
|
|
20
|
+
export type GameMoveDefinition<
|
|
21
|
+
TState,
|
|
22
|
+
TParams = any,
|
|
23
|
+
TCardMeta = any,
|
|
24
|
+
TCardDefinition = any,
|
|
25
|
+
> = {
|
|
26
|
+
/**
|
|
27
|
+
* Move reducer - executes the move using Immer draft
|
|
28
|
+
*
|
|
29
|
+
* Task 10.6: Reducer with Immer draft pattern
|
|
30
|
+
*
|
|
31
|
+
* Pure function that mutates the draft to update state.
|
|
32
|
+
* Immer converts mutations into immutable updates.
|
|
33
|
+
*
|
|
34
|
+
* The context includes fully-typed parameters specific to this move.
|
|
35
|
+
*
|
|
36
|
+
* @param draft - Immer draft (mutable proxy) of game state
|
|
37
|
+
* @param context - Move context (player, typed params, targets, timestamp, zones, cards, registry)
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* // For a move: playCard: { cardId: string; alternativeCost?: AlternativeCost }
|
|
42
|
+
* reducer: (draft, context) => {
|
|
43
|
+
* const { cardId, alternativeCost } = context.params; // ✅ Fully typed!
|
|
44
|
+
* // Implementation...
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
reducer: (
|
|
49
|
+
draft: Draft<TState>,
|
|
50
|
+
context: MoveContext<NormalizeParams<TParams>, TCardMeta, TCardDefinition>,
|
|
51
|
+
) => void;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Move condition - validates if move is legal
|
|
55
|
+
*
|
|
56
|
+
* Task 10.6: Optional condition for validation
|
|
57
|
+
*
|
|
58
|
+
* Pure predicate checked BEFORE reducer execution.
|
|
59
|
+
* If returns false, move is rejected without state changes.
|
|
60
|
+
*
|
|
61
|
+
* The context includes fully-typed parameters specific to this move.
|
|
62
|
+
*
|
|
63
|
+
* @param state - Current game state (readonly)
|
|
64
|
+
* @param context - Move context (player, typed params, targets, timestamp, zones, cards, registry)
|
|
65
|
+
* @returns True if move is legal, false otherwise
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```typescript
|
|
69
|
+
* // For a move: playCard: { cardId: string; alternativeCost?: AlternativeCost }
|
|
70
|
+
* condition: (state, context) => {
|
|
71
|
+
* const { cardId, alternativeCost } = context.params; // ✅ Fully typed!
|
|
72
|
+
* // Validation logic...
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
condition?: (
|
|
77
|
+
state: TState,
|
|
78
|
+
context: MoveContext<NormalizeParams<TParams>, TCardMeta, TCardDefinition>,
|
|
79
|
+
) => boolean | import("../moves/move-system").ConditionFailure;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Parameter enumerator (for move enumeration system)
|
|
83
|
+
*
|
|
84
|
+
* Optional function to generate candidate parameter combinations.
|
|
85
|
+
* Used by RuleEngine.enumerateMoves() to discover available moves for AI/UI.
|
|
86
|
+
* Each parameter combination returned will be validated against the move's condition.
|
|
87
|
+
*
|
|
88
|
+
* If not provided, move will still appear in enumeration results
|
|
89
|
+
* but will indicate that parameters are required.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* enumerator: (state, context) => {
|
|
94
|
+
* // Get all cards in player's hand
|
|
95
|
+
* const handCards = context.zones.getCardsInZone('hand', context.playerId);
|
|
96
|
+
* // Generate parameter for each card
|
|
97
|
+
* return handCards.map(cardId => ({ cardId }));
|
|
98
|
+
* }
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
enumerator?: (
|
|
102
|
+
state: TState,
|
|
103
|
+
context: import("../moves/move-enumeration").MoveEnumerationContext<
|
|
104
|
+
TCardMeta,
|
|
105
|
+
TCardDefinition
|
|
106
|
+
>,
|
|
107
|
+
) => TParams[];
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Optional metadata
|
|
111
|
+
*
|
|
112
|
+
* For categorization, UI display, AI hints, etc.
|
|
113
|
+
* Not used by engine, but available to game implementations.
|
|
114
|
+
*/
|
|
115
|
+
metadata?: {
|
|
116
|
+
/** Move category (e.g., 'combat', 'resource', 'draw') */
|
|
117
|
+
category?: string;
|
|
118
|
+
/** Tags for filtering/searching */
|
|
119
|
+
tags?: string[];
|
|
120
|
+
/** Custom metadata */
|
|
121
|
+
[key: string]: unknown;
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Game Move Definitions - Exhaustive mapping of moves with type-safe parameters
|
|
127
|
+
*
|
|
128
|
+
* Task 10.6: GameMoveDefinitions type with exhaustive mapping
|
|
129
|
+
*
|
|
130
|
+
* Maps each move name in TMoves to its GameMoveDefinition with the correct parameter type.
|
|
131
|
+
* TypeScript enforces that:
|
|
132
|
+
* - All moves in TMoves have definitions
|
|
133
|
+
* - No extra moves are defined
|
|
134
|
+
* - Each definition has the correct parameter type (TMoves[K])
|
|
135
|
+
* - Reducers and conditions receive fully-typed parameters via context.params
|
|
136
|
+
*
|
|
137
|
+
* @template TState - Game state type
|
|
138
|
+
* @template TMoves - Record of move names to parameter types
|
|
139
|
+
* @template TCardMeta - Card metadata type
|
|
140
|
+
* @template TCardDefinition - Card definition type
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```typescript
|
|
144
|
+
* type MyMoves = {
|
|
145
|
+
* playCard: { cardId: string; cost?: number };
|
|
146
|
+
* quest: { cardId: string };
|
|
147
|
+
* pass: void;
|
|
148
|
+
* };
|
|
149
|
+
*
|
|
150
|
+
* const moves: GameMoveDefinitions<GameState, MyMoves> = {
|
|
151
|
+
* playCard: {
|
|
152
|
+
* condition: (state, context) => {
|
|
153
|
+
* const { cardId, cost } = context.params; // ✅ Typed as { cardId: string; cost?: number }
|
|
154
|
+
* return true;
|
|
155
|
+
* },
|
|
156
|
+
* reducer: (draft, context) => {
|
|
157
|
+
* const { cardId, cost } = context.params; // ✅ Typed as { cardId: string; cost?: number }
|
|
158
|
+
* // Implementation...
|
|
159
|
+
* }
|
|
160
|
+
* },
|
|
161
|
+
* quest: {
|
|
162
|
+
* reducer: (draft, context) => {
|
|
163
|
+
* const { cardId } = context.params; // ✅ Typed as { cardId: string }
|
|
164
|
+
* // Implementation...
|
|
165
|
+
* }
|
|
166
|
+
* },
|
|
167
|
+
* pass: {
|
|
168
|
+
* reducer: (draft, context) => {
|
|
169
|
+
* // context.params is {} (empty object)
|
|
170
|
+
* // Implementation...
|
|
171
|
+
* }
|
|
172
|
+
* }
|
|
173
|
+
* };
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
export type GameMoveDefinitions<
|
|
177
|
+
TState,
|
|
178
|
+
TMoves extends Record<string, any>,
|
|
179
|
+
TCardMeta = any,
|
|
180
|
+
TCardDefinition = any,
|
|
181
|
+
> = {
|
|
182
|
+
[K in keyof TMoves]: GameMoveDefinition<
|
|
183
|
+
TState,
|
|
184
|
+
TMoves[K], // ✅ Each move gets its specific parameter type!
|
|
185
|
+
TCardMeta,
|
|
186
|
+
TCardDefinition
|
|
187
|
+
>;
|
|
188
|
+
};
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { GameDefinition } from "./game-definition";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Game Definition Validation Result
|
|
6
|
+
*
|
|
7
|
+
* Result of validating a GameDefinition.
|
|
8
|
+
*/
|
|
9
|
+
export type GameDefinitionValidationResult =
|
|
10
|
+
| { success: true }
|
|
11
|
+
| { success: false; error: string; errors?: string[] };
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Zod schema for GameDefinition validation
|
|
15
|
+
*
|
|
16
|
+
* Task 10.14: Implement Zod schema for GameDefinition validation
|
|
17
|
+
*
|
|
18
|
+
* Runtime validation to ensure GameDefinition is well-formed.
|
|
19
|
+
* Checks:
|
|
20
|
+
* - Required fields are present
|
|
21
|
+
* - Types are correct (functions, numbers, strings)
|
|
22
|
+
* - Constraints are met (minPlayers <= maxPlayers, etc.)
|
|
23
|
+
*/
|
|
24
|
+
const GameDefinitionSchema = z.object({
|
|
25
|
+
// Name validation
|
|
26
|
+
name: z.string().min(1, "Game name must not be empty"),
|
|
27
|
+
|
|
28
|
+
// Setup function validation
|
|
29
|
+
setup: z.function().args(z.array(z.string())).returns(z.record(z.any())),
|
|
30
|
+
|
|
31
|
+
// Moves validation
|
|
32
|
+
moves: z.record(
|
|
33
|
+
z.object({
|
|
34
|
+
reducer: z.function(),
|
|
35
|
+
condition: z.function().optional(),
|
|
36
|
+
metadata: z.record(z.any()).optional(),
|
|
37
|
+
}),
|
|
38
|
+
),
|
|
39
|
+
|
|
40
|
+
// Optional flow validation
|
|
41
|
+
flow: z
|
|
42
|
+
.object({
|
|
43
|
+
initial: z.string(),
|
|
44
|
+
states: z.record(z.any()),
|
|
45
|
+
hooks: z
|
|
46
|
+
.object({
|
|
47
|
+
onBegin: z.function().optional(),
|
|
48
|
+
onEnd: z.function().optional(),
|
|
49
|
+
})
|
|
50
|
+
.optional(),
|
|
51
|
+
})
|
|
52
|
+
.optional(),
|
|
53
|
+
|
|
54
|
+
// Optional endIf validation
|
|
55
|
+
endIf: z
|
|
56
|
+
.function()
|
|
57
|
+
.args(z.any())
|
|
58
|
+
.returns(
|
|
59
|
+
z.union([
|
|
60
|
+
z.object({ winner: z.string(), reason: z.string() }),
|
|
61
|
+
z.undefined(),
|
|
62
|
+
]),
|
|
63
|
+
)
|
|
64
|
+
.optional(),
|
|
65
|
+
|
|
66
|
+
// Optional playerView validation
|
|
67
|
+
playerView: z
|
|
68
|
+
.function()
|
|
69
|
+
.args(z.any(), z.string())
|
|
70
|
+
.returns(z.any())
|
|
71
|
+
.optional(),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Validate GameDefinition
|
|
76
|
+
*
|
|
77
|
+
* Task 10.13, 10.14: Validate GameDefinition using Zod schema
|
|
78
|
+
*
|
|
79
|
+
* Performs comprehensive validation:
|
|
80
|
+
* 1. Schema validation (types, required fields)
|
|
81
|
+
* 3. Move definition validation (each move has reducer)
|
|
82
|
+
* 4. Function signature validation
|
|
83
|
+
*
|
|
84
|
+
* @param definition - GameDefinition to validate
|
|
85
|
+
* @returns GameDefinitionValidationResult with success flag and error details
|
|
86
|
+
*/
|
|
87
|
+
export function validateGameDefinition<
|
|
88
|
+
TState,
|
|
89
|
+
TMoves extends Record<string, any>,
|
|
90
|
+
>(definition: GameDefinition<TState, TMoves>): GameDefinitionValidationResult {
|
|
91
|
+
const errors: string[] = [];
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
// Run Zod schema validation
|
|
95
|
+
GameDefinitionSchema.parse(definition);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (error instanceof z.ZodError) {
|
|
98
|
+
// Collect all Zod validation errors
|
|
99
|
+
for (const issue of error.issues) {
|
|
100
|
+
const path = issue.path.join(".");
|
|
101
|
+
errors.push(`${path}: ${issue.message}`);
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
errors.push("Unknown validation error");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Validate that setup is a function
|
|
109
|
+
if (typeof definition.setup !== "function") {
|
|
110
|
+
errors.push("setup must be a function");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Validate that moves exist and have reducers
|
|
114
|
+
if (!definition.moves || typeof definition.moves !== "object") {
|
|
115
|
+
errors.push("moves must be an object");
|
|
116
|
+
} else {
|
|
117
|
+
for (const [moveName, moveDef] of Object.entries(definition.moves)) {
|
|
118
|
+
if (!moveDef || typeof moveDef !== "object") {
|
|
119
|
+
errors.push(`Move "${moveName}" must be an object`);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (typeof moveDef.reducer !== "function") {
|
|
124
|
+
errors.push(`Move "${moveName}" must have a reducer function`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (moveDef.condition && typeof moveDef.condition !== "function") {
|
|
128
|
+
errors.push(`Move "${moveName}" condition must be a function`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Validate optional endIf
|
|
134
|
+
if (definition.endIf && typeof definition.endIf !== "function") {
|
|
135
|
+
errors.push("endIf must be a function");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Validate optional playerView
|
|
139
|
+
if (definition.playerView && typeof definition.playerView !== "function") {
|
|
140
|
+
errors.push("playerView must be a function");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Validate optional flow
|
|
144
|
+
if (definition.flow) {
|
|
145
|
+
if (typeof definition.flow !== "object") {
|
|
146
|
+
errors.push("flow must be an object");
|
|
147
|
+
} else {
|
|
148
|
+
// Flow validation - supports both simplified (turn) and full (gameSegments) syntax
|
|
149
|
+
const flow = definition.flow as any;
|
|
150
|
+
|
|
151
|
+
// Check for simplified syntax (turn property)
|
|
152
|
+
const hasSimplifiedSyntax = "turn" in flow && flow.turn;
|
|
153
|
+
|
|
154
|
+
// Check for full syntax (gameSegments property)
|
|
155
|
+
const hasFullSyntax = "gameSegments" in flow && flow.gameSegments;
|
|
156
|
+
|
|
157
|
+
if (!(hasSimplifiedSyntax || hasFullSyntax)) {
|
|
158
|
+
errors.push(
|
|
159
|
+
"flow must have either 'turn' property (simplified) or 'gameSegments' property (full syntax)",
|
|
160
|
+
);
|
|
161
|
+
} else if (hasSimplifiedSyntax) {
|
|
162
|
+
// Validate simplified syntax
|
|
163
|
+
if (typeof flow.turn !== "object") {
|
|
164
|
+
errors.push("flow.turn must be an object");
|
|
165
|
+
} else if (!flow.turn.phases || typeof flow.turn.phases !== "object") {
|
|
166
|
+
errors.push("flow.turn must have phases object");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Full syntax validation could be added here if needed
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Return result
|
|
174
|
+
if (errors.length > 0) {
|
|
175
|
+
return {
|
|
176
|
+
success: false,
|
|
177
|
+
error: errors.join("; "),
|
|
178
|
+
errors,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return { success: true };
|
|
183
|
+
}
|