UNPKG

wise-eyes-core

Version:

Web server to monitor the status of owlcms

430 lines (351 loc) 10.7 kB
import type { AthleteState, OwlcmsAthlete, OwlcmsAthleteList, } from './athlete'; import type { ClockState, } from './clock'; import Athlete from './athlete'; import Clock from './clock'; export type BreakType = | 'BEFORE_INTRODUCTION' | 'CEREMONY' | 'CHALLENGE' | 'FIRST_CJ' | 'FIRST_SNATCH' | 'GROUP_DONE' | 'JURY' | 'MARSHAL' | 'SNATCH_DONE' | 'TECHNICAL'; export interface CategoryFederationRecords { federation: string; snatch: number; clean: number; total: number; snatchAttempt: boolean; cleanAttempt: boolean; totalAttempt: boolean; } export interface CategoryRecords { category: string[]; data: CategoryFederationRecords[]; } export type CeremonyType = | 'INTRODUCTION' | 'MEDALS' | 'OFFICIALS_INTRODUCTION'; export type Decision = | 'bad' | 'good'; export type FopState = | 'BREAK' | 'CURRENT_ATHLETE_DISPLAYED' | 'DECISION_VISIBLE' | 'DOWN_SIGNAL_VISIBLE' | 'INACTIVE' | 'TIME_RUNNING' | 'TIME_STOPPED'; export type LiftTypeKey = | 'CLEAN_AND_JERK' | 'SNATCH'; export type Mode = | 'BEFORE_INTRODUCTION' | 'CEREMONY' | 'CURRENT_ATHLETE' | 'FIRST_CJ' | 'FIRST_SNATCH' | 'INTRO_COUNTDOWN' | 'INTRODUCTION' | 'LIFT_COUNTDOWN' | 'LIFT_COUNTDOWN_CEREMONY' | 'LIFTING' | 'MARSHAL' | 'TECHNICAL' | 'WAIT'; export interface OwlcmsCategoryFederationRecords { cjHighlight: string; CLEANJERK: number; SNATCH: number; snatchHighlight: string; TOTAL: number; totalHighlight: string; } export interface OwlcmsCategoryRecords { cat: string[]; records: OwlcmsCategoryFederationRecords[]; } export type OwlcmsLiftType = | 'Clean_and_Jerk' | 'Snatch'; export interface OwlcmsRecords { recordCategories: string[]; recordNames: string[]; recordTable: OwlcmsCategoryRecords[]; } export interface PlatformState { athlete: AthleteState | null; athleteClock: ClockState; breakClock: ClockState; breakType: BreakType | null; centerReferee: Decision | null; ceremonyType: CeremonyType | null; downSignal: boolean; fopState: FopState | null; juryDecision: Decision | null; juryReversal: boolean | null; leftReferee: Decision | null; leaders: AthleteState[]; liftType: string | null; liftTypeKey: LiftTypeKey | null; mode: Mode | null; name: string; recordKind: RecordKind; records: Records | null; rightReferee: Decision | null; sessionDescription: string | null; sessionInfo: string | null; sessionName: string | null; } export type RecordKind = | 'attempt' | 'denied' | 'new' | 'none'; export interface Records { federations: string[]; categories: string[]; records: CategoryRecords[]; } export interface Session { description: string; info: string; name: string; } const JURY_DECISION_DURATION = 3_000; export default class Platform { private static platforms = new Map<string, Platform>(); private athletes = new Map<number, Athlete>(); private athleteClock: Clock; private breakClock: Clock; private breakType: BreakType | null = null; private centerReferee: Decision | null = null; private ceremonyType: CeremonyType | null = null; private currentAthlete: Athlete | null = null; private currentSession: Session | null = null; private downSignal = false; private fopState: FopState = 'INACTIVE'; private juryDecision: Decision | null = null; private juryReversal: boolean | null = null; private leaders: Athlete[] = []; private leftReferee: Decision | null = null; private liftingOrder: number[] = []; private liftType: string | null = null; private liftTypeKey: LiftTypeKey | null = null; private mode: Mode = 'WAIT'; private name: string; private recordKind: RecordKind = 'none'; private records: Records | null = null; private rightReferee: Decision | null = null; public static getPlatform(name: string, { noPersist = false, }: { noPersist?: boolean; } = {}): Platform { let platform = this.platforms.get(name); if (!platform) { platform = new Platform({ name, }); if (!noPersist) { this.platforms.set(name, platform); } } return platform; } public static getPlatforms(): string[] { return Array.from(this.platforms.keys()); } public constructor({ name, }: { name: string; }) { this.athleteClock = new Clock(); this.breakClock = new Clock(); this.name = name; } public getAthleteClock(): Clock { return this.athleteClock; } public getBreakClock(): Clock { return this.breakClock; } public getCurrentAthlete(): Athlete | null { return this.currentAthlete || null; } public getLiftingOrder(): Athlete[] { return this.liftingOrder.map((startNumber) => { return this.athletes.get(startNumber) as Athlete; }); } public getState(): PlatformState { return { athlete: this.currentAthlete?.getState() || null, athleteClock: this.athleteClock.getState(), breakClock: this.breakClock.getState(), breakType: this.breakType, centerReferee: this.centerReferee, ceremonyType: this.ceremonyType, downSignal: this.downSignal, fopState: this.fopState, juryDecision: this.juryDecision, juryReversal: this.juryReversal, leaders: this.leaders.map((leader) => leader.getState()), leftReferee: this.leftReferee, liftType: this.liftType, liftTypeKey: this.liftTypeKey, mode: this.mode, name: this.name, recordKind: this.recordKind, records: this.records, rightReferee: this.rightReferee, sessionDescription: this.currentSession?.description || null, sessionInfo: this.currentSession?.info || null, sessionName: this.currentSession?.name || null, }; } public resetDecisions(): void { this.centerReferee = null; this.downSignal = false; this.leftReferee = null; this.rightReferee = null; } public setBreakType(breakType: BreakType | null): void { this.breakType = breakType; } public setCeremonyType(ceremonyType: CeremonyType | null): void { this.ceremonyType = ceremonyType; } public setCurrentAthlete(startNumber: number): void { this.currentAthlete = this.athletes.get(startNumber) || null; } public setDecisions({ centerReferee, leftReferee, rightReferee, }: { centerReferee: Decision | null; leftReferee: Decision | null; rightReferee: Decision | null; }): void { this.downSignal = false; this.centerReferee = centerReferee; this.leftReferee = leftReferee; this.rightReferee = rightReferee; } public setDownSignal(state: boolean): void { this.downSignal = state; if (state) { this.centerReferee = null; this.leftReferee = null; this.rightReferee = null; } } public setFopState(fopState: FopState): void { this.fopState = fopState; } public setJuryDecision({ decision, reversal, }: { decision: Decision; reversal: boolean; }) { this.juryDecision = decision; this.juryReversal = reversal; setTimeout(() => { this.juryDecision = null; this.juryReversal = null; }, JURY_DECISION_DURATION); } public setLeaders(athletes: OwlcmsAthleteList): void { const realAthletes = athletes.filter((athlete) => { return !('isSpacer' in athlete); }) as OwlcmsAthlete[]; this.leaders = realAthletes.map((athleteData: OwlcmsAthlete) => { return new Athlete(athleteData); }); } public setLiftType({ key, name, }: { key: OwlcmsLiftType; name: string | null; }): void { this.liftTypeKey = (key?.toUpperCase() as LiftTypeKey) || null; this.liftType = name; } public setMode(mode: Mode): void { this.mode = mode; } public setRecordKind(recordKind: RecordKind): void { this.recordKind = recordKind || 'none'; } public setRecords(records: OwlcmsRecords): void { if (!records) { this.records = null; return; } const federations = records.recordNames; this.records = { federations, categories: records.recordCategories, records: records.recordTable.map((record) => { return { category: record.cat, data: record.records.map((data, index) => { return { federation: federations[index], snatch: data.SNATCH, clean: data.CLEANJERK, total: data.TOTAL, snatchAttempt: !!data.snatchHighlight, cleanAttempt: !!data.cjHighlight, totalAttempt: !!data.totalHighlight, }; }), }; }), }; } public setSession(session: { description: string; info: string; name: string; }): void { this.currentSession = session; } private updateAthlete(data: OwlcmsAthlete): Athlete { const startNumber = parseInt(data.startNumber); let athlete = this.athletes.get(startNumber); if (!athlete) { athlete = new Athlete(data); } else { athlete.update(data); } this.athletes.set(startNumber, athlete); return athlete; } public updateAthletes(athletes: OwlcmsAthleteList): void { const realAthletes = athletes.filter((athlete) => { return !('isSpacer' in athlete); }) as OwlcmsAthlete[]; this.liftingOrder = realAthletes.map((athleteData: OwlcmsAthlete) => { const athlete = this.updateAthlete(athleteData); return athlete.getState().startNumber; }); } }