99 lines
3.1 KiB
TypeScript
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;
|
|
};
|
|
}
|
|
} |