@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,230 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { createPlayerId, createZoneId } from "../types";
|
|
3
|
+
import type { CardFilter, NumberFilter } from "./card-filter";
|
|
4
|
+
|
|
5
|
+
describe("Card Filter Types", () => {
|
|
6
|
+
describe("CardFilter", () => {
|
|
7
|
+
it("should support zone filtering with single zone", () => {
|
|
8
|
+
const filter: CardFilter = {
|
|
9
|
+
zone: createZoneId("play"),
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
expect(filter.zone).toBe(createZoneId("play"));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("should support zone filtering with multiple zones", () => {
|
|
16
|
+
const filter: CardFilter = {
|
|
17
|
+
zone: [createZoneId("hand"), createZoneId("graveyard")],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
expect(Array.isArray(filter.zone)).toBe(true);
|
|
21
|
+
expect(filter.zone).toHaveLength(2);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should support owner filtering with single player", () => {
|
|
25
|
+
const playerId = createPlayerId("player-1");
|
|
26
|
+
const filter: CardFilter = {
|
|
27
|
+
owner: playerId,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
expect(filter.owner).toBe(playerId);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should support owner filtering with multiple players", () => {
|
|
34
|
+
const filter: CardFilter = {
|
|
35
|
+
owner: [createPlayerId("player-1"), createPlayerId("player-2")],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
expect(Array.isArray(filter.owner)).toBe(true);
|
|
39
|
+
expect(filter.owner).toHaveLength(2);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should support controller filtering", () => {
|
|
43
|
+
const playerId = createPlayerId("player-1");
|
|
44
|
+
const filter: CardFilter = {
|
|
45
|
+
controller: playerId,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
expect(filter.controller).toBe(playerId);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should support type filtering with single type", () => {
|
|
52
|
+
const filter: CardFilter = {
|
|
53
|
+
type: "creature",
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
expect(filter.type).toBe("creature");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should support type filtering with multiple types", () => {
|
|
60
|
+
const filter: CardFilter = {
|
|
61
|
+
type: ["creature", "artifact"],
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
expect(Array.isArray(filter.type)).toBe(true);
|
|
65
|
+
expect(filter.type).toHaveLength(2);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should support name filtering with string", () => {
|
|
69
|
+
const filter: CardFilter = {
|
|
70
|
+
name: "Lightning Bolt",
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
expect(filter.name).toBe("Lightning Bolt");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should support name filtering with RegExp", () => {
|
|
77
|
+
const filter: CardFilter = {
|
|
78
|
+
name: /Lightning.*/,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
expect(filter.name).toBeInstanceOf(RegExp);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("should support generic property filtering", () => {
|
|
85
|
+
const filter: CardFilter = {
|
|
86
|
+
properties: {
|
|
87
|
+
baseCost: { gte: 3 },
|
|
88
|
+
basePower: { gt: 5 },
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
expect(filter.properties).toBeDefined();
|
|
93
|
+
expect(filter.properties?.baseCost).toEqual({ gte: 3 });
|
|
94
|
+
expect(filter.properties?.basePower).toEqual({ gt: 5 });
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("should support tapped state filtering", () => {
|
|
98
|
+
const filter: CardFilter = {
|
|
99
|
+
tapped: true,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
expect(filter.tapped).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("should support revealed state filtering", () => {
|
|
106
|
+
const filter: CardFilter = {
|
|
107
|
+
revealed: true,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
expect(filter.revealed).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("should support flipped state filtering", () => {
|
|
114
|
+
const filter: CardFilter = {
|
|
115
|
+
flipped: true,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
expect(filter.flipped).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("should support phased state filtering", () => {
|
|
122
|
+
const filter: CardFilter = {
|
|
123
|
+
phased: false,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
expect(filter.phased).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("should support composite AND filters", () => {
|
|
130
|
+
const filter: CardFilter = {
|
|
131
|
+
and: [{ type: "creature" }, { tapped: false }],
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
expect(Array.isArray(filter.and)).toBe(true);
|
|
135
|
+
expect(filter.and).toHaveLength(2);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("should support composite OR filters", () => {
|
|
139
|
+
const filter: CardFilter = {
|
|
140
|
+
or: [
|
|
141
|
+
{ zone: createZoneId("hand") },
|
|
142
|
+
{ zone: createZoneId("graveyard") },
|
|
143
|
+
],
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
expect(Array.isArray(filter.or)).toBe(true);
|
|
147
|
+
expect(filter.or).toHaveLength(2);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("should support NOT filters", () => {
|
|
151
|
+
const filter: CardFilter = {
|
|
152
|
+
not: { type: "land" },
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
expect(filter.not).toBeDefined();
|
|
156
|
+
expect(filter.not?.type).toBe("land");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("should support custom where predicate", () => {
|
|
160
|
+
const filter: CardFilter = {
|
|
161
|
+
where: (card, _state) => card.tapped === true,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
expect(typeof filter.where).toBe("function");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("should support combining multiple filter properties", () => {
|
|
168
|
+
const filter: CardFilter = {
|
|
169
|
+
zone: createZoneId("play"),
|
|
170
|
+
type: "creature",
|
|
171
|
+
controller: createPlayerId("player-1"),
|
|
172
|
+
tapped: false,
|
|
173
|
+
properties: {
|
|
174
|
+
basePower: { gte: 3 },
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
expect(filter.zone).toBeDefined();
|
|
179
|
+
expect(filter.type).toBe("creature");
|
|
180
|
+
expect(filter.controller).toBeDefined();
|
|
181
|
+
expect(filter.tapped).toBe(false);
|
|
182
|
+
expect(filter.properties?.basePower).toEqual({ gte: 3 });
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe("NumberFilter", () => {
|
|
187
|
+
it("should support exact number match", () => {
|
|
188
|
+
const filter: NumberFilter = 5;
|
|
189
|
+
|
|
190
|
+
expect(filter).toBe(5);
|
|
191
|
+
expect(typeof filter).toBe("number");
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("should support eq (equal) filter", () => {
|
|
195
|
+
const filter: NumberFilter = { eq: 5 };
|
|
196
|
+
|
|
197
|
+
expect(filter).toEqual({ eq: 5 });
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("should support gte (greater than or equal) filter", () => {
|
|
201
|
+
const filter: NumberFilter = { gte: 3 };
|
|
202
|
+
|
|
203
|
+
expect(filter).toEqual({ gte: 3 });
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("should support lte (less than or equal) filter", () => {
|
|
207
|
+
const filter: NumberFilter = { lte: 7 };
|
|
208
|
+
|
|
209
|
+
expect(filter).toEqual({ lte: 7 });
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("should support gt (greater than) filter", () => {
|
|
213
|
+
const filter: NumberFilter = { gt: 10 };
|
|
214
|
+
|
|
215
|
+
expect(filter).toEqual({ gt: 10 });
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("should support lt (less than) filter", () => {
|
|
219
|
+
const filter: NumberFilter = { lt: 2 };
|
|
220
|
+
|
|
221
|
+
expect(filter).toEqual({ lt: 2 });
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("should support between (range) filter", () => {
|
|
225
|
+
const filter: NumberFilter = { between: [2, 5] };
|
|
226
|
+
|
|
227
|
+
expect(filter).toEqual({ between: [2, 5] });
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { CardInstance } from "../cards/card-instance";
|
|
2
|
+
import type { PlayerId, ZoneId } from "../types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Number filter for numerical comparisons
|
|
6
|
+
* Can be a direct number (exact match) or an object with comparison operators
|
|
7
|
+
*/
|
|
8
|
+
export type NumberFilter =
|
|
9
|
+
| number // exact match
|
|
10
|
+
| { eq: number } // equal
|
|
11
|
+
| { gte: number } // greater than or equal
|
|
12
|
+
| { lte: number } // less than or equal
|
|
13
|
+
| { gt: number } // greater than
|
|
14
|
+
| { lt: number } // less than
|
|
15
|
+
| { between: [number, number] }; // range [min, max]
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* String filter for text comparisons
|
|
19
|
+
*/
|
|
20
|
+
export type StringFilter = string | RegExp | string[];
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Generic property filter that can match any value type
|
|
24
|
+
*/
|
|
25
|
+
export type PropertyFilter<T = any> =
|
|
26
|
+
| T // exact match
|
|
27
|
+
| { eq: T } // equal
|
|
28
|
+
| (T extends number ? NumberFilter : never) // numeric operations only for numbers
|
|
29
|
+
| (T extends string ? StringFilter : never); // string operations only for strings
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Core card filter - only properties ALL cards have
|
|
33
|
+
* All filter properties are optional and combined with AND logic
|
|
34
|
+
* @template TGameState - The game state type
|
|
35
|
+
*/
|
|
36
|
+
export type CardFilter<TGameState = unknown> = {
|
|
37
|
+
// Universal card properties (all TCGs have these)
|
|
38
|
+
/** Filter by zone(s) where the card is located */
|
|
39
|
+
zone?: ZoneId | ZoneId[];
|
|
40
|
+
|
|
41
|
+
/** Filter by card owner(s) */
|
|
42
|
+
owner?: PlayerId | PlayerId[];
|
|
43
|
+
|
|
44
|
+
/** Filter by card controller(s) */
|
|
45
|
+
controller?: PlayerId | PlayerId[];
|
|
46
|
+
|
|
47
|
+
/** Filter by card type(s) - all games have card types */
|
|
48
|
+
type?: string | string[];
|
|
49
|
+
|
|
50
|
+
/** Filter by card name (exact string or regex pattern) */
|
|
51
|
+
name?: string | RegExp;
|
|
52
|
+
|
|
53
|
+
// State filtering (from CardInstanceBase)
|
|
54
|
+
/** Filter by tapped/exhausted state */
|
|
55
|
+
tapped?: boolean;
|
|
56
|
+
|
|
57
|
+
/** Filter by revealed state */
|
|
58
|
+
revealed?: boolean;
|
|
59
|
+
|
|
60
|
+
/** Filter by flipped/face-down state */
|
|
61
|
+
flipped?: boolean;
|
|
62
|
+
|
|
63
|
+
/** Filter by phased state */
|
|
64
|
+
phased?: boolean;
|
|
65
|
+
|
|
66
|
+
// Extensible property filtering
|
|
67
|
+
/**
|
|
68
|
+
* Filter by properties from the card definition
|
|
69
|
+
* Allows filtering on any game-specific property like:
|
|
70
|
+
* - MTG: { basePower: 5, baseToughness: { gte: 3 } }
|
|
71
|
+
* - Pokemon: { hp: { gte: 100 }, weakness: "Fire" }
|
|
72
|
+
* - Lorcana: { inkCost: { lte: 3 }, strength: 2 }
|
|
73
|
+
*/
|
|
74
|
+
properties?: Record<string, PropertyFilter>;
|
|
75
|
+
|
|
76
|
+
// Composite filters
|
|
77
|
+
/** All filters must match (AND logic) */
|
|
78
|
+
and?: CardFilter<TGameState>[];
|
|
79
|
+
|
|
80
|
+
/** At least one filter must match (OR logic) */
|
|
81
|
+
or?: CardFilter<TGameState>[];
|
|
82
|
+
|
|
83
|
+
/** Filter must NOT match (NOT logic) */
|
|
84
|
+
not?: CardFilter<TGameState>;
|
|
85
|
+
|
|
86
|
+
// Custom predicates
|
|
87
|
+
/** Custom filter function for complex logic */
|
|
88
|
+
where?: TGameState extends { cards: Record<string, infer TCard> }
|
|
89
|
+
? (card: TCard, state: TGameState) => boolean
|
|
90
|
+
: (card: CardInstance<unknown>, state: TGameState) => boolean;
|
|
91
|
+
};
|