volleyballsimtypes 0.0.388 → 0.0.389
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.
|
@@ -36,20 +36,28 @@ class Standing {
|
|
|
36
36
|
this.totalMatches = this.matchesWon + this.matchesLost;
|
|
37
37
|
}
|
|
38
38
|
static sortFn(matches) {
|
|
39
|
+
// A zero denominator (no sets/points lost) is the BEST possible ratio, not the worst. The stored
|
|
40
|
+
// setsRatio/pointsRatio fall back to 0 in that case (and the table shows "MAX"), so we compare an
|
|
41
|
+
// effective ratio here that treats "won with nothing lost" as +Infinity, ranking it highest.
|
|
42
|
+
const effectiveRatio = (won, lost) => lost > 0 ? won / lost : (won > 0 ? Infinity : 0);
|
|
39
43
|
return (standingA, standingB) => {
|
|
40
44
|
if (standingB.points > standingA.points)
|
|
41
45
|
return 1;
|
|
42
|
-
|
|
46
|
+
if (standingA.points > standingB.points)
|
|
43
47
|
return -1;
|
|
44
|
-
|
|
48
|
+
const setRatioA = effectiveRatio(standingA.setsWon, standingA.setsLost);
|
|
49
|
+
const setRatioB = effectiveRatio(standingB.setsWon, standingB.setsLost);
|
|
50
|
+
if (setRatioB > setRatioA)
|
|
45
51
|
return 1;
|
|
46
|
-
|
|
52
|
+
if (setRatioA > setRatioB)
|
|
47
53
|
return -1;
|
|
48
|
-
|
|
54
|
+
const pointRatioA = effectiveRatio(standingA.pointsWon, standingA.pointsLost);
|
|
55
|
+
const pointRatioB = effectiveRatio(standingB.pointsWon, standingB.pointsLost);
|
|
56
|
+
if (pointRatioB > pointRatioA)
|
|
49
57
|
return 1;
|
|
50
|
-
|
|
58
|
+
if (pointRatioA > pointRatioB)
|
|
51
59
|
return -1;
|
|
52
|
-
|
|
60
|
+
if (matches.length > 0) {
|
|
53
61
|
const ab = matches.find((match) => match.homeTeam.id === standingA.teamId && match.awayTeam.id === standingB.teamId);
|
|
54
62
|
const ba = matches.find((match) => match.awayTeam.id === standingA.teamId && match.homeTeam.id === standingB.teamId);
|
|
55
63
|
if (ba == null && ab == null)
|
|
@@ -66,8 +74,7 @@ class Standing {
|
|
|
66
74
|
const b = (ba.homeScore ?? 0) + (ab.awayScore ?? 0);
|
|
67
75
|
return b - a;
|
|
68
76
|
}
|
|
69
|
-
|
|
70
|
-
return 0;
|
|
77
|
+
return 0;
|
|
71
78
|
};
|
|
72
79
|
}
|
|
73
80
|
calculateStanding(matches) {
|
|
@@ -136,6 +136,18 @@ function makeStanding(teamId, opts = {}) {
|
|
|
136
136
|
const sorted = [b, a].sort(standing_1.Standing.sortFn([]));
|
|
137
137
|
(0, globals_1.expect)(sorted[0].teamId).toBe(idA);
|
|
138
138
|
});
|
|
139
|
+
(0, globals_1.it)('ranks a team with no sets lost (MAX ratio) above a finite setsRatio when points tie', () => {
|
|
140
|
+
const idA = (0, uuid_1.v4)();
|
|
141
|
+
const idB = (0, uuid_1.v4)();
|
|
142
|
+
// Both 6 points. A swept its matches (won30=2 → setsWon=6, setsLost=0 → ratio is MAX/infinite);
|
|
143
|
+
// B also has 6 points but conceded sets (won31=2 → setsWon=6, setsLost=2 → setsRatio=3).
|
|
144
|
+
// The undefeated-in-sets team must rank FIRST, not last (the divide-by-zero bug ranked it last).
|
|
145
|
+
const a = makeStanding(idA, { won30: 2 });
|
|
146
|
+
const b = makeStanding(idB, { won31: 2 });
|
|
147
|
+
const sorted = [b, a].sort(standing_1.Standing.sortFn([]));
|
|
148
|
+
(0, globals_1.expect)(sorted[0].teamId).toBe(idA);
|
|
149
|
+
(0, globals_1.expect)(sorted[1].teamId).toBe(idB);
|
|
150
|
+
});
|
|
139
151
|
(0, globals_1.it)('falls back to pointsRatio when points and setsRatio are equal', () => {
|
|
140
152
|
const idA = (0, uuid_1.v4)();
|
|
141
153
|
const idB = (0, uuid_1.v4)();
|
|
@@ -33,20 +33,28 @@ export class Standing {
|
|
|
33
33
|
this.totalMatches = this.matchesWon + this.matchesLost;
|
|
34
34
|
}
|
|
35
35
|
static sortFn(matches) {
|
|
36
|
+
// A zero denominator (no sets/points lost) is the BEST possible ratio, not the worst. The stored
|
|
37
|
+
// setsRatio/pointsRatio fall back to 0 in that case (and the table shows "MAX"), so we compare an
|
|
38
|
+
// effective ratio here that treats "won with nothing lost" as +Infinity, ranking it highest.
|
|
39
|
+
const effectiveRatio = (won, lost) => lost > 0 ? won / lost : (won > 0 ? Infinity : 0);
|
|
36
40
|
return (standingA, standingB) => {
|
|
37
41
|
if (standingB.points > standingA.points)
|
|
38
42
|
return 1;
|
|
39
|
-
|
|
43
|
+
if (standingA.points > standingB.points)
|
|
40
44
|
return -1;
|
|
41
|
-
|
|
45
|
+
const setRatioA = effectiveRatio(standingA.setsWon, standingA.setsLost);
|
|
46
|
+
const setRatioB = effectiveRatio(standingB.setsWon, standingB.setsLost);
|
|
47
|
+
if (setRatioB > setRatioA)
|
|
42
48
|
return 1;
|
|
43
|
-
|
|
49
|
+
if (setRatioA > setRatioB)
|
|
44
50
|
return -1;
|
|
45
|
-
|
|
51
|
+
const pointRatioA = effectiveRatio(standingA.pointsWon, standingA.pointsLost);
|
|
52
|
+
const pointRatioB = effectiveRatio(standingB.pointsWon, standingB.pointsLost);
|
|
53
|
+
if (pointRatioB > pointRatioA)
|
|
46
54
|
return 1;
|
|
47
|
-
|
|
55
|
+
if (pointRatioA > pointRatioB)
|
|
48
56
|
return -1;
|
|
49
|
-
|
|
57
|
+
if (matches.length > 0) {
|
|
50
58
|
const ab = matches.find((match) => match.homeTeam.id === standingA.teamId && match.awayTeam.id === standingB.teamId);
|
|
51
59
|
const ba = matches.find((match) => match.awayTeam.id === standingA.teamId && match.homeTeam.id === standingB.teamId);
|
|
52
60
|
if (ba == null && ab == null)
|
|
@@ -63,8 +71,7 @@ export class Standing {
|
|
|
63
71
|
const b = (ba.homeScore ?? 0) + (ab.awayScore ?? 0);
|
|
64
72
|
return b - a;
|
|
65
73
|
}
|
|
66
|
-
|
|
67
|
-
return 0;
|
|
74
|
+
return 0;
|
|
68
75
|
};
|
|
69
76
|
}
|
|
70
77
|
calculateStanding(matches) {
|
|
@@ -134,6 +134,18 @@ describe('Standing', () => {
|
|
|
134
134
|
const sorted = [b, a].sort(Standing.sortFn([]));
|
|
135
135
|
expect(sorted[0].teamId).toBe(idA);
|
|
136
136
|
});
|
|
137
|
+
it('ranks a team with no sets lost (MAX ratio) above a finite setsRatio when points tie', () => {
|
|
138
|
+
const idA = uuidv4();
|
|
139
|
+
const idB = uuidv4();
|
|
140
|
+
// Both 6 points. A swept its matches (won30=2 → setsWon=6, setsLost=0 → ratio is MAX/infinite);
|
|
141
|
+
// B also has 6 points but conceded sets (won31=2 → setsWon=6, setsLost=2 → setsRatio=3).
|
|
142
|
+
// The undefeated-in-sets team must rank FIRST, not last (the divide-by-zero bug ranked it last).
|
|
143
|
+
const a = makeStanding(idA, { won30: 2 });
|
|
144
|
+
const b = makeStanding(idB, { won31: 2 });
|
|
145
|
+
const sorted = [b, a].sort(Standing.sortFn([]));
|
|
146
|
+
expect(sorted[0].teamId).toBe(idA);
|
|
147
|
+
expect(sorted[1].teamId).toBe(idB);
|
|
148
|
+
});
|
|
137
149
|
it('falls back to pointsRatio when points and setsRatio are equal', () => {
|
|
138
150
|
const idA = uuidv4();
|
|
139
151
|
const idB = uuidv4();
|