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 = [ {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((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 = [{...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); }) }