This commit is contained in:
2025-12-17 12:05:00 +01:00
parent 4d890863d3
commit 07dfefebe4
65 changed files with 6034 additions and 778 deletions

View File

@@ -0,0 +1,239 @@
import { PointsTable } from '@core/racing/domain/value-objects/PointsTable';
import type { ChampionshipConfig } from '@core/racing/domain/types/ChampionshipConfig';
import type { SessionType } from '@core/racing/domain/types/SessionType';
import type { BonusRule } from '@core/racing/domain/types/BonusRule';
import type { DropScorePolicy } from '@core/racing/domain/types/DropScorePolicy';
import type { ChampionshipType } from '@core/racing/domain/types/ChampionshipType';
import { LeagueScoringConfig } from '@core/racing/domain/entities/LeagueScoringConfig';
export type LeagueScoringPresetPrimaryChampionshipType =
| 'driver'
| 'team'
| 'nations'
| 'trophy';
export interface LeagueScoringPreset {
id: string;
name: string;
description: string;
primaryChampionshipType: LeagueScoringPresetPrimaryChampionshipType;
dropPolicySummary: string;
sessionSummary: string;
bonusSummary: string;
createConfig: (options: { seasonId: string }) => LeagueScoringConfig;
}
const mainPointsSprintMain = new PointsTable({
1: 25,
2: 18,
3: 15,
4: 12,
5: 10,
6: 8,
7: 6,
8: 4,
9: 2,
10: 1,
});
const sprintPointsSprintMain = new PointsTable({
1: 8,
2: 7,
3: 6,
4: 5,
5: 4,
6: 3,
7: 2,
8: 1,
});
const clubMainPoints = new PointsTable({
1: 20,
2: 15,
3: 12,
4: 10,
5: 8,
6: 6,
7: 4,
8: 2,
9: 1,
});
const enduranceMainPoints = new PointsTable({
1: 50,
2: 36,
3: 30,
4: 24,
5: 20,
6: 16,
7: 12,
8: 8,
9: 4,
10: 2,
});
export const leagueScoringPresets: LeagueScoringPreset[] = [
{
id: 'sprint-main-driver',
name: 'Sprint + Main',
description:
'Short sprint race plus main race; sprint gives fewer points.',
primaryChampionshipType: 'driver',
dropPolicySummary: 'Best 6 results of 8 count towards the championship.',
sessionSummary: 'Sprint + Main',
bonusSummary: 'Fastest lap +1 point in main race if finishing P10 or better.',
createConfig: ({ seasonId }) => {
const fastestLapBonus: BonusRule = {
id: 'fastest-lap-main',
type: 'fastestLap',
points: 1,
requiresFinishInTopN: 10,
};
const sessionTypes: SessionType[] = ['sprint', 'main'];
const pointsTableBySessionType: Record<SessionType, PointsTable> = {
sprint: sprintPointsSprintMain,
main: mainPointsSprintMain,
practice: new PointsTable({}),
qualifying: new PointsTable({}),
q1: new PointsTable({}),
q2: new PointsTable({}),
q3: new PointsTable({}),
timeTrial: new PointsTable({}),
};
const bonusRulesBySessionType: Record<SessionType, BonusRule[]> = {
sprint: [],
main: [fastestLapBonus],
practice: [],
qualifying: [],
q1: [],
q2: [],
q3: [],
timeTrial: [],
};
const dropScorePolicy: DropScorePolicy = {
strategy: 'bestNResults',
count: 6,
};
const championship: ChampionshipConfig = {
id: 'driver-champ-sprint-main',
name: 'Driver Championship',
type: 'driver' as ChampionshipType,
sessionTypes,
pointsTableBySessionType,
bonusRulesBySessionType,
dropScorePolicy,
};
return LeagueScoringConfig.create({
id: `lsc-${seasonId}-sprint-main-driver`,
seasonId,
scoringPresetId: 'sprint-main-driver',
championships: [championship],
});
},
},
{
id: 'club-default',
name: 'Club ladder',
description:
'Simple club ladder with a single main race and no bonuses or drop scores.',
primaryChampionshipType: 'driver',
dropPolicySummary: 'All race results count, no drop scores.',
sessionSummary: 'Main race only',
bonusSummary: 'No bonus points.',
createConfig: ({ seasonId }) => {
const sessionTypes: SessionType[] = ['main'];
const pointsTableBySessionType: Record<SessionType, PointsTable> = {
sprint: new PointsTable({}),
main: clubMainPoints,
practice: new PointsTable({}),
qualifying: new PointsTable({}),
q1: new PointsTable({}),
q2: new PointsTable({}),
q3: new PointsTable({}),
timeTrial: new PointsTable({}),
};
const dropScorePolicy: DropScorePolicy = {
strategy: 'none',
};
const championship: ChampionshipConfig = {
id: 'driver-champ-club-default',
name: 'Driver Championship',
type: 'driver' as ChampionshipType,
sessionTypes,
pointsTableBySessionType,
dropScorePolicy,
};
return LeagueScoringConfig.create({
id: `lsc-${seasonId}-club-default`,
seasonId,
scoringPresetId: 'club-default',
championships: [championship],
});
},
},
{
id: 'endurance-main-double',
name: 'Endurance weekend',
description:
'Single main endurance race with double points and a simple drop policy.',
primaryChampionshipType: 'driver',
dropPolicySummary: 'Best 4 results of 6 count towards the championship.',
sessionSummary: 'Main race only',
bonusSummary: 'No bonus points.',
createConfig: ({ seasonId }) => {
const sessionTypes: SessionType[] = ['main'];
const pointsTableBySessionType: Record<SessionType, PointsTable> = {
sprint: new PointsTable({}),
main: enduranceMainPoints,
practice: new PointsTable({}),
qualifying: new PointsTable({}),
q1: new PointsTable({}),
q2: new PointsTable({}),
q3: new PointsTable({}),
timeTrial: new PointsTable({}),
};
const dropScorePolicy: DropScorePolicy = {
strategy: 'bestNResults',
count: 4,
};
const championship: ChampionshipConfig = {
id: 'driver-champ-endurance-main-double',
name: 'Driver Championship',
type: 'driver' as ChampionshipType,
sessionTypes,
pointsTableBySessionType,
dropScorePolicy,
};
return {
id: `lsc-${seasonId}-endurance-main-double`,
seasonId,
scoringPresetId: 'endurance-main-double',
championships: [championship],
};
},
},
];
export function listLeagueScoringPresets(): LeagueScoringPreset[] {
return [...leagueScoringPresets];
}
export function getLeagueScoringPresetById(
id: string,
): LeagueScoringPreset | undefined {
return leagueScoringPresets.find((preset) => preset.id === id);
}

