149 lines
4.4 KiB
TypeScript
149 lines
4.4 KiB
TypeScript
import type { ChampionshipConfig } from '@gridpilot/racing/domain/value-objects/ChampionshipConfig';
|
|
import type { BonusRule } from '@gridpilot/racing/domain/value-objects/BonusRule';
|
|
import type {
|
|
ILeagueScoringConfigPresenter,
|
|
LeagueScoringConfigData,
|
|
LeagueScoringConfigViewModel,
|
|
LeagueScoringChampionshipViewModel,
|
|
} from '@gridpilot/racing/application/presenters/ILeagueScoringConfigPresenter';
|
|
|
|
export class LeagueScoringConfigPresenter implements ILeagueScoringConfigPresenter {
|
|
private viewModel: LeagueScoringConfigViewModel | null = null;
|
|
|
|
present(data: LeagueScoringConfigData): LeagueScoringConfigViewModel {
|
|
const championships: LeagueScoringChampionshipViewModel[] =
|
|
data.championships.map((champ) => this.mapChampionship(champ));
|
|
|
|
const dropPolicySummary =
|
|
data.preset?.dropPolicySummary ??
|
|
this.deriveDropPolicyDescriptionFromChampionships(data.championships);
|
|
|
|
this.viewModel = {
|
|
leagueId: data.leagueId,
|
|
seasonId: data.seasonId,
|
|
gameId: data.gameId,
|
|
gameName: data.gameName,
|
|
scoringPresetId: data.scoringPresetId,
|
|
scoringPresetName: data.preset?.name,
|
|
dropPolicySummary,
|
|
championships,
|
|
};
|
|
|
|
return this.viewModel;
|
|
}
|
|
|
|
getViewModel(): LeagueScoringConfigViewModel {
|
|
if (!this.viewModel) {
|
|
throw new Error('Presenter has not been called yet');
|
|
}
|
|
return this.viewModel;
|
|
}
|
|
|
|
private mapChampionship(championship: ChampionshipConfig): LeagueScoringChampionshipViewModel {
|
|
const sessionTypes = championship.sessionTypes.map((s) => s.toString());
|
|
const pointsPreview = this.buildPointsPreview(championship.pointsTableBySessionType);
|
|
const bonusSummary = this.buildBonusSummary(
|
|
championship.bonusRulesBySessionType ?? {},
|
|
);
|
|
const dropPolicyDescription = this.deriveDropPolicyDescription(
|
|
championship.dropScorePolicy,
|
|
);
|
|
|
|
return {
|
|
id: championship.id,
|
|
name: championship.name,
|
|
type: championship.type,
|
|
sessionTypes,
|
|
pointsPreview,
|
|
bonusSummary,
|
|
dropPolicyDescription,
|
|
};
|
|
}
|
|
|
|
private buildPointsPreview(
|
|
tables: Record<string, any>,
|
|
): Array<{ sessionType: string; position: number; points: number }> {
|
|
const preview: Array<{
|
|
sessionType: string;
|
|
position: number;
|
|
points: number;
|
|
}> = [];
|
|
|
|
const maxPositions = 10;
|
|
|
|
for (const [sessionType, table] of Object.entries(tables)) {
|
|
for (let pos = 1; pos <= maxPositions; pos++) {
|
|
const points = table.getPointsForPosition(pos);
|
|
if (points && points !== 0) {
|
|
preview.push({
|
|
sessionType,
|
|
position: pos,
|
|
points,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return preview;
|
|
}
|
|
|
|
private buildBonusSummary(
|
|
bonusRulesBySessionType: Record<string, BonusRule[]>,
|
|
): string[] {
|
|
const summaries: string[] = [];
|
|
|
|
for (const [sessionType, rules] of Object.entries(bonusRulesBySessionType)) {
|
|
for (const rule of rules) {
|
|
if (rule.type === 'fastestLap') {
|
|
const base = `Fastest lap in ${sessionType}`;
|
|
if (rule.requiresFinishInTopN) {
|
|
summaries.push(
|
|
`${base} +${rule.points} points if finishing P${rule.requiresFinishInTopN} or better`,
|
|
);
|
|
} else {
|
|
summaries.push(`${base} +${rule.points} points`);
|
|
}
|
|
} else {
|
|
summaries.push(
|
|
`${rule.type} bonus in ${sessionType} worth ${rule.points} points`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
return summaries;
|
|
}
|
|
|
|
private deriveDropPolicyDescriptionFromChampionships(
|
|
championships: ChampionshipConfig[],
|
|
): string {
|
|
const first = championships[0];
|
|
if (!first) {
|
|
return 'All results count';
|
|
}
|
|
return this.deriveDropPolicyDescription(first.dropScorePolicy);
|
|
}
|
|
|
|
private deriveDropPolicyDescription(policy: {
|
|
strategy: string;
|
|
count?: number;
|
|
dropCount?: number;
|
|
}): string {
|
|
if (!policy || policy.strategy === 'none') {
|
|
return 'All results count';
|
|
}
|
|
|
|
if (policy.strategy === 'bestNResults' && typeof policy.count === 'number') {
|
|
return `Best ${policy.count} results count towards the championship`;
|
|
}
|
|
|
|
if (
|
|
policy.strategy === 'dropWorstN' &&
|
|
typeof policy.dropCount === 'number'
|
|
) {
|
|
return `Worst ${policy.dropCount} results are dropped from the championship total`;
|
|
}
|
|
|
|
return 'Custom drop score rules apply';
|
|
}
|
|
} |