import { RouteNames, SpeedCompetitionCategoryResult, } from '../models/Competition'; import { Participant, participantFromApiParticipant, Result, } from '../models/Participant'; export interface SpeedRoundParticipant { id: string; firstName: string; hasWon: boolean; } export interface SpeedLane { result?: Result; participant: Participant; } export interface SpeedRoundPair { laneA?: SpeedLane; laneB?: SpeedLane; winner?: 'A' | 'B'; } export interface SpeedRound { pairs: SpeedRoundPair[]; roundIndex: number; roundName?: string; } export interface SpeedFlowchartResult { rounds: SpeedRound[]; } /** * * @param {number} roundNumber * @param {RouteNames} routeNames * @return {string | undefined} */ function getRoundName( roundNumber: number, routeNames: RouteNames, ): string | undefined { if (roundNumber < 2 || roundNumber > 6) return undefined; return routeNames[roundNumber]; } /** * * @param {string} name * @return {number} */ function getRoundRank(name: string): number { const match = name.match(/1\/([842])/); if (match === undefined || match === null || match.length !== 2) { return 2; } return parseInt(match[1]); } /** * * @param {SpeedRoundPair} pair * @param {number} roundIndex */ function computeWinnerOfPair(pair: SpeedRoundPair, roundIndex: number) { if ( pair.laneA?.participant.results[roundIndex]?.rank === undefined || pair.laneB?.participant.results[roundIndex]?.rank === undefined ) return; console.log('Conputing winner of round ', roundIndex); pair.laneA.result = pair.laneA.participant.results[roundIndex]; pair.laneB.result = pair.laneB.participant.results[roundIndex]; if (pair.winner === undefined) { pair.winner = pair.laneA.participant.results[roundIndex].rank > pair.laneB.participant.results[roundIndex].rank ? 'B' : 'A'; } } /** * * @param {SpeedRoundPair} pair * @param {number} roundNumber * @return {Participant | undefined} */ function getWinnerOfPair( pair: SpeedRoundPair, roundNumber: number, ): Participant | undefined { computeWinnerOfPair(pair, roundNumber); return { ['A']: pair.laneA?.participant, ['B']: pair.laneB?.participant, ['']: undefined, }[pair.winner ?? '']; } /** * * @param {SpeedRoundPair} pair * @param {number} roundNumber * @return {Participant | undefined} */ function getLooserOfPair( pair: SpeedRoundPair, roundNumber: number, ): Participant | undefined { computeWinnerOfPair(pair, roundNumber); return { ['A']: pair.laneB?.participant, ['B']: pair.laneA?.participant, ['']: undefined, }[pair.winner ?? '']; } /** * * @param {number} roundIndex index of the new round * @param {string} roundName name of the new round * @param {SpeedRound} previousRound * @param {number} roundRank * @param {boolean} takeLooser * @return {SpeedRound} */ function computeRoundFromPreviousRound( roundIndex: number, roundName: string, previousRound: SpeedRound, roundRank: number, takeLooser = false, ): SpeedRound { const getAdvancingParticipant = takeLooser ? getLooserOfPair : getWinnerOfPair; const nextRoundPairs = new Array(roundRank / 2).fill(0).map((_, i) => { const laneAParticipant = getAdvancingParticipant( previousRound.pairs[i * 2], previousRound.roundIndex, ); const laneBParticipant = getAdvancingParticipant( previousRound.pairs[i * 2 + 1], previousRound.roundIndex, ); return { laneA: laneAParticipant === undefined ? undefined : { participant: laneAParticipant, }, laneB: laneBParticipant === undefined ? undefined : { participant: laneBParticipant, }, }; }); return { pairs: nextRoundPairs, roundIndex: roundIndex, roundName: roundName, }; } /** * * @param {SpeedCompetitionCategoryResult} result The result to process * @return {SpeedFlowchartResult} */ export function convertResultsToSpeedFlowchartResult( result: SpeedCompetitionCategoryResult, ): SpeedFlowchartResult { const rounds: SpeedRound[] = []; const convertedParticipants = result.participants .map(fromApi => participantFromApiParticipant(fromApi)) // sort by qualification result .sort((a, b) => a.results[0].rank - b.results[0].rank); const roundIndices = Object.keys(result.route_names) .map(number => parseInt(number)) .filter(number => number > 0); // process first round const firstRoundName = getRoundName(roundIndices[0], result.route_names); const firstRoundRank = getRoundRank( getRoundName(roundIndices[0], result.route_names) ?? '', ); const getOpponent = (ofRank: number): Participant => { return convertedParticipants[firstRoundRank * 2 - 1 - ofRank]; }; // Should be: // 0, 1, 2, 3, 4, 5, 6, 7 // - 1,16, 8, 9, 4,13, 5,12, 2,15, 7,10, 3,14, 6,11 // - 1, 8, 4, 5, 2, 7, 3, 6 for firstRoundNumber=8 // - 1, 4, 2, 3 for firstRoundNumber=4 // - 1, 2 for firstRoundNumber=2 // TODO: come up with a proper alogorithm maybe const ranksOfLaneAInOrder = [ [1, 2], [1, 4, 2, 3], [1, 8, 4, 5, 2, 7, 3, 6], ][firstRoundRank / 4]; const firstRoundPairs = ranksOfLaneAInOrder.map(rank => { return { laneA: { participant: convertedParticipants[rank - 1] }, laneB: { participant: getOpponent(rank - 1) }, }; }); const firstRound: SpeedRound = { pairs: firstRoundPairs, roundIndex: roundIndices[0], roundName: firstRoundName, }; rounds.push(firstRound); console.log(firstRound); // compute following rounds let roundIndex = roundIndices[1]; for (let roundRank = firstRoundRank; roundRank > 2; roundRank /= 2) { console.log('Processing rank', roundRank); rounds.push( computeRoundFromPreviousRound( roundIndex, result.route_names[roundIndex] ?? '', rounds[rounds.length - 1], roundRank, ), ); roundIndex++; } // compute final and semi final const semifinalRoundIndex = roundIndex++; const semifinal = computeRoundFromPreviousRound( semifinalRoundIndex, result.route_names[semifinalRoundIndex] ?? '', rounds[rounds.length - 1], 2, true, ); computeWinnerOfPair(semifinal.pairs[0], semifinalRoundIndex); rounds.push(semifinal); const finalRoundIndex = roundIndex; const final = computeRoundFromPreviousRound( finalRoundIndex, result.route_names[finalRoundIndex] ?? '', rounds[rounds.length - 2], 2, ); computeWinnerOfPair(final.pairs[0], finalRoundIndex); rounds.push(final); return { rounds: rounds, }; }