@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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +82 -0
  3. package/dist/base/getter.d.ts +272 -0
  4. package/dist/base/getter.d.ts.map +1 -0
  5. package/dist/base/getter.js +545 -0
  6. package/dist/base/getter.js.map +1 -0
  7. package/dist/base/stage/creator.d.ts +269 -0
  8. package/dist/base/stage/creator.d.ts.map +1 -0
  9. package/dist/base/stage/creator.js +735 -0
  10. package/dist/base/stage/creator.js.map +1 -0
  11. package/dist/base/updater.d.ts +121 -0
  12. package/dist/base/updater.d.ts.map +1 -0
  13. package/dist/base/updater.js +323 -0
  14. package/dist/base/updater.js.map +1 -0
  15. package/dist/create.d.ts +25 -0
  16. package/dist/create.d.ts.map +1 -0
  17. package/dist/create.js +55 -0
  18. package/dist/create.js.map +1 -0
  19. package/dist/delete.d.ts +33 -0
  20. package/dist/delete.d.ts.map +1 -0
  21. package/dist/delete.js +57 -0
  22. package/dist/delete.js.map +1 -0
  23. package/dist/find.d.ts +60 -0
  24. package/dist/find.d.ts.map +1 -0
  25. package/dist/find.js +196 -0
  26. package/dist/find.js.map +1 -0
  27. package/dist/get.d.ts +121 -0
  28. package/dist/get.d.ts.map +1 -0
  29. package/dist/get.js +420 -0
  30. package/dist/get.js.map +1 -0
  31. package/dist/helpers.d.ts +804 -0
  32. package/dist/helpers.d.ts.map +1 -0
  33. package/dist/helpers.js +1897 -0
  34. package/dist/helpers.js.map +1 -0
  35. package/dist/index.d.ts +11 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +21 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/manager.d.ts +60 -0
  40. package/dist/manager.d.ts.map +1 -0
  41. package/dist/manager.js +189 -0
  42. package/dist/manager.js.map +1 -0
  43. package/dist/ordering.d.ts +7 -0
  44. package/dist/ordering.d.ts.map +1 -0
  45. package/dist/ordering.js +147 -0
  46. package/dist/ordering.js.map +1 -0
  47. package/dist/reset.d.ts +27 -0
  48. package/dist/reset.d.ts.map +1 -0
  49. package/dist/reset.js +82 -0
  50. package/dist/reset.js.map +1 -0
  51. package/dist/types.d.ts +260 -0
  52. package/dist/types.d.ts.map +1 -0
  53. package/dist/types.js +3 -0
  54. package/dist/types.js.map +1 -0
  55. package/dist/update.d.ts +111 -0
  56. package/dist/update.d.ts.map +1 -0
  57. package/dist/update.js +265 -0
  58. package/dist/update.js.map +1 -0
  59. package/package.json +67 -0
