app/resources/qml/Components/SpeedFlowChart.js
Dorian Zedler 6d7009b183
Some checks are pending
continuous-integration/drone/push Build is running
continuous-integration/drone/pr Build is running
Chore: refactor speed flowchart (fixes #26)
2022-08-14 17:43:38 +02:00

289 lines
7.1 KiB
JavaScript

.pragma library
/**
* Function to extract results from PartiipantFromApi
* @param {ParticipantFromApi} fromApi
* @return {Result[]}
*/
function _extractResults(fromApi) {
const results = Array(7);
const existingResults = Object.keys(fromApi).filter(key =>
key.match(/result[0-9]/),
);
for (const result of existingResults) {
let roundNumber = 0;
const match = result.match(/result([0-9])/);
if (match !== undefined && match !== null) {
roundNumber = parseInt(match[1]);
}
if (roundNumber < 0) {
continue;
}
results[roundNumber] = {
rank: parseInt(fromApi[`result_rank${roundNumber}`] as string),
result: fromApi[`result${roundNumber}`] as string,
};
}
return results;
}
/**
* Function to clean up participants from the api
* @param {ParticipantFromApi} fromApi
* @return {Participant}
*/
function participantFromApiParticipant(
fromApi,
) {
const results = _extractResults(fromApi);
return {
id: fromApi.PerId,
firstName: fromApi.firstname,
lastName: fromApi.lastname,
results: results,
overallRank: fromApi.result_rank,
startNumber: parseInt(fromApi.start_number),
};
}
/**
*
* @param {number} roundNumber
* @param {RouteNames} routeNames
* @return {string | undefined}
*/
function getRoundName(
roundNumber,
routeNames,
) {
if (roundNumber < 2 || roundNumber > 6) return undefined;
return routeNames[roundNumber];
}
/**
*
* @param {string} name
* @return {number}
*/
function getRoundRank(name) {
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, roundIndex) {
if (
!(pair.laneA && pair.laneA.participant.results[roundIndex] && pair.laneA.participant.results[roundIndex].rank) ||
!(pair.laneB && pair.laneB.participant.results[roundIndex] && pair.laneB.participant.results[roundIndex].rank)
)
return;
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,
roundNumber,
) {
computeWinnerOfPair(pair, roundNumber);
return {
['A']: pair.laneA ? pair.laneA.participant : undefined,
['B']: pair.laneB ? pair.laneB.participant : undefined,
['']: undefined,
}[pair.winner ? pair.winner:''];
}
/**
*
* @param {SpeedRoundPair} pair
* @param {number} roundNumber
* @return {Participant | undefined}
*/
function getLooserOfPair(
pair,
roundNumber,
) {
computeWinnerOfPair(pair, roundNumber);
return {
['A']: pair.laneB ? pair.laneB.participant : undefined,
['B']: pair.laneA ? pair.laneA.participant : undefined,
['']: undefined,
}[pair.winner ? 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,
roundName,
previousRound,
roundRank,
takeLooser = false,
) {
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}
*/
function convertResultsToSpeedFlowchartResult(
result,
) {
const rounds = [];
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 tmpRoundName = getRoundName(roundIndices[0], result.route_names);
const firstRoundRank = getRoundRank(
tmpRoundName ? tmpRoundName:'',
);
const getOpponent = (ofRank) => {
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],
][Math.floor(firstRoundRank / 4)];
const firstRoundPairs = ranksOfLaneAInOrder.map(rank => {
return {
laneA: { participant: convertedParticipants[rank - 1] },
laneB: { participant: getOpponent(rank - 1) },
};
});
const firstRound = {
pairs: firstRoundPairs,
roundIndex: roundIndices[0],
roundName: firstRoundName,
};
rounds.push(firstRound);
// compute following rounds
let roundIndex = roundIndices[1];
for (let roundRank = firstRoundRank; roundRank > 2; roundRank /= 2) {
rounds.push(
computeRoundFromPreviousRound(
roundIndex,
result.route_names[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] ? 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] ? result.route_names[finalRoundIndex]:'',
rounds[rounds.length - 2],
2,
);
computeWinnerOfPair(final.pairs[0], finalRoundIndex);
rounds.push(final);
return {
rounds: rounds,
};
}