doubletwelve 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/DominoHub.d.ts +8 -0
- package/dist/app/Viewport.d.ts +27 -0
- package/dist/app/trainBends.d.ts +77 -0
- package/dist/app/trainLayout.d.ts +42 -3
- package/dist/app/viewportMath.d.ts +28 -0
- package/dist/game/TrainData.d.ts +23 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +10 -3
- package/dist/index.js +1079 -703
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
package/dist/app/DominoHub.d.ts
CHANGED
|
@@ -13,5 +13,13 @@ interface DominoHubProps {
|
|
|
13
13
|
tableHeight: number;
|
|
14
14
|
pipColors?: PipColorMap;
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Distance from the hub center at which a train should start so its first tile
|
|
18
|
+
* clears its neighbours. Trains fan out 360/slots° apart, so the neighbour gap
|
|
19
|
+
* at distance d is ~2πd/slots; it must exceed a tile's footprint. Offset trains
|
|
20
|
+
* zigzag wider (a perpendicular half-tile seed) and need a bigger ring; linear
|
|
21
|
+
* trains are skinny and stay near the hub. Never smaller than `radius + 20`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function hubTrainStartDistance(slots: number, radius: number, dominoWidth: number, layoutStyle: 'offset' | 'linear'): number;
|
|
16
24
|
export declare const DominoHub: FC<DominoHubProps>;
|
|
17
25
|
export default DominoHub;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { FC, ReactNode } from 'react';
|
|
2
|
+
export interface ViewportProps {
|
|
3
|
+
/** Visible viewport size in pixels. */
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
/** Content (world) size, used by the fit/reset control. */
|
|
7
|
+
contentWidth: number;
|
|
8
|
+
contentHeight: number;
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
minScale?: number;
|
|
11
|
+
maxScale?: number;
|
|
12
|
+
/** Multiplier applied per wheel notch / zoom-button press. */
|
|
13
|
+
zoomStep?: number;
|
|
14
|
+
padding?: number;
|
|
15
|
+
background?: string;
|
|
16
|
+
/** Show the built-in zoom/reset control overlay. Default true. */
|
|
17
|
+
showControls?: boolean;
|
|
18
|
+
testId?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A pan/zoom canvas for content larger than the screen. Drag to slide, wheel or
|
|
22
|
+
* the on-screen buttons to zoom (zoom centers on the cursor for the wheel).
|
|
23
|
+
* Clicks pass through to children unless the pointer actually dragged, so
|
|
24
|
+
* interactive content (e.g. click-to-bend tiles) keeps working.
|
|
25
|
+
*/
|
|
26
|
+
export declare const Viewport: FC<ViewportProps>;
|
|
27
|
+
export default Viewport;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { TrainBend, TrainBranch } from '../game/TrainData';
|
|
2
|
+
import { TrainLayoutEntry, TrainLayoutStyle } from './trainLayout';
|
|
3
|
+
export type TurnSide = 'left' | 'right';
|
|
4
|
+
/** Default pivot magnitude. The interactive UI only produces square corners. */
|
|
5
|
+
export declare const TURN_DEGREES = 90;
|
|
6
|
+
/**
|
|
7
|
+
* Signed turn (degrees) for a side. Headings use the screen convention
|
|
8
|
+
* (0° = +x, +90° = +y / downward), so a `+90` turn rotates +x toward +y, which
|
|
9
|
+
* reads as a clockwise/"right" turn on screen.
|
|
10
|
+
*/
|
|
11
|
+
export declare function sideToTurn(side: TurnSide, degrees?: number): number;
|
|
12
|
+
export declare function oppositeSide(side: TurnSide): TurnSide;
|
|
13
|
+
/**
|
|
14
|
+
* Default turn side in offset mode: fold toward the empty side — the one
|
|
15
|
+
* opposite the lane the zigzag biases into (`outwardSign`). A `+90` turn heads
|
|
16
|
+
* toward the heading's `+perp`; outwardSign is measured on that same perp axis,
|
|
17
|
+
* so the empty side is `-outwardSign`, i.e. side = outwardSign >= 0 ? 'left' : 'right'.
|
|
18
|
+
*/
|
|
19
|
+
export declare function offsetDefaultSide(angle: number, outwardSign?: number): TurnSide;
|
|
20
|
+
export interface TableBounds {
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Default turn side in linear mode: fold toward whichever perpendicular side has
|
|
26
|
+
* more open table from the bend point. Distance is measured from `point` along
|
|
27
|
+
* each perpendicular until it exits the table rectangle; the roomier side wins.
|
|
28
|
+
* Ties (e.g. dead-center) fall back to 'right'.
|
|
29
|
+
*/
|
|
30
|
+
export declare function linearDefaultSide(point: {
|
|
31
|
+
x: number;
|
|
32
|
+
y: number;
|
|
33
|
+
}, angle: number, bounds: TableBounds): TurnSide;
|
|
34
|
+
export interface BuildTrainTilesInput {
|
|
35
|
+
startX: number;
|
|
36
|
+
startY: number;
|
|
37
|
+
angle: number;
|
|
38
|
+
layoutStyle: TrainLayoutStyle;
|
|
39
|
+
}
|
|
40
|
+
/** Flattens a branch (with feet and bends) to its world-space tiles. */
|
|
41
|
+
export declare function buildBranchTiles(branch: TrainBranch, input: BuildTrainTilesInput): TrainLayoutEntry[];
|
|
42
|
+
/** Replaces (or removes) the bend at `index`, returning a new bends array. */
|
|
43
|
+
export declare function withBendAt(bends: readonly TrainBend[] | undefined, index: number, turn: number | null): TrainBend[];
|
|
44
|
+
export interface ResolveBendResult {
|
|
45
|
+
/** The legal turn to apply, or null when no side is collision-free. */
|
|
46
|
+
turn: number | null;
|
|
47
|
+
/** Why null: 'blocked' = both sides collide; never set on success. */
|
|
48
|
+
reason?: 'blocked';
|
|
49
|
+
}
|
|
50
|
+
export interface ResolveBendInput {
|
|
51
|
+
branch: TrainBranch;
|
|
52
|
+
index: number;
|
|
53
|
+
build: BuildTrainTilesInput;
|
|
54
|
+
/** Tiles belonging to every OTHER path; a bend may not intersect these. */
|
|
55
|
+
obstacles: readonly TrainLayoutEntry[];
|
|
56
|
+
/** Preferred side to try first (from the mode's heuristic). */
|
|
57
|
+
preferredSide: TurnSide;
|
|
58
|
+
/** Turn magnitude in degrees (default 90). */
|
|
59
|
+
degrees?: number;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Picks a collision-free turn for a new bend at `index`. Tries the preferred
|
|
63
|
+
* side first, then the opposite; a candidate is rejected if the resulting path
|
|
64
|
+
* crosses itself or any obstacle path. Returns `{ turn: null, reason: 'blocked' }`
|
|
65
|
+
* when neither side is legal, so the caller can refuse the bend.
|
|
66
|
+
*/
|
|
67
|
+
export declare function resolveBend({ branch, index, build, obstacles, preferredSide, degrees, }: ResolveBendInput): ResolveBendResult;
|
|
68
|
+
/**
|
|
69
|
+
* Cycles a tile's bend on repeated clicks: none → preferred legal side →
|
|
70
|
+
* opposite legal side → none. Skips sides that collide. Returns the next bends
|
|
71
|
+
* array, or the unchanged input when no legal bend exists.
|
|
72
|
+
*/
|
|
73
|
+
export declare function cycleBendAt(branch: TrainBranch, index: number, build: BuildTrainTilesInput, obstacles: readonly TrainLayoutEntry[], preferredSide: TurnSide, degrees?: number): {
|
|
74
|
+
bends: TrainBend[];
|
|
75
|
+
changed: boolean;
|
|
76
|
+
blocked: boolean;
|
|
77
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DominoValue } from '../game/DominoValue';
|
|
2
|
-
import { TrainBranch } from '../game/TrainData';
|
|
2
|
+
import { TrainBend, TrainBranch } from '../game/TrainData';
|
|
3
3
|
export declare const DOMINO_WIDTH = 60;
|
|
4
4
|
export declare const DOMINO_HEIGHT = 120;
|
|
5
5
|
/**
|
|
@@ -45,6 +45,12 @@ export interface ComputeTrainLayoutInput {
|
|
|
45
45
|
* toes sit at equal, close distances on either side.
|
|
46
46
|
*/
|
|
47
47
|
hubIndex?: number;
|
|
48
|
+
/**
|
|
49
|
+
* Pivots that fold this run's path into Ls, Us, or snakes. When present, the
|
|
50
|
+
* run is split into straight sub-runs at each bend index and chained corner to
|
|
51
|
+
* corner. Hub-centering is skipped (corners relax centering by design).
|
|
52
|
+
*/
|
|
53
|
+
bends?: readonly TrainBend[];
|
|
48
54
|
}
|
|
49
55
|
export declare function halfExtentAlongTrain(isDouble: boolean, dominoWidth?: number, dominoHeight?: number): number;
|
|
50
56
|
export declare function stepAlongTrain(fromIsDouble: boolean, toIsDouble: boolean, dominoWidth?: number, dominoHeight?: number): number;
|
|
@@ -56,10 +62,31 @@ export declare function trainPerpendicular(angle: number): {
|
|
|
56
62
|
perpX: number;
|
|
57
63
|
perpY: number;
|
|
58
64
|
};
|
|
59
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Orients a value chain for rendering so each tile's connecting value (`value1`,
|
|
67
|
+
* the near end) faces the previous tile. A tile is flipped only when it is
|
|
68
|
+
* stored reversed (its `value2`, not `value1`, is the one that matches the
|
|
69
|
+
* previous tile's open end). A correctly-stored chain is left untouched, and
|
|
70
|
+
* doubles are never flipped. This is identical for linear and offset layouts —
|
|
71
|
+
* the connection rule doesn't depend on spacing.
|
|
72
|
+
*/
|
|
73
|
+
export declare function orientDominoValues(dominoes: DominoValue[]): DominoValue[];
|
|
60
74
|
export declare function outwardPerpSign(angle: number): number;
|
|
61
75
|
export declare function nextPerpOffset(current: number, outwardSign: number): number;
|
|
62
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Normalizes a run's bends: integer indices strictly inside the run, one per
|
|
78
|
+
* index (last wins), sorted. Index 0 is dropped — a run can't bend before its
|
|
79
|
+
* first tile. Returns the cleaned, sorted list.
|
|
80
|
+
*/
|
|
81
|
+
export declare function normalizeBends(bends: readonly TrainBend[] | undefined, tileCount: number): TrainBend[];
|
|
82
|
+
/**
|
|
83
|
+
* Local heading (degrees) of the tile at `index` in a (possibly bent) run: the
|
|
84
|
+
* base `angle` plus every bend turn at or before that index. With no bends this
|
|
85
|
+
* is just `angle`. Used to anchor chicken-foot toes off a double's *actual*
|
|
86
|
+
* heading when the double sits in a turned section of the path.
|
|
87
|
+
*/
|
|
88
|
+
export declare function headingAtIndex(angle: number, bends: readonly TrainBend[] | undefined, index: number, tileCount?: number): number;
|
|
89
|
+
export declare function computeTrainLayout({ startX, startY, angle, dominoes, layoutStyle, dominoWidth, dominoHeight, leadGap, outwardSign: outwardSignInput, hubIndex, bends, }: ComputeTrainLayoutInput): TrainLayoutEntry[];
|
|
63
90
|
/** The four world-space corners of a tile (its rotated rectangle). */
|
|
64
91
|
export declare function tileCorners(entry: TrainLayoutEntry, dominoWidth?: number, dominoHeight?: number): Array<{
|
|
65
92
|
x: number;
|
|
@@ -72,6 +99,18 @@ export declare function tileCorners(entry: TrainLayoutEntry, dominoWidth?: numbe
|
|
|
72
99
|
* double — pass cleanly while real collisions are caught.
|
|
73
100
|
*/
|
|
74
101
|
export declare function tilesOverlap(a: TrainLayoutEntry, b: TrainLayoutEntry, epsilon?: number, dominoWidth?: number, dominoHeight?: number): boolean;
|
|
102
|
+
/**
|
|
103
|
+
* True when any tile of `layout` overlaps any tile in `obstacles` — i.e. this
|
|
104
|
+
* path would physically intersect another path. Used to forbid a bend that
|
|
105
|
+
* would cross another train.
|
|
106
|
+
*/
|
|
107
|
+
export declare function layoutsCollide(layout: readonly TrainLayoutEntry[], obstacles: readonly TrainLayoutEntry[], epsilon?: number, dominoWidth?: number, dominoHeight?: number): boolean;
|
|
108
|
+
/**
|
|
109
|
+
* True when a path crosses itself — any two of its own tiles overlap. Adjacent
|
|
110
|
+
* tiles that merely touch are fine (tilesOverlap ignores contact), so this only
|
|
111
|
+
* fires when a fold (e.g. a too-tight U-turn) makes the path collide with itself.
|
|
112
|
+
*/
|
|
113
|
+
export declare function layoutSelfIntersects(layout: readonly TrainLayoutEntry[], epsilon?: number, dominoWidth?: number, dominoHeight?: number): boolean;
|
|
75
114
|
/**
|
|
76
115
|
* A single straight run of dominoes within a chicken-foot tree: the main line
|
|
77
116
|
* or one toe. `depth` is 0 for the main line, 1 for its toes, and so on.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Pan/zoom transform: content is scaled by `scale` then translated by (x, y). */
|
|
2
|
+
export interface ViewportTransform {
|
|
3
|
+
scale: number;
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
}
|
|
7
|
+
export interface Size {
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
}
|
|
11
|
+
export interface Point {
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function clampScale(scale: number, min: number, max: number): number;
|
|
16
|
+
/**
|
|
17
|
+
* Zooms by `factor` about a fixed screen `pivot` so the content point under the
|
|
18
|
+
* pivot stays put. Scale is clamped to [min, max]; the translation is adjusted
|
|
19
|
+
* by the *effective* factor after clamping so panning can't drift at the limits.
|
|
20
|
+
*/
|
|
21
|
+
export declare function zoomAt(view: ViewportTransform, factor: number, pivot: Point, min: number, max: number): ViewportTransform;
|
|
22
|
+
/**
|
|
23
|
+
* Centers `content` within `viewport` at the largest scale that fits inside the
|
|
24
|
+
* given padding (clamped to [min, max]). Use this for a "fit / reset" control.
|
|
25
|
+
*/
|
|
26
|
+
export declare function fitToBounds(content: Size, viewport: Size, padding: number, min: number, max: number): ViewportTransform;
|
|
27
|
+
/** Converts a screen point inside the viewport to content coordinates. */
|
|
28
|
+
export declare function screenToContent(view: ViewportTransform, screen: Point): Point;
|
package/dist/game/TrainData.d.ts
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
import { DominoValue } from './DominoValue';
|
|
2
|
+
/**
|
|
3
|
+
* A pivot in a run's path. At `index` (a tile position within `dominoes`) the run
|
|
4
|
+
* stops going straight and turns by `turn` degrees, so a player can fold a train
|
|
5
|
+
* into Ls, Us, and meandering snakes to avoid other trains. Bends are layout
|
|
6
|
+
* hints only — they never change which tiles connect (the value chain is
|
|
7
|
+
* untouched), so like-values keep touching across every corner.
|
|
8
|
+
*
|
|
9
|
+
* `turn` is signed degrees relative to the current heading, in the same
|
|
10
|
+
* convention as a branch's `angle` (0° = +x, 90° = +y / downward on screen).
|
|
11
|
+
* The interactive default is ±90°; the sign is chosen by a heuristic when the
|
|
12
|
+
* player first bends, then persisted here so the path is stable across renders.
|
|
13
|
+
*/
|
|
14
|
+
export interface TrainBend {
|
|
15
|
+
/** Index in `dominoes` of the first tile that continues in the new heading. */
|
|
16
|
+
index: number;
|
|
17
|
+
/** Signed turn in degrees applied to the heading at this point. */
|
|
18
|
+
turn: number;
|
|
19
|
+
}
|
|
2
20
|
/**
|
|
3
21
|
* A run of dominoes that can sprout chicken-foot side toes at its doubles.
|
|
4
22
|
*
|
|
@@ -15,6 +33,11 @@ export interface TrainBranch {
|
|
|
15
33
|
* entries: index 0 → -45°, index 1 → +45°).
|
|
16
34
|
*/
|
|
17
35
|
feet?: Record<number, TrainBranch[]>;
|
|
36
|
+
/**
|
|
37
|
+
* Optional pivots that fold this run's path. Order-independent (the engine
|
|
38
|
+
* sorts by index); at most one bend per index. Absent/empty = a straight run.
|
|
39
|
+
*/
|
|
40
|
+
bends?: TrainBend[];
|
|
18
41
|
}
|
|
19
42
|
export interface TrainData extends TrainBranch {
|
|
20
43
|
playerId: number;
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const v=require("react/jsx-runtime"),j=require("react"),Ee={"3x3":{rows:[20,50,80],cols:[20,50,80],size:"18%"},"3x4":{rows:[24,50,76],cols:[22,40,60,78],size:"12%"},"4x3":{rows:[15,38,62,85],cols:[20,50,80],size:"14%"}};function de(e){const o=Ee[e.gridSize];return{top:e.top??`${o.rows[e.row]}%`,left:e.left??`${o.cols[e.col]}%`,width:o.size,height:o.size}}const je=({row:e,col:o,gridSize:t,color:n,hollow:r,top:i,left:l})=>{const s=de({row:e,col:o,gridSize:t,top:i,left:l});return v.jsx("div",{"data-testid":"pip","data-row":e,"data-col":o,"data-grid":t,style:{position:"absolute",backgroundColor:r?"transparent":n,border:r?"2px solid #888":void 0,borderRadius:"50%",transform:"translate(-50%, -50%)",boxShadow:r?void 0:"1px 2px 3px rgba(0,0,0,0.3)",...s}})},fe={0:[],1:[{row:1,col:1,gridSize:"3x3"}],2:[{row:0,col:2,gridSize:"3x3"},{row:2,col:0,gridSize:"3x3"}],3:[{row:0,col:2,gridSize:"3x3"},{row:1,col:1,gridSize:"3x3"},{row:2,col:0,gridSize:"3x3"}],4:[{row:0,col:0,gridSize:"3x3"},{row:0,col:2,gridSize:"3x3"},{row:2,col:0,gridSize:"3x3"},{row:2,col:2,gridSize:"3x3"}],5:[{row:0,col:0,gridSize:"3x3"},{row:0,col:2,gridSize:"3x3"},{row:1,col:1,gridSize:"3x3"},{row:2,col:0,gridSize:"3x3"},{row:2,col:2,gridSize:"3x3"}],6:[{row:0,col:0,gridSize:"3x3"},{row:0,col:2,gridSize:"3x3"},{row:1,col:0,gridSize:"3x3"},{row:1,col:2,gridSize:"3x3"},{row:2,col:0,gridSize:"3x3"},{row:2,col:2,gridSize:"3x3"}],7:[{row:0,col:0,gridSize:"3x3"},{row:0,col:2,gridSize:"3x3"},{row:1,col:0,gridSize:"3x3"},{row:1,col:1,gridSize:"3x3"},{row:1,col:2,gridSize:"3x3"},{row:2,col:0,gridSize:"3x3"},{row:2,col:2,gridSize:"3x3"}],8:[{row:0,col:0,gridSize:"3x3"},{row:0,col:1,gridSize:"3x3"},{row:0,col:2,gridSize:"3x3"},{row:1,col:0,gridSize:"3x3"},{row:1,col:2,gridSize:"3x3"},{row:2,col:0,gridSize:"3x3"},{row:2,col:1,gridSize:"3x3"},{row:2,col:2,gridSize:"3x3"}],9:[{row:0,col:0,gridSize:"3x3"},{row:0,col:1,gridSize:"3x3"},{row:0,col:2,gridSize:"3x3"},{row:1,col:0,gridSize:"3x3"},{row:1,col:1,gridSize:"3x3"},{row:1,col:2,gridSize:"3x3"},{row:2,col:0,gridSize:"3x3"},{row:2,col:1,gridSize:"3x3"},{row:2,col:2,gridSize:"3x3"}],10:[{row:0,col:0,gridSize:"3x4"},{row:0,col:1,gridSize:"3x4"},{row:0,col:2,gridSize:"3x4"},{row:0,col:3,gridSize:"3x4"},{row:1,col:0,gridSize:"3x4"},{row:1,col:3,gridSize:"3x4"},{row:2,col:0,gridSize:"3x4"},{row:2,col:1,gridSize:"3x4"},{row:2,col:2,gridSize:"3x4"},{row:2,col:3,gridSize:"3x4"}],11:[{row:0,col:0,gridSize:"4x3"},{row:1,col:0,gridSize:"4x3"},{row:2,col:0,gridSize:"4x3"},{row:3,col:0,gridSize:"4x3"},{row:0,col:1,gridSize:"4x3"},{row:2,col:1,gridSize:"4x3",top:"50%"},{row:3,col:1,gridSize:"4x3"},{row:0,col:2,gridSize:"4x3"},{row:1,col:2,gridSize:"4x3"},{row:2,col:2,gridSize:"4x3"},{row:3,col:2,gridSize:"4x3"}],12:[{row:0,col:0,gridSize:"4x3"},{row:1,col:0,gridSize:"4x3"},{row:2,col:0,gridSize:"4x3"},{row:3,col:0,gridSize:"4x3"},{row:0,col:1,gridSize:"4x3"},{row:1,col:1,gridSize:"4x3"},{row:2,col:1,gridSize:"4x3"},{row:3,col:1,gridSize:"4x3"},{row:0,col:2,gridSize:"4x3"},{row:1,col:2,gridSize:"4x3"},{row:2,col:2,gridSize:"4x3"},{row:3,col:2,gridSize:"4x3"}]};function pe(e){return fe[e]??[]}const F={0:{color:"transparent"},1:{color:"#1a1a1a"},2:{color:"#8B1A1A"},3:{color:"#E6B800"},4:{color:"#e8e8e8",hollow:!0},5:{color:"#2E8B57"},6:{color:"#2563EB"},7:{color:"#E8A87C"},8:{color:"#DC2626"},9:{color:"#1E3A8A"},10:{color:"#EA580C"},11:{color:"#166534"},12:{color:"#DC2626"}},Fe=F;function Ae(e){return{...F,...e}}function K(e,o){if(o!==void 0)return o[e]??F[e]??{color:"#1a1a1a"}}function Le(e){return K(e,F)}const _e=(e,o,t)=>{const n=K(e,t);return n?{color:n.color,hollow:n.hollow}:{color:o}},Re=({value:e,pipColor:o,pipColors:t})=>{const{color:n,hollow:r}=_e(e,o,t),i=pe(e);return v.jsx(v.Fragment,{children:i.map((l,s)=>v.jsx(je,{row:l.row,col:l.col,gridSize:l.gridSize,color:n,hollow:r,top:l.top,left:l.left},s))})},ae=({value:e,pipColor:o,pipColors:t})=>v.jsx("div",{style:{width:"100%",height:"100%",position:"relative",padding:"0",overflow:"hidden"},children:v.jsx(Re,{value:e,pipColor:o,pipColors:t})}),J=({value1:e=0,value2:o=0,width:t=100,height:n=200,backgroundColor:r="white",pipColor:i="black",pipColors:l,borderColor:s="black",rotation:a=0})=>{const u=Math.min(Math.max(e,0),12),f=Math.min(Math.max(o,0),12);return v.jsxs("div",{style:{width:`${t}px`,height:`${n}px`,backgroundColor:r,borderColor:s,borderWidth:"1px",borderStyle:"solid",borderRadius:"10px",transform:`rotate(${a}deg)`,transformOrigin:"center center",boxShadow:"0 1px 2px rgba(0,0,0,0.2)",display:"flex",flexDirection:"column",overflow:"hidden"},children:[v.jsx("div",{style:{flex:1,position:"relative",borderBottomWidth:"1px",borderBottomStyle:"solid",borderBottomColor:s},children:v.jsx(ae,{value:u,pipColor:i,pipColors:l})}),v.jsx("div",{style:{flex:1,position:"relative"},children:v.jsx(ae,{value:f,pipColor:i,pipColors:l})})]})},D=60,O=120,ve=[-45,45];function ue(e,o=D,t=O){return e?o/2:t/2}function z(e,o,t=D,n=O){return ue(e,t,n)+ue(o,t,n)}function _(e){const o=e*Math.PI/180;return{dirX:Math.cos(o),dirY:Math.sin(o)}}function Y(e){const{dirX:o,dirY:t}=_(e);return{perpX:-t,perpY:o}}function Xe(e,o){const t=e.map(n=>({...n}));for(let n=1;n<t.length;n++){const r=t[n],l=t[n-1].value2,s=r.value1===r.value2;if(o==="linear"&&!s){t[n]={value1:r.value2,value2:r.value1};continue}o==="offset"&&!s&&r.value1!==l&&r.value2===l&&(t[n]={value1:r.value2,value2:r.value1})}return t}function R(e){const{dirX:o,dirY:t}=_(e);return Math.abs(o)>=Math.abs(t)?o>=0?1:-1:t>=0?1:-1}function xe(e,o){return e===0?o:e===o?-o:o}function ge({startX:e,startY:o,angle:t,dominoes:n,layoutStyle:r,dominoWidth:i=D,dominoHeight:l=O,leadGap:s=l*.3,outwardSign:a,hubIndex:u}){const f=[],{dirX:c,dirY:d}=_(t),{perpX:x,perpY:y}=Y(t),g=Xe([...n],r),S=a??R(t),T=r==="offset"&&u!=null,C=[];let m=e+c*s,p=o+d*s,b=0,M=0;const I=i/2,A=h=>{const w=(h-b)*I;m+=x*w,p+=y*w,b=h};for(let h=0;h<g.length;h++){const w=g[h],k=w.value1===w.value2,$=h>0&&g[h-1].value1===g[h-1].value2;r==="linear"?h>0&&(k?(m+=c*z($,!0,i,l),p+=d*z($,!0,i,l)):$?(m+=c*z(!0,!1,i,l),p+=d*z(!0,!1,i,l)):(m+=c*l,p+=d*l)):k?h>0&&(m+=c*z($,!0,i,l),p+=d*z($,!0,i,l)):(h===0?M=S:$?(m+=c*z(!0,!1,i,l),p+=d*z(!0,!1,i,l)):(m+=c*(l/2),p+=d*(l/2),M=xe(M,S)),A(M)),C.push(b),f.push({x:m,y:p,rotation:k?t+180:t-90,isDouble:k,value1:w.value1,value2:w.value2})}if(T&&u!=null){const h=-C[u]*I;if(h!==0)for(let w=0;w<f.length;w++)f[w]={...f[w],x:f[w].x+x*h,y:f[w].y+y*h}}return f}function G(e,o=D,t=O){const n=e.rotation*Math.PI/180,r=Math.cos(n),i=Math.sin(n),l=o/2,s=t/2;return[[-l,-s],[l,-s],[l,s],[-l,s]].map(([a,u])=>({x:e.x+a*r-u*i,y:e.y+a*i+u*r}))}function Ne(e,o,t){let n=1/0,r=-1/0,i=1/0,l=-1/0;for(const s of e){const a=s.x*t.x+s.y*t.y;n=Math.min(n,a),r=Math.max(r,a)}for(const s of o){const a=s.x*t.x+s.y*t.y;i=Math.min(i,a),l=Math.max(l,a)}return Math.min(r,l)-Math.max(n,i)}function Q(e,o,t=1,n=D,r=O){const i=G(e,n,r),l=G(o,n,r);for(const s of[i,l])for(let a=0;a<4;a++){const u=s[a],f=s[(a+1)%4],c=f.x-u.x,d=f.y-u.y,x=Math.hypot(c,d)||1,y={x:-d/x,y:c/x};if(Ne(i,l,y)<=t)return!1}return!0}function Ye(e,o,t,n){return o.some(r=>Q(e,r,1,t,n))}const P=D/4,ce=24;function Z({startX:e,startY:o,angle:t,branch:n,layoutStyle:r,dominoWidth:i=D,dominoHeight:l=O,leadGap:s,depth:a=0,anchor:u,outwardSign:f,placed:c=[],pushAxis:d,minPushSteps:x=0}){const y=f??R(t),g=n.feet?Object.keys(n.feet).map(Number).filter(p=>{const b=n.dominoes[p];return b&&b.value1===b.value2}).sort((p,b)=>p-b)[0]:void 0,S=(p,b)=>ge({startX:p,startY:b,angle:t,dominoes:n.dominoes,layoutStyle:r,dominoWidth:i,dominoHeight:l,leadGap:s,outwardSign:y,hubIndex:g});let T=S(e+(d?.x??0)*P*x,o+(d?.y??0)*P*x),C=u&&d?{x:u.x+d.x*P*x,y:u.y+d.y*P*x}:u;if(d&&c.length>0)for(let p=x;p<=ce;p++){const b=e+d.x*P*p,M=o+d.y*P*p,I=S(b,M);if(!I.some(h=>Ye(h,c,i,l))||p===ce){T=I,C=u&&{x:u.x+d.x*P*p,y:u.y+d.y*P*p};break}}c.push(...T);const m=[{angle:t,depth:a,layoutStyle:r,outwardSign:y,dominoes:n.dominoes,layout:T,anchor:C}];if(n.feet){const{dirX:p,dirY:b}=_(t),{perpX:M,perpY:I}=Y(t),A=i/2,h=l/2;for(const w of Object.keys(n.feet)){const k=Number(w),$=T[k],B=n.feet[k];if(!(!$||!$.isDouble||!B))for(let X=0;X<B.length;X++){const Ie=B[X],ne=ve[X]??0,L=Math.sign(ne),re=t+ne,ie=Y(re),V=-L,Pe=$.x+p*(i/2)+M*(l/2)*L,ke=$.y+b*(i/2)+I*(l/2)*L,le=Pe-ie.perpX*V*A,se=ke-ie.perpY*V*A;m.push(...Z({startX:le,startY:se,angle:re,branch:Ie,layoutStyle:r,dominoWidth:i,dominoHeight:l,leadGap:h,outwardSign:V,depth:a+1,anchor:{x:le,y:se},placed:c,pushAxis:{x:M*L,y:I*L}}))}}}return m}function he(e){return e.flatMap(o=>o.layout)}function Ue(e,o=24,t=D,n=O){const r=Math.hypot(t,n)/2;if(e.length===0)return{width:o*2+t,height:o*2+n,offsetX:o,offsetY:o};let i=1/0,l=1/0,s=-1/0,a=-1/0;for(const u of e)i=Math.min(i,u.x-r),l=Math.min(l,u.y-r),s=Math.max(s,u.x+r),a=Math.max(a,u.y+r);return{width:Math.ceil(s-i+o*2),height:Math.ceil(a-l+o*2),offsetX:o-i,offsetY:o-l}}const be=({startX:e,startY:o,angle:t,trainData:n,layoutStyle:r,tableWidth:i,tableHeight:l,centerX:s,centerY:a,pipColors:u})=>{const f=j.useMemo(()=>he(Z({startX:e,startY:o,angle:t,branch:{dominoes:n.dominoes,feet:n.feet},layoutStyle:r})),[e,o,t,n.dominoes,n.feet,r,i,l]);return v.jsx(v.Fragment,{children:f.map((c,d)=>{const x=n.isPublic;return v.jsx("div",{style:{position:"absolute",left:`${c.x-D/2}px`,top:`${c.y-O/2}px`,zIndex:5},children:v.jsx(J,{value1:c.value1,value2:c.value2,width:D,height:O,backgroundColor:"white",pipColor:"black",pipColors:u,borderColor:x?"red":"black",rotation:c.rotation})},`main-train-${n.playerId}-${d}`)})})},Se=({playerCount:e,centerX:o,centerY:t,radius:n,engineValue:r,trains:i,layoutStyle:l,tableWidth:s,tableHeight:a,pipColors:u})=>{const f=Math.max(8,e),c=120;return v.jsxs("div",{style:{position:"relative",width:"100%",height:"100%"},children:[v.jsx("div",{style:{position:"absolute",width:`${c}px`,height:`${c}px`,left:`${o-c/2}px`,top:`${t-c/2}px`,backgroundColor:"#d1d5db",borderWidth:"3px",borderStyle:"solid",borderColor:"#6b7280",borderRadius:"50%",boxShadow:"0 4px 6px rgba(0,0,0,0.1)",zIndex:10,display:"flex",justifyContent:"center",alignItems:"center"},children:v.jsx("div",{style:{transform:"rotate(0deg)"},children:v.jsx(J,{value1:r,value2:r,width:60,height:120,backgroundColor:"white",pipColor:"black",pipColors:u,borderColor:"#333"})})}),Array.from({length:f}).map((y,g)=>{const S=g*360/f,T=S*Math.PI/180,C=o+(n+20)*Math.cos(T),m=t+(n+20)*Math.sin(T),p=i.find(b=>b.playerId===g)||{dominoes:[],playerId:g,isPublic:!1};return v.jsx(be,{startX:C,startY:m,angle:S,trainData:p,layoutStyle:l,tableWidth:s,tableHeight:a,centerX:o,centerY:t,pipColors:u},g)})]})};function E(e,o){return e<=o?`${e}:${o}`:`${o}:${e}`}function H(e){return E(e.value1,e.value2)}function W(e){return e.value1===e.value2}function qe(e,o){return e.value1===o||e.value2===o}function Be(e,o){return e.value1===o?e.value2:e.value2===o?e.value1:null}function ee(e,o){return e.value1===o?{value1:e.value1,value2:e.value2}:e.value2===o?{value1:e.value2,value2:e.value1}:null}function Ve(e){const o=[];for(let t=0;t<=e;t++)for(let n=t;n<=e;n++)o.push({value1:t,value2:n});return o}function Ge(e){const o=e+1;return o*(o+1)/2}function Ke(e,o=12,t={}){const n=new Set([E(o,o)]),r=[];for(let i=0;i<e;i++){const l=4+Math.floor(Math.random()*7),s=[];let a=o,u=!1;for(let c=0;c<l;c++){const d=He(a,u,c===0,o,n);if(d===null)break;const x=d===a;n.add(E(a,d)),s.push({value1:a,value2:d}),u=x,a=d}const f=t.chickenFeet?Je(s,n):void 0;r.push({playerId:i,dominoes:s,isPublic:Math.random()>.7,...f?{feet:f}:{}})}return r}function Je(e,o){const t={};for(let n=0;n<e.length;n++){if(e[n].value1!==e[n].value2)continue;const r=e[n].value1,i=[];for(let l=0;l<2;l++){const s=Qe(r,o);s&&i.push(s)}i.length&&(t[n]=i)}return Object.keys(t).length?t:void 0}function Qe(e,o){const t=1+Math.floor(Math.random()*2),n=[];let r=e;for(let i=0;i<t;i++){const l=Ze(r,o);if(l===null)break;o.add(E(r,l)),n.push({value1:r,value2:l}),r=l}return n.length?{dominoes:n}:null}function Ze(e,o){const t=[];for(let n=0;n<13;n++)n!==e&&(o.has(E(e,n))||t.push(n));return t.length===0?null:t[Math.floor(Math.random()*t.length)]}function He(e,o,t,n,r){const i=Array.from({length:13},(l,s)=>s).filter(l=>We(e,l,o,t,n,r));return i.length===0?null:i[Math.floor(Math.random()*i.length)]}function We(e,o,t,n,r,i){const l=o===e;return!(n&&l&&e===r||l&&t||i.has(E(e,o)))}const eo={playerCount:8,trains:[],engineValue:12},oo=({initialState:e=eo,width:o=1200,height:t=800,pipColors:n,onPipColorsChange:r})=>{const[i,l]=j.useState(e),[s,a]=j.useState("offset"),[u,f]=j.useState(!1),[c,d]=j.useState(void 0),x=n??c,y=r??d,g=x!==void 0,S=o/2,T=t/2,C=(p=u)=>{const b=Ke(i.playerCount,i.engineValue,{chickenFeet:p});l(M=>({...M,trains:b}))};j.useEffect(()=>{C()},[]);const m=()=>{const p=!u;f(p),C(p)};return v.jsxs("div",{style:{width:`${o}px`,height:`${t}px`,position:"relative",backgroundColor:"#1f8a55",borderRadius:"8px",boxShadow:"0 4px 12px rgba(0,0,0,0.2)",overflow:"hidden"},children:[v.jsxs("div",{style:{position:"absolute",top:"10px",left:"10px",zIndex:100},children:[v.jsx("button",{onClick:()=>C(),style:{padding:"8px 12px",backgroundColor:"#fff",border:"1px solid #ccc",borderRadius:"4px",marginRight:"10px",cursor:"pointer"},children:"New trains"}),v.jsxs("button",{onClick:()=>a(s==="offset"?"linear":"offset"),style:{padding:"8px 12px",backgroundColor:"#fff",border:"1px solid #ccc",borderRadius:"4px",marginRight:"10px",cursor:"pointer"},children:["Layout: ",s==="offset"?"Offset":"Linear"]}),v.jsxs("button",{onClick:m,style:{padding:"8px 12px",backgroundColor:u?"#fef3c7":"#fff",border:`1px solid ${u?"#f59e0b":"#ccc"}`,borderRadius:"4px",marginRight:"10px",cursor:"pointer"},children:["Chicken Feet: ",u?"On":"Off"]}),v.jsxs("button",{onClick:()=>y(g?void 0:F),style:{padding:"8px 12px",backgroundColor:g?"#fef3c7":"#fff",border:`1px solid ${g?"#f59e0b":"#ccc"}`,borderRadius:"4px",cursor:"pointer"},children:["Pip Colors: ",g?"On":"Off"]})]}),v.jsxs("div",{style:{position:"absolute",top:"10px",right:"10px",zIndex:100,backgroundColor:"rgba(255,255,255,0.8)",padding:"8px",borderRadius:"4px",fontSize:"14px"},children:[v.jsxs("div",{children:["Engine: Double-",i.engineValue]}),v.jsxs("div",{children:["Players: ",i.playerCount]})]}),v.jsx(Se,{playerCount:i.playerCount,centerX:S,centerY:T,radius:80,engineValue:i.engineValue,trains:i.trains,layoutStyle:s,tableWidth:o,tableHeight:t,pipColors:x})]})},U=1;function we(e,o,t){const{dirX:n,dirY:r}=_(t);return e*n+o*r}function to(e,o,t){const{perpX:n,perpY:r}=Y(t);return e*n+o*r}function oe(e){const o=[];for(let t=1;t<e.length;t++)e[t].value1!==e[t-1].value2&&o.push({code:"chain-break",message:`Domino ${t} does not connect to domino ${t-1}`,index:t});for(let t=1;t<e.length;t++){const n=e[t-1].value1===e[t-1].value2,r=e[t].value1===e[t].value2;n&&r&&o.push({code:"consecutive-doubles",message:`Consecutive doubles at index ${t-1} and ${t}`,index:t})}return{valid:o.length===0,issues:o}}function ye(e,o,t,n=D,r=O){const i=n/2,l=e.map(c=>c.isDouble),s=[];let a=0,u=0,f=0;for(let c=0;c<e.length;c++){const d=l[c],x=c>0&&l[c-1];o==="linear"?(c>0&&(a+=z(x,d,n,r)),u=0):d?c>0&&(a+=z(x,!0,n,r)):c===0?(f=t,u=f*i):x?a+=z(!0,!1,n,r):(a+=r/2,f=xe(f,t),u=f*i),s.push({along:a,perp:u})}return s}function no(e,o,t,n=U,r){const i=[],l=r??R(o),s=ye(e,t,l);for(let a=1;a<e.length;a++){const u=e[a-1],f=e[a],c=f.x-u.x,d=f.y-u.y,x=we(c,d,o),y=to(c,d,o),g=s[a].along-s[a-1].along,S=s[a].perp-s[a-1].perp;Math.abs(x-g)>n&&i.push({code:"spacing-along-train",message:`Along-train spacing between domino ${a-1} and ${a} is ${x.toFixed(2)}px (expected ${g}px)`,index:a}),Math.abs(y-S)>n&&i.push({code:"spacing-perpendicular",message:`Perpendicular spacing between domino ${a-1} and ${a} is ${y.toFixed(2)}px (expected ${S}px)`,index:a})}return{valid:i.length===0,issues:i}}function ro(e,o,t,n=U,r){const i=[],l=r??R(o),s=ye(e,t,l);for(let a=1;a<e.length;a++){const u=e[a-1],f=e[a],c=f.x-u.x,d=f.y-u.y,x=Math.hypot(c,d),y=s[a].along-s[a-1].along,g=s[a].perp-s[a-1].perp,S=Math.hypot(y,g)*.9;x+n<S&&i.push({code:"overlap",message:`Domino ${a-1} and ${a} centers are ${x.toFixed(2)}px apart (minimum ${S.toFixed(2)}px)`,index:a})}return{valid:i.length===0,issues:i}}function io(e,o,t,n,r=U,i){const l=[...oe(o).issues,...no(e,t,n,r,i).issues,...ro(e,t,n,r,i).issues];return e.length!==o.length&&l.push({code:"layout-length",message:`Layout length ${e.length} does not match domino count ${o.length}`}),{valid:l.length===0,issues:l}}function lo(e){const o=[],t=(n,r)=>{if(o.push(...oe(n.dominoes).issues.map(i=>({...i,message:`[${r}] ${i.message}`}))),!!n.feet)for(const i of Object.keys(n.feet)){const l=Number(i),s=n.dominoes[l],a=n.feet[l]??[];if(!s){o.push({code:"foot-host-missing",message:`[${r}] Foot references missing tile ${l}`});continue}s.value1!==s.value2&&o.push({code:"foot-host-not-double",message:`[${r}] Foot host tile ${l} is not a double`}),a.length>2&&o.push({code:"foot-too-many-toes",message:`[${r}] Double ${l} has ${a.length} side toes (max 2; the center toe is the main line)`}),a.forEach((u,f)=>{const c=u.dominoes[0];c&&c.value1!==s.value1&&o.push({code:"foot-connection",message:`[${r}] Toe ${f} on double ${l} starts with ${c.value1} but the double is ${s.value1}`}),t(u,`${r}.${l}.${f}`)})}};return t(e,"main"),{valid:o.length===0,issues:o}}function so(e,o=U){const t=[];e.forEach((r,i)=>{if(t.push(...oe(r.dominoes).issues.map(l=>({...l,message:`[segment ${i} @${r.angle}°] ${l.message}`}))),r.layout.length!==r.dominoes.length&&t.push({code:"layout-length",message:`[segment ${i}] Layout length ${r.layout.length} does not match domino count ${r.dominoes.length}`}),r.anchor&&r.layout.length>0){const l=r.layout[0],s=we(l.x-r.anchor.x,l.y-r.anchor.y,r.angle),a=O/2;Math.abs(s-a)>o&&t.push({code:"foot-anchor",message:`[segment ${i}] First toe tile sits ${s.toFixed(2)}px from the double along the toe (expected ${a}px)`,index:0})}});const n=e.flatMap(r=>r.layout);for(let r=0;r<n.length;r++)for(let i=r+1;i<n.length;i++)Q(n[r],n[i])&&t.push({code:"tile-overlap",message:`Tiles ${r} and ${i} overlap`,index:i});return{valid:t.length===0,issues:t}}const ao=[{id:"regular-after-double",name:"Regular after double",description:"Double followed by a two-tile offset run",angle:0,dominoes:[{value1:12,value2:6},{value1:6,value2:6},{value1:6,value2:3},{value1:3,value2:1}],layoutStyles:["linear","offset"]},{id:"double-after-regular",name:"Double after regular",description:"Offset run, a double, then another offset run",angle:0,dominoes:[{value1:12,value2:9},{value1:9,value2:4},{value1:4,value2:4},{value1:4,value2:2},{value1:2,value2:7}],layoutStyles:["linear","offset"]},{id:"double-after-double",name:"Double after double",description:"Offset runs at the head, middle, and tail around two doubles",angle:90,dominoes:[{value1:12,value2:7},{value1:7,value2:8},{value1:8,value2:8},{value1:8,value2:3},{value1:3,value2:5},{value1:5,value2:5},{value1:5,value2:2},{value1:2,value2:1}],layoutStyles:["linear","offset"]},{id:"offset-zigzag",name:"Offset zigzag",description:"Alternating perpendicular tiles without doubles",angle:0,dominoes:[{value1:12,value2:5},{value1:5,value2:9},{value1:9,value2:2},{value1:2,value2:7},{value1:7,value2:1}],layoutStyles:["offset"]},{id:"horizontal-open",name:"Horizontal train",description:"Rightward train: offset head, double, offset tail",angle:0,dominoes:[{value1:5,value2:12},{value1:12,value2:11},{value1:11,value2:11},{value1:11,value2:6},{value1:6,value2:2}],layoutStyles:["linear","offset"]},{id:"vertical-open",name:"Vertical train",description:"Downward train: offset head, double, offset tail",angle:90,dominoes:[{value1:3,value2:12},{value1:12,value2:10},{value1:10,value2:10},{value1:10,value2:4},{value1:4,value2:1}],layoutStyles:["linear","offset"]}],uo=[{id:"single-foot",name:"Single foot",description:"A double fans two angled toes (±45°) while the main line continues straight as the center toe",angle:0,branch:{dominoes:[{value1:12,value2:6},{value1:6,value2:6},{value1:6,value2:3},{value1:3,value2:1}],feet:{1:[{dominoes:[{value1:6,value2:2},{value1:2,value2:5}]},{dominoes:[{value1:6,value2:4},{value1:4,value2:0}]}]}},layoutStyles:["linear","offset"]},{id:"foot-no-center",name:"Foot at the tail",description:"Double ends the main line, so both side toes are present with no straight continuation",angle:0,branch:{dominoes:[{value1:9,value2:7},{value1:7,value2:7}],feet:{1:[{dominoes:[{value1:7,value2:3},{value1:3,value2:8}]},{dominoes:[{value1:7,value2:5},{value1:5,value2:0}]}]}},layoutStyles:["linear","offset"]},{id:"nested-foot",name:"Nested foot",description:"A side toe contains its own double, which sprouts a second-level foot",angle:90,branch:{dominoes:[{value1:12,value2:8},{value1:8,value2:8},{value1:8,value2:3}],feet:{1:[{dominoes:[{value1:8,value2:5},{value1:5,value2:5},{value1:5,value2:2}],feet:{1:[{dominoes:[{value1:5,value2:9},{value1:9,value2:1}]},{dominoes:[{value1:5,value2:4},{value1:4,value2:6}]}]}},{dominoes:[{value1:8,value2:1},{value1:1,value2:7}]}]}},layoutStyles:["linear","offset"]}],N={maxPips:12,engineValue:12,allowConsecutiveDoubles:!1,requireUniqueTiles:!0,requireSequential:!0,doubleObligation:"cover",chickenFoot:{toeCount:3,sideToeAngles:[-45,45]}};function me(e){switch(e.doubleObligation){case"chicken-foot":return Math.max(1,e.chickenFoot.toeCount);case"cover":return 1;default:return 0}}function ze(e){return e.doubleObligation==="chicken-foot"?Math.max(0,e.chickenFoot.toeCount-1):0}function co(e={}){const o=e.maxPips??N.maxPips;return{...N,...e,maxPips:o,engineValue:e.engineValue??o,chickenFoot:{...N.chickenFoot,...e.chickenFoot??{}}}}function Te(e,o){let t=e;for(const n of o)if(t=t?.feet?.[n.doubleIndex]?.[n.toeIndex],!t)return;return t}function q(e,o,t){if(t(e,o),!!e.feet)for(const n of Object.keys(e.feet)){const r=Number(n);e.feet[r].forEach((i,l)=>{q(i,[...o,{doubleIndex:r,toeIndex:l}],t)})}}function fo(e){const o=[];return q(e,[],(t,n)=>{t.dominoes.forEach((r,i)=>{if(r.value1!==r.value2)return;const l=i<t.dominoes.length-1,s=t.feet?.[i]?.length??0;o.push({path:n,doubleIndex:i,value:r.value1,hasCenter:l,sideToes:s,answers:(l?1:0)+s})})}),o}function Ce(e,o){const t=me(o);return t<=0?[]:fo(e).filter(n=>n.answers<t)}function Me(e){const o=new Set;return q(e,[],t=>{for(const n of t.dominoes)o.add(H(n))}),o}function $e(e,o,t){if(e.dominoes.length===0)return[{path:[],attach:"run-tail",value:o,attachToDouble:!0,obligation:!1}];const n=Ce(e,t);if(t.doubleObligation!=="none"&&n.length>0){const i=ze(t),l=[];for(const s of n){const a=Te(e,s.path);a&&(!s.hasCenter&&s.doubleIndex===a.dominoes.length-1&&l.push({path:s.path,attach:"run-tail",value:s.value,attachToDouble:!0,obligation:!0}),s.sideToes<i&&l.push({path:s.path,attach:"side-toe",value:s.value,doubleIndex:s.doubleIndex,toeSlot:s.sideToes,attachToDouble:!0,obligation:!0}))}return l}const r=[];return q(e,[],(i,l)=>{const s=i.dominoes[i.dominoes.length-1];s&&r.push({path:l,attach:"run-tail",value:s.value2,attachToDouble:W(s),obligation:!1})}),r}function te(e,o,t,n){const r=[],i=ee(e,o.value);return n.requireSequential&&!i&&r.push("value-mismatch"),n.requireUniqueTiles&&t.has(H(e))&&r.push("duplicate-tile"),!n.allowConsecutiveDoubles&&o.attachToDouble&&W(e)&&r.push("consecutive-doubles"),{legal:r.length===0,violations:r}}function po(e,o,t,n,r){const i=$e(e,o,r),l=[];for(const s of i)for(const a of t)te(a,s,n,r).legal&&l.push({end:s,tile:a});return l}function De(e,o,t){if(o.length===0)return t(e);const[n,...r]=o,l=(e.feet?.[n.doubleIndex]??[]).map((s,a)=>a===n.toeIndex?De(s,r,t):s);return{...e,feet:{...e.feet,[n.doubleIndex]:l}}}function Oe(e,o,t){const n=ee(o.tile,o.end.value)??{...o.tile};return De(e,o.end.path,r=>{if(o.end.attach==="run-tail")return{...r,dominoes:[...r.dominoes,n]};const i=o.end.doubleIndex??0,l=o.end.toeSlot??r.feet?.[i]?.length??0,s=r.feet?.[i]?[...r.feet[i]]:[];return s[l]={dominoes:[n]},{...r,feet:{...r.feet,[i]:s}}})}function vo(e,o,t){const n=te(o.tile,o.end,Me(e),t);return n.legal?{ok:!0,board:Oe(e,o),violations:[]}:{ok:!1,board:e,violations:n.violations}}exports.CHICKEN_FOOT_FIXTURES=uo;exports.CHICKEN_FOOT_TOE_ANGLES=ve;exports.DEFAULT_PIP_COLORS=F;exports.DEFAULT_RULES=N;exports.DominoHub=Se;exports.DominoTrain=be;exports.DoubleTwelve=J;exports.MexicanTrainGame=oo;exports.PIP_COLORS=Fe;exports.PIP_LAYOUTS=fe;exports.TRAIN_FIXTURES=ao;exports.applyMove=Oe;exports.collectPlayedKeys=Me;exports.computeTrainLayout=ge;exports.computeTrainTree=Z;exports.dominoKey=H;exports.dominoSetSize=Ge;exports.evaluatePlacement=te;exports.flattenSegments=he;exports.generateDominoSet=Ve;exports.getBranchAt=Te;exports.getLegalMoves=po;exports.getOpenEnds=$e;exports.getPipLayout=pe;exports.getPipStyle=Le;exports.getTrainLayoutBounds=Ue;exports.getUnsatisfiedDoubles=Ce;exports.isDouble=W;exports.mergePipColors=Ae;exports.orientForConnection=ee;exports.otherEnd=Be;exports.outwardPerpSign=R;exports.playMove=vo;exports.requiredDoubleAnswers=me;exports.resolvePipPosition=de;exports.resolvePipStyle=K;exports.resolveRules=co;exports.sideToeSlots=ze;exports.stepAlongTrain=z;exports.tileCorners=G;exports.tileHasValue=qe;exports.tileKey=E;exports.tilesOverlap=Q;exports.validateChickenFootChain=lo;exports.validateTrainLayout=io;exports.validateTrainTree=so;
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const g=require("react/jsx-runtime"),k=require("react"),He={"3x3":{rows:[20,50,80],cols:[20,50,80],size:"18%"},"3x4":{rows:[24,50,76],cols:[22,40,60,78],size:"12%"},"4x3":{rows:[15,38,62,85],cols:[20,50,80],size:"14%"}};function Te(e){const t=He[e.gridSize];return{top:e.top??`${t.rows[e.row]}%`,left:e.left??`${t.cols[e.col]}%`,width:t.size,height:t.size}}const Je=({row:e,col:t,gridSize:n,color:o,hollow:r,top:i,left:l})=>{const s=Te({row:e,col:t,gridSize:n,top:i,left:l});return g.jsx("div",{"data-testid":"pip","data-row":e,"data-col":t,"data-grid":n,style:{position:"absolute",backgroundColor:r?"transparent":o,border:r?"2px solid #888":void 0,borderRadius:"50%",transform:"translate(-50%, -50%)",boxShadow:r?void 0:"1px 2px 3px rgba(0,0,0,0.3)",...s}})},Me={0:[],1:[{row:1,col:1,gridSize:"3x3"}],2:[{row:0,col:2,gridSize:"3x3"},{row:2,col:0,gridSize:"3x3"}],3:[{row:0,col:2,gridSize:"3x3"},{row:1,col:1,gridSize:"3x3"},{row:2,col:0,gridSize:"3x3"}],4:[{row:0,col:0,gridSize:"3x3"},{row:0,col:2,gridSize:"3x3"},{row:2,col:0,gridSize:"3x3"},{row:2,col:2,gridSize:"3x3"}],5:[{row:0,col:0,gridSize:"3x3"},{row:0,col:2,gridSize:"3x3"},{row:1,col:1,gridSize:"3x3"},{row:2,col:0,gridSize:"3x3"},{row:2,col:2,gridSize:"3x3"}],6:[{row:0,col:0,gridSize:"3x3"},{row:0,col:2,gridSize:"3x3"},{row:1,col:0,gridSize:"3x3"},{row:1,col:2,gridSize:"3x3"},{row:2,col:0,gridSize:"3x3"},{row:2,col:2,gridSize:"3x3"}],7:[{row:0,col:0,gridSize:"3x3"},{row:0,col:2,gridSize:"3x3"},{row:1,col:0,gridSize:"3x3"},{row:1,col:1,gridSize:"3x3"},{row:1,col:2,gridSize:"3x3"},{row:2,col:0,gridSize:"3x3"},{row:2,col:2,gridSize:"3x3"}],8:[{row:0,col:0,gridSize:"3x3"},{row:0,col:1,gridSize:"3x3"},{row:0,col:2,gridSize:"3x3"},{row:1,col:0,gridSize:"3x3"},{row:1,col:2,gridSize:"3x3"},{row:2,col:0,gridSize:"3x3"},{row:2,col:1,gridSize:"3x3"},{row:2,col:2,gridSize:"3x3"}],9:[{row:0,col:0,gridSize:"3x3"},{row:0,col:1,gridSize:"3x3"},{row:0,col:2,gridSize:"3x3"},{row:1,col:0,gridSize:"3x3"},{row:1,col:1,gridSize:"3x3"},{row:1,col:2,gridSize:"3x3"},{row:2,col:0,gridSize:"3x3"},{row:2,col:1,gridSize:"3x3"},{row:2,col:2,gridSize:"3x3"}],10:[{row:0,col:0,gridSize:"3x4"},{row:0,col:1,gridSize:"3x4"},{row:0,col:2,gridSize:"3x4"},{row:0,col:3,gridSize:"3x4"},{row:1,col:0,gridSize:"3x4"},{row:1,col:3,gridSize:"3x4"},{row:2,col:0,gridSize:"3x4"},{row:2,col:1,gridSize:"3x4"},{row:2,col:2,gridSize:"3x4"},{row:2,col:3,gridSize:"3x4"}],11:[{row:0,col:0,gridSize:"4x3"},{row:1,col:0,gridSize:"4x3"},{row:2,col:0,gridSize:"4x3"},{row:3,col:0,gridSize:"4x3"},{row:0,col:1,gridSize:"4x3"},{row:2,col:1,gridSize:"4x3",top:"50%"},{row:3,col:1,gridSize:"4x3"},{row:0,col:2,gridSize:"4x3"},{row:1,col:2,gridSize:"4x3"},{row:2,col:2,gridSize:"4x3"},{row:3,col:2,gridSize:"4x3"}],12:[{row:0,col:0,gridSize:"4x3"},{row:1,col:0,gridSize:"4x3"},{row:2,col:0,gridSize:"4x3"},{row:3,col:0,gridSize:"4x3"},{row:0,col:1,gridSize:"4x3"},{row:1,col:1,gridSize:"4x3"},{row:2,col:1,gridSize:"4x3"},{row:3,col:1,gridSize:"4x3"},{row:0,col:2,gridSize:"4x3"},{row:1,col:2,gridSize:"4x3"},{row:2,col:2,gridSize:"4x3"},{row:3,col:2,gridSize:"4x3"}]};function Ce(e){return Me[e]??[]}const Y={0:{color:"transparent"},1:{color:"#1a1a1a"},2:{color:"#8B1A1A"},3:{color:"#E6B800"},4:{color:"#e8e8e8",hollow:!0},5:{color:"#2E8B57"},6:{color:"#2563EB"},7:{color:"#E8A87C"},8:{color:"#DC2626"},9:{color:"#1E3A8A"},10:{color:"#EA580C"},11:{color:"#166534"},12:{color:"#DC2626"}},Qe=Y;function We(e){return{...Y,...e}}function se(e,t){if(t!==void 0)return t[e]??Y[e]??{color:"#1a1a1a"}}function et(e){return se(e,Y)}const tt=(e,t,n)=>{const o=se(e,n);return o?{color:o.color,hollow:o.hollow}:{color:t}},ot=({value:e,pipColor:t,pipColors:n})=>{const{color:o,hollow:r}=tt(e,t,n),i=Ce(e);return g.jsx(g.Fragment,{children:i.map((l,s)=>g.jsx(Je,{row:l.row,col:l.col,gridSize:l.gridSize,color:o,hollow:r,top:l.top,left:l.left},s))})},we=({value:e,pipColor:t,pipColors:n})=>g.jsx("div",{style:{width:"100%",height:"100%",position:"relative",padding:"0",overflow:"hidden"},children:g.jsx(ot,{value:e,pipColor:t,pipColors:n})}),ae=({value1:e=0,value2:t=0,width:n=100,height:o=200,backgroundColor:r="white",pipColor:i="black",pipColors:l,borderColor:s="black",rotation:a=0})=>{const c=Math.min(Math.max(e,0),12),d=Math.min(Math.max(t,0),12);return g.jsxs("div",{style:{width:`${n}px`,height:`${o}px`,backgroundColor:r,borderColor:s,borderWidth:"1px",borderStyle:"solid",borderRadius:"10px",transform:`rotate(${a}deg)`,transformOrigin:"center center",boxShadow:"0 1px 2px rgba(0,0,0,0.2)",display:"flex",flexDirection:"column",overflow:"hidden"},children:[g.jsx("div",{style:{flex:1,position:"relative",borderBottomWidth:"1px",borderBottomStyle:"solid",borderBottomColor:s},children:g.jsx(we,{value:c,pipColor:i,pipColors:l})}),g.jsx("div",{style:{flex:1,position:"relative"},children:g.jsx(we,{value:d,pipColor:i,pipColors:l})})]})},D=60,I=120,$e=[-45,45];function H(e,t=D,n=I){return e?t/2:n/2}function P(e,t,n=D,o=I){return H(e,n,o)+H(t,n,o)}function A(e){const t=e*Math.PI/180;return{dirX:Math.cos(t),dirY:Math.sin(t)}}function L(e){const{dirX:t,dirY:n}=A(e);return{perpX:-n,perpY:t}}function nt(e){const t=e.map(n=>({...n}));for(let n=1;n<t.length;n++){const o=t[n],r=t[n-1].value2;!(o.value1===o.value2)&&o.value1!==r&&o.value2===r&&(t[n]={value1:o.value2,value2:o.value1})}return t}function _(e){const{dirX:t,dirY:n}=A(e);return Math.abs(t)>=Math.abs(n)?t>=0?1:-1:n>=0?1:-1}function De(e,t){return e===0?t:e===t?-t:t}function Ie({orientedDominoes:e,startX:t,startY:n,angle:o,layoutStyle:r,dominoWidth:i,dominoHeight:l,leadGap:s,outwardSign:a,hubIndex:c}){const d=[],{dirX:u,dirY:f}=A(o),{perpX:p,perpY:y}=L(o),z=r==="offset"&&c!=null,m=[];let T=t+u*s,b=n+f*s,C=0,h=0;const w=i/2,$=v=>{const x=(v-C)*w;T+=p*x,b+=y*x,C=v};for(let v=0;v<e.length;v++){const x=e[v],S=x.value1===x.value2,M=v>0&&e[v-1].value1===e[v-1].value2;r==="linear"?v>0&&(S?(T+=u*P(M,!0,i,l),b+=f*P(M,!0,i,l)):M?(T+=u*P(!0,!1,i,l),b+=f*P(!0,!1,i,l)):(T+=u*l,b+=f*l)):S?v>0&&(T+=u*P(M,!0,i,l),b+=f*P(M,!0,i,l)):(v===0?h=a:M?(T+=u*P(!0,!1,i,l),b+=f*P(!0,!1,i,l)):(T+=u*(l/2),b+=f*(l/2),h=De(h,a)),$(h)),m.push(C),d.push({x:T,y:b,rotation:S?o+180:o-90,isDouble:S,value1:x.value1,value2:x.value2})}if(z&&c!=null){const v=-m[c]*w;if(v!==0)for(let x=0;x<d.length;x++)d[x]={...d[x],x:d[x].x+p*v,y:d[x].y+y*v}}return d}function ue(e,t){if(!e||e.length===0)return[];const n=new Map;for(const o of e)Number.isInteger(o.index)&&(o.index<=0||o.index>=t||n.set(o.index,o.turn));return[...n.entries()].map(([o,r])=>({index:o,turn:r})).sort((o,r)=>o.index-r.index)}function Pe(e,t,n,o=1/0){const r=ue(t,Number.isFinite(o)?o:n+1);let i=e;for(const l of r)if(l.index<=n)i+=l.turn;else break;return i}function rt(e,t,n,o){const{startX:r,startY:i,angle:l,layoutStyle:s,dominoWidth:a,dominoHeight:c,leadGap:d,outwardSign:u}=t,f=[0,...n.map(b=>b.index),e.length],p=[];let y=l,z=r,m=i,T=d;for(let b=0;b<f.length-1;b++){const C=e.slice(f[b],f[b+1]);if(C.length===0)continue;const h=b===0&&o!=null&&o<f[1]?o:void 0,w=Ie({orientedDominoes:C,startX:z,startY:m,angle:y,layoutStyle:s,dominoWidth:a,dominoHeight:c,leadGap:T,outwardSign:u,hubIndex:h});if(p.push(...w),b>=f.length-2)break;const v=w[w.length-1],x=A(y),S=H(v.isDouble,a,c);y+=n[b].turn;const M=e[f[b+1]],E=M.value1===M.value2,X=H(E,a,c),B=A(y),N=L(y),O=a/2,ne=v.x+x.dirX*(S-O)+B.dirX*(X+O),V=v.y+x.dirY*(S-O)+B.dirY*(X+O),j=s==="offset"&&!E,q=j?N.perpX*O*u:0,K=j?N.perpY*O*u:0;z=ne-q,m=V-K,T=0}return p}function ke({startX:e,startY:t,angle:n,dominoes:o,layoutStyle:r,dominoWidth:i=D,dominoHeight:l=I,leadGap:s=l*.3,outwardSign:a,hubIndex:c,bends:d}){const u=nt([...o]),f=a??_(n),p=ue(d,u.length);return p.length>0?rt(u,{startX:e,startY:t,angle:n,layoutStyle:r,dominoWidth:i,dominoHeight:l,leadGap:s,outwardSign:f},p,c):Ie({orientedDominoes:u,startX:e,startY:t,angle:n,layoutStyle:r,dominoWidth:i,dominoHeight:l,leadGap:s,outwardSign:f,hubIndex:c})}function ie(e,t=D,n=I){const o=e.rotation*Math.PI/180,r=Math.cos(o),i=Math.sin(o),l=t/2,s=n/2;return[[-l,-s],[l,-s],[l,s],[-l,s]].map(([a,c])=>({x:e.x+a*r-c*i,y:e.y+a*i+c*r}))}function it(e,t,n){let o=1/0,r=-1/0,i=1/0,l=-1/0;for(const s of e){const a=s.x*n.x+s.y*n.y;o=Math.min(o,a),r=Math.max(r,a)}for(const s of t){const a=s.x*n.x+s.y*n.y;i=Math.min(i,a),l=Math.max(l,a)}return Math.min(r,l)-Math.max(o,i)}function G(e,t,n=1,o=D,r=I){const i=ie(e,o,r),l=ie(t,o,r);for(const s of[i,l])for(let a=0;a<4;a++){const c=s[a],d=s[(a+1)%4],u=d.x-c.x,f=d.y-c.y,p=Math.hypot(u,f)||1,y={x:-f/p,y:u/p};if(it(i,l,y)<=n)return!1}return!0}function lt(e,t,n,o){return t.some(r=>G(e,r,1,n,o))}function ce(e,t,n=1,o=D,r=I){return e.some(i=>t.some(l=>G(i,l,n,o,r)))}function de(e,t=1,n=D,o=I){for(let r=0;r<e.length;r++)for(let i=r+1;i<e.length;i++)if(G(e[r],e[i],t,n,o))return!0;return!1}const R=D/4,ze=24;function Q({startX:e,startY:t,angle:n,branch:o,layoutStyle:r,dominoWidth:i=D,dominoHeight:l=I,leadGap:s,depth:a=0,anchor:c,outwardSign:d,placed:u=[],pushAxis:f,minPushSteps:p=0}){const y=d??_(n),z=o.feet?Object.keys(o.feet).map(Number).filter(h=>{const w=o.dominoes[h];return w&&w.value1===w.value2}).sort((h,w)=>h-w)[0]:void 0,m=(h,w)=>ke({startX:h,startY:w,angle:n,dominoes:o.dominoes,layoutStyle:r,dominoWidth:i,dominoHeight:l,leadGap:s,outwardSign:y,hubIndex:z,bends:o.bends});let T=m(e+(f?.x??0)*R*p,t+(f?.y??0)*R*p),b=c&&f?{x:c.x+f.x*R*p,y:c.y+f.y*R*p}:c;if(f&&u.length>0)for(let h=p;h<=ze;h++){const w=e+f.x*R*h,$=t+f.y*R*h,v=m(w,$);if(!v.some(S=>lt(S,u,i,l))||h===ze){T=v,b=c&&{x:c.x+f.x*R*h,y:c.y+f.y*R*h};break}}u.push(...T);const C=[{angle:n,depth:a,layoutStyle:r,outwardSign:y,dominoes:o.dominoes,layout:T,anchor:b}];if(o.feet){const h=i/2,w=l/2;for(const $ of Object.keys(o.feet)){const v=Number($),x=T[v],S=o.feet[v];if(!x||!x.isDouble||!S)continue;const M=Pe(n,o.bends,v,o.dominoes.length),{dirX:E,dirY:X}=A(M),{perpX:B,perpY:N}=L(M);for(let O=0;O<S.length;O++){const ne=S[O],V=$e[O]??0,j=Math.sign(V),q=M+V,K=L(q),re=-j,Ke=x.x+E*(i/2)+B*(l/2)*j,Ze=x.y+X*(i/2)+N*(l/2)*j,Se=Ke-K.perpX*re*h,me=Ze-K.perpY*re*h;C.push(...Q({startX:Se,startY:me,angle:q,branch:ne,layoutStyle:r,dominoWidth:i,dominoHeight:l,leadGap:w,outwardSign:re,depth:a+1,anchor:{x:Se,y:me},placed:u,pushAxis:{x:B*j,y:N*j}}))}}}return C}function fe(e){return e.flatMap(t=>t.layout)}function st(e,t=24,n=D,o=I){const r=Math.hypot(n,o)/2;if(e.length===0)return{width:t*2+n,height:t*2+o,offsetX:t,offsetY:t};let i=1/0,l=1/0,s=-1/0,a=-1/0;for(const c of e)i=Math.min(i,c.x-r),l=Math.min(l,c.y-r),s=Math.max(s,c.x+r),a=Math.max(a,c.y+r);return{width:Math.ceil(s-i+t*2),height:Math.ceil(a-l+t*2),offsetX:t-i,offsetY:t-l}}const W=1;function Oe(e,t,n){const{dirX:o,dirY:r}=A(n);return e*o+t*r}function at(e,t,n){const{perpX:o,perpY:r}=L(n);return e*o+t*r}function pe(e){const t=[];for(let n=1;n<e.length;n++)e[n].value1!==e[n-1].value2&&t.push({code:"chain-break",message:`Domino ${n} does not connect to domino ${n-1}`,index:n});for(let n=1;n<e.length;n++){const o=e[n-1].value1===e[n-1].value2,r=e[n].value1===e[n].value2;o&&r&&t.push({code:"consecutive-doubles",message:`Consecutive doubles at index ${n-1} and ${n}`,index:n})}return{valid:t.length===0,issues:t}}function Ee(e,t,n,o=D,r=I){const i=o/2,l=e.map(u=>u.isDouble),s=[];let a=0,c=0,d=0;for(let u=0;u<e.length;u++){const f=l[u],p=u>0&&l[u-1];t==="linear"?(u>0&&(a+=P(p,f,o,r)),c=0):f?u>0&&(a+=P(p,!0,o,r)):u===0?(d=n,c=d*i):p?a+=P(!0,!1,o,r):(a+=r/2,d=De(d,n),c=d*i),s.push({along:a,perp:c})}return s}function ut(e,t,n,o=W,r){const i=[],l=r??_(t),s=Ee(e,n,l);for(let a=1;a<e.length;a++){const c=e[a-1],d=e[a],u=d.x-c.x,f=d.y-c.y,p=Oe(u,f,t),y=at(u,f,t),z=s[a].along-s[a-1].along,m=s[a].perp-s[a-1].perp;Math.abs(p-z)>o&&i.push({code:"spacing-along-train",message:`Along-train spacing between domino ${a-1} and ${a} is ${p.toFixed(2)}px (expected ${z}px)`,index:a}),Math.abs(y-m)>o&&i.push({code:"spacing-perpendicular",message:`Perpendicular spacing between domino ${a-1} and ${a} is ${y.toFixed(2)}px (expected ${m}px)`,index:a})}return{valid:i.length===0,issues:i}}function ct(e,t,n,o=W,r){const i=[],l=r??_(t),s=Ee(e,n,l);for(let a=1;a<e.length;a++){const c=e[a-1],d=e[a],u=d.x-c.x,f=d.y-c.y,p=Math.hypot(u,f),y=s[a].along-s[a-1].along,z=s[a].perp-s[a-1].perp,m=Math.hypot(y,z)*.9;p+o<m&&i.push({code:"overlap",message:`Domino ${a-1} and ${a} centers are ${p.toFixed(2)}px apart (minimum ${m.toFixed(2)}px)`,index:a})}return{valid:i.length===0,issues:i}}function dt(e,t,n,o,r=W,i){const l=[...pe(t).issues,...ut(e,n,o,r,i).issues,...ct(e,n,o,r,i).issues];return e.length!==t.length&&l.push({code:"layout-length",message:`Layout length ${e.length} does not match domino count ${t.length}`}),{valid:l.length===0,issues:l}}function je(e){const t=[],n=(o,r)=>{if(t.push(...pe(o.dominoes).issues.map(i=>({...i,message:`[${r}] ${i.message}`}))),!!o.feet)for(const i of Object.keys(o.feet)){const l=Number(i),s=o.dominoes[l],a=o.feet[l]??[];if(!s){t.push({code:"foot-host-missing",message:`[${r}] Foot references missing tile ${l}`});continue}s.value1!==s.value2&&t.push({code:"foot-host-not-double",message:`[${r}] Foot host tile ${l} is not a double`}),a.length>2&&t.push({code:"foot-too-many-toes",message:`[${r}] Double ${l} has ${a.length} side toes (max 2; the center toe is the main line)`}),a.forEach((c,d)=>{const u=c.dominoes[0];u&&u.value1!==s.value1&&t.push({code:"foot-connection",message:`[${r}] Toe ${d} on double ${l} starts with ${u.value1} but the double is ${s.value1}`}),n(c,`${r}.${l}.${d}`)})}};return n(e,"main"),{valid:t.length===0,issues:t}}function ft(e,t=W){const n=[];e.forEach((r,i)=>{if(n.push(...pe(r.dominoes).issues.map(l=>({...l,message:`[segment ${i} @${r.angle}°] ${l.message}`}))),r.layout.length!==r.dominoes.length&&n.push({code:"layout-length",message:`[segment ${i}] Layout length ${r.layout.length} does not match domino count ${r.dominoes.length}`}),r.anchor&&r.layout.length>0){const l=r.layout[0],s=Oe(l.x-r.anchor.x,l.y-r.anchor.y,r.angle),a=I/2;Math.abs(s-a)>t&&n.push({code:"foot-anchor",message:`[segment ${i}] First toe tile sits ${s.toFixed(2)}px from the double along the toe (expected ${a}px)`,index:0})}});const o=e.flatMap(r=>r.layout);for(let r=0;r<o.length;r++)for(let i=r+1;i<o.length;i++)G(o[r],o[i])&&n.push({code:"tile-overlap",message:`Tiles ${r} and ${i} overlap`,index:i});return{valid:n.length===0,issues:n}}const pt=(()=>{try{return!1}catch{return!1}})(),Re=({startX:e,startY:t,angle:n,trainData:o,layoutStyle:r,tableWidth:i,tableHeight:l,centerX:s,centerY:a,pipColors:c})=>{k.useEffect(()=>{if(!pt)return;const u=je({dominoes:o.dominoes,feet:o.feet});u.valid||console.warn(`DominoTrain: player ${o.playerId} train does not follow the rules:`,u.issues.map(f=>f.message))},[o.dominoes,o.feet,o.playerId]);const d=k.useMemo(()=>fe(Q({startX:e,startY:t,angle:n,branch:{dominoes:o.dominoes,feet:o.feet},layoutStyle:r})),[e,t,n,o.dominoes,o.feet,r,i,l]);return g.jsx(g.Fragment,{children:d.map((u,f)=>{const p=o.isPublic;return g.jsx("div",{style:{position:"absolute",left:`${u.x-D/2}px`,top:`${u.y-I/2}px`,zIndex:5},children:g.jsx(ae,{value1:u.value1,value2:u.value2,width:D,height:I,backgroundColor:"white",pipColor:"black",pipColors:c,borderColor:p?"red":"black",rotation:u.rotation})},`main-train-${o.playerId}-${f}`)})})};function Ae(e,t,n,o){const r=n*(o==="offset"?2.5:1.3);return Math.max(t+20,Math.ceil(r*e/(2*Math.PI)))}const Fe=({playerCount:e,centerX:t,centerY:n,radius:o,engineValue:r,trains:i,layoutStyle:l,tableWidth:s,tableHeight:a,pipColors:c})=>{const d=Math.max(8,e),u=120,f=60,p=120,y=Ae(d,o,f,l);return g.jsxs("div",{style:{position:"relative",width:"100%",height:"100%"},children:[g.jsx("div",{style:{position:"absolute",width:`${u}px`,height:`${u}px`,left:`${t-u/2}px`,top:`${n-u/2}px`,backgroundColor:"#d1d5db",borderWidth:"3px",borderStyle:"solid",borderColor:"#6b7280",borderRadius:"50%",boxShadow:"0 4px 6px rgba(0,0,0,0.1)",zIndex:10,display:"flex",justifyContent:"center",alignItems:"center"},children:g.jsx("div",{style:{transform:"rotate(0deg)"},children:g.jsx(ae,{value1:r,value2:r,width:f,height:p,backgroundColor:"white",pipColor:"black",pipColors:c,borderColor:"#333"})})}),Array.from({length:d}).map((z,m)=>{const T=m*360/d,b=T*Math.PI/180,C=t+y*Math.cos(b),h=n+y*Math.sin(b),w=i.find($=>$.playerId===m)||{dominoes:[],playerId:m,isPublic:!1};return g.jsx(Re,{startX:C,startY:h,angle:T,trainData:w,layoutStyle:l,tableWidth:s,tableHeight:a,centerX:t,centerY:n,pipColors:c},m)})]})};function F(e,t){return e<=t?`${e}:${t}`:`${t}:${e}`}function xe(e){return F(e.value1,e.value2)}function ge(e){return e.value1===e.value2}function xt(e,t){return e.value1===t||e.value2===t}function gt(e,t){return e.value1===t?e.value2:e.value2===t?e.value1:null}function he(e,t){return e.value1===t?{value1:e.value1,value2:e.value2}:e.value2===t?{value1:e.value2,value2:e.value1}:null}function ht(e){const t=[];for(let n=0;n<=e;n++)for(let o=n;o<=e;o++)t.push({value1:n,value2:o});return t}function vt(e){const t=e+1;return t*(t+1)/2}function Xe(e,t=12,n={}){const o=new Set([F(t,t)]),r=[];for(let i=0;i<e;i++){const l=4+Math.floor(Math.random()*7),s=[];let a=t,c=!1;for(let u=0;u<l;u++){const f=mt(a,c,u===0,t,o);if(f===null)break;const p=f===a;o.add(F(a,f)),s.push({value1:a,value2:f}),c=p,a=f}const d=n.chickenFeet?bt(s,o):void 0;r.push({playerId:i,dominoes:s,isPublic:Math.random()>.7,...d?{feet:d}:{}})}return r}function bt(e,t){const n={};for(let o=0;o<e.length;o++){if(e[o].value1!==e[o].value2)continue;const r=e[o].value1,i=[];for(let l=0;l<2;l++){const s=yt(r,t);s&&i.push(s)}i.length&&(n[o]=i)}return Object.keys(n).length?n:void 0}function yt(e,t){const n=1+Math.floor(Math.random()*2),o=[];let r=e;for(let i=0;i<n;i++){const l=St(r,t);if(l===null)break;t.add(F(r,l)),o.push({value1:r,value2:l}),r=l}return o.length?{dominoes:o}:null}function St(e,t){const n=[];for(let o=0;o<13;o++)o!==e&&(t.has(F(e,o))||n.push(o));return n.length===0?null:n[Math.floor(Math.random()*n.length)]}function mt(e,t,n,o,r){const i=Array.from({length:13},(l,s)=>s).filter(l=>wt(e,l,t,n,o,r));return i.length===0?null:i[Math.floor(Math.random()*i.length)]}function wt(e,t,n,o,r,i){const l=t===e;return!(o&&l&&e===r||l&&n||i.has(F(e,t)))}const zt={playerCount:8,trains:[],engineValue:12},Tt=({initialState:e=zt,width:t=1200,height:n=800,pipColors:o,onPipColorsChange:r})=>{const[i,l]=k.useState(e),[s,a]=k.useState("offset"),[c,d]=k.useState(!1),[u,f]=k.useState(void 0),p=o??u,y=r??f,z=p!==void 0,m=t/2,T=n/2,b=(h=c)=>{const w=Xe(i.playerCount,i.engineValue,{chickenFeet:h});l($=>({...$,trains:w}))};k.useEffect(()=>{b()},[]);const C=()=>{const h=!c;d(h),b(h)};return g.jsxs("div",{style:{width:`${t}px`,height:`${n}px`,position:"relative",backgroundColor:"#1f8a55",borderRadius:"8px",boxShadow:"0 4px 12px rgba(0,0,0,0.2)",overflow:"hidden"},children:[g.jsxs("div",{style:{position:"absolute",top:"10px",left:"10px",zIndex:100},children:[g.jsx("button",{onClick:()=>b(),style:{padding:"8px 12px",backgroundColor:"#fff",border:"1px solid #ccc",borderRadius:"4px",marginRight:"10px",cursor:"pointer"},children:"New trains"}),g.jsxs("button",{onClick:()=>a(s==="offset"?"linear":"offset"),style:{padding:"8px 12px",backgroundColor:"#fff",border:"1px solid #ccc",borderRadius:"4px",marginRight:"10px",cursor:"pointer"},children:["Layout: ",s==="offset"?"Offset":"Linear"]}),g.jsxs("button",{onClick:C,style:{padding:"8px 12px",backgroundColor:c?"#fef3c7":"#fff",border:`1px solid ${c?"#f59e0b":"#ccc"}`,borderRadius:"4px",marginRight:"10px",cursor:"pointer"},children:["Chicken Feet: ",c?"On":"Off"]}),g.jsxs("button",{onClick:()=>y(z?void 0:Y),style:{padding:"8px 12px",backgroundColor:z?"#fef3c7":"#fff",border:`1px solid ${z?"#f59e0b":"#ccc"}`,borderRadius:"4px",cursor:"pointer"},children:["Pip Colors: ",z?"On":"Off"]})]}),g.jsxs("div",{style:{position:"absolute",top:"10px",right:"10px",zIndex:100,backgroundColor:"rgba(255,255,255,0.8)",padding:"8px",borderRadius:"4px",fontSize:"14px"},children:[g.jsxs("div",{children:["Engine: Double-",i.engineValue]}),g.jsxs("div",{children:["Players: ",i.playerCount]})]}),g.jsx(Fe,{playerCount:i.playerCount,centerX:m,centerY:T,radius:80,engineValue:i.engineValue,trains:i.trains,layoutStyle:s,tableWidth:t,tableHeight:n,pipColors:p})]})},ee=90;function J(e,t=ee){return e==="right"?t:-t}function ve(e){return e==="right"?"left":"right"}function Mt(e,t){return(t??_(e))>=0?"left":"right"}function Ct(e,t,n){const{perpX:o,perpY:r}=L(t),i=(a,c)=>{let d=1/0;return a>0?d=Math.min(d,(n.width-e.x)/a):a<0&&(d=Math.min(d,(0-e.x)/a)),c>0?d=Math.min(d,(n.height-e.y)/c):c<0&&(d=Math.min(d,(0-e.y)/c)),Number.isFinite(d)?Math.max(0,d):1/0},l=i(o,r),s=i(-o,-r);return l>=s?"right":"left"}function be(e,t){return fe(Q({startX:t.startX,startY:t.startY,angle:t.angle,branch:e,layoutStyle:t.layoutStyle}))}function U(e,t,n){const o=(e??[]).filter(r=>r.index!==t);return n===null?o:[...o,{index:t,turn:n}].sort((r,i)=>r.index-i.index)}function $t({branch:e,index:t,build:n,obstacles:o,preferredSide:r,degrees:i=ee}){const l=[r,ve(r)];for(const s of l){const a=J(s,i),c={...e,bends:U(e.bends,t,a)},d=be(c,n);if(!de(d)&&!ce(d,o))return{turn:a}}return{turn:null,reason:"blocked"}}function Dt(e,t,n,o,r,i=ee){const l=(e.bends??[]).find(u=>u.index===t),s=J(r,i),a=J(ve(r),i),c=u=>{const f={...e,bends:U(e.bends,t,u)},p=be(f,n);return!de(p)&&!ce(p,o)};let d;l?l.turn===s?d=[a,null]:l.turn===a?d=[null]:d=[s,a,null]:d=[s,a];for(const u of d){if(u===null)return{bends:U(e.bends,t,null),changed:!0,blocked:!1};if(c(u))return{bends:U(e.bends,t,u),changed:!0,blocked:!1}}return{bends:e.bends??[],changed:!1,blocked:!0}}function te(e,t,n){return Math.min(n,Math.max(t,e))}function le(e,t,n,o,r){const i=te(e.scale*t,o,r),l=i/e.scale;return{scale:i,x:n.x-(n.x-e.x)*l,y:n.y-(n.y-e.y)*l}}function Le(e,t,n,o,r){const i=Math.max(1,e.width),l=Math.max(1,e.height),s=Math.min((t.width-n*2)/i,(t.height-n*2)/l),a=te(s,o,r);return{scale:a,x:(t.width-i*a)/2,y:(t.height-l*a)/2}}function It(e,t){return{x:(t.x-e.x)/e.scale,y:(t.y-e.y)/e.scale}}const Pt=3,kt=({width:e,height:t,contentWidth:n,contentHeight:o,children:r,minScale:i=.2,maxScale:l=4,zoomStep:s=1.15,padding:a=40,background:c="#1f8a55",showControls:d=!0,testId:u="viewport"})=>{const f=k.useRef(null),p=k.useCallback(()=>Le({width:n,height:o},{width:e,height:t},a,i,l),[n,o,e,t,a,i,l]),[y,z]=k.useState(p);k.useEffect(()=>{z(p())},[p]);const m=k.useRef(null),T=(x,S)=>{const M=f.current?.getBoundingClientRect();return{x:x-(M?.left??0),y:S-(M?.top??0)}},b=x=>{m.current={pointerX:x.clientX,pointerY:x.clientY,startX:y.x,startY:y.y,moved:!1}},C=x=>{const S=m.current;if(!S)return;const M=x.clientX-S.pointerX,E=x.clientY-S.pointerY;!S.moved&&Math.hypot(M,E)<Pt||(S.moved=!0,f.current?.setPointerCapture?.(x.pointerId),z(X=>({...X,x:S.startX+M,y:S.startY+E})))},h=x=>{m.current?.moved&&x.preventDefault(),m.current=null},w=x=>{x.preventDefault();const S=T(x.clientX,x.clientY),M=x.deltaY<0?s:1/s;z(E=>le(E,M,S,i,l))},$=x=>z(S=>le(S,x,{x:e/2,y:t/2},i,l)),v={width:32,height:32,fontSize:18,lineHeight:"30px",textAlign:"center",cursor:"pointer",background:"#fff",border:"1px solid #d1d5db",borderRadius:6,userSelect:"none"};return g.jsxs("div",{ref:f,"data-testid":u,onPointerDown:b,onPointerMove:C,onPointerUp:h,onPointerLeave:h,onWheel:w,style:{position:"relative",width:e,height:t,overflow:"hidden",background:c,borderRadius:8,cursor:"grab",touchAction:"none"},children:[g.jsx("div",{"data-testid":`${u}-content`,style:{position:"absolute",left:0,top:0,transformOrigin:"0 0",transform:`translate(${y.x}px, ${y.y}px) scale(${y.scale})`},children:r}),d&&g.jsxs("div",{"data-testid":`${u}-controls`,style:{position:"absolute",right:12,bottom:12,display:"flex",flexDirection:"column",gap:6},children:[g.jsx("div",{style:v,role:"button","aria-label":"Zoom in",onClick:()=>$(s),children:"+"}),g.jsx("div",{style:v,role:"button","aria-label":"Zoom out",onClick:()=>$(1/s),children:"−"}),g.jsx("div",{style:{...v,fontSize:12,lineHeight:"30px"},role:"button","aria-label":"Reset view",onClick:()=>z(p()),children:"⤢"}),g.jsx("div",{"data-testid":`${u}-zoom-readout`,style:{...v,fontSize:11,cursor:"default"},children:Math.round(te(y.scale,i,l)*100)})]})]})},Ot=[{id:"regular-after-double",name:"Regular after double",description:"Double followed by a two-tile offset run",angle:0,dominoes:[{value1:12,value2:6},{value1:6,value2:6},{value1:6,value2:3},{value1:3,value2:1}],layoutStyles:["linear","offset"]},{id:"double-after-regular",name:"Double after regular",description:"Offset run, a double, then another offset run",angle:0,dominoes:[{value1:12,value2:9},{value1:9,value2:4},{value1:4,value2:4},{value1:4,value2:2},{value1:2,value2:7}],layoutStyles:["linear","offset"]},{id:"double-after-double",name:"Double after double",description:"Offset runs at the head, middle, and tail around two doubles",angle:90,dominoes:[{value1:12,value2:7},{value1:7,value2:8},{value1:8,value2:8},{value1:8,value2:3},{value1:3,value2:5},{value1:5,value2:5},{value1:5,value2:2},{value1:2,value2:1}],layoutStyles:["linear","offset"]},{id:"offset-zigzag",name:"Offset zigzag",description:"Alternating perpendicular tiles without doubles",angle:0,dominoes:[{value1:12,value2:5},{value1:5,value2:9},{value1:9,value2:2},{value1:2,value2:7},{value1:7,value2:1}],layoutStyles:["offset"]},{id:"horizontal-open",name:"Horizontal train",description:"Rightward train: offset head, double, offset tail",angle:0,dominoes:[{value1:5,value2:12},{value1:12,value2:11},{value1:11,value2:11},{value1:11,value2:6},{value1:6,value2:2}],layoutStyles:["linear","offset"]},{id:"vertical-open",name:"Vertical train",description:"Downward train: offset head, double, offset tail",angle:90,dominoes:[{value1:3,value2:12},{value1:12,value2:10},{value1:10,value2:10},{value1:10,value2:4},{value1:4,value2:1}],layoutStyles:["linear","offset"]}],Et=[{id:"single-foot",name:"Single foot",description:"A double fans two angled toes (±45°) while the main line continues straight as the center toe",angle:0,branch:{dominoes:[{value1:12,value2:6},{value1:6,value2:6},{value1:6,value2:3},{value1:3,value2:1}],feet:{1:[{dominoes:[{value1:6,value2:2},{value1:2,value2:5}]},{dominoes:[{value1:6,value2:4},{value1:4,value2:0}]}]}},layoutStyles:["linear","offset"]},{id:"foot-no-center",name:"Foot at the tail",description:"Double ends the main line, so both side toes are present with no straight continuation",angle:0,branch:{dominoes:[{value1:9,value2:7},{value1:7,value2:7}],feet:{1:[{dominoes:[{value1:7,value2:3},{value1:3,value2:8}]},{dominoes:[{value1:7,value2:5},{value1:5,value2:0}]}]}},layoutStyles:["linear","offset"]},{id:"nested-foot",name:"Nested foot",description:"A side toe contains its own double, which sprouts a second-level foot",angle:90,branch:{dominoes:[{value1:12,value2:8},{value1:8,value2:8},{value1:8,value2:3}],feet:{1:[{dominoes:[{value1:8,value2:5},{value1:5,value2:5},{value1:5,value2:2}],feet:{1:[{dominoes:[{value1:5,value2:9},{value1:9,value2:1}]},{dominoes:[{value1:5,value2:4},{value1:4,value2:6}]}]}},{dominoes:[{value1:8,value2:1},{value1:1,value2:7}]}]}},layoutStyles:["linear","offset"]}],Z={maxPips:12,engineValue:12,allowConsecutiveDoubles:!1,requireUniqueTiles:!0,requireSequential:!0,doubleObligation:"cover",chickenFoot:{toeCount:3,sideToeAngles:[-45,45]}};function Ye(e){switch(e.doubleObligation){case"chicken-foot":return Math.max(1,e.chickenFoot.toeCount);case"cover":return 1;default:return 0}}function _e(e){return e.doubleObligation==="chicken-foot"?Math.max(0,e.chickenFoot.toeCount-1):0}function jt(e={}){const t=e.maxPips??Z.maxPips;return{...Z,...e,maxPips:t,engineValue:e.engineValue??t,chickenFoot:{...Z.chickenFoot,...e.chickenFoot??{}}}}function Be(e,t){let n=e;for(const o of t)if(n=n?.feet?.[o.doubleIndex]?.[o.toeIndex],!n)return;return n}function oe(e,t,n){if(n(e,t),!!e.feet)for(const o of Object.keys(e.feet)){const r=Number(o);e.feet[r].forEach((i,l)=>{oe(i,[...t,{doubleIndex:r,toeIndex:l}],n)})}}function Rt(e){const t=[];return oe(e,[],(n,o)=>{n.dominoes.forEach((r,i)=>{if(r.value1!==r.value2)return;const l=i<n.dominoes.length-1,s=n.feet?.[i]?.length??0;t.push({path:o,doubleIndex:i,value:r.value1,hasCenter:l,sideToes:s,answers:(l?1:0)+s})})}),t}function Ne(e,t){const n=Ye(t);return n<=0?[]:Rt(e).filter(o=>o.answers<n)}function Ue(e){const t=new Set;return oe(e,[],n=>{for(const o of n.dominoes)t.add(xe(o))}),t}function Ge(e,t,n){if(e.dominoes.length===0)return[{path:[],attach:"run-tail",value:t,attachToDouble:!0,obligation:!1}];const o=Ne(e,n);if(n.doubleObligation!=="none"&&o.length>0){const i=_e(n),l=[];for(const s of o){const a=Be(e,s.path);a&&(!s.hasCenter&&s.doubleIndex===a.dominoes.length-1&&l.push({path:s.path,attach:"run-tail",value:s.value,attachToDouble:!0,obligation:!0}),s.sideToes<i&&l.push({path:s.path,attach:"side-toe",value:s.value,doubleIndex:s.doubleIndex,toeSlot:s.sideToes,attachToDouble:!0,obligation:!0}))}return l}const r=[];return oe(e,[],(i,l)=>{const s=i.dominoes[i.dominoes.length-1];s&&r.push({path:l,attach:"run-tail",value:s.value2,attachToDouble:ge(s),obligation:!1})}),r}function ye(e,t,n,o){const r=[],i=he(e,t.value);return o.requireSequential&&!i&&r.push("value-mismatch"),o.requireUniqueTiles&&n.has(xe(e))&&r.push("duplicate-tile"),!o.allowConsecutiveDoubles&&t.attachToDouble&&ge(e)&&r.push("consecutive-doubles"),{legal:r.length===0,violations:r}}function At(e,t,n,o,r){const i=Ge(e,t,r),l=[];for(const s of i)for(const a of n)ye(a,s,o,r).legal&&l.push({end:s,tile:a});return l}function Ve(e,t,n){if(t.length===0)return n(e);const[o,...r]=t,l=(e.feet?.[o.doubleIndex]??[]).map((s,a)=>a===o.toeIndex?Ve(s,r,n):s);return{...e,feet:{...e.feet,[o.doubleIndex]:l}}}function qe(e,t,n){const o=he(t.tile,t.end.value)??{...t.tile};return Ve(e,t.end.path,r=>{if(t.end.attach==="run-tail")return{...r,dominoes:[...r.dominoes,o]};const i=t.end.doubleIndex??0,l=t.end.toeSlot??r.feet?.[i]?.length??0,s=r.feet?.[i]?[...r.feet[i]]:[];return s[l]={dominoes:[o]},{...r,feet:{...r.feet,[i]:s}}})}function Ft(e,t,n){const o=ye(t.tile,t.end,Ue(e),n);return o.legal?{ok:!0,board:qe(e,t),violations:[]}:{ok:!1,board:e,violations:o.violations}}exports.CHICKEN_FOOT_FIXTURES=Et;exports.CHICKEN_FOOT_TOE_ANGLES=$e;exports.DEFAULT_PIP_COLORS=Y;exports.DEFAULT_RULES=Z;exports.DominoHub=Fe;exports.DominoTrain=Re;exports.DoubleTwelve=ae;exports.MexicanTrainGame=Tt;exports.PIP_COLORS=Qe;exports.PIP_LAYOUTS=Me;exports.TRAIN_FIXTURES=Ot;exports.TURN_DEGREES=ee;exports.Viewport=kt;exports.applyMove=qe;exports.buildBranchTiles=be;exports.clampScale=te;exports.collectPlayedKeys=Ue;exports.computeTrainLayout=ke;exports.computeTrainTree=Q;exports.cycleBendAt=Dt;exports.dominoKey=xe;exports.dominoSetSize=vt;exports.evaluatePlacement=ye;exports.fitToBounds=Le;exports.flattenSegments=fe;exports.generateDominoSet=ht;exports.generateSampleTrains=Xe;exports.getBranchAt=Be;exports.getLegalMoves=At;exports.getOpenEnds=Ge;exports.getPipLayout=Ce;exports.getPipStyle=et;exports.getTrainLayoutBounds=st;exports.getUnsatisfiedDoubles=Ne;exports.headingAtIndex=Pe;exports.hubTrainStartDistance=Ae;exports.isDouble=ge;exports.layoutSelfIntersects=de;exports.layoutsCollide=ce;exports.linearDefaultSide=Ct;exports.mergePipColors=We;exports.normalizeBends=ue;exports.offsetDefaultSide=Mt;exports.oppositeSide=ve;exports.orientForConnection=he;exports.otherEnd=gt;exports.outwardPerpSign=_;exports.playMove=Ft;exports.requiredDoubleAnswers=Ye;exports.resolveBend=$t;exports.resolvePipPosition=Te;exports.resolvePipStyle=se;exports.resolveRules=jt;exports.screenToContent=It;exports.sideToTurn=J;exports.sideToeSlots=_e;exports.stepAlongTrain=P;exports.tileCorners=ie;exports.tileHasValue=xt;exports.tileKey=F;exports.tilesOverlap=G;exports.validateChickenFootChain=je;exports.validateTrainLayout=dt;exports.validateTrainTree=ft;exports.withBendAt=U;exports.zoomAt=le;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|