@@ -0,0 +1,1897 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setExtraFields = exports.resetMatchResults = exports.setMatchResults = exports.getMatchStatus = exports.hasBye = exports.isMatchParticipantLocked = exports.isMatchUpdateLocked = exports.isMatchByeCompleted = exports.isMatchWinCompleted = exports.isMatchDrawCompleted = exports.isMatchResultCompleted = exports.isMatchForfeitCompleted = exports.isMatchStale = exports.isMatchOngoing = exports.isMatchCompleted = exports.isMatchStarted = exports.isMatchPending = exports.getOtherSide = exports.getSide = exports.isParticipantInMatch = exports.findPosition = exports.getMatchResult = exports.byeLoser = exports.byeWinnerToGrandFinal = exports.byeWinner = exports.getLoser = exports.getWinner = exports.toResultWithPosition = exports.toResult = exports.convertTBDtoBYE = exports.ensureNotTied = exports.ensureValidSize = exports.isPowerOfTwo = exports.fixSeeding = exports.ensureEquallySized = exports.ensureNoDuplicates = exports.ensureEvenSized = exports.makePairs = exports.setArraySize = exports.normalizeParticipant = exports.makeNormalizedIdMapping = exports.normalizeIds = exports.balanceByes = exports.makeGroups = exports.assertRoundRobin = exports.makeRoundRobinDistribution = exports.makeRoundRobinMatches = exports.splitByParity = exports.splitBy = exports.isDefined = void 0;
4
+ exports.getLowerBracketRoundCount = exports.getLoserOrdering = exports.getLoserCountFromWbForLbRound = exports.getLoserRoundMatchCount = exports.findLoserMatchNumber = exports.isDoubleEliminationNecessary = exports.getRoundPairCount = exports.getUpperBracketRoundCount = exports.isOrderingSupportedLoserBracket = exports.isOrderingSupportedUpperBracket = exports.ensureOrderingSupported = exports.getSeedCount = exports.getSeeds = exports.getChildGamesResults = exports.getUpdatedMatchResults = exports.getParentMatchResults = exports.setParentMatchCompleted = exports.transitionToMinor = exports.transitionToMajor = exports.isMinorRound = exports.isMajorRound = exports.uniqueBy = exports.getNonNull = exports.sortSeeding = exports.convertSlotsToSeeding = exports.convertMatchesToSeeding = exports.mapParticipantsToDatabase = exports.mapParticipantsIdsToDatabase = exports.mapParticipantsNamesToDatabase = exports.extractParticipantsFromSeeding = exports.isSeedingWithIds = exports.setForfeits = exports.setResults = exports.setCompleted = exports.getInferredResult = exports.setScores = exports.invertOpponents = exports.handleGivenStatus = exports.handleOpponentsInversion = exports.resetNextOpponent = exports.setNextOpponent = exports.getNextSideConsolationFinalDoubleElimination = exports.getNextSideLoserBracket = exports.getNextSide = exports.findParticipant = exports.getGrandFinalDecisiveMatch = exports.makeFinalStandings = exports.getLosers = exports.getOriginPosition = exports.getOpponentId = void 0;
5
+ exports.getRanking = exports.getFractionOfFinal = exports.getMatchLocation = exports.isFinalGroup = exports.isLoserBracket = exports.isWinnerBracket = exports.isRoundCompleted = exports.ensureNotRoundRobin = exports.isRoundRobin = exports.minScoreToWinBestOfX = exports.getNearestPowerOfTwo = exports.getDiagonalMatchNumber = void 0;
6
+ const brackets_model_1 = require("brackets-model");
7
+ const ordering_1 = require("./ordering");
8
+ /**
9
+ * Checks whether a value is defined (i.e. not null nor undefined).
10
+ *
11
+ * @param value The value to check.
12
+ */
13
+ function isDefined(value) {
14
+ return value !== null && value !== undefined;
15
+ }
16
+ exports.isDefined = isDefined;
17
+ /**
18
+ * Splits an array of objects based on their values at a given key.
19
+ *
20
+ * @param objects The array to split.
21
+ * @param key The key of T.
22
+ */
23
+ function splitBy(objects, key) {
24
+ const map = {};
25
+ for (const obj of objects) {
26
+ const commonValue = obj[key];
27
+ if (!map[commonValue])
28
+ map[commonValue] = [];
29
+ map[commonValue].push(obj);
30
+ }
31
+ return Object.values(map);
32
+ }
33
+ exports.splitBy = splitBy;
34
+ /**
35
+ * Splits an array in two parts: one with even indices and the other with odd indices.
36
+ *
37
+ * @param array The array to split.
38
+ */
39
+ function splitByParity(array) {
40
+ return {
41
+ even: array.filter((_, i) => i % 2 === 0),
42
+ odd: array.filter((_, i) => i % 2 === 1),
43
+ };
44
+ }
45
+ exports.splitByParity = splitByParity;
46
+ /**
47
+ * Makes a list of rounds containing the matches of a round-robin group.
48
+ *
49
+ * @param participants The participants to distribute.
50
+ * @param mode The round-robin mode.
51
+ */
52
+ function makeRoundRobinMatches(participants, mode = 'simple') {
53
+ const distribution = makeRoundRobinDistribution(participants);
54
+ if (mode === 'simple')
55
+ return distribution;
56
+ // Reverse rounds and their content.
57
+ const symmetry = distribution
58
+ .map((round) => [...round].reverse())
59
+ .reverse();
60
+ return [...distribution, ...symmetry];
61
+ }
62
+ exports.makeRoundRobinMatches = makeRoundRobinMatches;
63
+ /**
64
+ * Distributes participants in rounds for a round-robin group.
65
+ *
66
+ * Conditions:
67
+ * - Each participant plays each other once.
68
+ * - Each participant plays once in each round.
69
+ *
70
+ * @param participants The participants to distribute.
71
+ */
72
+ function makeRoundRobinDistribution(participants) {
73
+ const n = participants.length;
74
+ const n1 = n % 2 === 0 ? n : n + 1;
75
+ const roundCount = n1 - 1;
76
+ const matchPerRound = n1 / 2;
77
+ const rounds = [];
78
+ for (let roundId = 0; roundId < roundCount; roundId++) {
79
+ const matches = [];
80
+ for (let matchId = 0; matchId < matchPerRound; matchId++) {
81
+ if (matchId === 0 && n % 2 === 1)
82
+ continue;
83
+ const opponentsIds = [
84
+ (roundId - matchId - 1 + n1) % (n1 - 1),
85
+ matchId === 0 ? n1 - 1 : (roundId + matchId) % (n1 - 1),
86
+ ];
87
+ matches.push([
88
+ participants[opponentsIds[0]],
89
+ participants[opponentsIds[1]],
90
+ ]);
91
+ }
92
+ rounds.push(matches);
93
+ }
94
+ return rounds;
95
+ }
96
+ exports.makeRoundRobinDistribution = makeRoundRobinDistribution;
97
+ /**
98
+ * A helper to assert our generated round-robin is correct.
99
+ *
100
+ * @param input The input seeding.
101
+ * @param output The resulting distribution of seeds in groups.
102
+ */
103
+ function assertRoundRobin(input, output) {
104
+ const n = input.length;
105
+ const matchPerRound = Math.floor(n / 2);
106
+ const roundCount = n % 2 === 0 ? n - 1 : n;
107
+ if (output.length !== roundCount)
108
+ throw Error('Round count is wrong');
109
+ if (!output.every((round) => round.length === matchPerRound))
110
+ throw Error('Not every round has the good number of matches');
111
+ const checkAllOpponents = Object.fromEntries(input.map((element) => [element, new Set()]));
112
+ for (const round of output) {
113
+ const checkUnique = new Set();
114
+ for (const match of round) {
115
+ if (match.length !== 2)
116
+ throw Error('One match is not a pair');
117
+ if (checkUnique.has(match[0]))
118
+ throw Error('This team is already playing');
119
+ checkUnique.add(match[0]);
120
+ if (checkUnique.has(match[1]))
121
+ throw Error('This team is already playing');
122
+ checkUnique.add(match[1]);
123
+ if (checkAllOpponents[match[0]].has(match[1]))
124
+ throw Error('The team has already matched this team');
125
+ checkAllOpponents[match[0]].add(match[1]);
126
+ if (checkAllOpponents[match[1]].has(match[0]))
127
+ throw Error('The team has already matched this team');
128
+ checkAllOpponents[match[1]].add(match[0]);
129
+ }
130
+ }
131
+ }
132
+ exports.assertRoundRobin = assertRoundRobin;
133
+ /**
134
+ * Distributes elements in groups of equal size.
135
+ *
136
+ * @param elements A list of elements to distribute in groups.
137
+ * @param groupCount The group count.
138
+ */
139
+ function makeGroups(elements, groupCount) {
140
+ const groupSize = Math.ceil(elements.length / groupCount);
141
+ const result = [];
142
+ for (let i = 0; i < elements.length; i++) {
143
+ if (i % groupSize === 0)
144
+ result.push([]);
145
+ result[result.length - 1].push(elements[i]);
146
+ }
147
+ return result;
148
+ }
149
+ exports.makeGroups = makeGroups;
150
+ /**
151
+ * Balances BYEs to prevents having BYE against BYE in matches.
152
+ *
153
+ * @param seeding The seeding of the stage.
154
+ * @param participantCount The number of participants in the stage.
155
+ */
156
+ function balanceByes(seeding, participantCount) {
157
+ seeding = seeding.filter((v) => v !== null);
158
+ participantCount = participantCount || getNearestPowerOfTwo(seeding.length);
159
+ if (seeding.length < participantCount / 2) {
160
+ const flat = seeding.flatMap((v) => [v, null]);
161
+ return setArraySize(flat, participantCount, null);
162
+ }
163
+ const nonNullCount = seeding.length;
164
+ const nullCount = participantCount - nonNullCount;
165
+ const againstEachOther = seeding
166
+ .slice(0, nonNullCount - nullCount)
167
+ .filter((_, i) => i % 2 === 0)
168
+ .map((_, i) => [seeding[2 * i], seeding[2 * i + 1]]);
169
+ const againstNull = seeding
170
+ .slice(nonNullCount - nullCount, nonNullCount)
171
+ .map((v) => [v, null]);
172
+ const flat = [...againstEachOther.flat(), ...againstNull.flat()];
173
+ return setArraySize(flat, participantCount, null);
174
+ }
175
+ exports.balanceByes = balanceByes;
176
+ /**
177
+ * Normalizes IDs in a database.
178
+ *
179
+ * All IDs (and references to them) are remapped to consecutive IDs starting from 0.
180
+ *
181
+ * @param data Data to normalize.
182
+ */
183
+ function normalizeIds(data) {
184
+ const mappings = {
185
+ tournament: data.tournament
186
+ ? makeNormalizedIdMapping(data.tournament)
187
+ : {},
188
+ participant: makeNormalizedIdMapping(data.participant),
189
+ stage: makeNormalizedIdMapping(data.stage),
190
+ group: makeNormalizedIdMapping(data.group),
191
+ round: makeNormalizedIdMapping(data.round),
192
+ match: makeNormalizedIdMapping(data.match),
193
+ match_game: makeNormalizedIdMapping(data.match_game),
194
+ };
195
+ const result = {
196
+ participant: data.participant.map((value) => ({
197
+ ...value,
198
+ id: mappings.participant[value.id],
199
+ })),
200
+ stage: data.stage.map((value) => ({
201
+ ...value,
202
+ id: mappings.stage[value.id],
203
+ })),
204
+ group: data.group.map((value) => ({
205
+ ...value,
206
+ id: mappings.group[value.id],
207
+ stage_id: mappings.stage[value.stage_id],
208
+ })),
209
+ round: data.round.map((value) => ({
210
+ ...value,
211
+ id: mappings.round[value.id],
212
+ stage_id: mappings.stage[value.stage_id],
213
+ group_id: mappings.group[value.group_id],
214
+ })),
215
+ match: data.match.map((value) => ({
216
+ ...value,
217
+ id: mappings.match[value.id],
218
+ stage_id: mappings.stage[value.stage_id],
219
+ group_id: mappings.group[value.group_id],
220
+ round_id: mappings.round[value.round_id],
221
+ opponent1: normalizeParticipant(value.opponent1, mappings.participant),
222
+ opponent2: normalizeParticipant(value.opponent2, mappings.participant),
223
+ })),
224
+ match_game: data.match_game.map((value) => ({
225
+ ...value,
226
+ id: mappings.match_game[value.id],
227
+ stage_id: mappings.stage[value.stage_id],
228
+ parent_id: mappings.match[value.parent_id],
229
+ opponent1: normalizeParticipant(value.opponent1, mappings.participant),
230
+ opponent2: normalizeParticipant(value.opponent2, mappings.participant),
231
+ })),
232
+ };
233
+ if (data.tournament) {
234
+ result.tournament = data.tournament.map((value) => ({
235
+ ...value,
236
+ id: mappings.tournament[value.id],
237
+ }));
238
+ }
239
+ return result;
240
+ }
241
+ exports.normalizeIds = normalizeIds;
242
+ /**
243
+ * Makes a mapping between old IDs and new normalized IDs.
244
+ *
245
+ * @param elements A list of elements with IDs.
246
+ */
247
+ function makeNormalizedIdMapping(elements) {
248
+ let currentId = 0;
249
+ return elements.reduce((acc, current) => ({
250
+ ...acc,
251
+ [current.id]: currentId++,
252
+ }), {});
253
+ }
254
+ exports.makeNormalizedIdMapping = makeNormalizedIdMapping;
255
+ /**
256
+ * Apply a normalizing mapping to a participant.
257
+ *
258
+ * @param participant The participant.
259
+ * @param mapping The mapping of IDs.
260
+ */
261
+ function normalizeParticipant(participant, mapping) {
262
+ if (participant === null)
263
+ return null;
264
+ return {
265
+ ...participant,
266
+ id: participant.id !== null ? mapping[participant.id] : null,
267
+ };
268
+ }
269
+ exports.normalizeParticipant = normalizeParticipant;
270
+ /**
271
+ * Sets the size of an array with a placeholder if the size is bigger.
272
+ *
273
+ * @param array The original array.
274
+ * @param length The new length.
275
+ * @param placeholder A placeholder to use to fill the empty space.
276
+ */
277
+ function setArraySize(array, length, placeholder) {
278
+ return Array.from({ length }, (_, i) => array[i] || placeholder);
279
+ }
280
+ exports.setArraySize = setArraySize;
281
+ /**
282
+ * Makes pairs with each element and its next one.
283
+ *
284
+ * @example [1, 2, 3, 4] --> [[1, 2], [3, 4]]
285
+ * @param array A list of elements.
286
+ */
287
+ function makePairs(array) {
288
+ return array
289
+ .map((_, i) => (i % 2 === 0 ? [array[i], array[i + 1]] : []))
290
+ .filter((v) => v.length === 2);
291
+ }
292
+ exports.makePairs = makePairs;
293
+ /**
294
+ * Ensures that a list of elements has an even size.
295
+ *
296
+ * @param array A list of elements.
297
+ */
298
+ function ensureEvenSized(array) {
299
+ if (array.length % 2 === 1)
300
+ throw Error('Array size must be even.');
301
+ }
302
+ exports.ensureEvenSized = ensureEvenSized;
303
+ /**
304
+ * Ensures there are no duplicates in a list of elements.
305
+ *
306
+ * @param array A list of elements.
307
+ */
308
+ function ensureNoDuplicates(array) {
309
+ const nonNull = getNonNull(array);
310
+ const unique = nonNull.filter((item, index) => {
311
+ const stringifiedItem = JSON.stringify(item);
312
+ return (nonNull.findIndex((obj) => JSON.stringify(obj) === stringifiedItem) === index);
313
+ });
314
+ if (unique.length < nonNull.length)
315
+ throw new Error('The seeding has a duplicate participant.');
316
+ }
317
+ exports.ensureNoDuplicates = ensureNoDuplicates;
318
+ /**
319
+ * Ensures that two lists of elements have the same size.
320
+ *
321
+ * @param left The first list of elements.
322
+ * @param right The second list of elements.
323
+ */
324
+ function ensureEquallySized(left, right) {
325
+ if (left.length !== right.length)
326
+ throw Error('Arrays size must be equal.');
327
+ }
328
+ exports.ensureEquallySized = ensureEquallySized;
329
+ /**
330
+ * Fixes the seeding by enlarging it if it's not complete.
331
+ *
332
+ * @param seeding The seeding of the stage.
333
+ * @param participantCount The number of participants in the stage.
334
+ */
335
+ function fixSeeding(seeding, participantCount) {
336
+ if (seeding.length > participantCount) {
337
+ throw Error('The seeding has more participants than the size of the stage.');
338
+ }
339
+ if (seeding.length < participantCount)
340
+ return setArraySize(seeding, participantCount, null);
341
+ return seeding;
342
+ }
343
+ exports.fixSeeding = fixSeeding;
344
+ /**
345
+ * Indicates whether a number is a power of two.
346
+ *
347
+ * @param number The number to test.
348
+ */
349
+ function isPowerOfTwo(number) {
350
+ return Number.isInteger(Math.log2(number));
351
+ }
352
+ exports.isPowerOfTwo = isPowerOfTwo;
353
+ /**
354
+ * Ensures that the participant count is valid.
355
+ *
356
+ * @param stageType Type of the stage to test.
357
+ * @param participantCount The number to test.
358
+ */
359
+ function ensureValidSize(stageType, participantCount) {
360
+ if (participantCount === 0) {
361
+ throw Error('Impossible to create an empty stage. If you want an empty seeding, just set the size of the stage.');
362
+ }
363
+ if (participantCount < 2) {
364
+ throw Error('Impossible to create a stage with less than 2 participants.');
365
+ }
366
+ if (stageType === 'round_robin') {
367
+ // Round robin supports any number of participants.
368
+ return;
369
+ }
370
+ if (!isPowerOfTwo(participantCount)) {
371
+ throw Error('The library only supports a participant count which is a power of two.');
372
+ }
373
+ }
374
+ exports.ensureValidSize = ensureValidSize;
375
+ /**
376
+ * Ensures that a match scores aren't tied.
377
+ *
378
+ * @param scores Two numbers which are scores.
379
+ */
380
+ function ensureNotTied(scores) {
381
+ if (scores[0] === scores[1])
382
+ throw Error(`${scores[0]} and ${scores[1]} are tied. It cannot be.`);
383
+ }
384
+ exports.ensureNotTied = ensureNotTied;
385
+ /**
386
+ * Converts a TBD to a BYE.
387
+ *
388
+ * @param slot The slot to convert.
389
+ */
390
+ function convertTBDtoBYE(slot) {
391
+ if (slot === null)
392
+ return null; // Already a BYE.
393
+ if ((slot === null || slot === void 0 ? void 0 : slot.id) === null)
394
+ return null; // It's a TBD: make it a BYE.
395
+ return slot; // It's a determined participant.
396
+ }
397
+ exports.convertTBDtoBYE = convertTBDtoBYE;
398
+ /**
399
+ * Converts a participant slot to a result stored in storage.
400
+ *
401
+ * @param slot A participant slot.
402
+ */
403
+ function toResult(slot) {
404
+ return (slot && {
405
+ id: slot.id,
406
+ });
407
+ }
408
+ exports.toResult = toResult;
409
+ /**
410
+ * Converts a participant slot to a result stored in storage, with the position the participant is coming from.
411
+ *
412
+ * @param slot A participant slot.
413
+ */
414
+ function toResultWithPosition(slot) {
415
+ return (slot && {
416
+ id: slot.id,
417
+ position: slot.position,
418
+ });
419
+ }
420
+ exports.toResultWithPosition = toResultWithPosition;
421
+ /**
422
+ * Returns the winner of a match.
423
+ *
424
+ * @param match The match.
425
+ */
426
+ function getWinner(match) {
427
+ const winnerSide = getMatchResult(match);
428
+ if (!winnerSide)
429
+ return null;
430
+ return match[winnerSide];
431
+ }
432
+ exports.getWinner = getWinner;
433
+ /**
434
+ * Returns the loser of a match.
435
+ *
436
+ * @param match The match.
437
+ */
438
+ function getLoser(match) {
439
+ const winnerSide = getMatchResult(match);
440
+ if (!winnerSide)
441
+ return null;
442
+ return match[getOtherSide(winnerSide)];
443
+ }
444
+ exports.getLoser = getLoser;
445
+ /**
446
+ * Returns the pre-computed winner for a match because of BYEs.
447
+ *
448
+ * @param opponents Two opponents.
449
+ */
450
+ function byeWinner(opponents) {
451
+ if (opponents[0] === null && opponents[1] === null)
452
+ // Double BYE.
453
+ return null; // BYE.
454
+ if (opponents[0] === null && opponents[1] !== null)
455
+ // opponent1 BYE.
456
+ return { id: opponents[1].id }; // opponent2.
457
+ if (opponents[0] !== null && opponents[1] === null)
458
+ // opponent2 BYE.
459
+ return { id: opponents[0].id }; // opponent1.
460
+ return { id: null }; // Normal.
461
+ }
462
+ exports.byeWinner = byeWinner;
463
+ /**
464
+ * Returns the pre-computed winner for a match because of BYEs in a lower bracket.
465
+ *
466
+ * @param opponents Two opponents.
467
+ */
468
+ function byeWinnerToGrandFinal(opponents) {
469
+ const winner = byeWinner(opponents);
470
+ if (winner)
471
+ winner.position = 1;
472
+ return winner;
473
+ }
474
+ exports.byeWinnerToGrandFinal = byeWinnerToGrandFinal;
475
+ /**
476
+ * Returns the pre-computed loser for a match because of BYEs.
477
+ *
478
+ * Only used for loser bracket.
479
+ *
480
+ * @param opponents Two opponents.
481
+ * @param index The index of the duel in the round.
482
+ */
483
+ function byeLoser(opponents, index) {
484
+ if (opponents[0] === null || opponents[1] === null)
485
+ // At least one BYE.
486
+ return null; // BYE.
487
+ return { id: null, position: index + 1 }; // Normal.
488
+ }
489
+ exports.byeLoser = byeLoser;
490
+ /**
491
+ * Returns the winner side or `null` if no winner.
492
+ *
493
+ * @param match A match's results.
494
+ */
495
+ function getMatchResult(match) {
496
+ var _a, _b;
497
+ if (!isMatchCompleted(match))
498
+ return null;
499
+ if (isMatchDrawCompleted(match))
500
+ return null;
501
+ if (match.opponent1 === null && match.opponent2 === null)
502
+ return null;
503
+ let winner = null;
504
+ if (((_a = match.opponent1) === null || _a === void 0 ? void 0 : _a.result) === 'win' ||
505
+ match.opponent2 === null ||
506
+ match.opponent2.forfeit)
507
+ winner = 'opponent1';
508
+ if (((_b = match.opponent2) === null || _b === void 0 ? void 0 : _b.result) === 'win' ||
509
+ match.opponent1 === null ||
510
+ match.opponent1.forfeit) {
511
+ if (winner !== null)
512
+ throw Error('There are two winners.');
513
+ winner = 'opponent2';
514
+ }
515
+ return winner;
516
+ }
517
+ exports.getMatchResult = getMatchResult;
518
+ /**
519
+ * Finds a position in a list of matches.
520
+ *
521
+ * @param matches A list of matches to search into.
522
+ * @param position The position to find.
523
+ */
524
+ function findPosition(matches, position) {
525
+ var _a, _b;
526
+ for (const match of matches) {
527
+ if (((_a = match.opponent1) === null || _a === void 0 ? void 0 : _a.position) === position)
528
+ return match.opponent1;
529
+ if (((_b = match.opponent2) === null || _b === void 0 ? void 0 : _b.position) === position)
530
+ return match.opponent2;
531
+ }
532
+ return null;
533
+ }
534
+ exports.findPosition = findPosition;
535
+ /**
536
+ * Checks if a participant is involved in a given match.
537
+ *
538
+ * @param match A match.
539
+ * @param participantId ID of a participant.
540
+ */
541
+ function isParticipantInMatch(match, participantId) {
542
+ return [match.opponent1, match.opponent2].some((m) => (m === null || m === void 0 ? void 0 : m.id) === participantId);
543
+ }
544
+ exports.isParticipantInMatch = isParticipantInMatch;
545
+ /**
546
+ * Gets the side where the winner of the given match will go in the next match.
547
+ *
548
+ * @param matchNumber Number of the match.
549
+ */
550
+ function getSide(matchNumber) {
551
+ return matchNumber % 2 === 1 ? 'opponent1' : 'opponent2';
552
+ }
553
+ exports.getSide = getSide;
554
+ /**
555
+ * Gets the other side of a match.
556
+ *
557
+ * @param side The side that we don't want.
558
+ */
559
+ function getOtherSide(side) {
560
+ return side === 'opponent1' ? 'opponent2' : 'opponent1';
561
+ }
562
+ exports.getOtherSide = getOtherSide;
563
+ /**
564
+ * Checks if a match is pending (i.e. locked or waiting).
565
+ *
566
+ * [Locked > Waiting] > Ready > Running > Completed > Archived
567
+ *
568
+ * @param match Partial match results.
569
+ */
570
+ function isMatchPending(match) {
571
+ var _a, _b;
572
+ return !((_a = match.opponent1) === null || _a === void 0 ? void 0 : _a.id) || !((_b = match.opponent2) === null || _b === void 0 ? void 0 : _b.id); // At least one BYE or TBD
573
+ }
574
+ exports.isMatchPending = isMatchPending;
575
+ /**
576
+ * Checks if a match is started.
577
+ *
578
+ * Note: this is score-based. A completed or archived match is seen as "started" as well.
579
+ *
580
+ * Locked > Waiting > Ready > [Running > Completed > Archived]
581
+ *
582
+ * @param match Partial match results.
583
+ */
584
+ function isMatchStarted(match) {
585
+ var _a, _b;
586
+ return (((_a = match.opponent1) === null || _a === void 0 ? void 0 : _a.score) !== undefined ||
587
+ ((_b = match.opponent2) === null || _b === void 0 ? void 0 : _b.score) !== undefined);
588
+ }
589
+ exports.isMatchStarted = isMatchStarted;
590
+ /**
591
+ * Checks if a match is completed (based on BYEs, forfeit or result).
592
+ *
593
+ * Note: archived matches are not seen as completed by this helper.
594
+ *
595
+ * Locked > Waiting > Ready > Running > [Completed] > Archived
596
+ *
597
+ * @param match Partial match results.
598
+ */
599
+ function isMatchCompleted(match) {
600
+ return (isMatchByeCompleted(match) ||
601
+ isMatchForfeitCompleted(match) ||
602
+ isMatchResultCompleted(match));
603
+ }
604
+ exports.isMatchCompleted = isMatchCompleted;
605
+ /**
606
+ * Checks if a match is ongoing (i.e. ready or running).
607
+ *
608
+ * Locked > Waiting > [Ready > Running] > Completed > Archived
609
+ *
610
+ * @param match Partial match results.
611
+ */
612
+ function isMatchOngoing(match) {
613
+ return [brackets_model_1.Status.Ready, brackets_model_1.Status.Running].includes(match.status);
614
+ }
615
+ exports.isMatchOngoing = isMatchOngoing;
616
+ /**
617
+ * Checks if a match is stale (i.e. it should not change anymore).
618
+ *
619
+ * [Locked - BYE] > Waiting > Ready > Running > [Completed > Archived]
620
+ *
621
+ * @param match Partial match results.
622
+ */
623
+ function isMatchStale(match) {
624
+ return match.status >= brackets_model_1.Status.Completed || isMatchByeCompleted(match);
625
+ }
626
+ exports.isMatchStale = isMatchStale;
627
+ /**
628
+ * Checks if a match is completed because of a forfeit.
629
+ *
630
+ * @param match Partial match results.
631
+ */
632
+ function isMatchForfeitCompleted(match) {
633
+ var _a, _b;
634
+ return (((_a = match.opponent1) === null || _a === void 0 ? void 0 : _a.forfeit) !== undefined ||
635
+ ((_b = match.opponent2) === null || _b === void 0 ? void 0 : _b.forfeit) !== undefined);
636
+ }
637
+ exports.isMatchForfeitCompleted = isMatchForfeitCompleted;
638
+ /**
639
+ * Checks if a match is completed because of a either a draw or a win.
640
+ *
641
+ * @param match Partial match results.
642
+ */
643
+ function isMatchResultCompleted(match) {
644
+ return isMatchDrawCompleted(match) || isMatchWinCompleted(match);
645
+ }
646
+ exports.isMatchResultCompleted = isMatchResultCompleted;
647
+ /**
648
+ * Checks if a match is completed because of a draw.
649
+ *
650
+ * @param match Partial match results.
651
+ */
652
+ function isMatchDrawCompleted(match) {
653
+ var _a, _b;
654
+ return (((_a = match.opponent1) === null || _a === void 0 ? void 0 : _a.result) === 'draw' && ((_b = match.opponent2) === null || _b === void 0 ? void 0 : _b.result) === 'draw');
655
+ }
656
+ exports.isMatchDrawCompleted = isMatchDrawCompleted;
657
+ /**
658
+ * Checks if a match is completed because of a win.
659
+ *
660
+ * @param match Partial match results.
661
+ */
662
+ function isMatchWinCompleted(match) {
663
+ var _a, _b, _c, _d;
664
+ return (((_a = match.opponent1) === null || _a === void 0 ? void 0 : _a.result) === 'win' ||
665
+ ((_b = match.opponent2) === null || _b === void 0 ? void 0 : _b.result) === 'win' ||
666
+ ((_c = match.opponent1) === null || _c === void 0 ? void 0 : _c.result) === 'loss' ||
667
+ ((_d = match.opponent2) === null || _d === void 0 ? void 0 : _d.result) === 'loss');
668
+ }
669
+ exports.isMatchWinCompleted = isMatchWinCompleted;
670
+ /**
671
+ * Checks if a match is completed because of at least one BYE.
672
+ *
673
+ * A match "BYE vs. TBD" isn't considered completed yet.
674
+ *
675
+ * @param match Partial match results.
676
+ */
677
+ function isMatchByeCompleted(match) {
678
+ var _a, _b;
679
+ return ((match.opponent1 === null && ((_a = match.opponent2) === null || _a === void 0 ? void 0 : _a.id) !== null) || // BYE vs. someone
680
+ (match.opponent2 === null && ((_b = match.opponent1) === null || _b === void 0 ? void 0 : _b.id) !== null) || // someone vs. BYE
681
+ (match.opponent1 === null && match.opponent2 === null)); // BYE vs. BYE
682
+ }
683
+ exports.isMatchByeCompleted = isMatchByeCompleted;
684
+ /**
685
+ * Checks if a match's results can't be updated.
686
+ *
687
+ * @param match The match to check.
688
+ */
689
+ function isMatchUpdateLocked(match) {
690
+ return (match.status === brackets_model_1.Status.Locked ||
691
+ match.status === brackets_model_1.Status.Waiting ||
692
+ match.status === brackets_model_1.Status.Archived ||
693
+ isMatchByeCompleted(match));
694
+ }
695
+ exports.isMatchUpdateLocked = isMatchUpdateLocked;
696
+ /**
697
+ * Checks if a match's participants can't be updated.
698
+ *
699
+ * @param match The match to check.
700
+ */
701
+ function isMatchParticipantLocked(match) {
702
+ return match.status >= brackets_model_1.Status.Running;
703
+ }
704
+ exports.isMatchParticipantLocked = isMatchParticipantLocked;
705
+ /**
706
+ * Indicates whether a match has at least one BYE or not.
707
+ *
708
+ * @param match Partial match results.
709
+ */
710
+ function hasBye(match) {
711
+ return match.opponent1 === null || match.opponent2 === null;
712
+ }
713
+ exports.hasBye = hasBye;
714
+ /**
715
+ * Returns the status of a match based on information about it.
716
+ *
717
+ * @param arg The opponents or partial results of the match.
718
+ */
719
+ function getMatchStatus(arg) {
720
+ var _a, _b, _c, _d;
721
+ const match = Array.isArray(arg)
722
+ ? {
723
+ opponent1: arg[0],
724
+ opponent2: arg[1],
725
+ }
726
+ : arg;
727
+ if (hasBye(match))
728
+ // At least one BYE.
729
+ return brackets_model_1.Status.Locked;
730
+ if (((_a = match.opponent1) === null || _a === void 0 ? void 0 : _a.id) === null && ((_b = match.opponent2) === null || _b === void 0 ? void 0 : _b.id) === null)
731
+ // Two TBD opponents.
732
+ return brackets_model_1.Status.Locked;
733
+ if (((_c = match.opponent1) === null || _c === void 0 ? void 0 : _c.id) === null || ((_d = match.opponent2) === null || _d === void 0 ? void 0 : _d.id) === null)
734
+ // One TBD opponent.
735
+ return brackets_model_1.Status.Waiting;
736
+ if (isMatchCompleted(match))
737
+ return brackets_model_1.Status.Completed;
738
+ if (isMatchStarted(match))
739
+ return brackets_model_1.Status.Running;
740
+ return brackets_model_1.Status.Ready;
741
+ }
742
+ exports.getMatchStatus = getMatchStatus;
743
+ /**
744
+ * Updates a match results based on an input.
745
+ *
746
+ * @param stored A reference to what will be updated in the storage.
747
+ * @param match Input of the update.
748
+ * @param inRoundRobin Indicates whether the match is in a round-robin stage.
749
+ */
750
+ function setMatchResults(stored, match, inRoundRobin) {
751
+ var _a, _b;
752
+ handleGivenStatus(stored, match);
753
+ if (!inRoundRobin &&
754
+ (((_a = match.opponent1) === null || _a === void 0 ? void 0 : _a.result) === 'draw' ||
755
+ ((_b = match.opponent2) === null || _b === void 0 ? void 0 : _b.result) === 'draw'))
756
+ throw Error('Having a draw is forbidden in an elimination tournament.');
757
+ const completed = isMatchCompleted(match);
758
+ const currentlyCompleted = isMatchCompleted(stored);
759
+ setExtraFields(stored, match);
760
+ handleOpponentsInversion(stored, match);
761
+ const statusChanged = setScores(stored, match);
762
+ if (completed && currentlyCompleted) {
763
+ // Ensure everything is good.
764
+ setCompleted(stored, match, inRoundRobin);
765
+ return { statusChanged: false, resultChanged: true };
766
+ }
767
+ if (completed && !currentlyCompleted) {
768
+ setCompleted(stored, match, inRoundRobin);
769
+ return { statusChanged: true, resultChanged: true };
770
+ }
771
+ if (!completed && currentlyCompleted) {
772
+ resetMatchResults(stored);
773
+ return { statusChanged: true, resultChanged: true };
774
+ }
775
+ return { statusChanged, resultChanged: false };
776
+ }
777
+ exports.setMatchResults = setMatchResults;
778
+ /**
779
+ * Resets the results of a match. (status, forfeit, result)
780
+ *
781
+ * @param stored A reference to what will be updated in the storage.
782
+ */
783
+ function resetMatchResults(stored) {
784
+ if (stored.opponent1) {
785
+ stored.opponent1.forfeit = undefined;
786
+ stored.opponent1.result = undefined;
787
+ }
788
+ if (stored.opponent2) {
789
+ stored.opponent2.forfeit = undefined;
790
+ stored.opponent2.result = undefined;
791
+ }
792
+ stored.status = getMatchStatus(stored);
793
+ }
794
+ exports.resetMatchResults = resetMatchResults;
795
+ /**
796
+ * Passes user-defined extra fields to the stored match.
797
+ *
798
+ * @param stored A reference to what will be updated in the storage.
799
+ * @param match Input of the update.
800
+ */
801
+ function setExtraFields(stored, match) {
802
+ const partialAssign = (target, update, ignoredKeys) => {
803
+ if (!target || !update)
804
+ return;
805
+ const retainedKeys = Object.keys(update).filter((key) => !ignoredKeys.includes(key));
806
+ retainedKeys.forEach((key) => {
807
+ target[key] = update[key];
808
+ });
809
+ };
810
+ const ignoredKeys = [
811
+ 'id',
812
+ 'number',
813
+ 'stage_id',
814
+ 'group_id',
815
+ 'round_id',
816
+ 'status',
817
+ 'opponent1',
818
+ 'opponent2',
819
+ 'child_count',
820
+ 'parent_id',
821
+ ];
822
+ const ignoredOpponentKeys = [
823
+ 'id',
824
+ 'score',
825
+ 'position',
826
+ 'forfeit',
827
+ 'result',
828
+ ];
829
+ partialAssign(stored, match, ignoredKeys);
830
+ partialAssign(stored.opponent1, match.opponent1, ignoredOpponentKeys);
831
+ partialAssign(stored.opponent2, match.opponent2, ignoredOpponentKeys);
832
+ }
833
+ exports.setExtraFields = setExtraFields;
834
+ /**
835
+ * Gets the id of the opponent at the given side of the given match.
836
+ *
837
+ * @param match The match to get the opponent from.
838
+ * @param side The side where to get the opponent from.
839
+ */
840
+ function getOpponentId(match, side) {
841
+ const opponent = match[side];
842
+ return opponent && opponent.id;
843
+ }
844
+ exports.getOpponentId = getOpponentId;
845
+ /**
846
+ * Gets the origin position of a side of a match.
847
+ *
848
+ * @param match The match.
849
+ * @param side The side.
850
+ */
851
+ function getOriginPosition(match, side) {
852
+ var _a;
853
+ const matchNumber = (_a = match[side]) === null || _a === void 0 ? void 0 : _a.position;
854
+ if (matchNumber === undefined)
855
+ throw Error('Position is undefined.');
856
+ return matchNumber;
857
+ }
858
+ exports.getOriginPosition = getOriginPosition;
859
+ /**
860
+ * Returns every loser in a list of matches.
861
+ *
862
+ * @param participants The list of participants.
863
+ * @param matches A list of matches to get losers of.
864
+ */
865
+ function getLosers(participants, matches) {
866
+ const losers = [];
867
+ let currentRound = null;
868
+ let roundIndex = -1;
869
+ for (const match of matches) {
870
+ if (match.round_id !== currentRound) {
871
+ currentRound = match.round_id;
872
+ roundIndex++;
873
+ losers[roundIndex] = [];
874
+ }
875
+ const loser = getLoser(match);
876
+ if (loser === null)
877
+ continue;
878
+ losers[roundIndex].push(findParticipant(participants, loser));
879
+ }
880
+ return losers;
881
+ }
882
+ exports.getLosers = getLosers;
883
+ /**
884
+ * Makes final standings based on participants grouped by ranking.
885
+ *
886
+ * @param grouped A list of participants grouped by ranking.
887
+ */
888
+ function makeFinalStandings(grouped) {
889
+ const standings = [];
890
+ let rank = 1;
891
+ for (const group of grouped) {
892
+ for (const participant of group) {
893
+ standings.push({
894
+ id: participant.id,
895
+ name: participant.name,
896
+ rank,
897
+ });
898
+ }
899
+ rank++;
900
+ }
901
+ return standings;
902
+ }
903
+ exports.makeFinalStandings = makeFinalStandings;
904
+ /**
905
+ * Returns the decisive match of a Grand Final.
906
+ *
907
+ * @param type The type of Grand Final.
908
+ * @param matches The matches in the Grand Final.
909
+ */
910
+ function getGrandFinalDecisiveMatch(type, matches) {
911
+ if (type === 'simple')
912
+ return matches[0];
913
+ if (type === 'double') {
914
+ const result = getMatchResult(matches[0]);
915
+ if (result === 'opponent2')
916
+ return matches[1];
917
+ return matches[0];
918
+ }
919
+ throw Error('The Grand Final is disabled.');
920
+ }
921
+ exports.getGrandFinalDecisiveMatch = getGrandFinalDecisiveMatch;
922
+ /**
923
+ * Finds a participant in a list.
924
+ *
925
+ * @param participants The list of participants.
926
+ * @param slot The slot of the participant to find.
927
+ */
928
+ function findParticipant(participants, slot) {
929
+ if (!slot)
930
+ throw Error('Cannot find a BYE participant.');
931
+ const participant = participants.find((participant) => participant.id === (slot === null || slot === void 0 ? void 0 : slot.id));
932
+ if (!participant)
933
+ throw Error('Participant not found.');
934
+ return participant;
935
+ }
936
+ exports.findParticipant = findParticipant;
937
+ /**
938
+ * Gets the side the winner of the current match will go to in the next match.
939
+ *
940
+ * @param matchNumber Number of the current match.
941
+ * @param roundNumber Number of the current round.
942
+ * @param roundCount Count of rounds.
943
+ * @param matchLocation Location of the current match.
944
+ */
945
+ function getNextSide(matchNumber, roundNumber, roundCount, matchLocation) {
946
+ // The nextSide comes from the same bracket.
947
+ if (matchLocation === 'loser_bracket' && roundNumber % 2 === 1)
948
+ return 'opponent2';
949
+ // The nextSide comes from the loser bracket to the final group.
950
+ if (matchLocation === 'loser_bracket' && roundNumber === roundCount)
951
+ return 'opponent2';
952
+ return getSide(matchNumber);
953
+ }
954
+ exports.getNextSide = getNextSide;
955
+ /**
956
+ * Gets the side the winner of the current match in loser bracket will go in the next match.
957
+ *
958
+ * @param matchNumber Number of the match.
959
+ * @param nextMatch The next match.
960
+ * @param roundNumber Number of the current round.
961
+ */
962
+ function getNextSideLoserBracket(matchNumber, nextMatch, roundNumber) {
963
+ var _a;
964
+ // The nextSide comes from the WB.
965
+ if (roundNumber > 1)
966
+ return 'opponent1';
967
+ // The nextSide comes from the WB round 1.
968
+ if (((_a = nextMatch.opponent1) === null || _a === void 0 ? void 0 : _a.position) === matchNumber)
969
+ return 'opponent1';
970
+ return 'opponent2';
971
+ }
972
+ exports.getNextSideLoserBracket = getNextSideLoserBracket;
973
+ /**
974
+ * Gets the side the loser of the current match in loser bracket will go in the next match.
975
+ *
976
+ * @param roundNumber Number of the current round.
977
+ */
978
+ function getNextSideConsolationFinalDoubleElimination(roundNumber) {
979
+ return isMajorRound(roundNumber) ? 'opponent1' : 'opponent2';
980
+ }
981
+ exports.getNextSideConsolationFinalDoubleElimination = getNextSideConsolationFinalDoubleElimination;
982
+ /**
983
+ * Sets an opponent in the next match he has to go.
984
+ *
985
+ * @param nextMatch A match which follows the current one.
986
+ * @param nextSide The side the opponent will be on in the next match.
987
+ * @param match The current match.
988
+ * @param currentSide The side the opponent is currently on.
989
+ */
990
+ function setNextOpponent(nextMatch, nextSide, match, currentSide) {
991
+ var _a;
992
+ nextMatch[nextSide] = match[currentSide] && {
993
+ // Keep BYE.
994
+ id: getOpponentId(match, currentSide),
995
+ position: (_a = nextMatch[nextSide]) === null || _a === void 0 ? void 0 : _a.position, // Keep position.
996
+ };
997
+ nextMatch.status = getMatchStatus(nextMatch);
998
+ }
999
+ exports.setNextOpponent = setNextOpponent;
1000
+ /**
1001
+ * Resets an opponent in the match following the current one.
1002
+ *
1003
+ * @param nextMatch A match which follows the current one.
1004
+ * @param nextSide The side the opponent will be on in the next match.
1005
+ */
1006
+ function resetNextOpponent(nextMatch, nextSide) {
1007
+ var _a;
1008
+ nextMatch[nextSide] = nextMatch[nextSide] && {
1009
+ // Keep BYE.
1010
+ id: null,
1011
+ position: (_a = nextMatch[nextSide]) === null || _a === void 0 ? void 0 : _a.position, // Keep position.
1012
+ };
1013
+ nextMatch.status = brackets_model_1.Status.Locked;
1014
+ }
1015
+ exports.resetNextOpponent = resetNextOpponent;
1016
+ /**
1017
+ * Inverts opponents if requested by the input.
1018
+ *
1019
+ * @param stored A reference to what will be updated in the storage.
1020
+ * @param match Input of the update.
1021
+ */
1022
+ function handleOpponentsInversion(stored, match) {
1023
+ var _a, _b, _c, _d;
1024
+ const id1 = (_a = match.opponent1) === null || _a === void 0 ? void 0 : _a.id;
1025
+ const id2 = (_b = match.opponent2) === null || _b === void 0 ? void 0 : _b.id;
1026
+ const storedId1 = (_c = stored.opponent1) === null || _c === void 0 ? void 0 : _c.id;
1027
+ const storedId2 = (_d = stored.opponent2) === null || _d === void 0 ? void 0 : _d.id;
1028
+ if (isDefined(id1) && id1 !== storedId1 && id1 !== storedId2)
1029
+ throw Error('The given opponent1 ID does not exist in this match.');
1030
+ if (isDefined(id2) && id2 !== storedId1 && id2 !== storedId2)
1031
+ throw Error('The given opponent2 ID does not exist in this match.');
1032
+ if ((isDefined(id1) && id1 === storedId2) ||
1033
+ (isDefined(id2) && id2 === storedId1))
1034
+ invertOpponents(match);
1035
+ }
1036
+ exports.handleOpponentsInversion = handleOpponentsInversion;
1037
+ /**
1038
+ * Sets the `result` of both opponents based on their scores.
1039
+ *
1040
+ * @param stored A reference to what will be updated in the storage.
1041
+ * @param match Input of the update.
1042
+ */
1043
+ function handleGivenStatus(stored, match) {
1044
+ var _a, _b, _c, _d;
1045
+ if (match.status === brackets_model_1.Status.Running) {
1046
+ (_a = stored.opponent1) === null || _a === void 0 ? true : delete _a.result;
1047
+ (_b = stored.opponent2) === null || _b === void 0 ? true : delete _b.result;
1048
+ stored.status = brackets_model_1.Status.Running;
1049
+ }
1050
+ else if (match.status === brackets_model_1.Status.Completed) {
1051
+ if (((_c = match.opponent1) === null || _c === void 0 ? void 0 : _c.score) === undefined ||
1052
+ ((_d = match.opponent2) === null || _d === void 0 ? void 0 : _d.score) === undefined)
1053
+ return;
1054
+ if (match.opponent1.score > match.opponent2.score)
1055
+ match.opponent1.result = 'win';
1056
+ else if (match.opponent2.score > match.opponent1.score)
1057
+ match.opponent2.result = 'win';
1058
+ else {
1059
+ // This will throw in an elimination stage.
1060
+ match.opponent1.result = 'draw';
1061
+ match.opponent2.result = 'draw';
1062
+ }
1063
+ stored.status = brackets_model_1.Status.Completed;
1064
+ }
1065
+ }
1066
+ exports.handleGivenStatus = handleGivenStatus;
1067
+ /**
1068
+ * Inverts `opponent1` and `opponent2` in a match.
1069
+ *
1070
+ * @param match A match to update.
1071
+ */
1072
+ function invertOpponents(match) {
1073
+ [match.opponent1, match.opponent2] = [match.opponent2, match.opponent1];
1074
+ }
1075
+ exports.invertOpponents = invertOpponents;
1076
+ /**
1077
+ * Updates the scores of a match.
1078
+ *
1079
+ * @param stored A reference to what will be updated in the storage.
1080
+ * @param match Input of the update.
1081
+ * @returns `true` if the status of the match changed, `false` otherwise.
1082
+ */
1083
+ function setScores(stored, match) {
1084
+ var _a, _b, _c, _d;
1085
+ // Skip if no score update.
1086
+ if (((_a = match.opponent1) === null || _a === void 0 ? void 0 : _a.score) === ((_b = stored.opponent1) === null || _b === void 0 ? void 0 : _b.score) &&
1087
+ ((_c = match.opponent2) === null || _c === void 0 ? void 0 : _c.score) === ((_d = stored.opponent2) === null || _d === void 0 ? void 0 : _d.score))
1088
+ return false;
1089
+ const oldStatus = stored.status;
1090
+ stored.status = brackets_model_1.Status.Running;
1091
+ if (match.opponent1 && stored.opponent1)
1092
+ stored.opponent1.score = match.opponent1.score;
1093
+ if (match.opponent2 && stored.opponent2)
1094
+ stored.opponent2.score = match.opponent2.score;
1095
+ return stored.status !== oldStatus;
1096
+ }
1097
+ exports.setScores = setScores;
1098
+ /**
1099
+ * Infers the win result based on BYEs.
1100
+ *
1101
+ * @param opponent1 Opponent 1.
1102
+ * @param opponent2 Opponent 2.
1103
+ */
1104
+ function getInferredResult(opponent1, opponent2) {
1105
+ if (opponent1 && !opponent2)
1106
+ // someone vs. BYE
1107
+ return { opponent1: { ...opponent1, result: 'win' }, opponent2: null };
1108
+ if (!opponent1 && opponent2)
1109
+ // BYE vs. someone
1110
+ return { opponent1: null, opponent2: { ...opponent2, result: 'win' } };
1111
+ return { opponent1, opponent2 }; // Do nothing if both BYE or both someone
1112
+ }
1113
+ exports.getInferredResult = getInferredResult;
1114
+ /**
1115
+ * Completes a match and handles results and forfeits.
1116
+ *
1117
+ * @param stored A reference to what will be updated in the storage.
1118
+ * @param match Input of the update.
1119
+ * @param inRoundRobin Indicates whether the match is in a round-robin stage.
1120
+ */
1121
+ function setCompleted(stored, match, inRoundRobin) {
1122
+ stored.status = brackets_model_1.Status.Completed;
1123
+ setResults(stored, match, 'win', 'loss', inRoundRobin);
1124
+ setResults(stored, match, 'loss', 'win', inRoundRobin);
1125
+ setResults(stored, match, 'draw', 'draw', inRoundRobin);
1126
+ const { opponent1, opponent2 } = getInferredResult(stored.opponent1, stored.opponent2);
1127
+ stored.opponent1 = opponent1;
1128
+ stored.opponent2 = opponent2;
1129
+ setForfeits(stored, match);
1130
+ }
1131
+ exports.setCompleted = setCompleted;
1132
+ /**
1133
+ * Enforces the symmetry between opponents.
1134
+ *
1135
+ * Sets an opponent's result to something, based on the result on the other opponent.
1136
+ *
1137
+ * @param stored A reference to what will be updated in the storage.
1138
+ * @param match Input of the update.
1139
+ * @param check A result to check in each opponent.
1140
+ * @param change A result to set in each other opponent if `check` is correct.
1141
+ * @param inRoundRobin Indicates whether the match is in a round-robin stage.
1142
+ */
1143
+ function setResults(stored, match, check, change, inRoundRobin) {
1144
+ var _a, _b;
1145
+ if (match.opponent1 && match.opponent2) {
1146
+ if (match.opponent1.result === 'win' &&
1147
+ match.opponent2.result === 'win')
1148
+ throw Error('There are two winners.');
1149
+ if (match.opponent1.result === 'loss' &&
1150
+ match.opponent2.result === 'loss')
1151
+ throw Error('There are two losers.');
1152
+ if (!inRoundRobin &&
1153
+ match.opponent1.forfeit === true &&
1154
+ match.opponent2.forfeit === true)
1155
+ throw Error('There are two forfeits.');
1156
+ }
1157
+ if (((_a = match.opponent1) === null || _a === void 0 ? void 0 : _a.result) === check) {
1158
+ if (stored.opponent1)
1159
+ stored.opponent1.result = check;
1160
+ else
1161
+ stored.opponent1 = { id: null, result: check };
1162
+ if (stored.opponent2)
1163
+ stored.opponent2.result = change;
1164
+ else
1165
+ stored.opponent2 = { id: null, result: change };
1166
+ }
1167
+ if (((_b = match.opponent2) === null || _b === void 0 ? void 0 : _b.result) === check) {
1168
+ if (stored.opponent2)
1169
+ stored.opponent2.result = check;
1170
+ else
1171
+ stored.opponent2 = { id: null, result: check };
1172
+ if (stored.opponent1)
1173
+ stored.opponent1.result = change;
1174
+ else
1175
+ stored.opponent1 = { id: null, result: change };
1176
+ }
1177
+ }
1178
+ exports.setResults = setResults;
1179
+ /**
1180
+ * Sets forfeits for each opponent (if needed).
1181
+ *
1182
+ * @param stored A reference to what will be updated in the storage.
1183
+ * @param match Input of the update.
1184
+ */
1185
+ function setForfeits(stored, match) {
1186
+ var _a, _b, _c, _d;
1187
+ if (((_a = match.opponent1) === null || _a === void 0 ? void 0 : _a.forfeit) === true &&
1188
+ ((_b = match.opponent2) === null || _b === void 0 ? void 0 : _b.forfeit) === true) {
1189
+ if (stored.opponent1)
1190
+ stored.opponent1.forfeit = true;
1191
+ if (stored.opponent2)
1192
+ stored.opponent2.forfeit = true;
1193
+ // Don't set any result (win/draw/loss) with a double forfeit
1194
+ // so that it doesn't count any point in the ranking.
1195
+ return;
1196
+ }
1197
+ if (((_c = match.opponent1) === null || _c === void 0 ? void 0 : _c.forfeit) === true) {
1198
+ if (stored.opponent1)
1199
+ stored.opponent1.forfeit = true;
1200
+ if (stored.opponent2)
1201
+ stored.opponent2.result = 'win';
1202
+ else
1203
+ stored.opponent2 = { id: null, result: 'win' };
1204
+ }
1205
+ if (((_d = match.opponent2) === null || _d === void 0 ? void 0 : _d.forfeit) === true) {
1206
+ if (stored.opponent2)
1207
+ stored.opponent2.forfeit = true;
1208
+ if (stored.opponent1)
1209
+ stored.opponent1.result = 'win';
1210
+ else
1211
+ stored.opponent1 = { id: null, result: 'win' };
1212
+ }
1213
+ }
1214
+ exports.setForfeits = setForfeits;
1215
+ /**
1216
+ * Indicates if a seeding is filled with participants' IDs.
1217
+ *
1218
+ * @param seeding The seeding.
1219
+ */
1220
+ function isSeedingWithIds(seeding) {
1221
+ return seeding.some((value) => typeof value === 'number');
1222
+ }
1223
+ exports.isSeedingWithIds = isSeedingWithIds;
1224
+ /**
1225
+ * Extracts participants from a seeding, without the BYEs.
1226
+ *
1227
+ * @param tournamentId ID of the tournament.
1228
+ * @param seeding The seeding (no IDs).
1229
+ */
1230
+ function extractParticipantsFromSeeding(tournamentId, seeding) {
1231
+ const withoutByes = seeding.filter((name) => name !== null);
1232
+ const participants = withoutByes.map((item) => {
1233
+ if (typeof item === 'string') {
1234
+ return {
1235
+ tournament_id: tournamentId,
1236
+ name: item,
1237
+ };
1238
+ }
1239
+ return {
1240
+ ...item,
1241
+ tournament_id: tournamentId,
1242
+ name: item.name,
1243
+ };
1244
+ });
1245
+ return participants;
1246
+ }
1247
+ exports.extractParticipantsFromSeeding = extractParticipantsFromSeeding;
1248
+ /**
1249
+ * Returns participant slots mapped to the instances stored in the database thanks to their name.
1250
+ *
1251
+ * @param seeding The seeding.
1252
+ * @param database The participants stored in the database.
1253
+ * @param positions An optional list of positions (seeds) for a manual ordering.
1254
+ */
1255
+ function mapParticipantsNamesToDatabase(seeding, database, positions) {
1256
+ return mapParticipantsToDatabase('name', seeding, database, positions);
1257
+ }
1258
+ exports.mapParticipantsNamesToDatabase = mapParticipantsNamesToDatabase;
1259
+ /**
1260
+ * Returns participant slots mapped to the instances stored in the database thanks to their id.
1261
+ *
1262
+ * @param seeding The seeding.
1263
+ * @param database The participants stored in the database.
1264
+ * @param positions An optional list of positions (seeds) for a manual ordering.
1265
+ */
1266
+ function mapParticipantsIdsToDatabase(seeding, database, positions) {
1267
+ return mapParticipantsToDatabase('id', seeding, database, positions);
1268
+ }
1269
+ exports.mapParticipantsIdsToDatabase = mapParticipantsIdsToDatabase;
1270
+ /**
1271
+ * Returns participant slots mapped to the instances stored in the database thanks to a property of theirs.
1272
+ *
1273
+ * @param prop The property to search participants with.
1274
+ * @param seeding The seeding.
1275
+ * @param database The participants stored in the database.
1276
+ * @param positions An optional list of positions (seeds) for a manual ordering.
1277
+ */
1278
+ function mapParticipantsToDatabase(prop, seeding, database, positions) {
1279
+ const slots = seeding.map((slot, i) => {
1280
+ if (slot === null)
1281
+ return null; // BYE.
1282
+ const found = database.find((participant) => typeof slot === 'object'
1283
+ ? participant[prop] === slot[prop]
1284
+ : participant[prop] === slot);
1285
+ if (!found)
1286
+ throw Error(`Participant ${prop} not found in database.`);
1287
+ return { id: found.id, position: i + 1 };
1288
+ });
1289
+ if (!positions)
1290
+ return slots;
1291
+ if (positions.length !== slots.length) {
1292
+ throw Error('Not enough seeds in at least one group of the manual ordering.');
1293
+ }
1294
+ return positions.map((position) => slots[position - 1]); // Because `position` is `i + 1`.
1295
+ }
1296
+ exports.mapParticipantsToDatabase = mapParticipantsToDatabase;
1297
+ /**
1298
+ * Converts a list of matches to a seeding.
1299
+ *
1300
+ * @param matches The input matches.
1301
+ */
1302
+ function convertMatchesToSeeding(matches) {
1303
+ const flattened = [].concat(...matches.map((match) => [match.opponent1, match.opponent2]));
1304
+ return sortSeeding(flattened);
1305
+ }
1306
+ exports.convertMatchesToSeeding = convertMatchesToSeeding;
1307
+ /**
1308
+ * Converts a list of slots to an input seeding.
1309
+ *
1310
+ * @param slots The slots to convert.
1311
+ */
1312
+ function convertSlotsToSeeding(slots) {
1313
+ return slots.map((slot) => {
1314
+ if (slot === null || slot.id === null)
1315
+ return null; // BYE or TBD.
1316
+ return slot.id; // Let's return the ID instead of the name to be sure we keep the same reference.
1317
+ });
1318
+ }
1319
+ exports.convertSlotsToSeeding = convertSlotsToSeeding;
1320
+ /**
1321
+ * Sorts the seeding with the BYEs in the correct position.
1322
+ *
1323
+ * @param slots A list of slots to sort.
1324
+ */
1325
+ function sortSeeding(slots) {
1326
+ const withoutByes = slots.filter((v) => v !== null);
1327
+ // a and b are not null because of the filter.
1328
+ // The slots are from seeding slots, thus they have a position.
1329
+ withoutByes.sort((a, b) => a.position - b.position);
1330
+ if (withoutByes.length === slots.length)
1331
+ return withoutByes;
1332
+ // Same for v and position.
1333
+ const placed = Object.fromEntries(withoutByes.map((v) => [v.position - 1, v]));
1334
+ const sortedWithByes = Array.from({ length: slots.length }, (_, i) => placed[i] || null);
1335
+ return sortedWithByes;
1336
+ }
1337
+ exports.sortSeeding = sortSeeding;
1338
+ /**
1339
+ * Returns only the non null elements.
1340
+ *
1341
+ * @param array The array to process.
1342
+ */
1343
+ function getNonNull(array) {
1344
+ // Use a TS type guard to exclude null from the resulting type.
1345
+ const nonNull = array.filter((element) => element !== null);
1346
+ return nonNull;
1347
+ }
1348
+ exports.getNonNull = getNonNull;
1349
+ /**
1350
+ * Returns a list of objects which have unique values of a specific key.
1351
+ *
1352
+ * @param array The array to process.
1353
+ * @param key The key to filter by.
1354
+ */
1355
+ function uniqueBy(array, key) {
1356
+ const seen = new Set();
1357
+ return array.filter((item) => {
1358
+ const value = key(item);
1359
+ if (!value)
1360
+ return true;
1361
+ if (seen.has(value))
1362
+ return false;
1363
+ seen.add(value);
1364
+ return true;
1365
+ });
1366
+ }
1367
+ exports.uniqueBy = uniqueBy;
1368
+ /**
1369
+ * Indicates whether the loser bracket round is major.
1370
+ *
1371
+ * @param roundNumber Number of the round.
1372
+ */
1373
+ function isMajorRound(roundNumber) {
1374
+ return roundNumber % 2 === 1;
1375
+ }
1376
+ exports.isMajorRound = isMajorRound;
1377
+ /**
1378
+ * Indicates whether the loser bracket round is minor.
1379
+ *
1380
+ * @param roundNumber Number of the round.
1381
+ */
1382
+ function isMinorRound(roundNumber) {
1383
+ return !isMajorRound(roundNumber);
1384
+ }
1385
+ exports.isMinorRound = isMinorRound;
1386
+ /**
1387
+ * Makes the transition to a major round for duels of the previous round. The duel count is divided by 2.
1388
+ *
1389
+ * @param previousDuels The previous duels to transition from.
1390
+ */
1391
+ function transitionToMajor(previousDuels) {
1392
+ const currentDuelCount = previousDuels.length / 2;
1393
+ const currentDuels = [];
1394
+ for (let duelIndex = 0; duelIndex < currentDuelCount; duelIndex++) {
1395
+ const prevDuelId = duelIndex * 2;
1396
+ currentDuels.push([
1397
+ byeWinner(previousDuels[prevDuelId]),
1398
+ byeWinner(previousDuels[prevDuelId + 1]),
1399
+ ]);
1400
+ }
1401
+ return currentDuels;
1402
+ }
1403
+ exports.transitionToMajor = transitionToMajor;
1404
+ /**
1405
+ * Makes the transition to a minor round for duels of the previous round. The duel count stays the same.
1406
+ *
1407
+ * @param previousDuels The previous duels to transition from.
1408
+ * @param losers Losers from the previous major round.
1409
+ * @param method The ordering method for the losers.
1410
+ */
1411
+ function transitionToMinor(previousDuels, losers, method) {
1412
+ const orderedLosers = method ? ordering_1.ordering[method](losers) : losers;
1413
+ const currentDuelCount = previousDuels.length;
1414
+ const currentDuels = [];
1415
+ for (let duelIndex = 0; duelIndex < currentDuelCount; duelIndex++) {
1416
+ const prevDuelId = duelIndex;
1417
+ currentDuels.push([
1418
+ orderedLosers[prevDuelId],
1419
+ byeWinner(previousDuels[prevDuelId]),
1420
+ ]);
1421
+ }
1422
+ return currentDuels;
1423
+ }
1424
+ exports.transitionToMinor = transitionToMinor;
1425
+ /**
1426
+ * Sets the parent match to a completed status if all its child games are completed.
1427
+ *
1428
+ * @param parent The partial parent match to update.
1429
+ * @param childCount Child count of this parent match.
1430
+ * @param inRoundRobin Indicates whether the parent match is in a round-robin stage.
1431
+ */
1432
+ function setParentMatchCompleted(parent, childCount, inRoundRobin) {
1433
+ var _a, _b;
1434
+ if (((_a = parent.opponent1) === null || _a === void 0 ? void 0 : _a.score) === undefined ||
1435
+ ((_b = parent.opponent2) === null || _b === void 0 ? void 0 : _b.score) === undefined)
1436
+ throw Error('Either opponent1, opponent2 or their scores are falsy.');
1437
+ const minToWin = minScoreToWinBestOfX(childCount);
1438
+ if (parent.opponent1.score >= minToWin) {
1439
+ parent.opponent1.result = 'win';
1440
+ return;
1441
+ }
1442
+ if (parent.opponent2.score >= minToWin) {
1443
+ parent.opponent2.result = 'win';
1444
+ return;
1445
+ }
1446
+ if (parent.opponent1.score === parent.opponent2.score &&
1447
+ parent.opponent1.score + parent.opponent2.score > childCount - 1) {
1448
+ if (inRoundRobin) {
1449
+ parent.opponent1.result = 'draw';
1450
+ parent.opponent2.result = 'draw';
1451
+ return;
1452
+ }
1453
+ throw Error('Match games result in a tie for the parent match.');
1454
+ }
1455
+ }
1456
+ exports.setParentMatchCompleted = setParentMatchCompleted;
1457
+ /**
1458
+ * Returns a parent match results based on its child games scores.
1459
+ *
1460
+ * @param storedParent The parent match stored in the database.
1461
+ * @param scores The scores of the match child games.
1462
+ */
1463
+ function getParentMatchResults(storedParent, scores) {
1464
+ return {
1465
+ opponent1: {
1466
+ id: storedParent.opponent1 && storedParent.opponent1.id,
1467
+ score: scores.opponent1,
1468
+ },
1469
+ opponent2: {
1470
+ id: storedParent.opponent2 && storedParent.opponent2.id,
1471
+ score: scores.opponent2,
1472
+ },
1473
+ };
1474
+ }
1475
+ exports.getParentMatchResults = getParentMatchResults;
1476
+ /**
1477
+ * Gets the values which need to be updated in a match when it's updated on insertion.
1478
+ *
1479
+ * @param match The up to date match.
1480
+ * @param existing The base match.
1481
+ * @param enableByes Whether to use BYEs or TBDs for `null` values in an input seeding.
1482
+ */
1483
+ function getUpdatedMatchResults(match, existing, enableByes) {
1484
+ return {
1485
+ ...existing,
1486
+ ...match,
1487
+ ...(enableByes
1488
+ ? {
1489
+ opponent1: match.opponent1 === null
1490
+ ? null
1491
+ : { ...existing.opponent1, ...match.opponent1 },
1492
+ opponent2: match.opponent2 === null
1493
+ ? null
1494
+ : { ...existing.opponent2, ...match.opponent2 },
1495
+ }
1496
+ : {
1497
+ opponent1: match.opponent1 === null
1498
+ ? { id: null }
1499
+ : { ...existing.opponent1, ...match.opponent1 },
1500
+ opponent2: match.opponent2 === null
1501
+ ? { id: null }
1502
+ : { ...existing.opponent2, ...match.opponent2 },
1503
+ }),
1504
+ };
1505
+ }
1506
+ exports.getUpdatedMatchResults = getUpdatedMatchResults;
1507
+ /**
1508
+ * Calculates the score of a parent match based on its child games.
1509
+ *
1510
+ * @param games The child games to process.
1511
+ */
1512
+ function getChildGamesResults(games) {
1513
+ const scores = {
1514
+ opponent1: 0,
1515
+ opponent2: 0,
1516
+ };
1517
+ for (const game of games) {
1518
+ const result = getMatchResult(game);
1519
+ if (result === 'opponent1')
1520
+ scores.opponent1++;
1521
+ else if (result === 'opponent2')
1522
+ scores.opponent2++;
1523
+ }
1524
+ return scores;
1525
+ }
1526
+ exports.getChildGamesResults = getChildGamesResults;
1527
+ /**
1528
+ * Gets the default list of seeds for a round's matches.
1529
+ *
1530
+ * @param inLoserBracket Whether the match is in the loser bracket.
1531
+ * @param roundNumber The number of the current round.
1532
+ * @param roundCountLB The count of rounds in loser bracket.
1533
+ * @param matchCount The count of matches in the round.
1534
+ */
1535
+ function getSeeds(inLoserBracket, roundNumber, roundCountLB, matchCount) {
1536
+ const seedCount = getSeedCount(inLoserBracket, roundNumber, roundCountLB, matchCount);
1537
+ return Array.from({ length: seedCount }, (_, i) => i + 1);
1538
+ }
1539
+ exports.getSeeds = getSeeds;
1540
+ /**
1541
+ * Gets the number of seeds for a round's matches.
1542
+ *
1543
+ * @param inLoserBracket Whether the match is in the loser bracket.
1544
+ * @param roundNumber The number of the current round.
1545
+ * @param roundCountLB The count of rounds in loser bracket.
1546
+ * @param matchCount The count of matches in the round.
1547
+ */
1548
+ function getSeedCount(inLoserBracket, roundNumber, roundCountLB, matchCount) {
1549
+ ensureOrderingSupported(inLoserBracket, roundNumber, roundCountLB);
1550
+ return roundNumber === 1
1551
+ ? matchCount * 2 // Two per match for upper or lower bracket round 1.
1552
+ : matchCount; // One per match for loser bracket minor rounds.
1553
+ }
1554
+ exports.getSeedCount = getSeedCount;
1555
+ /**
1556
+ * Throws if the ordering is not supported on the given round number.
1557
+ *
1558
+ * @param inLoserBracket Whether the match is in the loser bracket.
1559
+ * @param roundNumber The number of the round.
1560
+ * @param roundCountLB The count of rounds in loser bracket.
1561
+ */
1562
+ function ensureOrderingSupported(inLoserBracket, roundNumber, roundCountLB) {
1563
+ if (inLoserBracket &&
1564
+ !isOrderingSupportedLoserBracket(roundNumber, roundCountLB))
1565
+ throw Error('This round does not support ordering.');
1566
+ if (!inLoserBracket && !isOrderingSupportedUpperBracket(roundNumber))
1567
+ throw Error('This round does not support ordering.');
1568
+ }
1569
+ exports.ensureOrderingSupported = ensureOrderingSupported;
1570
+ /**
1571
+ * Indicates whether the ordering is supported in upper bracket, given the round number.
1572
+ *
1573
+ * @param roundNumber The number of the round.
1574
+ */
1575
+ function isOrderingSupportedUpperBracket(roundNumber) {
1576
+ return roundNumber === 1;
1577
+ }
1578
+ exports.isOrderingSupportedUpperBracket = isOrderingSupportedUpperBracket;
1579
+ /**
1580
+ * Indicates whether the ordering is supported in loser bracket, given the round number.
1581
+ *
1582
+ * @param roundNumber The number of the round.
1583
+ * @param roundCount The count of rounds.
1584
+ */
1585
+ function isOrderingSupportedLoserBracket(roundNumber, roundCount) {
1586
+ return (roundNumber === 1 ||
1587
+ (isMinorRound(roundNumber) && roundNumber < roundCount));
1588
+ }
1589
+ exports.isOrderingSupportedLoserBracket = isOrderingSupportedLoserBracket;
1590
+ /**
1591
+ * Returns the number of rounds an upper bracket has given the number of participants in the stage.
1592
+ *
1593
+ * @param participantCount The number of participants in the stage.
1594
+ */
1595
+ function getUpperBracketRoundCount(participantCount) {
1596
+ return Math.log2(participantCount);
1597
+ }
1598
+ exports.getUpperBracketRoundCount = getUpperBracketRoundCount;
1599
+ /**
1600
+ * Returns the count of round pairs (major & minor) in a loser bracket.
1601
+ *
1602
+ * @param participantCount The number of participants in the stage.
1603
+ */
1604
+ function getRoundPairCount(participantCount) {
1605
+ return getUpperBracketRoundCount(participantCount) - 1;
1606
+ }
1607
+ exports.getRoundPairCount = getRoundPairCount;
1608
+ /**
1609
+ * Determines whether a double elimination stage is really necessary.
1610
+ *
1611
+ * If the size is only two (less is impossible), then a lower bracket and a grand final are not necessary.
1612
+ *
1613
+ * @param participantCount The number of participants in the stage.
1614
+ */
1615
+ function isDoubleEliminationNecessary(participantCount) {
1616
+ return participantCount > 2;
1617
+ }
1618
+ exports.isDoubleEliminationNecessary = isDoubleEliminationNecessary;
1619
+ /**
1620
+ * Returns the real (because of loser ordering) number of a match in a loser bracket.
1621
+ *
1622
+ * @param participantCount The number of participants in a stage.
1623
+ * @param roundNumber Number of the round.
1624
+ * @param matchNumber Number of the match.
1625
+ * @param method The method used for the round.
1626
+ */
1627
+ function findLoserMatchNumber(participantCount, roundNumber, matchNumber, method) {
1628
+ const loserCount = getLoserCountFromWbForLbRound(participantCount, roundNumber);
1629
+ const losers = Array.from({ length: loserCount }, (_, i) => i + 1);
1630
+ const ordered = method ? ordering_1.ordering[method](losers) : losers;
1631
+ const matchNumberLB = ordered.indexOf(matchNumber) + 1;
1632
+ // For LB round 1, the list of losers is spread over the matches 2 by 2.
1633
+ if (roundNumber === 1)
1634
+ return Math.ceil(matchNumberLB / 2);
1635
+ return matchNumberLB;
1636
+ }
1637
+ exports.findLoserMatchNumber = findLoserMatchNumber;
1638
+ /**
1639
+ * Returns the count of matches in a round of a loser bracket.
1640
+ *
1641
+ * @param participantCount The number of participants in a stage.
1642
+ * @param roundNumber Number of the round.
1643
+ */
1644
+ function getLoserRoundMatchCount(participantCount, roundNumber) {
1645
+ const roundPairIndex = Math.ceil(roundNumber / 2) - 1;
1646
+ const roundPairCount = getRoundPairCount(participantCount);
1647
+ const matchCount = Math.pow(2, roundPairCount - roundPairIndex - 1);
1648
+ if (roundNumber === 0)
1649
+ throw Error('Round number must start at 1.');
1650
+ if (matchCount < 1) {
1651
+ throw Error(`Round number ${roundNumber} is too big for a loser bracket in a stage of ${participantCount} participants.`);
1652
+ }
1653
+ return matchCount;
1654
+ }
1655
+ exports.getLoserRoundMatchCount = getLoserRoundMatchCount;
1656
+ /**
1657
+ * Returns the count of losers coming from the winner bracket in a round of loser bracket.
1658
+ *
1659
+ * @param participantCount The number of participants in the stage.
1660
+ * @param roundNumber Number of the round.
1661
+ */
1662
+ function getLoserCountFromWbForLbRound(participantCount, roundNumber) {
1663
+ const matchCount = getLoserRoundMatchCount(participantCount, roundNumber);
1664
+ // Two per match for LB round 1 (losers coming from WB round 1).
1665
+ if (roundNumber === 1)
1666
+ return matchCount * 2;
1667
+ return matchCount; // One per match for LB minor rounds.
1668
+ }
1669
+ exports.getLoserCountFromWbForLbRound = getLoserCountFromWbForLbRound;
1670
+ /**
1671
+ * Returns the ordering method of a round of a loser bracket.
1672
+ *
1673
+ * @param seedOrdering The list of seed orderings.
1674
+ * @param roundNumber Number of the round.
1675
+ */
1676
+ function getLoserOrdering(seedOrdering, roundNumber) {
1677
+ const orderingIndex = 1 + Math.floor(roundNumber / 2);
1678
+ return seedOrdering[orderingIndex];
1679
+ }
1680
+ exports.getLoserOrdering = getLoserOrdering;
1681
+ /**
1682
+ * Returns the number of rounds a lower bracket has given the number of participants in a double elimination stage.
1683
+ *
1684
+ * @param participantCount The number of participants in the stage.
1685
+ */
1686
+ function getLowerBracketRoundCount(participantCount) {
1687
+ const roundPairCount = getRoundPairCount(participantCount);
1688
+ return roundPairCount * 2;
1689
+ }
1690
+ exports.getLowerBracketRoundCount = getLowerBracketRoundCount;
1691
+ /**
1692
+ * Returns the match number of the corresponding match in the next round by dividing by two.
1693
+ *
1694
+ * @param matchNumber The current match number.
1695
+ */
1696
+ function getDiagonalMatchNumber(matchNumber) {
1697
+ return Math.ceil(matchNumber / 2);
1698
+ }
1699
+ exports.getDiagonalMatchNumber = getDiagonalMatchNumber;
1700
+ /**
1701
+ * Returns the nearest power of two **greater than** or equal to the given number.
1702
+ *
1703
+ * @param input The input number.
1704
+ */
1705
+ function getNearestPowerOfTwo(input) {
1706
+ return Math.pow(2, Math.ceil(Math.log2(input)));
1707
+ }
1708
+ exports.getNearestPowerOfTwo = getNearestPowerOfTwo;
1709
+ /**
1710
+ * Returns the minimum score a participant must have to win a Best Of X series match.
1711
+ *
1712
+ * @param x The count of child games in the series.
1713
+ */
1714
+ function minScoreToWinBestOfX(x) {
1715
+ return (x + 1) / 2;
1716
+ }
1717
+ exports.minScoreToWinBestOfX = minScoreToWinBestOfX;
1718
+ /**
1719
+ * Checks if a stage is a round-robin stage.
1720
+ *
1721
+ * @param stage The stage to check.
1722
+ */
1723
+ function isRoundRobin(stage) {
1724
+ return stage.type === 'round_robin';
1725
+ }
1726
+ exports.isRoundRobin = isRoundRobin;
1727
+ /**
1728
+ * Throws if a stage is round-robin.
1729
+ *
1730
+ * @param stage The stage to check.
1731
+ */
1732
+ function ensureNotRoundRobin(stage) {
1733
+ const inRoundRobin = isRoundRobin(stage);
1734
+ if (inRoundRobin)
1735
+ throw Error('Impossible to update ordering in a round-robin stage.');
1736
+ }
1737
+ exports.ensureNotRoundRobin = ensureNotRoundRobin;
1738
+ // TODO: delete this helper in a future release.
1739
+ /**
1740
+ * Checks if a round is completed based on its matches.
1741
+ *
1742
+ * @param roundMatches Matches of the round.
1743
+ * @deprecated This is both functionally and semantically incorrect because:
1744
+ * 1. A match could be completed because of BYEs.
1745
+ * 2. You could totally give a list of matches from different rounds to this function, and it wouldn't complain
1746
+ * although the result will **not** tell you whether a _round_ is completed.
1747
+ *
1748
+ * Please do something like `matches.every(m => isMatchCompleted(m))` instead.
1749
+ */
1750
+ function isRoundCompleted(roundMatches) {
1751
+ return roundMatches.every((match) => match.status >= brackets_model_1.Status.Completed);
1752
+ }
1753
+ exports.isRoundCompleted = isRoundCompleted;
1754
+ /**
1755
+ * Checks if a group is a winner bracket.
1756
+ *
1757
+ * It's not always the opposite of `inLoserBracket()`: it could be the only bracket of a single elimination stage.
1758
+ *
1759
+ * @param stageType Type of the stage.
1760
+ * @param groupNumber Number of the group.
1761
+ */
1762
+ function isWinnerBracket(stageType, groupNumber) {
1763
+ return stageType === 'double_elimination' && groupNumber === 1;
1764
+ }
1765
+ exports.isWinnerBracket = isWinnerBracket;
1766
+ /**
1767
+ * Checks if a group is a loser bracket.
1768
+ *
1769
+ * @param stageType Type of the stage.
1770
+ * @param groupNumber Number of the group.
1771
+ */
1772
+ function isLoserBracket(stageType, groupNumber) {
1773
+ return stageType === 'double_elimination' && groupNumber === 2;
1774
+ }
1775
+ exports.isLoserBracket = isLoserBracket;
1776
+ /**
1777
+ * Checks if a group is a final group (consolation final or grand final).
1778
+ *
1779
+ * @param stageType Type of the stage.
1780
+ * @param groupNumber Number of the group.
1781
+ */
1782
+ function isFinalGroup(stageType, groupNumber) {
1783
+ return ((stageType === 'single_elimination' && groupNumber === 2) ||
1784
+ (stageType === 'double_elimination' && groupNumber === 3));
1785
+ }
1786
+ exports.isFinalGroup = isFinalGroup;
1787
+ /**
1788
+ * Returns the type of group the match is located into.
1789
+ *
1790
+ * @param stageType Type of the stage.
1791
+ * @param groupNumber Number of the group.
1792
+ */
1793
+ function getMatchLocation(stageType, groupNumber) {
1794
+ if (isWinnerBracket(stageType, groupNumber))
1795
+ return 'winner_bracket';
1796
+ if (isLoserBracket(stageType, groupNumber))
1797
+ return 'loser_bracket';
1798
+ if (isFinalGroup(stageType, groupNumber))
1799
+ return 'final_group';
1800
+ return 'single_bracket';
1801
+ }
1802
+ exports.getMatchLocation = getMatchLocation;
1803
+ /**
1804
+ * Returns the fraction of final for the current round (e.g. `1/2` for semi finals or `1/4` for quarter finals).
1805
+ *
1806
+ * @param roundNumber Number of the current round.
1807
+ * @param roundCount Count of rounds.
1808
+ */
1809
+ function getFractionOfFinal(roundNumber, roundCount) {
1810
+ if (roundNumber > roundCount) {
1811
+ throw Error(`There are more rounds than possible. ${JSON.stringify({
1812
+ roundNumber,
1813
+ roundCount,
1814
+ })}`);
1815
+ }
1816
+ const denominator = Math.pow(2, roundCount - roundNumber);
1817
+ return 1 / denominator;
1818
+ }
1819
+ exports.getFractionOfFinal = getFractionOfFinal;
1820
+ /**
1821
+ * Calculates a ranking based on a list of matches and a formula.
1822
+ *
1823
+ * @param matches The list of matches.
1824
+ * @param formula The points formula to apply.
1825
+ */
1826
+ function getRanking(matches, formula) {
1827
+ const rankingMap = {};
1828
+ for (const match of matches) {
1829
+ updateRankingMap(rankingMap, formula, match.opponent1, match.opponent2);
1830
+ updateRankingMap(rankingMap, formula, match.opponent2, match.opponent1);
1831
+ }
1832
+ return createRanking(rankingMap);
1833
+ }
1834
+ exports.getRanking = getRanking;
1835
+ /**
1836
+ * Updates the ranking map with the results of a match.
1837
+ *
1838
+ * @param rankingMap The ranking map to edit.
1839
+ * @param formula The points formula to apply.
1840
+ * @param current The current participant.
1841
+ * @param opponent The opponent.
1842
+ */
1843
+ function updateRankingMap(rankingMap, formula, current, opponent) {
1844
+ if (!current || current.id === null)
1845
+ return;
1846
+ const item = rankingMap[current.id] || {
1847
+ rank: 0,
1848
+ id: 0,
1849
+ played: 0,
1850
+ wins: 0,
1851
+ draws: 0,
1852
+ losses: 0,
1853
+ forfeits: 0,
1854
+ scoreFor: 0,
1855
+ scoreAgainst: 0,
1856
+ scoreDifference: 0,
1857
+ points: 0,
1858
+ };
1859
+ item.id = current.id;
1860
+ if (current.forfeit || current.result)
1861
+ item.played++;
1862
+ if (current.result === 'win')
1863
+ item.wins++;
1864
+ if (current.result === 'draw')
1865
+ item.draws++;
1866
+ if (current.result === 'loss')
1867
+ item.losses++;
1868
+ if (current.forfeit)
1869
+ item.forfeits++;
1870
+ item.scoreFor += current.score || 0;
1871
+ item.scoreAgainst += (opponent && opponent.score) || 0;
1872
+ item.scoreDifference = item.scoreFor - item.scoreAgainst;
1873
+ item.points = formula(item);
1874
+ rankingMap[current.id] = item;
1875
+ }
1876
+ /**
1877
+ * Creates the final ranking based on a ranking map. (Sort + Total points)
1878
+ *
1879
+ * @param rankingMap The ranking map (object).
1880
+ */
1881
+ function createRanking(rankingMap) {
1882
+ const ranking = Object.values(rankingMap).sort((a, b) => a.points !== b.points
1883
+ ? b.points - a.points
1884
+ : a.played !== b.played
1885
+ ? b.played - a.played
1886
+ : b.scoreDifference - a.scoreDifference);
1887
+ const rank = {
1888
+ value: 0,
1889
+ lastPoints: -1,
1890
+ };
1891
+ for (const item of ranking) {
1892
+ item.rank = rank.lastPoints !== item.points ? ++rank.value : rank.value;
1893
+ rank.lastPoints = item.points;
1894
+ }
1895
+ return ranking;
1896
+ }
1897
+ //# sourceMappingURL=helpers.js.map