View File

@@ -0,0 +1,82 @@
import { Game } from '@core/racing/domain/entities/Game';
import { Season } from '@core/racing/domain/entities/season/Season';
import type { LeagueScoringConfig } from '@core/racing/domain/entities/LeagueScoringConfig';
import { InMemoryGameRepository } from '../racing/persistence/inmemory/InMemoryScoringRepositories';
import { InMemorySeasonRepository } from '../racing/persistence/inmemory/InMemoryScoringRepositories';
import { InMemoryLeagueScoringConfigRepository } from '../racing/persistence/inmemory/InMemoryScoringRepositories';
import { InMemoryChampionshipStandingRepository } from '../racing/persistence/inmemory/InMemoryScoringRepositories';
import type { Logger } from '@core/shared/application';
import { getLeagueScoringPresetById } from './LeagueScoringPresets';
/* eslint-disable @typescript-eslint/no-unused-vars */
class SilentLogger implements Logger {
debug(...args: unknown[]): void {
// console.debug(...args);
}
info(...args: unknown[]): void {
// console.info(...args);
}
warn(...args: unknown[]): void {
// console.warn(...args);
}
error(...args: unknown[]): void {
// console.error(...args);
}
}
export function createSprintMainDemoScoringSetup(params: {
leagueId: string;
seasonId?: string;
}): {
gameRepo: InMemoryGameRepository;
seasonRepo: InMemorySeasonRepository;
scoringConfigRepo: InMemoryLeagueScoringConfigRepository;
championshipStandingRepo: InMemoryChampionshipStandingRepository;
seasonId: string;
championshipId: string;
} {
const { leagueId } = params;
const seasonId = params.seasonId ?? 'season-sprint-main-demo';
const championshipId = 'driver-champ';
const logger = new SilentLogger();
const game = Game.create({ id: 'iracing', name: 'iRacing' });
const season = Season.create({
id: seasonId,
leagueId,
gameId: game.id.toString(),
name: 'Sprint + Main Demo Season',
year: 2025,
order: 1,
status: 'active',
startDate: new Date('2025-01-01'),
endDate: new Date('2025-12-31'),
});
const preset = getLeagueScoringPresetById('sprint-main-driver');
if (!preset) {
throw new Error('Missing sprint-main-driver scoring preset');
}
const leagueScoringConfig: LeagueScoringConfig = preset.createConfig({
seasonId: season.id,
});
const gameRepo = new InMemoryGameRepository(logger, [game]);
const seasonRepo = new InMemorySeasonRepository(logger, [season]);
const scoringConfigRepo = new InMemoryLeagueScoringConfigRepository(logger, [
leagueScoringConfig,
]);
const championshipStandingRepo = new InMemoryChampionshipStandingRepository(logger);
return {
gameRepo,
seasonRepo,
scoringConfigRepo,
championshipStandingRepo,
seasonId: season.id,
championshipId,
};
}