volleyballsimtypes 0.0.480 → 0.0.482
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/dist/cjs/src/api/index.d.ts +8 -2
- package/dist/cjs/src/data/models/tactics.d.ts +11 -3
- package/dist/cjs/src/data/models/tactics.js +9 -0
- package/dist/cjs/src/data/transformers/tactics.js +12 -4
- package/dist/cjs/src/data/transformers/tactics.test.js +23 -9
- package/dist/cjs/src/service/competition/standing.js +5 -0
- package/dist/cjs/src/service/competition/standing.test.js +19 -8
- package/dist/cjs/src/service/team/blocking-preference.d.ts +5 -0
- package/dist/cjs/src/service/team/blocking-preference.js +2 -0
- package/dist/cjs/src/service/team/index.d.ts +1 -0
- package/dist/cjs/src/service/team/index.js +1 -0
- package/dist/cjs/src/service/team/offensive-preference.d.ts +1 -2
- package/dist/cjs/src/service/team/schemas/tactics.z.d.ts +11 -3
- package/dist/cjs/src/service/team/schemas/tactics.z.js +43 -10
- package/dist/cjs/src/service/team/schemas/tactics.z.test.js +81 -8
- package/dist/cjs/src/service/team/schemas/team.z.d.ts +10 -2
- package/dist/cjs/src/service/team/tactics.d.ts +5 -0
- package/dist/cjs/src/service/team/tactics.js +3 -1
- package/dist/esm/src/api/index.d.ts +8 -2
- package/dist/esm/src/data/models/tactics.d.ts +11 -3
- package/dist/esm/src/data/models/tactics.js +9 -0
- package/dist/esm/src/data/transformers/tactics.js +12 -4
- package/dist/esm/src/data/transformers/tactics.test.js +24 -10
- package/dist/esm/src/service/competition/standing.js +5 -0
- package/dist/esm/src/service/competition/standing.test.js +19 -8
- package/dist/esm/src/service/team/blocking-preference.d.ts +5 -0
- package/dist/esm/src/service/team/blocking-preference.js +1 -0
- package/dist/esm/src/service/team/index.d.ts +1 -0
- package/dist/esm/src/service/team/index.js +1 -0
- package/dist/esm/src/service/team/offensive-preference.d.ts +1 -2
- package/dist/esm/src/service/team/schemas/tactics.z.d.ts +11 -3
- package/dist/esm/src/service/team/schemas/tactics.z.js +44 -11
- package/dist/esm/src/service/team/schemas/tactics.z.test.js +81 -8
- package/dist/esm/src/service/team/schemas/team.z.d.ts +10 -2
- package/dist/esm/src/service/team/tactics.d.ts +5 -0
- package/dist/esm/src/service/team/tactics.js +3 -1
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect } from '@jest/globals';
|
|
2
2
|
import { TacticsInputSchema } from './tactics.z';
|
|
3
|
+
import { RoleEnum } from '../../player';
|
|
3
4
|
import { makeCountry, makePlayer } from '../../test-helpers';
|
|
4
5
|
describe('TacticsInputSchema — second libero rules', () => {
|
|
5
6
|
const country = makeCountry();
|
|
@@ -93,7 +94,7 @@ describe('TacticsInputSchema — per-set tactics (systemSets / offensivePreferen
|
|
|
93
94
|
{ rotationSystem: '4-2', designatedSetters: [p1, p4] }
|
|
94
95
|
],
|
|
95
96
|
offensivePreferenceSets: [
|
|
96
|
-
[{ rotation: 1,
|
|
97
|
+
[{ rotation: 1, weights: { [p2.id]: 60, [p3.id]: 40 } }],
|
|
97
98
|
[]
|
|
98
99
|
]
|
|
99
100
|
}));
|
|
@@ -102,7 +103,7 @@ describe('TacticsInputSchema — per-set tactics (systemSets / offensivePreferen
|
|
|
102
103
|
it('carries the per-set fields through Tactics.create parsing', () => {
|
|
103
104
|
const res = TacticsInputSchema.safeParse(baseInput({
|
|
104
105
|
systemSets: [{ rotationSystem: '5-1', designatedSetters: [p1] }],
|
|
105
|
-
offensivePreferenceSets: [[{ rotation: 2,
|
|
106
|
+
offensivePreferenceSets: [[{ rotation: 2, weights: { [p3.id]: 100 } }]]
|
|
106
107
|
}));
|
|
107
108
|
expect(res.success).toBe(true);
|
|
108
109
|
if (res.success) {
|
|
@@ -151,7 +152,7 @@ describe('TacticsInputSchema — per-set tactics (systemSets / offensivePreferen
|
|
|
151
152
|
});
|
|
152
153
|
it('rejects a per-set preferences list with a duplicate rotation', () => {
|
|
153
154
|
const res = TacticsInputSchema.safeParse(baseInput({
|
|
154
|
-
offensivePreferenceSets: [[{ rotation: 1,
|
|
155
|
+
offensivePreferenceSets: [[{ rotation: 1, weights: { [p2.id]: 100 } }, { rotation: 1, weights: { [p3.id]: 100 } }]]
|
|
155
156
|
}));
|
|
156
157
|
expect(res.success).toBe(false);
|
|
157
158
|
if (!res.success) {
|
|
@@ -159,18 +160,27 @@ describe('TacticsInputSchema — per-set tactics (systemSets / offensivePreferen
|
|
|
159
160
|
expect(issue?.path).toEqual(['offensivePreferenceSets', 0]);
|
|
160
161
|
}
|
|
161
162
|
});
|
|
162
|
-
it('rejects a per-set preferences
|
|
163
|
+
it('rejects a per-set preferences map that keys a non-starter', () => {
|
|
163
164
|
const res = TacticsInputSchema.safeParse(baseInput({
|
|
164
|
-
offensivePreferenceSets: [[{ rotation: 1,
|
|
165
|
+
offensivePreferenceSets: [[{ rotation: 1, weights: { [reserve.id]: 100 } }]]
|
|
165
166
|
}));
|
|
166
167
|
expect(res.success).toBe(false);
|
|
167
168
|
if (!res.success) {
|
|
168
169
|
expect(res.error.issues.some(i => i.message === 'OFFENSIVE_PREFERENCE_ATTACKER_NOT_A_STARTER')).toBe(true);
|
|
169
170
|
}
|
|
170
171
|
});
|
|
172
|
+
it('rejects offensive preference weights that do not sum to 100', () => {
|
|
173
|
+
const res = TacticsInputSchema.safeParse(baseInput({
|
|
174
|
+
offensivePreferences: [{ rotation: 1, weights: { [p2.id]: 50, [p3.id]: 40 } }]
|
|
175
|
+
}));
|
|
176
|
+
expect(res.success).toBe(false);
|
|
177
|
+
if (!res.success) {
|
|
178
|
+
expect(res.error.issues.some(i => i.message === 'OFFENSIVE_PREFERENCE_WEIGHTS_MUST_SUM_TO_100')).toBe(true);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
171
181
|
it('accepts offensive preferences with a starter-keyed tempos map', () => {
|
|
172
182
|
const res = TacticsInputSchema.safeParse(baseInput({
|
|
173
|
-
offensivePreferences: [{ rotation: 1,
|
|
183
|
+
offensivePreferences: [{ rotation: 1, weights: { [p2.id]: 60, [p3.id]: 40 }, tempos: { [p2.id]: 'FAST', [p3.id]: 'HIGH' } }]
|
|
174
184
|
}));
|
|
175
185
|
expect(res.success).toBe(true);
|
|
176
186
|
if (res.success) {
|
|
@@ -179,7 +189,7 @@ describe('TacticsInputSchema — per-set tactics (systemSets / offensivePreferen
|
|
|
179
189
|
});
|
|
180
190
|
it('rejects a tempos map keyed by a non-starter', () => {
|
|
181
191
|
const res = TacticsInputSchema.safeParse(baseInput({
|
|
182
|
-
offensivePreferences: [{ rotation: 1,
|
|
192
|
+
offensivePreferences: [{ rotation: 1, weights: { [p2.id]: 100 }, tempos: { [reserve.id]: 'FAST' } }]
|
|
183
193
|
}));
|
|
184
194
|
expect(res.success).toBe(false);
|
|
185
195
|
if (!res.success) {
|
|
@@ -188,7 +198,7 @@ describe('TacticsInputSchema — per-set tactics (systemSets / offensivePreferen
|
|
|
188
198
|
});
|
|
189
199
|
it('rejects an invalid tempo value', () => {
|
|
190
200
|
const res = TacticsInputSchema.safeParse(baseInput({
|
|
191
|
-
offensivePreferences: [{ rotation: 1,
|
|
201
|
+
offensivePreferences: [{ rotation: 1, weights: { [p2.id]: 100 }, tempos: { [p2.id]: 'SLOW' } }]
|
|
192
202
|
}));
|
|
193
203
|
expect(res.success).toBe(false);
|
|
194
204
|
});
|
|
@@ -206,3 +216,66 @@ describe('TacticsInputSchema — per-set tactics (systemSets / offensivePreferen
|
|
|
206
216
|
}
|
|
207
217
|
});
|
|
208
218
|
});
|
|
219
|
+
describe('TacticsInputSchema — blocking preferences', () => {
|
|
220
|
+
const country = makeCountry();
|
|
221
|
+
const p1 = makePlayer(country);
|
|
222
|
+
const p2 = makePlayer(country);
|
|
223
|
+
const p3 = makePlayer(country);
|
|
224
|
+
const p4 = makePlayer(country);
|
|
225
|
+
const p5 = makePlayer(country);
|
|
226
|
+
const p6 = makePlayer(country);
|
|
227
|
+
const reserve = makePlayer(country);
|
|
228
|
+
// Front-row starters (zones 2/3/4) are p2, p3, p4; the validator only requires target keys to be starters.
|
|
229
|
+
function baseInput(overrides = {}) {
|
|
230
|
+
return {
|
|
231
|
+
lineup: { 4: p4, 3: p3, 2: p2, 5: p5, 6: p6, 1: p1, bench: [] },
|
|
232
|
+
liberoReplacements: [],
|
|
233
|
+
...overrides
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
it('accepts a blocking preference that keys starters to attacking functions', () => {
|
|
237
|
+
const res = TacticsInputSchema.safeParse(baseInput({
|
|
238
|
+
blockingPreferences: [{ rotation: 1, targets: { [p4.id]: RoleEnum.OUTSIDE_HITTER, [p3.id]: RoleEnum.MIDDLE_BLOCKER } }]
|
|
239
|
+
}));
|
|
240
|
+
expect(res.success).toBe(true);
|
|
241
|
+
if (res.success) {
|
|
242
|
+
expect(res.data.blockingPreferences[0].targets[p4.id]).toBe(RoleEnum.OUTSIDE_HITTER);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
it('rejects a blocking target that is not a starter', () => {
|
|
246
|
+
const res = TacticsInputSchema.safeParse(baseInput({
|
|
247
|
+
blockingPreferences: [{ rotation: 1, targets: { [reserve.id]: RoleEnum.OUTSIDE_HITTER } }]
|
|
248
|
+
}));
|
|
249
|
+
expect(res.success).toBe(false);
|
|
250
|
+
if (!res.success) {
|
|
251
|
+
expect(res.error.issues.some(i => i.message === 'BLOCKING_PREFERENCE_BLOCKER_NOT_A_STARTER')).toBe(true);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
it('rejects a blocking target function of libero', () => {
|
|
255
|
+
const res = TacticsInputSchema.safeParse(baseInput({
|
|
256
|
+
blockingPreferences: [{ rotation: 1, targets: { [p4.id]: RoleEnum.LIBERO } }]
|
|
257
|
+
}));
|
|
258
|
+
expect(res.success).toBe(false);
|
|
259
|
+
if (!res.success) {
|
|
260
|
+
expect(res.error.issues.some(i => i.message === 'BLOCKING_TARGET_CANNOT_BE_LIBERO')).toBe(true);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
it('rejects a duplicate rotation in blocking preferences', () => {
|
|
264
|
+
const res = TacticsInputSchema.safeParse(baseInput({
|
|
265
|
+
blockingPreferences: [
|
|
266
|
+
{ rotation: 1, targets: { [p4.id]: RoleEnum.OUTSIDE_HITTER } },
|
|
267
|
+
{ rotation: 1, targets: { [p3.id]: RoleEnum.MIDDLE_BLOCKER } }
|
|
268
|
+
]
|
|
269
|
+
}));
|
|
270
|
+
expect(res.success).toBe(false);
|
|
271
|
+
if (!res.success) {
|
|
272
|
+
expect(res.error.issues.some(i => i.message === 'BLOCKING_PREFERENCE_DUPLICATE_ROTATION')).toBe(true);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
it('accepts per-set blocking preference overrides', () => {
|
|
276
|
+
const res = TacticsInputSchema.safeParse(baseInput({
|
|
277
|
+
blockingPreferenceSets: [[{ rotation: 1, targets: { [p4.id]: RoleEnum.OPPOSITE_HITTER } }], []]
|
|
278
|
+
}));
|
|
279
|
+
expect(res.success).toBe(true);
|
|
280
|
+
});
|
|
281
|
+
});
|
|
@@ -277,7 +277,7 @@ export declare const TeamInputSchema: z.ZodObject<{
|
|
|
277
277
|
designatedSetters: z.ZodDefault<z.ZodArray<z.ZodCustom<Player, Player>>>;
|
|
278
278
|
offensivePreferences: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
279
279
|
rotation: z.ZodNumber;
|
|
280
|
-
|
|
280
|
+
weights: z.ZodRecord<z.ZodString, z.ZodNumber>;
|
|
281
281
|
tempos: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
282
282
|
FAST: "FAST";
|
|
283
283
|
HIGH: "HIGH";
|
|
@@ -289,12 +289,20 @@ export declare const TeamInputSchema: z.ZodObject<{
|
|
|
289
289
|
}, z.core.$strip>>>;
|
|
290
290
|
offensivePreferenceSets: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodObject<{
|
|
291
291
|
rotation: z.ZodNumber;
|
|
292
|
-
|
|
292
|
+
weights: z.ZodRecord<z.ZodString, z.ZodNumber>;
|
|
293
293
|
tempos: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodEnum<{
|
|
294
294
|
FAST: "FAST";
|
|
295
295
|
HIGH: "HIGH";
|
|
296
296
|
}>>>;
|
|
297
297
|
}, z.core.$strip>>>>;
|
|
298
|
+
blockingPreferences: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
299
|
+
rotation: z.ZodNumber;
|
|
300
|
+
targets: z.ZodRecord<z.ZodString, z.ZodEnum<typeof import("../../player").RoleEnum> & z.ZodType<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum, z.core.$ZodTypeInternals<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum>>>;
|
|
301
|
+
}, z.core.$strip>>>;
|
|
302
|
+
blockingPreferenceSets: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodObject<{
|
|
303
|
+
rotation: z.ZodNumber;
|
|
304
|
+
targets: z.ZodRecord<z.ZodString, z.ZodEnum<typeof import("../../player").RoleEnum> & z.ZodType<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum, z.core.$ZodTypeInternals<import("../../player").RoleEnum.SETTER | import("../../player").RoleEnum.OUTSIDE_HITTER | import("../../player").RoleEnum.OPPOSITE_HITTER | import("../../player").RoleEnum.MIDDLE_BLOCKER, import("../../player").RoleEnum>>>;
|
|
305
|
+
}, z.core.$strip>>>>;
|
|
298
306
|
replaceKnockedImmediately: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodBoolean>>;
|
|
299
307
|
injuryReplacements: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
300
308
|
starter: z.ZodCustom<Player, Player>;
|
|
@@ -5,6 +5,7 @@ import { LiberoSubConfig } from './libero-sub';
|
|
|
5
5
|
import { SubBand } from './energy-band';
|
|
6
6
|
import { RotationSystemEnum } from './rotation-system';
|
|
7
7
|
import { OffensivePreference } from './offensive-preference';
|
|
8
|
+
import { BlockingPreference } from './blocking-preference';
|
|
8
9
|
import { InjuryReplacement } from './injury-replacement';
|
|
9
10
|
import { LiberoInjuryConfig } from './libero-injury';
|
|
10
11
|
export interface StartingLineup {
|
|
@@ -38,6 +39,8 @@ export interface TacticsOpts {
|
|
|
38
39
|
readonly replaceKnockedImmediately: Record<string, boolean>;
|
|
39
40
|
readonly injuryReplacements: InjuryReplacement[];
|
|
40
41
|
readonly liberoInjury?: LiberoInjuryConfig;
|
|
42
|
+
readonly blockingPreferences: BlockingPreference[];
|
|
43
|
+
readonly blockingPreferenceSets?: BlockingPreference[][];
|
|
41
44
|
}
|
|
42
45
|
export declare class Tactics {
|
|
43
46
|
readonly lineup: StartingLineup;
|
|
@@ -56,6 +59,8 @@ export declare class Tactics {
|
|
|
56
59
|
readonly replaceKnockedImmediately: Record<string, boolean>;
|
|
57
60
|
readonly injuryReplacements: InjuryReplacement[];
|
|
58
61
|
readonly liberoInjury?: LiberoInjuryConfig;
|
|
62
|
+
readonly blockingPreferences: BlockingPreference[];
|
|
63
|
+
readonly blockingPreferenceSets?: BlockingPreference[][];
|
|
59
64
|
static create(input: unknown): Tactics;
|
|
60
65
|
private constructor();
|
|
61
66
|
findPlayerInLineup(id: string): CourtPosition | undefined;
|
|
@@ -15,7 +15,7 @@ export class Tactics {
|
|
|
15
15
|
}
|
|
16
16
|
return new Tactics(result.data);
|
|
17
17
|
}
|
|
18
|
-
constructor({ lineup, liberoReplacements, receiveRotationOffset = false, designatedSubs = [], substitutionBand = EnergyBand.TIRED, secondLibero, liberoSub, liberoReplacementSets, rotationSystem = RotationSystemEnum.SIX_ZERO, designatedSetters = [], offensivePreferences = [], systemSets, offensivePreferenceSets, replaceKnockedImmediately = {}, injuryReplacements = [], liberoInjury }) {
|
|
18
|
+
constructor({ lineup, liberoReplacements, receiveRotationOffset = false, designatedSubs = [], substitutionBand = EnergyBand.TIRED, secondLibero, liberoSub, liberoReplacementSets, rotationSystem = RotationSystemEnum.SIX_ZERO, designatedSetters = [], offensivePreferences = [], systemSets, offensivePreferenceSets, replaceKnockedImmediately = {}, injuryReplacements = [], liberoInjury, blockingPreferences = [], blockingPreferenceSets }) {
|
|
19
19
|
this.lineup = lineup;
|
|
20
20
|
this.liberoReplacements = liberoReplacements;
|
|
21
21
|
this.receiveRotationOffset = receiveRotationOffset;
|
|
@@ -32,6 +32,8 @@ export class Tactics {
|
|
|
32
32
|
this.replaceKnockedImmediately = replaceKnockedImmediately;
|
|
33
33
|
this.injuryReplacements = injuryReplacements;
|
|
34
34
|
this.liberoInjury = liberoInjury;
|
|
35
|
+
this.blockingPreferences = blockingPreferences;
|
|
36
|
+
this.blockingPreferenceSets = blockingPreferenceSets;
|
|
35
37
|
}
|
|
36
38
|
findPlayerInLineup(id) {
|
|
37
39
|
if (this.lineup.bench.some(p => p.id === id))
|