import { Driver } from '@core/racing/domain/entities/Driver'; import { Race } from '@core/racing/domain/entities/Race'; import { Result as RaceResult } from '@core/racing/domain/entities/result/Result'; import { seedId } from './SeedIdHelper'; export class RacingResultFactory { constructor(private readonly persistence: 'postgres' | 'inmemory' = 'inmemory') {} create(drivers: Driver[], races: Race[]): RaceResult[] { const results: RaceResult[] = []; const completed = races.filter((r) => r.status.toString() === 'completed'); for (const race of completed) { if (drivers.length === 0) continue; const rng = this.mulberry32(this.hashToSeed(`${race.id}:${race.leagueId}:${race.trackId}`)); const minSize = Math.min(6, drivers.length); const maxSize = Math.min(26, drivers.length); const participantCount = Math.max( minSize, Math.min(maxSize, minSize + Math.floor(rng() * (maxSize - minSize + 1))), ); const offset = Math.floor(rng() * drivers.length); const participants = Array.from({ length: participantCount }, (_, idx) => drivers[(offset + idx) % drivers.length]!); const finishOrder = [...participants]; this.shuffleInPlace(finishOrder, rng); const gridOrder = [...participants]; this.shuffleInPlace(gridOrder, rng); const baseLap = 82_000 + Math.floor(rng() * 12_000); // 1:22.000 - 1:34.000-ish for (let idx = 0; idx < finishOrder.length; idx++) { const driver = finishOrder[idx]!; const position = idx + 1; const startPosition = gridOrder.findIndex(d => d.id.toString() === driver.id.toString()) + 1; const lapJitter = Math.floor(rng() * 900); const fastestLap = baseLap + lapJitter + (position - 1) * 35; const incidents = rng() < 0.55 ? 0 : rng() < 0.85 ? 1 : rng() < 0.95 ? 2 : 3 + Math.floor(rng() * 6); // Calculate points based on position const points = this.calculatePoints(position); results.push( RaceResult.create({ id: seedId(`${race.id}:${driver.id}`, this.persistence), raceId: race.id, driverId: driver.id, position, startPosition: Math.max(1, startPosition), fastestLap, incidents, points, }), ); } } return results; } private shuffleInPlace(items: T[], rng: () => number): void { for (let i = items.length - 1; i > 0; i--) { const j = Math.floor(rng() * (i + 1)); const tmp = items[i]!; items[i] = items[j]!; items[j] = tmp; } } private hashToSeed(input: string): number { let hash = 2166136261; for (let i = 0; i < input.length; i++) { hash ^= input.charCodeAt(i); hash = Math.imul(hash, 16777619); } return hash >>> 0; } private mulberry32(seed: number): () => number { let a = seed >>> 0; return () => { a |= 0; a = (a + 0x6D2B79F5) | 0; let t = Math.imul(a ^ (a >>> 15), 1 | a); t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; } private calculatePoints(position: number): number { // Standard F1-style points system const pointsMap: Record = { 1: 25, 2: 18, 3: 15, 4: 12, 5: 10, 6: 8, 7: 6, 8: 4, 9: 2, 10: 1, }; return pointsMap[position] || 0; } }