137 lines
5.4 KiB
TypeScript
137 lines
5.4 KiB
TypeScript
import * as math from 'mathjs';
|
|
import { Phase, type Stat, type Surface } from './race';
|
|
|
|
export interface Race {
|
|
location: RaceLocation;
|
|
length: number;
|
|
surface: Surface;
|
|
direction: Direction;
|
|
spans: Span[];
|
|
slopes: Slope[];
|
|
thresholds: [] | [Stat] | [Stat, Stat];
|
|
}
|
|
|
|
export enum RaceLocation {
|
|
Sapporo = 'Sapporo',
|
|
Hakodate = 'Hakodate',
|
|
Niigata = 'Niigata',
|
|
Fukushima = 'Fukushima',
|
|
Nakayama = 'Nakayama',
|
|
Tokyo = 'Tokyo',
|
|
Chukyo = 'Chukyo',
|
|
Kyoto = 'Kyoto',
|
|
Hanshin = 'Hanshin',
|
|
Kokura = 'Kokura',
|
|
Ooi = 'Ooi',
|
|
Kawasaki = 'Kawasaki',
|
|
Funabashi = 'Funabashi',
|
|
Morioka = 'Morioka',
|
|
Longchamp = 'Longchamp',
|
|
SantaAnitaPark = 'Santa Anita Park',
|
|
DelMar = 'Del Mar',
|
|
}
|
|
|
|
export enum Direction {
|
|
Right,
|
|
Left,
|
|
Stretch,
|
|
}
|
|
|
|
export enum SpanType {
|
|
FrontStraight,
|
|
AcrossStraight,
|
|
FalseStraight,
|
|
Corner1,
|
|
Corner2,
|
|
Corner3,
|
|
Corner4,
|
|
}
|
|
|
|
export interface Span {
|
|
type: SpanType;
|
|
span: [number, number];
|
|
}
|
|
|
|
export interface Slope {
|
|
per: -2 | -1.5 | -1 | 1 | 1.5 | 2;
|
|
span: [number, number];
|
|
}
|
|
|
|
function sortSlopes(slopes: Slope[]): Slope[] {
|
|
return slopes.toSorted((a, b) => a.span[0] - b.span[0]);
|
|
}
|
|
|
|
/**
|
|
* Get the distances into a race where each phase ends.
|
|
* @param raceLen Length of the race in meters
|
|
* @returns Quadruple of phase end points, including separate late and last spurt phases
|
|
*/
|
|
export function phases(raceLen: number): [number, number, number, number] {
|
|
const sixth = raceLen / 6;
|
|
const half = raceLen / 2;
|
|
return [sixth, sixth + half, 2 * sixth + half, raceLen];
|
|
}
|
|
|
|
export interface SectionInfo {
|
|
section: number;
|
|
phase: Phase;
|
|
endRate: number;
|
|
posKeep: boolean;
|
|
rush: boolean;
|
|
spotStruggleEnter: boolean;
|
|
}
|
|
|
|
export const SECTIONS: ReadonlyArray<SectionInfo> = [
|
|
{ section: 1, phase: Phase.EarlyRace, endRate: 1 / 24, posKeep: true, rush: false, spotStruggleEnter: false },
|
|
{ section: 2, phase: Phase.EarlyRace, endRate: 2 / 24, posKeep: true, rush: true, spotStruggleEnter: true },
|
|
{ section: 3, phase: Phase.EarlyRace, endRate: 3 / 24, posKeep: true, rush: true, spotStruggleEnter: true },
|
|
{ section: 4, phase: Phase.EarlyRace, endRate: 4 / 24, posKeep: true, rush: true, spotStruggleEnter: true },
|
|
{ section: 5, phase: Phase.MidRace, endRate: 5 / 24, posKeep: true, rush: true, spotStruggleEnter: true },
|
|
{ section: 6, phase: Phase.MidRace, endRate: 6 / 24, posKeep: true, rush: true, spotStruggleEnter: false },
|
|
{ section: 7, phase: Phase.MidRace, endRate: 7 / 24, posKeep: true, rush: true, spotStruggleEnter: false },
|
|
{ section: 8, phase: Phase.MidRace, endRate: 8 / 24, posKeep: true, rush: true, spotStruggleEnter: false },
|
|
{ section: 9, phase: Phase.MidRace, endRate: 9 / 24, posKeep: true, rush: true, spotStruggleEnter: false },
|
|
{ section: 10, phase: Phase.MidRace, endRate: 10 / 24, posKeep: true, rush: false, spotStruggleEnter: false },
|
|
{ section: 11, phase: Phase.MidRace, endRate: 11 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
|
{ section: 12, phase: Phase.MidRace, endRate: 12 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
|
{ section: 13, phase: Phase.MidRace, endRate: 13 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
|
{ section: 14, phase: Phase.MidRace, endRate: 14 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
|
{ section: 15, phase: Phase.MidRace, endRate: 15 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
|
{ section: 16, phase: Phase.MidRace, endRate: 16 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
|
{ section: 17, phase: Phase.LateRace, endRate: 17 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
|
{ section: 18, phase: Phase.LateRace, endRate: 18 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
|
{ section: 19, phase: Phase.LateRace, endRate: 19 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
|
{ section: 20, phase: Phase.LateRace, endRate: 20 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
|
{ section: 21, phase: Phase.LateRace, endRate: 21 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
|
{ section: 22, phase: Phase.LateRace, endRate: 22 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
|
{ section: 23, phase: Phase.LateRace, endRate: 23 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
|
{ section: 24, phase: Phase.LateRace, endRate: 24 / 24, posKeep: false, rush: false, spotStruggleEnter: false },
|
|
];
|
|
|
|
export interface SectionSlopeSegment extends SectionInfo {
|
|
startDist: number;
|
|
endDist: number;
|
|
slope: -2 | -1.5 | -1 | 0 | 1 | 1.5 | 2;
|
|
}
|
|
|
|
export function splitSectionSlopes(raceLen: number, slopes: Slope[]): SectionSlopeSegment[] {
|
|
const ss = sortSlopes(slopes);
|
|
return SECTIONS.flatMap<SectionSlopeSegment>((sec) => {
|
|
const startDist = ((sec.section - 1) * raceLen) / 24;
|
|
const endDist = (sec.section * raceLen) / 24;
|
|
const s = ss.filter((sl) => sl.span[0] < endDist && sl.span[1] > startDist);
|
|
if (s.length === 0) {
|
|
return [{ ...sec, startDist, endDist, slope: 0 }];
|
|
}
|
|
const r: Array<SectionSlopeSegment> = [{ ...sec, startDist, endDist: math.min(s[0].span[0], endDist), slope: 0 }];
|
|
for (const [i, sl] of s.entries()) {
|
|
const slopeEnd = math.min(endDist, sl.span[1]);
|
|
r.push(
|
|
{ ...sec, startDist: math.max(startDist, sl.span[0]), endDist: slopeEnd, slope: sl.per },
|
|
{ ...sec, startDist: slopeEnd, endDist: math.min(s[i + 1]?.span[0] ?? endDist, endDist), slope: 0 },
|
|
);
|
|
}
|
|
return r.filter((sl) => sl.startDist < sl.endDist);
|
|
});
|
|
}
|