import { DominoValue } from '../game/DominoValue'; import { TrainBend, TrainBranch } from '../game/TrainData'; export declare const DOMINO_WIDTH = 60; export declare const DOMINO_HEIGHT = 120; /** * Side-toe angles (degrees) relative to the branch direction for a chicken-foot * double. The 0° center toe is the straight main-line continuation and is not * listed here; these are the two angled toes that fan off the double's open end. */ export declare const CHICKEN_FOOT_TOE_ANGLES: readonly [-45, 45]; export type TrainLayoutStyle = 'offset' | 'linear'; export interface TrainLayoutEntry { x: number; y: number; rotation: number; isDouble: boolean; value1: number; value2: number; } export interface ComputeTrainLayoutInput { startX: number; startY: number; angle: number; dominoes: readonly DominoValue[]; layoutStyle: TrainLayoutStyle; dominoWidth?: number; dominoHeight?: number; /** * Distance from (startX, startY) to the center of the first tile, along the * train direction. Defaults to a small hub gap; chicken-foot toes pass half a * domino-height so the first toe tile butts against the host double's far end. */ leadGap?: number; /** * Which side the offset zigzag seeds on (+1 / -1). Defaults to the natural * outward side for `angle`. Chicken-foot toes override this so each toe's * zigzag starts toward the outside of the foot, clear of the center row. */ outwardSign?: number; /** * Offset mode only: index of a chicken-foot double that should act as a * centered hub. The double and the tile feeding into it are snapped onto the * train axis (perp 0) so the inbound tile reads as centered on the double and * the offset center toe fans out symmetrically — which lets the two angled * toes sit at equal, close distances on either side. */ hubIndex?: number; /** * Pivots that fold this run's path into Ls, Us, or snakes. When present, the * run is split into straight sub-runs at each bend index and chained corner to * corner. Hub-centering is skipped (corners relax centering by design). */ bends?: readonly TrainBend[]; } export declare function halfExtentAlongTrain(isDouble: boolean, dominoWidth?: number, dominoHeight?: number): number; export declare function stepAlongTrain(fromIsDouble: boolean, toIsDouble: boolean, dominoWidth?: number, dominoHeight?: number): number; export declare function trainDirection(angle: number): { dirX: number; dirY: number; }; export declare function trainPerpendicular(angle: number): { perpX: number; perpY: number; }; /** * Orients a value chain for rendering so each tile's connecting value (`value1`, * the near end) faces the previous tile. A tile is flipped only when it is * stored reversed (its `value2`, not `value1`, is the one that matches the * previous tile's open end). A correctly-stored chain is left untouched, and * doubles are never flipped. This is identical for linear and offset layouts — * the connection rule doesn't depend on spacing. */ export declare function orientDominoValues(dominoes: DominoValue[]): DominoValue[]; export declare function outwardPerpSign(angle: number): number; export declare function nextPerpOffset(current: number, outwardSign: number): number; /** * Normalizes a run's bends: integer indices strictly inside the run, one per * index (last wins), sorted. Index 0 is dropped — a run can't bend before its * first tile. Returns the cleaned, sorted list. */ export declare function normalizeBends(bends: readonly TrainBend[] | undefined, tileCount: number): TrainBend[]; /** * Local heading (degrees) of the tile at `index` in a (possibly bent) run: the * base `angle` plus every bend turn at or before that index. With no bends this * is just `angle`. Used to anchor chicken-foot toes off a double's *actual* * heading when the double sits in a turned section of the path. */ export declare function headingAtIndex(angle: number, bends: readonly TrainBend[] | undefined, index: number, tileCount?: number): number; export declare function computeTrainLayout({ startX, startY, angle, dominoes, layoutStyle, dominoWidth, dominoHeight, leadGap, outwardSign: outwardSignInput, hubIndex, bends, }: ComputeTrainLayoutInput): TrainLayoutEntry[]; /** The four world-space corners of a tile (its rotated rectangle). */ export declare function tileCorners(entry: TrainLayoutEntry, dominoWidth?: number, dominoHeight?: number): Array<{ x: number; y: number; }>; /** * True when two tiles physically overlap (separating-axis test on their rotated * rectangles). Tiles that merely touch (within `epsilon`) are not overlapping, * so legitimately adjacent dominoes — bricked, end-to-end, or butted against a * double — pass cleanly while real collisions are caught. */ export declare function tilesOverlap(a: TrainLayoutEntry, b: TrainLayoutEntry, epsilon?: number, dominoWidth?: number, dominoHeight?: number): boolean; /** * True when any tile of `layout` overlaps any tile in `obstacles` — i.e. this * path would physically intersect another path. Used to forbid a bend that * would cross another train. */ export declare function layoutsCollide(layout: readonly TrainLayoutEntry[], obstacles: readonly TrainLayoutEntry[], epsilon?: number, dominoWidth?: number, dominoHeight?: number): boolean; /** * True when a path crosses itself — any two of its own tiles overlap. Adjacent * tiles that merely touch are fine (tilesOverlap ignores contact), so this only * fires when a fold (e.g. a too-tight U-turn) makes the path collide with itself. */ export declare function layoutSelfIntersects(layout: readonly TrainLayoutEntry[], epsilon?: number, dominoWidth?: number, dominoHeight?: number): boolean; /** * A single straight run of dominoes within a chicken-foot tree: the main line * or one toe. `depth` is 0 for the main line, 1 for its toes, and so on. */ export interface TrainSegment { angle: number; depth: number; layoutStyle: TrainLayoutStyle; /** Outward side this segment's zigzag seeds on (needed to re-derive layout). */ outwardSign: number; dominoes: readonly DominoValue[]; layout: TrainLayoutEntry[]; /** Anchor point this segment hangs off (host double's open end), if any. */ anchor?: { x: number; y: number; }; } export interface ComputeTrainTreeInput { startX: number; startY: number; angle: number; branch: TrainBranch; layoutStyle: TrainLayoutStyle; dominoWidth?: number; dominoHeight?: number; leadGap?: number; depth?: number; anchor?: { x: number; y: number; }; outwardSign?: number; /** * Accumulator of every tile already placed in the tree. Toes are nudged * outward until they clear everything in here, so no two dominoes overlap. * Callers normally omit this; the recursion threads it through. */ placed?: TrainLayoutEntry[]; /** * Unit direction a toe may be nudged along (outward, parallel to the host * double's open edge) to resolve overlaps. The trunk passes none. */ pushAxis?: { x: number; y: number; }; /** * Minimum number of nudge steps to apply before checking for clearance. Both * toes of a foot share this so they stay symmetric about the double even when * only one side is crowded by the offset center toe. */ minPushSteps?: number; } /** * Lays out a branch and, recursively, the chicken-foot side toes hanging off any * of its doubles. Returns a flat list of segments (main line first, then toes in * depth-first order) so callers can render every tile and validate each run. */ export declare function computeTrainTree({ startX, startY, angle, branch, layoutStyle, dominoWidth, dominoHeight, leadGap, depth, anchor, outwardSign, placed, pushAxis, minPushSteps, }: ComputeTrainTreeInput): TrainSegment[]; /** Flattens a list of segments into a single list of tiles for rendering. */ export declare function flattenSegments(segments: readonly TrainSegment[]): TrainLayoutEntry[]; export interface TrainLayoutBounds { width: number; height: number; offsetX: number; offsetY: number; } /** Bounding box for rendering a train layout on a felt canvas. */ export declare function getTrainLayoutBounds(layout: readonly TrainLayoutEntry[], padding?: number, dominoWidth?: number, dominoHeight?: number): TrainLayoutBounds;