curltastic/frontend/src/protocol.ts
Jason Dekarske 99a4f59a39 actual fix
2026-07-11 13:17:41 -07:00

171 lines
4.2 KiB
TypeScript

export const TICK_RATE_HZ = 120
export const SAMPLE_RATE_HZ = 40
export const STONES_PER_TEAM = 8
export const ENDS = 10
export const SHEET_WIDTH = 5.0
export const SHEET_LENGTH = 45.0
export const FEET_TO_METERS = 0.3048
export const HOUSE_CENTER = { x: 0, y: 38.5 }
export const HOUSE_RADIUS = 6 * FEET_TO_METERS
export const BUTTON_RADIUS = 0.5 * FEET_TO_METERS
export const FOUR_FT_RADIUS = 2 * FEET_TO_METERS
export const EIGHT_FT_RADIUS = 4 * FEET_TO_METERS
export const TWELVE_FT_RADIUS = 6 * FEET_TO_METERS
export const HOG_LINE_Y = 21.0
/** Backline on outer house ring (tee + 6 ft). */
export const BACK_LINE_Y = HOUSE_CENTER.y + HOUSE_RADIUS
export const HACK_Y = 2.0
export const STONE_RADIUS = 0.15
/** Feet relative to tee for weights 1..10 (front is negative). */
export const WEIGHT_STOP_OFFSET_FT = [-11, -9, -7, -5, -3, -1, 0, 1, 3, 5] as const
export const HACK_STOP_OFFSET_FT = 12
/** Calibrated m/s for weights 1..10 (open-ice stop vs tee offsets). */
export const WEIGHT_SPEEDS = [
2.2573, 2.2783, 2.2992, 2.3199, 2.3404, 2.3608, 2.3709, 2.3810, 2.4012, 2.4211,
] as const
export const HACK_SPEED = 2.4899
/** Takeouts: open-ice stop at hack+N feet (N = 6,12,18,24). */
export const BOARD_SPEED = 2.5492
export const CONTROL_SPEED = 2.6408
export const NORMAL_SPEED = 2.7448
export const PEEL_SPEED = 2.8499
export const BOARD_STOP_OFFSET_FT = HACK_STOP_OFFSET_FT + 6
export const CONTROL_STOP_OFFSET_FT = HACK_STOP_OFFSET_FT + 15
export const NORMAL_STOP_OFFSET_FT = HACK_STOP_OFFSET_FT + 25
export const PEEL_STOP_OFFSET_FT = HACK_STOP_OFFSET_FT + 35
/** Tee-line weight (category 7). */
export const DRAW_VELOCITY = WEIGHT_SPEEDS[6]
export const MIN_SPEED = WEIGHT_SPEEDS[0]
export const MAX_SPEED = WEIGHT_SPEEDS[9]
export type SpeedLabel =
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10
| 'hack' | 'board' | 'control' | 'normal' | 'peel'
export const SPEED_TABLE: Record<SpeedLabel, number> = {
1: WEIGHT_SPEEDS[0],
2: WEIGHT_SPEEDS[1],
3: WEIGHT_SPEEDS[2],
4: WEIGHT_SPEEDS[3],
5: WEIGHT_SPEEDS[4],
6: WEIGHT_SPEEDS[5],
7: WEIGHT_SPEEDS[6],
8: WEIGHT_SPEEDS[7],
9: WEIGHT_SPEEDS[8],
10: WEIGHT_SPEEDS[9],
hack: HACK_SPEED,
board: BOARD_SPEED,
control: CONTROL_SPEED,
normal: NORMAL_SPEED,
peel: PEEL_SPEED,
}
export type Team = 'team1' | 'team2'
export type Phase = 'waiting' | 'playing' | 'simulating' | 'scoring' | 'end_complete' | 'game_complete'
export interface StoneId {
team: Team
n: number
}
export interface StoneState {
id: StoneId
team: Team
x: number
y: number
rotation: number
}
export interface DrawableStone {
x: number
y: number
rotation: number
team: Team
}
export interface EndScore {
end: number
hammer: Team
team1: number
team2: number
}
export interface ClientThrowMessage {
type: 'throw'
team: Team
broom_x: number
broom_y: number
velocity: number
curl: number
}
export interface ServerJoinedMessage {
type: 'joined'
room: string
}
export interface ServerWaitingMessage {
type: 'waiting'
message: string
}
export interface ServerGameStateMessage {
type: 'game_state'
end: number
scores: number[]
hammer: Team
turn_team: Team
scoreboard: EndScore[]
stones_remaining: number[]
stones: StoneState[]
phase: Phase
}
/** Path samples are (x, y, theta). Time is sample_index / SAMPLE_RATE_HZ. */
export interface StonePath {
stone_id: StoneId
rotation: number
team: Team
trajectory: [number, number, number][]
}
export interface ServerTrajectoriesMessage {
type: 'trajectories'
stones: StonePath[]
}
export interface ServerGameOverMessage {
type: 'game_over'
scores: number[]
winner: Team | null
}
export interface ServerErrorMessage {
type: 'error'
message: string
}
export type ServerMessageTyped =
| ServerJoinedMessage
| ServerWaitingMessage
| ServerGameStateMessage
| ServerTrajectoriesMessage
| ServerGameOverMessage
| ServerErrorMessage
export type ServerMessage = ServerMessageTyped
export function isTeam(value: unknown): value is Team {
return value === 'team1' || value === 'team2'
}
export function stoneIdKey(id: StoneId): string {
return `${id.team}:${id.n}`
}
export function stoneIdsEqual(a: StoneId, b: StoneId): boolean {
return a.team === b.team && a.n === b.n
}