From 0069cae9b2b55cc406d80fedbec34783865fe14a Mon Sep 17 00:00:00 2001 From: corentin Date: Tue, 8 Aug 2023 00:22:08 +0200 Subject: [PATCH] Fix bug with `get.nextMatches()` for winner of WB final --- src/find.ts | 78 ++++++++++++++++++++++++++++++++++++++++------- src/helpers.ts | 11 +++++++ test/find.spec.js | 9 ++++++ 3 files changed, 87 insertions(+), 11 deletions(-) diff --git a/src/find.ts b/src/find.ts index 66bfcf5..f9367d0 100644 --- a/src/find.ts +++ b/src/find.ts @@ -105,27 +105,42 @@ export class Find extends BaseGetter { ); if (participantId !== undefined) { + if (!helpers.isParticipantInMatch(match, participantId)) + throw Error('The participant does not belong to this match.'); + + if (!helpers.isMatchStale(match)) + throw Error('The match is not stale yet, so it is not possible to conclude the next matches for this participant.'); + const loser = helpers.getLoser(match); if (stage.type === 'single_elimination' && loser?.id === participantId) return []; // Eliminated. if (stage.type === 'double_elimination') { - const [upperBracketMatch, lowerBracketMatch] = nextMatches; + const { winnerBracketMatch, loserBracketMatch, finalGroupMatch } = await this.getMatchesByGroupDoubleElimination(nextMatches, new Map([[group.id, group]])); + const winner = helpers.getWinner(match); - if (loser?.id === participantId) { - if (lowerBracketMatch) - return [lowerBracketMatch]; - else + if (matchLocation === 'loser_bracket') { + if (participantId === loser?.id) return []; // Eliminated from lower bracket. - } - const winner = helpers.getWinner(match); - if (winner?.id === participantId) - return [upperBracketMatch]; + if (participantId === winner?.id) + return loserBracketMatch ? [loserBracketMatch] : []; + } else if (matchLocation === 'winner_bracket') { + if (!loserBracketMatch) + throw Error('All matches of winner bracket should lead to loser bracket.'); - throw Error('The participant does not belong to this match.'); - } + if (participantId === loser?.id) + return [loserBracketMatch]; // Eliminated from upper bracket, going to lower bracket. + + if (participantId === winner?.id) + return winnerBracketMatch ? [winnerBracketMatch] : []; + } else if (matchLocation === 'final_group') { + if (!finalGroupMatch) + throw Error('All matches of a final group should also lead to the final group.'); + return [finalGroupMatch]; + } + } } return nextMatches; @@ -152,4 +167,45 @@ export class Find extends BaseGetter { public async matchGame(game: Partial): Promise { return this.findMatchGame(game); } + + /** + * Returns an object with 1 match per group type. Only supports double elimination. + * + * @param matches A list of matches. + * @param fetchedGroups A map of groups which were already fetched. + */ + private async getMatchesByGroupDoubleElimination(matches: Match[], fetchedGroups: Map): Promise<{ + winnerBracketMatch?: Match; + loserBracketMatch?: Match; + finalGroupMatch?: Match; + }> { + const getGroup = async (groupId: Id): Promise => { + const existing = fetchedGroups.get(groupId); + if (existing) + return existing; + + const group = await this.storage.select('group', groupId); + if (!group) throw Error('Group not found.'); + fetchedGroups.set(groupId, group); + return group; + }; + + let matchByGroupType: { + winnerBracketMatch?: Match + loserBracketMatch?: Match + finalGroupMatch?: Match + } = {}; + + for (const match of matches) { + const group = await getGroup(match.group_id); + + matchByGroupType = { + winnerBracketMatch: matchByGroupType['winnerBracketMatch'] ?? (helpers.isWinnerBracket('double_elimination', group.number) ? match : undefined), + loserBracketMatch: matchByGroupType['loserBracketMatch'] ?? (helpers.isLoserBracket('double_elimination', group.number) ? match : undefined), + finalGroupMatch: matchByGroupType['finalGroupMatch'] ?? (helpers.isFinalGroup('double_elimination', group.number) ? match : undefined), + }; + } + + return matchByGroupType; + } } diff --git a/src/helpers.ts b/src/helpers.ts index 2d14e69..c4feac5 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -621,6 +621,17 @@ export function isMatchOngoing(match: MatchResults): boolean { return [Status.Ready, Status.Running].includes(match.status); } +/** + * Checks if a match is stale (i.e. it should not change anymore). + * + * [Locked - BYE] > Waiting > Ready > Running > [Completed > Archived] + * + * @param match Partial match results. + */ +export function isMatchStale(match: MatchResults): boolean { + return match.status >= Status.Completed || isMatchByeCompleted(match); +} + /** * Checks if a match is completed because of a forfeit. * diff --git a/test/find.spec.js b/test/find.spec.js index 0cb81cb..7a298b4 100644 --- a/test/find.spec.js +++ b/test/find.spec.js @@ -232,6 +232,15 @@ describe('Find previous and next matches in double elimination', () => { assert.strictEqual(afterLowerBracketContinued.length, 1); assert.strictEqual(afterLowerBracketContinued[0].id, 4); + await assert.isRejected(manager.find.nextMatches(2, 1), 'The match is not stale yet, so it is not possible to conclude the next matches for this participant.'); + + await manager.update.match({ id: 2, opponent1: { result: 'win' } }); + const afterFinalWBForWinner = await manager.find.nextMatches(2, 1); + assert.strictEqual(afterFinalWBForWinner.length, 0); + const afterFinalWBForLoser = await manager.find.nextMatches(2, 2); + assert.strictEqual(afterFinalWBForLoser.length, 1); + assert.strictEqual(afterFinalWBForLoser[0].id, 4); + await assert.isRejected(manager.find.nextMatches(3, 42), 'The participant does not belong to this match.'); }); });