Files
horse/zenno/src/lib/course.ts
T

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);
});
}