Files
gridpilot.gg/adapters/bootstrap/racing/RacingResultFactory.ts
2025-12-29 19:44:11 +01:00

99 lines
3.1 KiB
TypeScript

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);
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,
}),
);
}
}
return results;
}
private shuffleInPlace<T>(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;
};
}
}