@mshowes/brackets-manager 1.8.1
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/LICENSE +21 -0
- package/README.md +82 -0
- package/dist/base/getter.d.ts +272 -0
- package/dist/base/getter.d.ts.map +1 -0
- package/dist/base/getter.js +545 -0
- package/dist/base/getter.js.map +1 -0
- package/dist/base/stage/creator.d.ts +269 -0
- package/dist/base/stage/creator.d.ts.map +1 -0
- package/dist/base/stage/creator.js +735 -0
- package/dist/base/stage/creator.js.map +1 -0
- package/dist/base/updater.d.ts +121 -0
- package/dist/base/updater.d.ts.map +1 -0
- package/dist/base/updater.js +323 -0
- package/dist/base/updater.js.map +1 -0
- package/dist/create.d.ts +25 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +55 -0
- package/dist/create.js.map +1 -0
- package/dist/delete.d.ts +33 -0
- package/dist/delete.d.ts.map +1 -0
- package/dist/delete.js +57 -0
- package/dist/delete.js.map +1 -0
- package/dist/find.d.ts +60 -0
- package/dist/find.d.ts.map +1 -0
- package/dist/find.js +196 -0
- package/dist/find.js.map +1 -0
- package/dist/get.d.ts +121 -0
- package/dist/get.d.ts.map +1 -0
- package/dist/get.js +420 -0
- package/dist/get.js.map +1 -0
- package/dist/helpers.d.ts +804 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +1897 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/manager.d.ts +60 -0
- package/dist/manager.d.ts.map +1 -0
- package/dist/manager.js +189 -0
- package/dist/manager.js.map +1 -0
- package/dist/ordering.d.ts +7 -0
- package/dist/ordering.d.ts.map +1 -0
- package/dist/ordering.js +147 -0
- package/dist/ordering.js.map +1 -0
- package/dist/reset.d.ts +27 -0
- package/dist/reset.d.ts.map +1 -0
- package/dist/reset.js +82 -0
- package/dist/reset.js.map +1 -0
- package/dist/types.d.ts +260 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/update.d.ts +111 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +265 -0
- package/dist/update.js.map +1 -0
- package/package.json +67 -0
|
@@ -0,0 +1,735 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StageCreator = exports.create = void 0;
|
|
4
|
+
const ordering_1 = require("../../ordering");
|
|
5
|
+
const helpers = require("../../helpers");
|
|
6
|
+
/**
|
|
7
|
+
* Creates a stage.
|
|
8
|
+
*
|
|
9
|
+
* @param this Instance of BracketsManager.
|
|
10
|
+
* @param stage The stage to create.
|
|
11
|
+
*/
|
|
12
|
+
async function create(stage) {
|
|
13
|
+
const creator = new StageCreator(this.storage, stage);
|
|
14
|
+
return creator.run();
|
|
15
|
+
}
|
|
16
|
+
exports.create = create;
|
|
17
|
+
class StageCreator {
|
|
18
|
+
/**
|
|
19
|
+
* Creates an instance of StageCreator, which will handle the creation of the stage.
|
|
20
|
+
*
|
|
21
|
+
* @param storage The implementation of Storage.
|
|
22
|
+
* @param stage The stage to create.
|
|
23
|
+
*/
|
|
24
|
+
constructor(storage, stage) {
|
|
25
|
+
this.storage = storage;
|
|
26
|
+
this.stage = stage;
|
|
27
|
+
this.stage.settings = this.stage.settings || {};
|
|
28
|
+
this.seedOrdering = [...this.stage.settings.seedOrdering || []];
|
|
29
|
+
this.updateMode = false;
|
|
30
|
+
this.enableByesInUpdate = false;
|
|
31
|
+
if (!this.stage.name)
|
|
32
|
+
throw Error('You must provide a name for the stage.');
|
|
33
|
+
if (this.stage.tournamentId === undefined)
|
|
34
|
+
throw Error('You must provide a tournament id for the stage.');
|
|
35
|
+
if (stage.type === 'round_robin')
|
|
36
|
+
this.stage.settings.roundRobinMode = this.stage.settings.roundRobinMode || 'simple';
|
|
37
|
+
if (stage.type === 'single_elimination')
|
|
38
|
+
this.stage.settings.consolationFinal = this.stage.settings.consolationFinal || false;
|
|
39
|
+
if (stage.type === 'double_elimination')
|
|
40
|
+
this.stage.settings.grandFinal = this.stage.settings.grandFinal || 'none';
|
|
41
|
+
this.stage.settings.matchesChildCount = this.stage.settings.matchesChildCount || 0;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Run the creation process.
|
|
45
|
+
*/
|
|
46
|
+
async run() {
|
|
47
|
+
let stage;
|
|
48
|
+
switch (this.stage.type) {
|
|
49
|
+
case 'round_robin':
|
|
50
|
+
stage = await this.roundRobin();
|
|
51
|
+
break;
|
|
52
|
+
case 'single_elimination':
|
|
53
|
+
stage = await this.singleElimination();
|
|
54
|
+
break;
|
|
55
|
+
case 'double_elimination':
|
|
56
|
+
stage = await this.doubleElimination();
|
|
57
|
+
break;
|
|
58
|
+
default:
|
|
59
|
+
throw Error('Unknown stage type.');
|
|
60
|
+
}
|
|
61
|
+
if (stage.id === -1)
|
|
62
|
+
throw Error('Something went wrong when creating the stage.');
|
|
63
|
+
await this.ensureSeedOrdering(stage.id);
|
|
64
|
+
return stage;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Enables the update mode.
|
|
68
|
+
*
|
|
69
|
+
* @param stageId ID of the stage.
|
|
70
|
+
* @param enableByes Whether to use BYEs or TBDs for `null` values in an input seeding.
|
|
71
|
+
*/
|
|
72
|
+
setExisting(stageId, enableByes) {
|
|
73
|
+
this.updateMode = true;
|
|
74
|
+
this.currentStageId = stageId;
|
|
75
|
+
this.enableByesInUpdate = enableByes;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Creates a round-robin stage.
|
|
79
|
+
*
|
|
80
|
+
* Group count must be given. It will distribute participants in groups and rounds.
|
|
81
|
+
*/
|
|
82
|
+
async roundRobin() {
|
|
83
|
+
const groups = await this.getRoundRobinGroups();
|
|
84
|
+
const stage = await this.createStage();
|
|
85
|
+
for (let i = 0; i < groups.length; i++)
|
|
86
|
+
await this.createRoundRobinGroup(stage.id, i + 1, groups[i]);
|
|
87
|
+
return stage;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Creates a single elimination stage.
|
|
91
|
+
*
|
|
92
|
+
* One bracket and optionally a consolation final between semi-final losers.
|
|
93
|
+
*/
|
|
94
|
+
async singleElimination() {
|
|
95
|
+
var _a, _b;
|
|
96
|
+
if (Array.isArray((_a = this.stage.settings) === null || _a === void 0 ? void 0 : _a.seedOrdering) &&
|
|
97
|
+
((_b = this.stage.settings) === null || _b === void 0 ? void 0 : _b.seedOrdering.length) !== 1)
|
|
98
|
+
throw Error('You must specify one seed ordering method.');
|
|
99
|
+
const slots = await this.getSlots();
|
|
100
|
+
const stage = await this.createStage();
|
|
101
|
+
const method = this.getStandardBracketFirstRoundOrdering();
|
|
102
|
+
const ordered = ordering_1.ordering[method](slots);
|
|
103
|
+
const { losers } = await this.createStandardBracket(stage.id, 1, ordered);
|
|
104
|
+
await this.createConsolationFinal(stage.id, losers);
|
|
105
|
+
return stage;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Creates a double elimination stage.
|
|
109
|
+
*
|
|
110
|
+
* One upper bracket (winner bracket, WB), one lower bracket (loser bracket, LB) and optionally a grand final
|
|
111
|
+
* between the winner of both bracket, which can be simple or double.
|
|
112
|
+
*/
|
|
113
|
+
async doubleElimination() {
|
|
114
|
+
var _a;
|
|
115
|
+
if (this.stage.settings && Array.isArray(this.stage.settings.seedOrdering) &&
|
|
116
|
+
this.stage.settings.seedOrdering.length < 1)
|
|
117
|
+
throw Error('You must specify at least one seed ordering method.');
|
|
118
|
+
const slots = await this.getSlots();
|
|
119
|
+
const stage = await this.createStage();
|
|
120
|
+
const method = this.getStandardBracketFirstRoundOrdering();
|
|
121
|
+
const ordered = ordering_1.ordering[method](slots);
|
|
122
|
+
if ((_a = this.stage.settings) === null || _a === void 0 ? void 0 : _a.skipFirstRound)
|
|
123
|
+
await this.createDoubleEliminationSkipFirstRound(stage.id, ordered);
|
|
124
|
+
else
|
|
125
|
+
await this.createDoubleElimination(stage.id, ordered);
|
|
126
|
+
return stage;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Creates a double elimination stage with skip first round option.
|
|
130
|
+
*
|
|
131
|
+
* @param stageId ID of the stage.
|
|
132
|
+
* @param slots A list of slots.
|
|
133
|
+
*/
|
|
134
|
+
async createDoubleEliminationSkipFirstRound(stageId, slots) {
|
|
135
|
+
var _a;
|
|
136
|
+
const { even: directInWb, odd: directInLb } = helpers.splitByParity(slots);
|
|
137
|
+
const { losers: losersWb, winner: winnerWb } = await this.createStandardBracket(stageId, 1, directInWb);
|
|
138
|
+
if (helpers.isDoubleEliminationNecessary((_a = this.stage.settings) === null || _a === void 0 ? void 0 : _a.size)) {
|
|
139
|
+
const winnerLb = await this.createLowerBracket(stageId, 2, [directInLb, ...losersWb]);
|
|
140
|
+
await this.createGrandFinal(stageId, winnerWb, winnerLb);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Creates a double elimination stage.
|
|
145
|
+
*
|
|
146
|
+
* @param stageId ID of the stage.
|
|
147
|
+
* @param slots A list of slots.
|
|
148
|
+
*/
|
|
149
|
+
async createDoubleElimination(stageId, slots) {
|
|
150
|
+
var _a;
|
|
151
|
+
const { losers: losersWb, winner: winnerWb } = await this.createStandardBracket(stageId, 1, slots);
|
|
152
|
+
if (helpers.isDoubleEliminationNecessary((_a = this.stage.settings) === null || _a === void 0 ? void 0 : _a.size)) {
|
|
153
|
+
const winnerLb = await this.createLowerBracket(stageId, 2, losersWb);
|
|
154
|
+
const finalGroupId = await this.createGrandFinal(stageId, winnerWb, winnerLb);
|
|
155
|
+
await this.createConsolationFinal(stageId, losersWb, {
|
|
156
|
+
existingGroupId: finalGroupId,
|
|
157
|
+
// Arbitrary way to differentiate the grand final and consolation final matches.
|
|
158
|
+
// Grand final matches always have had `number: 1`. Now, consolation final matches always have `number: 2`.
|
|
159
|
+
matchNumberStart: 2,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Creates a round-robin group.
|
|
165
|
+
*
|
|
166
|
+
* This will make as many rounds as needed to let each participant match every other once.
|
|
167
|
+
*
|
|
168
|
+
* @param stageId ID of the parent stage.
|
|
169
|
+
* @param groupNumber Number of the group in the stage.
|
|
170
|
+
* @param slots A list of slots.
|
|
171
|
+
*/
|
|
172
|
+
async createRoundRobinGroup(stageId, groupNumber, slots) {
|
|
173
|
+
var _a;
|
|
174
|
+
const groupId = await this.insertGroup({
|
|
175
|
+
stage_id: stageId,
|
|
176
|
+
number: groupNumber,
|
|
177
|
+
});
|
|
178
|
+
if (groupId === -1)
|
|
179
|
+
throw Error('Could not insert the group.');
|
|
180
|
+
const rounds = helpers.makeRoundRobinMatches(slots, (_a = this.stage.settings) === null || _a === void 0 ? void 0 : _a.roundRobinMode);
|
|
181
|
+
for (let i = 0; i < rounds.length; i++)
|
|
182
|
+
await this.createRound(stageId, groupId, i + 1, rounds[0].length, rounds[i]);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Creates a standard bracket, which is the only one in single elimination and the upper one in double elimination.
|
|
186
|
+
*
|
|
187
|
+
* This will make as many rounds as needed to end with one winner.
|
|
188
|
+
*
|
|
189
|
+
* @param stageId ID of the parent stage.
|
|
190
|
+
* @param groupNumber Number of the group in the stage.
|
|
191
|
+
* @param slots A list of slots.
|
|
192
|
+
*/
|
|
193
|
+
async createStandardBracket(stageId, groupNumber, slots) {
|
|
194
|
+
const roundCount = helpers.getUpperBracketRoundCount(slots.length);
|
|
195
|
+
const groupId = await this.insertGroup({
|
|
196
|
+
stage_id: stageId,
|
|
197
|
+
number: groupNumber,
|
|
198
|
+
});
|
|
199
|
+
if (groupId === -1)
|
|
200
|
+
throw Error('Could not insert the group.');
|
|
201
|
+
let duels = helpers.makePairs(slots);
|
|
202
|
+
let roundNumber = 1;
|
|
203
|
+
const losers = [];
|
|
204
|
+
for (let i = roundCount - 1; i >= 0; i--) {
|
|
205
|
+
const matchCount = Math.pow(2, i);
|
|
206
|
+
duels = this.getCurrentDuels(duels, matchCount);
|
|
207
|
+
losers.push(duels.map(helpers.byeLoser));
|
|
208
|
+
await this.createRound(stageId, groupId, roundNumber++, matchCount, duels);
|
|
209
|
+
}
|
|
210
|
+
return { losers, winner: helpers.byeWinner(duels[0]) };
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Creates a lower bracket, alternating between major and minor rounds.
|
|
214
|
+
*
|
|
215
|
+
* - A major round is a regular round.
|
|
216
|
+
* - A minor round matches the previous (major) round's winners against upper bracket losers of the corresponding round.
|
|
217
|
+
*
|
|
218
|
+
* @param stageId ID of the parent stage.
|
|
219
|
+
* @param groupNumber Number of the group in the stage.
|
|
220
|
+
* @param losers One list of losers per upper bracket round.
|
|
221
|
+
*/
|
|
222
|
+
async createLowerBracket(stageId, groupNumber, losers) {
|
|
223
|
+
var _a;
|
|
224
|
+
const participantCount = (_a = this.stage.settings) === null || _a === void 0 ? void 0 : _a.size;
|
|
225
|
+
const roundPairCount = helpers.getRoundPairCount(participantCount);
|
|
226
|
+
let losersId = 0;
|
|
227
|
+
const method = this.getMajorOrdering(participantCount);
|
|
228
|
+
const ordered = ordering_1.ordering[method](losers[losersId++]);
|
|
229
|
+
const groupId = await this.insertGroup({
|
|
230
|
+
stage_id: stageId,
|
|
231
|
+
number: groupNumber,
|
|
232
|
+
});
|
|
233
|
+
if (groupId === -1)
|
|
234
|
+
throw Error('Could not insert the group.');
|
|
235
|
+
let duels = helpers.makePairs(ordered);
|
|
236
|
+
let roundNumber = 1;
|
|
237
|
+
for (let i = 0; i < roundPairCount; i++) {
|
|
238
|
+
const matchCount = Math.pow(2, roundPairCount - i - 1);
|
|
239
|
+
// Major round.
|
|
240
|
+
duels = this.getCurrentDuels(duels, matchCount, true);
|
|
241
|
+
await this.createRound(stageId, groupId, roundNumber++, matchCount, duels);
|
|
242
|
+
// Minor round.
|
|
243
|
+
const minorOrdering = this.getMinorOrdering(participantCount, i, roundPairCount);
|
|
244
|
+
duels = this.getCurrentDuels(duels, matchCount, false, losers[losersId++], minorOrdering);
|
|
245
|
+
await this.createRound(stageId, groupId, roundNumber++, matchCount, duels);
|
|
246
|
+
}
|
|
247
|
+
return helpers.byeWinnerToGrandFinal(duels[0]);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Creates a bracket with rounds that only have 1 match each. Used for finals.
|
|
251
|
+
*
|
|
252
|
+
* @param stageId ID of the parent stage.
|
|
253
|
+
* @param groupNumber Number of the group in the stage.
|
|
254
|
+
* @param duels A list of duels.
|
|
255
|
+
* @param overrides Optional overrides.
|
|
256
|
+
*/
|
|
257
|
+
async createUniqueMatchBracket(stageId, groupNumber, duels, overrides = {}) {
|
|
258
|
+
let groupId = overrides.existingGroupId;
|
|
259
|
+
let roundNumberStart = 1;
|
|
260
|
+
if (groupId !== undefined) {
|
|
261
|
+
const rounds = await this.storage.select('round', { group_id: groupId });
|
|
262
|
+
if (!rounds)
|
|
263
|
+
throw Error('Error getting rounds.');
|
|
264
|
+
// When we add rounds to an existing group, we resume the round numbering.
|
|
265
|
+
roundNumberStart = rounds.length + 1;
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
groupId = await this.insertGroup({
|
|
269
|
+
stage_id: stageId,
|
|
270
|
+
number: groupNumber,
|
|
271
|
+
});
|
|
272
|
+
if (groupId === -1)
|
|
273
|
+
throw Error('Could not insert the group.');
|
|
274
|
+
}
|
|
275
|
+
for (let i = 0; i < duels.length; i++)
|
|
276
|
+
await this.createRound(stageId, groupId, roundNumberStart + i, 1, [duels[i]], overrides.matchNumberStart);
|
|
277
|
+
return groupId;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Creates a round, which contain matches.
|
|
281
|
+
*
|
|
282
|
+
* @param stageId ID of the parent stage.
|
|
283
|
+
* @param groupId ID of the parent group.
|
|
284
|
+
* @param roundNumber Number in the group.
|
|
285
|
+
* @param matchCount Duel/match count.
|
|
286
|
+
* @param duels A list of duels.
|
|
287
|
+
* @param matchNumberStart Optionally give the starting point for the match numbers. Starts at 1 by default.
|
|
288
|
+
*/
|
|
289
|
+
async createRound(stageId, groupId, roundNumber, matchCount, duels, matchNumberStart = 1) {
|
|
290
|
+
const matchesChildCount = this.getMatchesChildCount();
|
|
291
|
+
const roundId = await this.insertRound({
|
|
292
|
+
number: roundNumber,
|
|
293
|
+
stage_id: stageId,
|
|
294
|
+
group_id: groupId,
|
|
295
|
+
});
|
|
296
|
+
if (roundId === -1)
|
|
297
|
+
throw Error('Could not insert the round.');
|
|
298
|
+
for (let i = 0; i < matchCount; i++)
|
|
299
|
+
await this.createMatch(stageId, groupId, roundId, matchNumberStart + i, duels[i], matchesChildCount);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Creates a match, possibly with match games.
|
|
303
|
+
*
|
|
304
|
+
* - If `childCount` is 0, then there is no children. The score of the match is directly its intrinsic score.
|
|
305
|
+
* - If `childCount` is greater than 0, then the score of the match will automatically be calculated based on its child games.
|
|
306
|
+
*
|
|
307
|
+
* @param stageId ID of the parent stage.
|
|
308
|
+
* @param groupId ID of the parent group.
|
|
309
|
+
* @param roundId ID of the parent round.
|
|
310
|
+
* @param matchNumber Number in the round.
|
|
311
|
+
* @param opponents The two opponents matching against each other.
|
|
312
|
+
* @param childCount Child count for this match (number of games).
|
|
313
|
+
*/
|
|
314
|
+
async createMatch(stageId, groupId, roundId, matchNumber, opponents, childCount) {
|
|
315
|
+
const opponent1 = helpers.toResultWithPosition(opponents[0]);
|
|
316
|
+
const opponent2 = helpers.toResultWithPosition(opponents[1]);
|
|
317
|
+
// Round-robin matches can easily be removed. Prevent BYE vs. BYE matches.
|
|
318
|
+
if (this.stage.type === 'round_robin' && opponent1 === null && opponent2 === null)
|
|
319
|
+
return;
|
|
320
|
+
let existing = null;
|
|
321
|
+
let status = helpers.getMatchStatus(opponents);
|
|
322
|
+
if (this.updateMode) {
|
|
323
|
+
existing = await this.storage.selectFirst('match', {
|
|
324
|
+
round_id: roundId,
|
|
325
|
+
number: matchNumber,
|
|
326
|
+
});
|
|
327
|
+
const currentChildCount = existing === null || existing === void 0 ? void 0 : existing.child_count;
|
|
328
|
+
childCount = currentChildCount === undefined ? childCount : currentChildCount;
|
|
329
|
+
if (existing) {
|
|
330
|
+
// Keep the most advanced status when updating a match.
|
|
331
|
+
const existingStatus = helpers.getMatchStatus(existing);
|
|
332
|
+
if (existingStatus > status)
|
|
333
|
+
status = existingStatus;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
const parentId = await this.insertMatch({
|
|
337
|
+
number: matchNumber,
|
|
338
|
+
stage_id: stageId,
|
|
339
|
+
group_id: groupId,
|
|
340
|
+
round_id: roundId,
|
|
341
|
+
child_count: childCount,
|
|
342
|
+
status: status,
|
|
343
|
+
...helpers.getInferredResult(opponent1, opponent2),
|
|
344
|
+
}, existing);
|
|
345
|
+
if (parentId === -1)
|
|
346
|
+
throw Error('Could not insert the match.');
|
|
347
|
+
for (let i = 0; i < childCount; i++) {
|
|
348
|
+
const id = await this.insertMatchGame({
|
|
349
|
+
number: i + 1,
|
|
350
|
+
stage_id: stageId,
|
|
351
|
+
parent_id: parentId,
|
|
352
|
+
status: status,
|
|
353
|
+
...helpers.getInferredResult(helpers.toResult(opponents[0]), helpers.toResult(opponents[1])),
|
|
354
|
+
});
|
|
355
|
+
if (id === -1)
|
|
356
|
+
throw Error('Could not insert the match game.');
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Generic implementation.
|
|
361
|
+
*
|
|
362
|
+
* @param previousDuels Always given.
|
|
363
|
+
* @param currentDuelCount Always given.
|
|
364
|
+
* @param major Only for loser bracket.
|
|
365
|
+
* @param losers Only for minor rounds of loser bracket.
|
|
366
|
+
* @param method Only for minor rounds. Ordering method for the losers.
|
|
367
|
+
*/
|
|
368
|
+
getCurrentDuels(previousDuels, currentDuelCount, major, losers, method) {
|
|
369
|
+
if ((major === undefined || major) && previousDuels.length === currentDuelCount) {
|
|
370
|
+
// First round.
|
|
371
|
+
return previousDuels;
|
|
372
|
+
}
|
|
373
|
+
if (major === undefined || major) {
|
|
374
|
+
// From major to major (WB) or minor to major (LB).
|
|
375
|
+
return helpers.transitionToMajor(previousDuels);
|
|
376
|
+
}
|
|
377
|
+
// From major to minor (LB).
|
|
378
|
+
// Losers and method won't be undefined.
|
|
379
|
+
return helpers.transitionToMinor(previousDuels, losers, method);
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Returns a list of slots.
|
|
383
|
+
* - If `seeding` was given, inserts them in the storage.
|
|
384
|
+
* - If `size` was given, only returns a list of empty slots.
|
|
385
|
+
*
|
|
386
|
+
* @param positions An optional list of positions (seeds) for a manual ordering.
|
|
387
|
+
*/
|
|
388
|
+
async getSlots(positions) {
|
|
389
|
+
var _a;
|
|
390
|
+
let seeding = this.stage.seedingIds || this.stage.seeding;
|
|
391
|
+
const size = ((_a = this.stage.settings) === null || _a === void 0 ? void 0 : _a.size) || (seeding === null || seeding === void 0 ? void 0 : seeding.length) || 0;
|
|
392
|
+
helpers.ensureValidSize(this.stage.type, size);
|
|
393
|
+
if (size && !seeding)
|
|
394
|
+
return Array.from({ length: size }, (_, i) => ({ id: null, position: i + 1 }));
|
|
395
|
+
if (!seeding)
|
|
396
|
+
throw Error('Either size or seeding must be given.');
|
|
397
|
+
this.stage.settings = {
|
|
398
|
+
...this.stage.settings,
|
|
399
|
+
size, // Always set the size.
|
|
400
|
+
};
|
|
401
|
+
helpers.ensureNoDuplicates(seeding);
|
|
402
|
+
seeding = helpers.fixSeeding(seeding, size);
|
|
403
|
+
if (this.stage.type !== 'round_robin' && this.stage.settings.balanceByes)
|
|
404
|
+
seeding = helpers.balanceByes(seeding, this.stage.settings.size);
|
|
405
|
+
this.stage.seeding = seeding;
|
|
406
|
+
if (this.stage.seedingIds !== undefined || helpers.isSeedingWithIds(seeding))
|
|
407
|
+
return this.getSlotsUsingIds(seeding, positions);
|
|
408
|
+
return this.getSlotsUsingNames(seeding, positions);
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Returns the list of slots with a seeding containing names. Participants may be added to database.
|
|
412
|
+
*
|
|
413
|
+
* @param seeding The seeding (names).
|
|
414
|
+
* @param positions An optional list of positions (seeds) for a manual ordering.
|
|
415
|
+
*/
|
|
416
|
+
async getSlotsUsingNames(seeding, positions) {
|
|
417
|
+
const participants = helpers.extractParticipantsFromSeeding(this.stage.tournamentId, seeding);
|
|
418
|
+
if (!await this.registerParticipants(participants))
|
|
419
|
+
throw Error('Error registering the participants.');
|
|
420
|
+
// Get participants back with IDs.
|
|
421
|
+
const added = await this.storage.select('participant', { tournament_id: this.stage.tournamentId });
|
|
422
|
+
if (!added)
|
|
423
|
+
throw Error('Error getting registered participant.');
|
|
424
|
+
return helpers.mapParticipantsNamesToDatabase(seeding, added, positions);
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Returns the list of slots with a seeding containing IDs. No database mutation.
|
|
428
|
+
*
|
|
429
|
+
* @param seeding The seeding (IDs).
|
|
430
|
+
* @param positions An optional list of positions (seeds) for a manual ordering.
|
|
431
|
+
*/
|
|
432
|
+
async getSlotsUsingIds(seeding, positions) {
|
|
433
|
+
const participants = await this.storage.select('participant', { tournament_id: this.stage.tournamentId });
|
|
434
|
+
if (!participants)
|
|
435
|
+
throw Error('No available participants.');
|
|
436
|
+
return helpers.mapParticipantsIdsToDatabase(seeding, participants, positions);
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Gets the current stage number based on existing stages.
|
|
440
|
+
*/
|
|
441
|
+
async getStageNumber() {
|
|
442
|
+
const stages = await this.storage.select('stage', { tournament_id: this.stage.tournamentId });
|
|
443
|
+
const stageNumbers = stages === null || stages === void 0 ? void 0 : stages.map(stage => { var _a; return (_a = stage.number) !== null && _a !== void 0 ? _a : 0; });
|
|
444
|
+
if (this.stage.number !== undefined) {
|
|
445
|
+
if (stageNumbers === null || stageNumbers === void 0 ? void 0 : stageNumbers.includes(this.stage.number))
|
|
446
|
+
throw Error('The given stage number already exists.');
|
|
447
|
+
return this.stage.number;
|
|
448
|
+
}
|
|
449
|
+
if (!(stageNumbers === null || stageNumbers === void 0 ? void 0 : stageNumbers.length))
|
|
450
|
+
return 1;
|
|
451
|
+
const maxNumber = Math.max(...stageNumbers);
|
|
452
|
+
return maxNumber + 1;
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Safely gets `matchesChildCount` in the stage input settings.
|
|
456
|
+
*/
|
|
457
|
+
getMatchesChildCount() {
|
|
458
|
+
var _a;
|
|
459
|
+
if (!((_a = this.stage.settings) === null || _a === void 0 ? void 0 : _a.matchesChildCount))
|
|
460
|
+
return 0;
|
|
461
|
+
return this.stage.settings.matchesChildCount;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Safely gets an ordering by its index in the stage input settings.
|
|
465
|
+
*
|
|
466
|
+
* @param orderingIndex Index of the ordering.
|
|
467
|
+
* @param stageType A value indicating if the method should be a group method or not.
|
|
468
|
+
* @param defaultMethod The default method to use if not given.
|
|
469
|
+
*/
|
|
470
|
+
getOrdering(orderingIndex, stageType, defaultMethod) {
|
|
471
|
+
var _a;
|
|
472
|
+
if (!((_a = this.stage.settings) === null || _a === void 0 ? void 0 : _a.seedOrdering)) {
|
|
473
|
+
this.seedOrdering.push(defaultMethod);
|
|
474
|
+
return defaultMethod;
|
|
475
|
+
}
|
|
476
|
+
const method = this.stage.settings.seedOrdering[orderingIndex];
|
|
477
|
+
if (!method) {
|
|
478
|
+
this.seedOrdering.push(defaultMethod);
|
|
479
|
+
return defaultMethod;
|
|
480
|
+
}
|
|
481
|
+
if (stageType === 'elimination' && method.match(/^groups\./))
|
|
482
|
+
throw Error('You must specify a seed ordering method without a \'groups\' prefix');
|
|
483
|
+
if (stageType === 'groups' && method !== 'natural' && !method.match(/^groups\./))
|
|
484
|
+
throw Error('You must specify a seed ordering method with a \'groups\' prefix');
|
|
485
|
+
return method;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Gets the duels in groups for a round-robin stage.
|
|
489
|
+
*/
|
|
490
|
+
async getRoundRobinGroups() {
|
|
491
|
+
var _a, _b, _c, _d, _e;
|
|
492
|
+
if (((_a = this.stage.settings) === null || _a === void 0 ? void 0 : _a.groupCount) === undefined || !Number.isInteger(this.stage.settings.groupCount))
|
|
493
|
+
throw Error('You must specify a group count for round-robin stages.');
|
|
494
|
+
if (this.stage.settings.groupCount <= 0)
|
|
495
|
+
throw Error('You must provide a strictly positive group count.');
|
|
496
|
+
if ((_b = this.stage.settings) === null || _b === void 0 ? void 0 : _b.manualOrdering) {
|
|
497
|
+
if (((_c = this.stage.settings) === null || _c === void 0 ? void 0 : _c.manualOrdering.length) !== ((_d = this.stage.settings) === null || _d === void 0 ? void 0 : _d.groupCount))
|
|
498
|
+
throw Error('Group count in the manual ordering does not correspond to the given group count.');
|
|
499
|
+
const positions = (_e = this.stage.settings) === null || _e === void 0 ? void 0 : _e.manualOrdering.flat();
|
|
500
|
+
const slots = await this.getSlots(positions);
|
|
501
|
+
return helpers.makeGroups(slots, this.stage.settings.groupCount);
|
|
502
|
+
}
|
|
503
|
+
if (Array.isArray(this.stage.settings.seedOrdering) && this.stage.settings.seedOrdering.length !== 1)
|
|
504
|
+
throw Error('You must specify one seed ordering method.');
|
|
505
|
+
const method = this.getRoundRobinOrdering();
|
|
506
|
+
const slots = await this.getSlots();
|
|
507
|
+
const ordered = ordering_1.ordering[method](slots, this.stage.settings.groupCount);
|
|
508
|
+
return helpers.makeGroups(ordered, this.stage.settings.groupCount);
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Returns the ordering method for the groups in a round-robin stage.
|
|
512
|
+
*/
|
|
513
|
+
getRoundRobinOrdering() {
|
|
514
|
+
return this.getOrdering(0, 'groups', 'groups.effort_balanced');
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Returns the ordering method for the first round of the upper bracket of an elimination stage.
|
|
518
|
+
*/
|
|
519
|
+
getStandardBracketFirstRoundOrdering() {
|
|
520
|
+
return this.getOrdering(0, 'elimination', 'inner_outer');
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Safely gets the only major ordering for the lower bracket.
|
|
524
|
+
*
|
|
525
|
+
* @param participantCount Number of participants in the stage.
|
|
526
|
+
*/
|
|
527
|
+
getMajorOrdering(participantCount) {
|
|
528
|
+
var _a;
|
|
529
|
+
return this.getOrdering(1, 'elimination', ((_a = ordering_1.defaultMinorOrdering[participantCount]) === null || _a === void 0 ? void 0 : _a[0]) || 'natural');
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Safely gets a minor ordering for the lower bracket by its index.
|
|
533
|
+
*
|
|
534
|
+
* @param participantCount Number of participants in the stage.
|
|
535
|
+
* @param index Index of the minor round.
|
|
536
|
+
* @param minorRoundCount Number of minor rounds.
|
|
537
|
+
*/
|
|
538
|
+
getMinorOrdering(participantCount, index, minorRoundCount) {
|
|
539
|
+
var _a;
|
|
540
|
+
// No ordering for the last minor round. There is only one participant to order.
|
|
541
|
+
if (index === minorRoundCount - 1)
|
|
542
|
+
return undefined;
|
|
543
|
+
return this.getOrdering(2 + index, 'elimination', ((_a = ordering_1.defaultMinorOrdering[participantCount]) === null || _a === void 0 ? void 0 : _a[1 + index]) || 'natural');
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Inserts a stage or finds an existing one.
|
|
547
|
+
*
|
|
548
|
+
* @param stage The stage to insert.
|
|
549
|
+
*/
|
|
550
|
+
async insertStage(stage) {
|
|
551
|
+
let existing = null;
|
|
552
|
+
if (this.updateMode) {
|
|
553
|
+
existing = await this.storage.select('stage', this.currentStageId);
|
|
554
|
+
if (!existing)
|
|
555
|
+
throw Error('Stage not found.');
|
|
556
|
+
const update = {
|
|
557
|
+
...existing,
|
|
558
|
+
...stage,
|
|
559
|
+
settings: {
|
|
560
|
+
...existing.settings,
|
|
561
|
+
...stage.settings,
|
|
562
|
+
},
|
|
563
|
+
};
|
|
564
|
+
if (!await this.storage.update('stage', this.currentStageId, update))
|
|
565
|
+
throw Error('Could not update the stage.');
|
|
566
|
+
}
|
|
567
|
+
if (!existing)
|
|
568
|
+
return this.storage.insert('stage', stage);
|
|
569
|
+
return existing.id;
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Inserts a group or finds an existing one.
|
|
573
|
+
*
|
|
574
|
+
* @param group The group to insert.
|
|
575
|
+
*/
|
|
576
|
+
async insertGroup(group) {
|
|
577
|
+
let existing = null;
|
|
578
|
+
if (this.updateMode) {
|
|
579
|
+
existing = await this.storage.selectFirst('group', {
|
|
580
|
+
stage_id: group.stage_id,
|
|
581
|
+
number: group.number,
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
if (!existing)
|
|
585
|
+
return this.storage.insert('group', group);
|
|
586
|
+
return existing.id;
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Inserts a round or finds an existing one.
|
|
590
|
+
*
|
|
591
|
+
* @param round The round to insert.
|
|
592
|
+
*/
|
|
593
|
+
async insertRound(round) {
|
|
594
|
+
let existing = null;
|
|
595
|
+
if (this.updateMode) {
|
|
596
|
+
existing = await this.storage.selectFirst('round', {
|
|
597
|
+
group_id: round.group_id,
|
|
598
|
+
number: round.number,
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
if (!existing)
|
|
602
|
+
return this.storage.insert('round', round);
|
|
603
|
+
return existing.id;
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Inserts a match or updates an existing one.
|
|
607
|
+
*
|
|
608
|
+
* @param match The match to insert.
|
|
609
|
+
* @param existing An existing match corresponding to the current one.
|
|
610
|
+
*/
|
|
611
|
+
async insertMatch(match, existing) {
|
|
612
|
+
if (!existing)
|
|
613
|
+
return this.storage.insert('match', match);
|
|
614
|
+
const updated = helpers.getUpdatedMatchResults(match, existing, this.enableByesInUpdate);
|
|
615
|
+
if (!await this.storage.update('match', existing.id, updated))
|
|
616
|
+
throw Error('Could not update the match.');
|
|
617
|
+
return existing.id;
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Inserts a match game or finds an existing one (and updates it).
|
|
621
|
+
*
|
|
622
|
+
* @param matchGame The match game to insert.
|
|
623
|
+
*/
|
|
624
|
+
async insertMatchGame(matchGame) {
|
|
625
|
+
let existing = null;
|
|
626
|
+
if (this.updateMode) {
|
|
627
|
+
existing = await this.storage.selectFirst('match_game', {
|
|
628
|
+
parent_id: matchGame.parent_id,
|
|
629
|
+
number: matchGame.number,
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
if (!existing)
|
|
633
|
+
return this.storage.insert('match_game', matchGame);
|
|
634
|
+
const updated = helpers.getUpdatedMatchResults(matchGame, existing, this.enableByesInUpdate);
|
|
635
|
+
if (!await this.storage.update('match_game', existing.id, updated))
|
|
636
|
+
throw Error('Could not update the match game.');
|
|
637
|
+
return existing.id;
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Inserts missing participants.
|
|
641
|
+
*
|
|
642
|
+
* @param participants The list of participants to process.
|
|
643
|
+
*/
|
|
644
|
+
async registerParticipants(participants) {
|
|
645
|
+
const existing = await this.storage.select('participant', { tournament_id: this.stage.tournamentId });
|
|
646
|
+
// Insert all if nothing.
|
|
647
|
+
if (!existing || existing.length === 0)
|
|
648
|
+
return this.storage.insert('participant', participants);
|
|
649
|
+
// Insert only missing otherwise.
|
|
650
|
+
for (const participant of participants) {
|
|
651
|
+
if (existing.some(value => value.name === participant.name))
|
|
652
|
+
continue;
|
|
653
|
+
const result = await this.storage.insert('participant', participant);
|
|
654
|
+
if (result === -1)
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
return true;
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Creates a new stage.
|
|
661
|
+
*/
|
|
662
|
+
async createStage() {
|
|
663
|
+
const stageNumber = await this.getStageNumber();
|
|
664
|
+
const stage = {
|
|
665
|
+
tournament_id: this.stage.tournamentId,
|
|
666
|
+
name: this.stage.name,
|
|
667
|
+
type: this.stage.type,
|
|
668
|
+
number: stageNumber,
|
|
669
|
+
settings: this.stage.settings || {},
|
|
670
|
+
};
|
|
671
|
+
const stageId = await this.insertStage(stage);
|
|
672
|
+
if (stageId === -1)
|
|
673
|
+
throw Error('Could not insert the stage.');
|
|
674
|
+
return { ...stage, id: stageId };
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Creates a consolation final for the semi final losers of an upper bracket (single or double elimination).
|
|
678
|
+
*
|
|
679
|
+
* @param stageId ID of the stage.
|
|
680
|
+
* @param losers The semi final losers who will play the consolation final.
|
|
681
|
+
* @param overrides Optional overrides.
|
|
682
|
+
*/
|
|
683
|
+
async createConsolationFinal(stageId, losers, overrides = {}) {
|
|
684
|
+
var _a;
|
|
685
|
+
if (!((_a = this.stage.settings) === null || _a === void 0 ? void 0 : _a.consolationFinal))
|
|
686
|
+
return;
|
|
687
|
+
const semiFinalLosers = losers[losers.length - 2];
|
|
688
|
+
await this.createUniqueMatchBracket(stageId, 2, [semiFinalLosers], overrides);
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Creates a grand final (none, simple or double) for winners of both bracket in a double elimination stage.
|
|
692
|
+
*
|
|
693
|
+
* @param stageId ID of the stage.
|
|
694
|
+
* @param winnerWb The winner of the winner bracket.
|
|
695
|
+
* @param winnerLb The winner of the loser bracket.
|
|
696
|
+
*/
|
|
697
|
+
async createGrandFinal(stageId, winnerWb, winnerLb) {
|
|
698
|
+
var _a;
|
|
699
|
+
// No Grand Final by default.
|
|
700
|
+
const grandFinal = (_a = this.stage.settings) === null || _a === void 0 ? void 0 : _a.grandFinal;
|
|
701
|
+
if (grandFinal === 'none')
|
|
702
|
+
return;
|
|
703
|
+
// One duel by default.
|
|
704
|
+
const finalDuels = [[winnerWb, winnerLb]];
|
|
705
|
+
// Second duel.
|
|
706
|
+
if (grandFinal === 'double')
|
|
707
|
+
finalDuels.push([{ id: null }, { id: null }]);
|
|
708
|
+
const groupId = await this.createUniqueMatchBracket(stageId, 3, finalDuels);
|
|
709
|
+
return groupId;
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Ensures that the seed ordering list is stored even if it was not given in the first place.
|
|
713
|
+
*
|
|
714
|
+
* @param stageId ID of the stage.
|
|
715
|
+
*/
|
|
716
|
+
async ensureSeedOrdering(stageId) {
|
|
717
|
+
var _a, _b;
|
|
718
|
+
if (((_b = (_a = this.stage.settings) === null || _a === void 0 ? void 0 : _a.seedOrdering) === null || _b === void 0 ? void 0 : _b.length) === this.seedOrdering.length)
|
|
719
|
+
return;
|
|
720
|
+
const existing = await this.storage.select('stage', stageId);
|
|
721
|
+
if (!existing)
|
|
722
|
+
throw Error('Stage not found.');
|
|
723
|
+
const update = {
|
|
724
|
+
...existing,
|
|
725
|
+
settings: {
|
|
726
|
+
...existing.settings,
|
|
727
|
+
seedOrdering: this.seedOrdering,
|
|
728
|
+
},
|
|
729
|
+
};
|
|
730
|
+
if (!await this.storage.update('stage', stageId, update))
|
|
731
|
+
throw Error('Could not update the stage.');
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
exports.StageCreator = StageCreator;
|
|
735
|
+
//# sourceMappingURL=creator.js.map
|