doubletwelve 0.1.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../src/app/pipGrid.ts","../src/app/Pip.tsx","../src/app/pipLayouts.ts","../src/app/pipColors.ts","../src/app/PipPattern.tsx","../src/app/DominoHalf.tsx","../src/app/DoubleTwelve.tsx","../src/app/trainLayout.ts","../src/app/DominoTrain.tsx","../src/app/DominoHub.tsx","../src/rules/dominoSet.ts","../src/game/generateSampleTrains.ts","../src/app/MexicanTrainGame.tsx","../src/harness/layoutValidation.ts","../src/harness/trainFixtures.ts","../src/rules/rulesConfig.ts","../src/rules/placement.ts"],"sourcesContent":["export type PipGridSize = '3x3' | '3x4' | '4x3';\n\nexport interface PipLayoutCell {\n row: number;\n col: number;\n gridSize: PipGridSize;\n top?: string;\n left?: string;\n}\n\nconst GRID_POSITIONS: Record<\n PipGridSize,\n { rows: number[]; cols: number[]; size: string }\n> = {\n '3x3': { rows: [20, 50, 80], cols: [20, 50, 80], size: '18%' },\n '3x4': { rows: [24, 50, 76], cols: [22, 40, 60, 78], size: '12%' },\n '4x3': { rows: [15, 38, 62, 85], cols: [20, 50, 80], size: '14%' },\n};\n\nexport function resolvePipPosition(cell: PipLayoutCell): {\n top: string;\n left: string;\n width: string;\n height: string;\n} {\n const grid = GRID_POSITIONS[cell.gridSize];\n\n return {\n top: cell.top ?? `${grid.rows[cell.row]}%`,\n left: cell.left ?? `${grid.cols[cell.col]}%`,\n width: grid.size,\n height: grid.size,\n };\n}\n","import { FC } from 'react';\nimport { PipGridSize, resolvePipPosition } from './pipGrid';\n\nexport interface PipProps {\n row: number;\n col: number;\n gridSize: PipGridSize;\n color: string;\n hollow?: boolean;\n top?: string;\n left?: string;\n}\n\nexport const Pip: FC<PipProps> = ({\n row,\n col,\n gridSize,\n color,\n hollow,\n top,\n left,\n}) => {\n const positionStyle = resolvePipPosition({ row, col, gridSize, top, left });\n\n return (\n <div\n data-testid=\"pip\"\n data-row={row}\n data-col={col}\n data-grid={gridSize}\n style={{\n position: 'absolute',\n backgroundColor: hollow ? 'transparent' : color,\n border: hollow ? '2px solid #888' : undefined,\n borderRadius: '50%',\n transform: 'translate(-50%, -50%)',\n boxShadow: hollow ? undefined : '1px 2px 3px rgba(0,0,0,0.3)',\n ...positionStyle,\n }}\n />\n );\n};\n\nexport default Pip;\n","import { PipLayoutCell } from './pipGrid';\n\n/** Canonical double-12 pip layouts (0–12). */\nexport const PIP_LAYOUTS: Record<number, readonly PipLayoutCell[]> = {\n 0: [],\n 1: [{ row: 1, col: 1, gridSize: '3x3' }],\n 2: [\n { row: 0, col: 2, gridSize: '3x3' },\n { row: 2, col: 0, gridSize: '3x3' },\n ],\n 3: [\n { row: 0, col: 2, gridSize: '3x3' },\n { row: 1, col: 1, gridSize: '3x3' },\n { row: 2, col: 0, gridSize: '3x3' },\n ],\n 4: [\n { row: 0, col: 0, gridSize: '3x3' },\n { row: 0, col: 2, gridSize: '3x3' },\n { row: 2, col: 0, gridSize: '3x3' },\n { row: 2, col: 2, gridSize: '3x3' },\n ],\n 5: [\n { row: 0, col: 0, gridSize: '3x3' },\n { row: 0, col: 2, gridSize: '3x3' },\n { row: 1, col: 1, gridSize: '3x3' },\n { row: 2, col: 0, gridSize: '3x3' },\n { row: 2, col: 2, gridSize: '3x3' },\n ],\n 6: [\n { row: 0, col: 0, gridSize: '3x3' },\n { row: 0, col: 2, gridSize: '3x3' },\n { row: 1, col: 0, gridSize: '3x3' },\n { row: 1, col: 2, gridSize: '3x3' },\n { row: 2, col: 0, gridSize: '3x3' },\n { row: 2, col: 2, gridSize: '3x3' },\n ],\n 7: [\n { row: 0, col: 0, gridSize: '3x3' },\n { row: 0, col: 2, gridSize: '3x3' },\n { row: 1, col: 0, gridSize: '3x3' },\n { row: 1, col: 1, gridSize: '3x3' },\n { row: 1, col: 2, gridSize: '3x3' },\n { row: 2, col: 0, gridSize: '3x3' },\n { row: 2, col: 2, gridSize: '3x3' },\n ],\n 8: [\n { row: 0, col: 0, gridSize: '3x3' },\n { row: 0, col: 1, gridSize: '3x3' },\n { row: 0, col: 2, gridSize: '3x3' },\n { row: 1, col: 0, gridSize: '3x3' },\n { row: 1, col: 2, gridSize: '3x3' },\n { row: 2, col: 0, gridSize: '3x3' },\n { row: 2, col: 1, gridSize: '3x3' },\n { row: 2, col: 2, gridSize: '3x3' },\n ],\n 9: [\n { row: 0, col: 0, gridSize: '3x3' },\n { row: 0, col: 1, gridSize: '3x3' },\n { row: 0, col: 2, gridSize: '3x3' },\n { row: 1, col: 0, gridSize: '3x3' },\n { row: 1, col: 1, gridSize: '3x3' },\n { row: 1, col: 2, gridSize: '3x3' },\n { row: 2, col: 0, gridSize: '3x3' },\n { row: 2, col: 1, gridSize: '3x3' },\n { row: 2, col: 2, gridSize: '3x3' },\n ],\n 10: [\n { row: 0, col: 0, gridSize: '3x4' },\n { row: 0, col: 1, gridSize: '3x4' },\n { row: 0, col: 2, gridSize: '3x4' },\n { row: 0, col: 3, gridSize: '3x4' },\n { row: 1, col: 0, gridSize: '3x4' },\n { row: 1, col: 3, gridSize: '3x4' },\n { row: 2, col: 0, gridSize: '3x4' },\n { row: 2, col: 1, gridSize: '3x4' },\n { row: 2, col: 2, gridSize: '3x4' },\n { row: 2, col: 3, gridSize: '3x4' },\n ],\n 11: [\n { row: 0, col: 0, gridSize: '4x3' },\n { row: 1, col: 0, gridSize: '4x3' },\n { row: 2, col: 0, gridSize: '4x3' },\n { row: 3, col: 0, gridSize: '4x3' },\n { row: 0, col: 1, gridSize: '4x3' },\n { row: 2, col: 1, gridSize: '4x3', top: '50%' },\n { row: 3, col: 1, gridSize: '4x3' },\n { row: 0, col: 2, gridSize: '4x3' },\n { row: 1, col: 2, gridSize: '4x3' },\n { row: 2, col: 2, gridSize: '4x3' },\n { row: 3, col: 2, gridSize: '4x3' },\n ],\n 12: [\n { row: 0, col: 0, gridSize: '4x3' },\n { row: 1, col: 0, gridSize: '4x3' },\n { row: 2, col: 0, gridSize: '4x3' },\n { row: 3, col: 0, gridSize: '4x3' },\n { row: 0, col: 1, gridSize: '4x3' },\n { row: 1, col: 1, gridSize: '4x3' },\n { row: 2, col: 1, gridSize: '4x3' },\n { row: 3, col: 1, gridSize: '4x3' },\n { row: 0, col: 2, gridSize: '4x3' },\n { row: 1, col: 2, gridSize: '4x3' },\n { row: 2, col: 2, gridSize: '4x3' },\n { row: 3, col: 2, gridSize: '4x3' },\n ],\n};\n\nexport function getPipLayout(value: number): readonly PipLayoutCell[] {\n return PIP_LAYOUTS[value] ?? [];\n}\n","export interface PipColorStyle {\n color: string;\n hollow?: boolean;\n}\n\n/** Partial map of domino values (0–12) to pip styles. */\nexport type PipColorMap = Partial<Record<number, PipColorStyle>>;\n\n/** Standard double-12 domino pip colors by value. */\nexport const DEFAULT_PIP_COLORS: PipColorMap = {\n 0: { color: 'transparent' },\n 1: { color: '#1a1a1a' },\n 2: { color: '#8B1A1A' },\n 3: { color: '#E6B800' },\n 4: { color: '#e8e8e8', hollow: true },\n 5: { color: '#2E8B57' },\n 6: { color: '#2563EB' },\n 7: { color: '#E8A87C' },\n 8: { color: '#DC2626' },\n 9: { color: '#1E3A8A' },\n 10: { color: '#EA580C' },\n 11: { color: '#166534' },\n 12: { color: '#DC2626' },\n};\n\n/** @deprecated Use DEFAULT_PIP_COLORS instead. */\nexport const PIP_COLORS = DEFAULT_PIP_COLORS;\n\n/** Merge custom overrides onto the default double-12 color set. */\nexport function mergePipColors(overrides?: PipColorMap): PipColorMap {\n return { ...DEFAULT_PIP_COLORS, ...overrides };\n}\n\n/** Resolve the pip style for a value when colored pips are enabled. */\nexport function resolvePipStyle(\n value: number,\n pipColors?: PipColorMap\n): PipColorStyle | undefined {\n if (pipColors === undefined) {\n return undefined;\n }\n\n return (\n pipColors[value] ??\n DEFAULT_PIP_COLORS[value] ?? { color: '#1a1a1a' }\n );\n}\n\n/** @deprecated Use resolvePipStyle instead. */\nexport function getPipStyle(value: number): PipColorStyle {\n return resolvePipStyle(value, DEFAULT_PIP_COLORS)!;\n}\n","import { FC } from 'react';\nimport { Pip } from './Pip';\nimport { getPipLayout } from './pipLayouts';\nimport { PipColorMap, resolvePipStyle } from './pipColors';\n\ninterface PipPatternProps {\n value: number;\n pipColor: string;\n pipColors?: PipColorMap;\n}\n\nconst pipProps = (\n value: number,\n pipColor: string,\n pipColors?: PipColorMap\n): { color: string; hollow?: boolean } => {\n const style = resolvePipStyle(value, pipColors);\n if (style) {\n return { color: style.color, hollow: style.hollow };\n }\n return { color: pipColor };\n};\n\nexport const PipPattern: FC<PipPatternProps> = ({\n value,\n pipColor,\n pipColors,\n}) => {\n const { color, hollow } = pipProps(value, pipColor, pipColors);\n const layout = getPipLayout(value);\n\n return (\n <>\n {layout.map((cell, index) => (\n <Pip\n key={index}\n row={cell.row}\n col={cell.col}\n gridSize={cell.gridSize}\n color={color}\n hollow={hollow}\n top={cell.top}\n left={cell.left}\n />\n ))}\n </>\n );\n};\n\nexport default PipPattern;\n","import { FC } from 'react';\nimport { PipPattern } from './PipPattern';\nimport { PipColorMap } from './pipColors';\n\ninterface DominoHalfProps {\n value: number;\n pipColor: string;\n pipColors?: PipColorMap;\n}\n\nexport const DominoHalf: FC<DominoHalfProps> = ({\n value,\n pipColor,\n pipColors,\n}) => {\n return (\n <div\n style={{\n width: '100%',\n height: '100%',\n position: 'relative',\n padding: '0',\n overflow: 'hidden',\n }}\n >\n <PipPattern value={value} pipColor={pipColor} pipColors={pipColors} />\n </div>\n );\n};\n\nexport default DominoHalf;\n","import { FC } from 'react';\nimport { DominoHalf } from './DominoHalf';\nimport { PipColorMap } from './pipColors';\n\nexport interface DoubleTwelveProps {\n /** Pip count on the top half (0–12). Defaults to 0 (blank). */\n value1?: number;\n /** Pip count on the bottom half (0–12). Defaults to 0 (blank). */\n value2?: number;\n width?: number;\n height?: number;\n backgroundColor?: string;\n /** Fallback pip color when pipColors is not set. */\n pipColor?: string;\n /** Per-value pip colors. Pass DEFAULT_PIP_COLORS or a custom/merged map. */\n pipColors?: PipColorMap;\n borderColor?: string;\n rotation?: number;\n}\n\nexport const DoubleTwelve: FC<DoubleTwelveProps> = ({\n value1 = 0,\n value2 = 0,\n width = 100,\n height = 200,\n backgroundColor = 'white',\n pipColor = 'black',\n pipColors,\n borderColor = 'black',\n rotation = 0,\n}) => {\n // Validate input values\n const val1 = Math.min(Math.max(value1, 0), 12);\n const val2 = Math.min(Math.max(value2, 0), 12);\n\n return (\n <div\n style={{\n width: `${width}px`,\n height: `${height}px`,\n backgroundColor,\n borderColor,\n borderWidth: '1px',\n borderStyle: 'solid',\n borderRadius: '10px',\n transform: `rotate(${rotation}deg)`,\n transformOrigin: 'center center',\n boxShadow: '0 1px 2px rgba(0,0,0,0.2)',\n display: 'flex',\n flexDirection: 'column',\n overflow: 'hidden',\n }}\n >\n <div\n style={{\n flex: 1,\n position: 'relative',\n borderBottomWidth: '1px',\n borderBottomStyle: 'solid',\n borderBottomColor: borderColor,\n }}\n >\n <DominoHalf value={val1} pipColor={pipColor} pipColors={pipColors} />\n </div>\n <div\n style={{\n flex: 1,\n position: 'relative',\n }}\n >\n <DominoHalf value={val2} pipColor={pipColor} pipColors={pipColors} />\n </div>\n </div>\n );\n};\n\nexport default DoubleTwelve;\n","import { DominoValue } from '@/game/DominoValue';\nimport { TrainBranch } from '@/game/TrainData';\n\nexport const DOMINO_WIDTH = 60;\nexport const DOMINO_HEIGHT = 120;\n\n/**\n * Side-toe angles (degrees) relative to the branch direction for a chicken-foot\n * double. The 0° center toe is the straight main-line continuation and is not\n * listed here; these are the two angled toes that fan off the double's open end.\n */\nexport const CHICKEN_FOOT_TOE_ANGLES = [-45, 45] as const;\n\nexport type TrainLayoutStyle = 'offset' | 'linear';\n\nexport interface TrainLayoutEntry {\n x: number;\n y: number;\n rotation: number;\n isDouble: boolean;\n value1: number;\n value2: number;\n}\n\nexport interface ComputeTrainLayoutInput {\n startX: number;\n startY: number;\n angle: number;\n dominoes: readonly DominoValue[];\n layoutStyle: TrainLayoutStyle;\n dominoWidth?: number;\n dominoHeight?: number;\n /**\n * Distance from (startX, startY) to the center of the first tile, along the\n * train direction. Defaults to a small hub gap; chicken-foot toes pass half a\n * domino-height so the first toe tile butts against the host double's far end.\n */\n leadGap?: number;\n /**\n * Which side the offset zigzag seeds on (+1 / -1). Defaults to the natural\n * outward side for `angle`. Chicken-foot toes override this so each toe's\n * zigzag starts toward the outside of the foot, clear of the center row.\n */\n outwardSign?: number;\n /**\n * Offset mode only: index of a chicken-foot double that should act as a\n * centered hub. The double and the tile feeding into it are snapped onto the\n * train axis (perp 0) so the inbound tile reads as centered on the double and\n * the offset center toe fans out symmetrically — which lets the two angled\n * toes sit at equal, close distances on either side.\n */\n hubIndex?: number;\n}\n\nexport function halfExtentAlongTrain(\n isDouble: boolean,\n dominoWidth = DOMINO_WIDTH,\n dominoHeight = DOMINO_HEIGHT\n): number {\n return isDouble ? dominoWidth / 2 : dominoHeight / 2;\n}\n\nexport function stepAlongTrain(\n fromIsDouble: boolean,\n toIsDouble: boolean,\n dominoWidth = DOMINO_WIDTH,\n dominoHeight = DOMINO_HEIGHT\n): number {\n return (\n halfExtentAlongTrain(fromIsDouble, dominoWidth, dominoHeight) +\n halfExtentAlongTrain(toIsDouble, dominoWidth, dominoHeight)\n );\n}\n\nexport function trainDirection(angle: number): { dirX: number; dirY: number } {\n const angleRad = (angle * Math.PI) / 180;\n return {\n dirX: Math.cos(angleRad),\n dirY: Math.sin(angleRad),\n };\n}\n\nexport function trainPerpendicular(angle: number): { perpX: number; perpY: number } {\n const { dirX, dirY } = trainDirection(angle);\n return { perpX: -dirY, perpY: dirX };\n}\n\nexport function orientDominoValues(\n dominoes: DominoValue[],\n layoutStyle: TrainLayoutStyle\n): DominoValue[] {\n const oriented = dominoes.map((domino) => ({ ...domino }));\n\n for (let i = 1; i < oriented.length; i++) {\n const domino = oriented[i];\n const prevDomino = oriented[i - 1];\n const prevValue = prevDomino.value2;\n const isDouble = domino.value1 === domino.value2;\n\n if (layoutStyle === 'linear' && !isDouble) {\n oriented[i] = { value1: domino.value2, value2: domino.value1 };\n continue;\n }\n\n if (\n layoutStyle === 'offset' &&\n !isDouble &&\n domino.value1 !== prevValue &&\n domino.value2 === prevValue\n ) {\n oriented[i] = { value1: domino.value2, value2: domino.value1 };\n }\n }\n\n return oriented;\n}\n\nexport function outwardPerpSign(angle: number): number {\n const { dirX, dirY } = trainDirection(angle);\n\n if (Math.abs(dirX) >= Math.abs(dirY)) {\n return dirX >= 0 ? 1 : -1;\n }\n\n return dirY >= 0 ? 1 : -1;\n}\n\nexport function nextPerpOffset(current: number, outwardSign: number): number {\n if (current === 0) {\n return outwardSign;\n }\n\n return current === outwardSign ? -outwardSign : outwardSign;\n}\n\nexport function computeTrainLayout({\n startX,\n startY,\n angle,\n dominoes,\n layoutStyle,\n dominoWidth = DOMINO_WIDTH,\n dominoHeight = DOMINO_HEIGHT,\n leadGap = dominoHeight * 0.3,\n outwardSign: outwardSignInput,\n hubIndex,\n}: ComputeTrainLayoutInput): TrainLayoutEntry[] {\n const layout: TrainLayoutEntry[] = [];\n const { dirX, dirY } = trainDirection(angle);\n const { perpX, perpY } = trainPerpendicular(angle);\n const orientedDominoes = orientDominoValues([...dominoes], layoutStyle);\n const outwardSign = outwardSignInput ?? outwardPerpSign(angle);\n const isHub = layoutStyle === 'offset' && hubIndex != null;\n // Lane (perpStep units) of each placed tile, used to recenter the inbound run\n // onto the hub double afterward.\n const laneByIndex: number[] = [];\n\n let currentX = startX + dirX * leadGap;\n let currentY = startY + dirY * leadGap;\n let perpOffset = 0;\n // Lane (in perpStep units) of the current tile. Regular tiles brick by\n // flipping lanes; a double stays in the lane of the tile it connects to, and\n // the tile coming out of the double stays in that lane too — so the run out\n // of a double mirrors the run into it and the train holds two fixed rows.\n let laneSign = 0;\n\n // Regular tiles alternate half a domino-width to each side of the centerline\n // so the two rows touch along the spine (no gap) and interlock cleanly.\n const perpStep = dominoWidth / 2;\n\n // perpOffset is the current net perpendicular position in units of perpStep.\n // Moving to a new lane steps by the delta.\n const setPerpOffset = (target: number) => {\n const delta = (target - perpOffset) * perpStep;\n currentX += perpX * delta;\n currentY += perpY * delta;\n perpOffset = target;\n };\n\n for (let i = 0; i < orientedDominoes.length; i++) {\n const domino = orientedDominoes[i];\n const isDouble = domino.value1 === domino.value2;\n const prevIsDouble =\n i > 0 &&\n orientedDominoes[i - 1].value1 === orientedDominoes[i - 1].value2;\n\n if (layoutStyle === 'linear') {\n if (i > 0) {\n if (isDouble) {\n currentX += dirX * stepAlongTrain(prevIsDouble, true, dominoWidth, dominoHeight);\n currentY += dirY * stepAlongTrain(prevIsDouble, true, dominoWidth, dominoHeight);\n } else if (prevIsDouble) {\n currentX += dirX * stepAlongTrain(true, false, dominoWidth, dominoHeight);\n currentY += dirY * stepAlongTrain(true, false, dominoWidth, dominoHeight);\n } else {\n currentX += dirX * dominoHeight;\n currentY += dirY * dominoHeight;\n }\n }\n } else if (isDouble) {\n // A double aligns with the tile it connects to: it stays in the current\n // lane (no perpendicular move) and only advances along the train.\n if (i > 0) {\n currentX += dirX * stepAlongTrain(prevIsDouble, true, dominoWidth, dominoHeight);\n currentY += dirY * stepAlongTrain(prevIsDouble, true, dominoWidth, dominoHeight);\n }\n } else {\n // Regular tile.\n if (i === 0) {\n laneSign = outwardSign;\n } else if (prevIsDouble) {\n // First tile out of a double: stay in the double's lane (centered on\n // it), so the outro mirrors the intro. Advance along only.\n currentX += dirX * stepAlongTrain(true, false, dominoWidth, dominoHeight);\n currentY += dirY * stepAlongTrain(true, false, dominoWidth, dominoHeight);\n } else {\n // Brick against the previous regular: flip lanes, overlap 50% along.\n currentX += dirX * (dominoHeight / 2);\n currentY += dirY * (dominoHeight / 2);\n laneSign = nextPerpOffset(laneSign, outwardSign);\n }\n\n setPerpOffset(laneSign);\n }\n\n laneByIndex.push(perpOffset);\n\n layout.push({\n x: currentX,\n y: currentY,\n rotation: isDouble ? angle + 180 : angle - 90,\n isDouble,\n value1: domino.value1,\n value2: domino.value2,\n });\n }\n\n // Center the hub double on the train axis by rigidly sliding the whole run\n // perpendicular. Because the inbound tile, the double, and the outgoing tile\n // all share the double's lane, this lands all three on the axis (each reads as\n // centered on the double) while the offset zigzag — and the no-overlap\n // guarantee of the original lattice — is preserved. The center toe still fans\n // off-axis, so the two angled toes end up symmetric and close on either side.\n if (isHub && hubIndex != null) {\n const shift = -laneByIndex[hubIndex] * perpStep;\n if (shift !== 0) {\n for (let i = 0; i < layout.length; i++) {\n layout[i] = {\n ...layout[i],\n x: layout[i].x + perpX * shift,\n y: layout[i].y + perpY * shift,\n };\n }\n }\n }\n\n return layout;\n}\n\n/** The four world-space corners of a tile (its rotated rectangle). */\nexport function tileCorners(\n entry: TrainLayoutEntry,\n dominoWidth = DOMINO_WIDTH,\n dominoHeight = DOMINO_HEIGHT\n): Array<{ x: number; y: number }> {\n const r = (entry.rotation * Math.PI) / 180;\n const cos = Math.cos(r);\n const sin = Math.sin(r);\n const hw = dominoWidth / 2;\n const hh = dominoHeight / 2;\n return [\n [-hw, -hh],\n [hw, -hh],\n [hw, hh],\n [-hw, hh],\n ].map(([x, y]) => ({\n x: entry.x + x * cos - y * sin,\n y: entry.y + x * sin + y * cos,\n }));\n}\n\nfunction projectionGap(\n a: Array<{ x: number; y: number }>,\n b: Array<{ x: number; y: number }>,\n axis: { x: number; y: number }\n): number {\n let aMin = Infinity;\n let aMax = -Infinity;\n let bMin = Infinity;\n let bMax = -Infinity;\n for (const p of a) {\n const d = p.x * axis.x + p.y * axis.y;\n aMin = Math.min(aMin, d);\n aMax = Math.max(aMax, d);\n }\n for (const p of b) {\n const d = p.x * axis.x + p.y * axis.y;\n bMin = Math.min(bMin, d);\n bMax = Math.max(bMax, d);\n }\n return Math.min(aMax, bMax) - Math.max(aMin, bMin);\n}\n\n/**\n * True when two tiles physically overlap (separating-axis test on their rotated\n * rectangles). Tiles that merely touch (within `epsilon`) are not overlapping,\n * so legitimately adjacent dominoes — bricked, end-to-end, or butted against a\n * double — pass cleanly while real collisions are caught.\n */\nexport function tilesOverlap(\n a: TrainLayoutEntry,\n b: TrainLayoutEntry,\n epsilon = 1,\n dominoWidth = DOMINO_WIDTH,\n dominoHeight = DOMINO_HEIGHT\n): boolean {\n const ca = tileCorners(a, dominoWidth, dominoHeight);\n const cb = tileCorners(b, dominoWidth, dominoHeight);\n for (const corners of [ca, cb]) {\n for (let i = 0; i < 4; i++) {\n const p = corners[i];\n const q = corners[(i + 1) % 4];\n const ex = q.x - p.x;\n const ey = q.y - p.y;\n const len = Math.hypot(ex, ey) || 1;\n const axis = { x: -ey / len, y: ex / len };\n if (projectionGap(ca, cb, axis) <= epsilon) {\n return false;\n }\n }\n }\n return true;\n}\n\nfunction overlapsAny(\n tile: TrainLayoutEntry,\n others: readonly TrainLayoutEntry[],\n dominoWidth: number,\n dominoHeight: number\n): boolean {\n return others.some((other) =>\n tilesOverlap(tile, other, 1, dominoWidth, dominoHeight)\n );\n}\n\n/**\n * A single straight run of dominoes within a chicken-foot tree: the main line\n * or one toe. `depth` is 0 for the main line, 1 for its toes, and so on.\n */\nexport interface TrainSegment {\n angle: number;\n depth: number;\n layoutStyle: TrainLayoutStyle;\n /** Outward side this segment's zigzag seeds on (needed to re-derive layout). */\n outwardSign: number;\n dominoes: readonly DominoValue[];\n layout: TrainLayoutEntry[];\n /** Anchor point this segment hangs off (host double's open end), if any. */\n anchor?: { x: number; y: number };\n}\n\nexport interface ComputeTrainTreeInput {\n startX: number;\n startY: number;\n angle: number;\n branch: TrainBranch;\n layoutStyle: TrainLayoutStyle;\n dominoWidth?: number;\n dominoHeight?: number;\n leadGap?: number;\n depth?: number;\n anchor?: { x: number; y: number };\n outwardSign?: number;\n /**\n * Accumulator of every tile already placed in the tree. Toes are nudged\n * outward until they clear everything in here, so no two dominoes overlap.\n * Callers normally omit this; the recursion threads it through.\n */\n placed?: TrainLayoutEntry[];\n /**\n * Unit direction a toe may be nudged along (outward, parallel to the host\n * double's open edge) to resolve overlaps. The trunk passes none.\n */\n pushAxis?: { x: number; y: number };\n /**\n * Minimum number of nudge steps to apply before checking for clearance. Both\n * toes of a foot share this so they stay symmetric about the double even when\n * only one side is crowded by the offset center toe.\n */\n minPushSteps?: number;\n}\n\n/** Outward nudge increment and cap used to space chicken-foot toes apart. */\nconst TOE_PUSH_STEP = DOMINO_WIDTH / 4;\nconst TOE_PUSH_MAX_STEPS = 24;\n\n/**\n * Lays out a branch and, recursively, the chicken-foot side toes hanging off any\n * of its doubles. Returns a flat list of segments (main line first, then toes in\n * depth-first order) so callers can render every tile and validate each run.\n */\nexport function computeTrainTree({\n startX,\n startY,\n angle,\n branch,\n layoutStyle,\n dominoWidth = DOMINO_WIDTH,\n dominoHeight = DOMINO_HEIGHT,\n leadGap,\n depth = 0,\n anchor,\n outwardSign,\n placed = [],\n pushAxis,\n minPushSteps = 0,\n}: ComputeTrainTreeInput): TrainSegment[] {\n const segmentOutward = outwardSign ?? outwardPerpSign(angle);\n\n // The first double that sprouts a foot becomes a centered hub so its inbound\n // tile reads centered and its two angled toes stay symmetric.\n const hubIndex = branch.feet\n ? Object.keys(branch.feet)\n .map(Number)\n .filter((index) => {\n const tile = branch.dominoes[index];\n return tile && tile.value1 === tile.value2;\n })\n .sort((a, b) => a - b)[0]\n : undefined;\n\n const buildLayout = (originX: number, originY: number) =>\n computeTrainLayout({\n startX: originX,\n startY: originY,\n angle,\n dominoes: branch.dominoes,\n layoutStyle,\n dominoWidth,\n dominoHeight,\n leadGap,\n outwardSign: segmentOutward,\n hubIndex,\n });\n\n // Nudge this run outward (only toes get a pushAxis) until none of its tiles\n // overlap anything already placed, so dominoes never sit on top of each other.\n // Start from minPushSteps so a foot's two toes share a nudge and stay symmetric.\n let layout = buildLayout(\n startX + (pushAxis?.x ?? 0) * TOE_PUSH_STEP * minPushSteps,\n startY + (pushAxis?.y ?? 0) * TOE_PUSH_STEP * minPushSteps\n );\n let appliedAnchor =\n anchor && pushAxis\n ? {\n x: anchor.x + pushAxis.x * TOE_PUSH_STEP * minPushSteps,\n y: anchor.y + pushAxis.y * TOE_PUSH_STEP * minPushSteps,\n }\n : anchor;\n if (pushAxis && placed.length > 0) {\n for (let k = minPushSteps; k <= TOE_PUSH_MAX_STEPS; k++) {\n const originX = startX + pushAxis.x * TOE_PUSH_STEP * k;\n const originY = startY + pushAxis.y * TOE_PUSH_STEP * k;\n const trial = buildLayout(originX, originY);\n const clear = !trial.some((tile) =>\n overlapsAny(tile, placed, dominoWidth, dominoHeight)\n );\n if (clear || k === TOE_PUSH_MAX_STEPS) {\n layout = trial;\n appliedAnchor = anchor\n ? {\n x: anchor.x + pushAxis.x * TOE_PUSH_STEP * k,\n y: anchor.y + pushAxis.y * TOE_PUSH_STEP * k,\n }\n : anchor;\n break;\n }\n }\n }\n\n placed.push(...layout);\n\n const segments: TrainSegment[] = [\n {\n angle,\n depth,\n layoutStyle,\n outwardSign: segmentOutward,\n dominoes: branch.dominoes,\n layout,\n anchor: appliedAnchor,\n },\n ];\n\n if (branch.feet) {\n const { dirX, dirY } = trainDirection(angle);\n const { perpX, perpY } = trainPerpendicular(angle);\n const perpStep = dominoWidth / 2;\n const leadGap = dominoHeight / 2;\n\n for (const key of Object.keys(branch.feet)) {\n const hostIndex = Number(key);\n const host = layout[hostIndex];\n const toes = branch.feet[hostIndex];\n if (!host || !host.isDouble || !toes) {\n continue;\n }\n\n for (let toeIndex = 0; toeIndex < toes.length; toeIndex++) {\n const toe = toes[toeIndex];\n const toeOffset = CHICKEN_FOOT_TOE_ANGLES[toeIndex] ?? 0;\n const sideSign = Math.sign(toeOffset);\n const toeAngle = angle + toeOffset;\n const toePerp = trainPerpendicular(toeAngle);\n // Seed the zigzag on the toe's INNER lane so each toe splays outward\n // (away from the center toe) as it extends rather than curling in.\n const outward = -sideSign;\n\n // The double's open corner on this toe's side: half a domino-width out\n // along the train, half a domino-height across to the corner.\n const cornerX =\n host.x + dirX * (dominoWidth / 2) + perpX * (dominoHeight / 2) * sideSign;\n const cornerY =\n host.y + dirY * (dominoWidth / 2) + perpY * (dominoHeight / 2) * sideSign;\n\n // Snug placement: the first toe tile butts its inner edge midpoint\n // against that corner (its center lands at corner + toeDir * leadGap).\n // Pick the origin so the offset seed cancels and that lands exactly.\n const originX = cornerX - toePerp.perpX * outward * perpStep;\n const originY = cornerY - toePerp.perpY * outward * perpStep;\n\n segments.push(\n ...computeTrainTree({\n startX: originX,\n startY: originY,\n angle: toeAngle,\n branch: toe,\n // Toes inherit the main style so they zigzag in offset mode.\n layoutStyle,\n dominoWidth,\n dominoHeight,\n leadGap,\n outwardSign: outward,\n depth: depth + 1,\n anchor: { x: originX, y: originY },\n placed,\n // If the snug spot is still blocked (the offset center toe leans into\n // one side), slide this toe along the double's open edge, away from\n // center, until it clears. This keeps it butted against the double\n // while stepping past the obstacle — independent per toe, so a foot\n // ends up snug and only as asymmetric as the obstruction requires.\n pushAxis: { x: perpX * sideSign, y: perpY * sideSign },\n })\n );\n }\n }\n }\n\n return segments;\n}\n\n/** Flattens a list of segments into a single list of tiles for rendering. */\nexport function flattenSegments(\n segments: readonly TrainSegment[]\n): TrainLayoutEntry[] {\n return segments.flatMap((segment) => segment.layout);\n}\n\nexport interface TrainLayoutBounds {\n width: number;\n height: number;\n offsetX: number;\n offsetY: number;\n}\n\n/** Bounding box for rendering a train layout on a felt canvas. */\nexport function getTrainLayoutBounds(\n layout: readonly TrainLayoutEntry[],\n padding = 24,\n dominoWidth = DOMINO_WIDTH,\n dominoHeight = DOMINO_HEIGHT\n): TrainLayoutBounds {\n const halfExtent = Math.hypot(dominoWidth, dominoHeight) / 2;\n\n if (layout.length === 0) {\n return {\n width: padding * 2 + dominoWidth,\n height: padding * 2 + dominoHeight,\n offsetX: padding,\n offsetY: padding,\n };\n }\n\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n\n for (const entry of layout) {\n minX = Math.min(minX, entry.x - halfExtent);\n minY = Math.min(minY, entry.y - halfExtent);\n maxX = Math.max(maxX, entry.x + halfExtent);\n maxY = Math.max(maxY, entry.y + halfExtent);\n }\n\n return {\n width: Math.ceil(maxX - minX + padding * 2),\n height: Math.ceil(maxY - minY + padding * 2),\n offsetX: padding - minX,\n offsetY: padding - minY,\n };\n}\n","import { FC, useMemo } from 'react';\nimport { DoubleTwelve } from '@/app/DoubleTwelve';\nimport {\n DOMINO_HEIGHT,\n DOMINO_WIDTH,\n computeTrainTree,\n flattenSegments,\n} from '@/app/trainLayout';\nimport { TrainData } from '@/game/TrainData';\nimport { PipColorMap } from '@/app/pipColors';\n\ninterface DominoTrainProps {\n startX: number;\n startY: number;\n angle: number;\n trainData: TrainData;\n layoutStyle: 'offset' | 'linear';\n tableWidth: number;\n tableHeight: number;\n centerX: number;\n centerY: number;\n pipColors?: PipColorMap;\n}\n\nexport const DominoTrain: FC<DominoTrainProps> = ({\n startX,\n startY,\n angle,\n trainData,\n layoutStyle,\n tableWidth,\n tableHeight,\n centerX,\n centerY,\n pipColors,\n}) => {\n const trainLayout = useMemo(\n () =>\n flattenSegments(\n computeTrainTree({\n startX,\n startY,\n angle,\n branch: { dominoes: trainData.dominoes, feet: trainData.feet },\n layoutStyle,\n })\n ),\n [\n startX,\n startY,\n angle,\n trainData.dominoes,\n trainData.feet,\n layoutStyle,\n tableWidth,\n tableHeight,\n ]\n );\n\n return (\n <>\n {trainLayout.map((entry, index) => {\n const showMarker = trainData.isPublic;\n\n return (\n <div\n key={`main-train-${trainData.playerId}-${index}`}\n style={{\n position: 'absolute',\n left: `${entry.x - DOMINO_WIDTH / 2}px`,\n top: `${entry.y - DOMINO_HEIGHT / 2}px`,\n zIndex: 5,\n }}\n >\n <DoubleTwelve\n value1={entry.value1}\n value2={entry.value2}\n width={DOMINO_WIDTH}\n height={DOMINO_HEIGHT}\n backgroundColor=\"white\"\n pipColor=\"black\"\n pipColors={pipColors}\n borderColor={showMarker ? 'red' : 'black'}\n rotation={entry.rotation}\n />\n </div>\n );\n })}\n </>\n );\n};\n\nexport default DominoTrain;\n","import { FC } from 'react';\nimport { DominoTrain } from '@/app/DominoTrain';\nimport { DoubleTwelve } from '@/app/DoubleTwelve';\nimport { TrainData } from '@/game/TrainData';\nimport { PipColorMap } from '@/app/pipColors';\n\ninterface DominoHubProps {\n playerCount: number;\n centerX: number;\n centerY: number;\n radius: number;\n engineValue: number;\n trains: TrainData[];\n layoutStyle: 'offset' | 'linear';\n tableWidth: number;\n tableHeight: number;\n pipColors?: PipColorMap;\n}\n\nexport const DominoHub: FC<DominoHubProps> = ({\n playerCount,\n centerX,\n centerY,\n radius,\n engineValue,\n trains,\n layoutStyle,\n tableWidth,\n tableHeight,\n pipColors,\n}) => {\n // Ensure we have at least 8 player slots\n const slots = Math.max(8, playerCount);\n const hubSize = 120; // Increased to fit the standard domino size\n const dominoWidth = 60;\n const dominoHeight = 120; // Standard domino size to match the rest of the dominoes\n\n return (\n <div style={{ position: 'relative', width: '100%', height: '100%' }}>\n {/* Central hub */}\n <div\n style={{\n position: 'absolute',\n width: `${hubSize}px`,\n height: `${hubSize}px`,\n left: `${centerX - hubSize / 2}px`,\n top: `${centerY - hubSize / 2}px`,\n backgroundColor: '#d1d5db',\n borderWidth: '3px',\n borderStyle: 'solid',\n borderColor: '#6b7280',\n borderRadius: '50%',\n boxShadow: '0 4px 6px rgba(0,0,0,0.1)',\n zIndex: 10,\n display: 'flex',\n justifyContent: 'center',\n alignItems: 'center',\n }}\n >\n {/* Engine domino in the center */}\n <div style={{ transform: 'rotate(0deg)' }}>\n <DoubleTwelve\n value1={engineValue}\n value2={engineValue}\n width={dominoWidth}\n height={dominoHeight}\n backgroundColor=\"white\"\n pipColor=\"black\"\n pipColors={pipColors}\n borderColor=\"#333\"\n />\n </div>\n </div>\n\n {/* Trains radiating from hub */}\n {Array.from({ length: slots }).map((_, index) => {\n const angle = (index * 360) / slots;\n const radians = (angle * Math.PI) / 180;\n\n // Calculate starting point for the train\n const startX = centerX + (radius + 20) * Math.cos(radians);\n const startY = centerY + (radius + 20) * Math.sin(radians);\n\n // Get train data for this position (if exists)\n const trainData = trains.find((t) => t.playerId === index) || {\n dominoes: [],\n playerId: index,\n isPublic: false,\n };\n\n return (\n <DominoTrain\n key={index}\n startX={startX}\n startY={startY}\n angle={angle}\n trainData={trainData}\n layoutStyle={layoutStyle}\n tableWidth={tableWidth}\n tableHeight={tableHeight}\n centerX={centerX}\n centerY={centerY}\n pipColors={pipColors}\n />\n );\n })}\n </div>\n );\n};\n\nexport default DominoHub;\n","import { DominoValue } from '@/game/DominoValue';\n\n/**\n * Canonical, order-independent key for a tile. `6-3` and `3-6` are the same\n * physical domino, so they share a key. Used to enforce tile uniqueness.\n */\nexport function tileKey(value1: number, value2: number): string {\n return value1 <= value2 ? `${value1}:${value2}` : `${value2}:${value1}`;\n}\n\nexport function dominoKey(tile: DominoValue): string {\n return tileKey(tile.value1, tile.value2);\n}\n\nexport function isDouble(tile: DominoValue): boolean {\n return tile.value1 === tile.value2;\n}\n\nexport function tileHasValue(tile: DominoValue, value: number): boolean {\n return tile.value1 === value || tile.value2 === value;\n}\n\n/** The pip value on the opposite end from `value`, or null if it doesn't touch. */\nexport function otherEnd(tile: DominoValue, value: number): number | null {\n if (tile.value1 === value) return tile.value2;\n if (tile.value2 === value) return tile.value1;\n return null;\n}\n\n/**\n * Orients a tile so its `value1` is the end that connects to `connectingValue`.\n * Returns null if the tile has no such end.\n */\nexport function orientForConnection(\n tile: DominoValue,\n connectingValue: number\n): DominoValue | null {\n if (tile.value1 === connectingValue) {\n return { value1: tile.value1, value2: tile.value2 };\n }\n if (tile.value2 === connectingValue) {\n return { value1: tile.value2, value2: tile.value1 };\n }\n return null;\n}\n\n/** Every unique tile in a double-`maxPips` set (e.g. maxPips=12 → 91 tiles). */\nexport function generateDominoSet(maxPips: number): DominoValue[] {\n const tiles: DominoValue[] = [];\n for (let a = 0; a <= maxPips; a++) {\n for (let b = a; b <= maxPips; b++) {\n tiles.push({ value1: a, value2: b });\n }\n }\n return tiles;\n}\n\n/** Count of tiles in a double-`maxPips` set: (n+1)(n+2)/2 where n = maxPips. */\nexport function dominoSetSize(maxPips: number): number {\n const n = maxPips + 1;\n return (n * (n + 1)) / 2;\n}\n","import { DominoValue } from './DominoValue';\nimport { TrainBranch, TrainData } from './TrainData';\nimport { tileKey } from '@/rules/dominoSet';\n\nexport { tileKey };\n\nexport interface GenerateSampleTrainsOptions {\n /** Attach chicken-foot side toes (±45°) to every double. */\n chickenFeet?: boolean;\n}\n\n/** Demo train generator that respects double-12 tile uniqueness constraints. */\nexport function generateSampleTrains(\n playerCount: number,\n engineValue = 12,\n options: GenerateSampleTrainsOptions = {}\n): TrainData[] {\n const usedTiles = new Set<string>([tileKey(engineValue, engineValue)]);\n const trains: TrainData[] = [];\n\n for (let playerId = 0; playerId < playerCount; playerId++) {\n const dominoCount = 4 + Math.floor(Math.random() * 7);\n const dominoes: DominoValue[] = [];\n let openValue = engineValue;\n let prevWasDouble = false;\n\n for (let j = 0; j < dominoCount; j++) {\n const value2 = pickNextValue(\n openValue,\n prevWasDouble,\n j === 0,\n engineValue,\n usedTiles\n );\n\n // No tile left that keeps the chain legal and unique: stop this train.\n if (value2 === null) {\n break;\n }\n\n const isDouble = value2 === openValue;\n usedTiles.add(tileKey(openValue, value2));\n\n dominoes.push({ value1: openValue, value2 });\n prevWasDouble = isDouble;\n openValue = value2;\n }\n\n const feet = options.chickenFeet\n ? buildFeet(dominoes, usedTiles)\n : undefined;\n\n trains.push({\n playerId,\n dominoes,\n isPublic: Math.random() > 0.7,\n ...(feet ? { feet } : {}),\n });\n }\n\n return trains;\n}\n\n/**\n * Builds chicken-foot side toes for every double in a chain. Each toe is a short\n * straight run (no doubles, so no nested feet) starting from the double's value.\n */\nfunction buildFeet(\n dominoes: readonly DominoValue[],\n usedTiles: Set<string>\n): Record<number, TrainBranch[]> | undefined {\n const feet: Record<number, TrainBranch[]> = {};\n\n for (let i = 0; i < dominoes.length; i++) {\n if (dominoes[i].value1 !== dominoes[i].value2) {\n continue;\n }\n\n const doubleValue = dominoes[i].value1;\n const toes: TrainBranch[] = [];\n for (let t = 0; t < 2; t++) {\n const toe = buildToe(doubleValue, usedTiles);\n if (toe) {\n toes.push(toe);\n }\n }\n\n if (toes.length) {\n feet[i] = toes;\n }\n }\n\n return Object.keys(feet).length ? feet : undefined;\n}\n\nfunction buildToe(\n startValue: number,\n usedTiles: Set<string>\n): TrainBranch | null {\n const length = 1 + Math.floor(Math.random() * 2);\n const dominoes: DominoValue[] = [];\n let openValue = startValue;\n\n for (let j = 0; j < length; j++) {\n const next = pickNonDouble(openValue, usedTiles);\n if (next === null) {\n break;\n }\n usedTiles.add(tileKey(openValue, next));\n dominoes.push({ value1: openValue, value2: next });\n openValue = next;\n }\n\n return dominoes.length ? { dominoes } : null;\n}\n\nfunction pickNonDouble(\n openValue: number,\n usedTiles: Set<string>\n): number | null {\n const candidates: number[] = [];\n for (let value = 0; value < 13; value++) {\n if (value === openValue) {\n continue;\n }\n if (usedTiles.has(tileKey(openValue, value))) {\n continue;\n }\n candidates.push(value);\n }\n\n if (candidates.length === 0) {\n return null;\n }\n\n return candidates[Math.floor(Math.random() * candidates.length)];\n}\n\nfunction pickNextValue(\n openValue: number,\n prevWasDouble: boolean,\n isFirstDomino: boolean,\n engineValue: number,\n usedTiles: Set<string>\n): number | null {\n const candidates = Array.from({ length: 13 }, (_, value) => value).filter(\n (value) =>\n isValidNextValue(\n openValue,\n value,\n prevWasDouble,\n isFirstDomino,\n engineValue,\n usedTiles\n )\n );\n\n if (candidates.length === 0) {\n return null;\n }\n\n return candidates[Math.floor(Math.random() * candidates.length)];\n}\n\nfunction isValidNextValue(\n openValue: number,\n value: number,\n prevWasDouble: boolean,\n isFirstDomino: boolean,\n engineValue: number,\n usedTiles: Set<string>\n): boolean {\n const isDouble = value === openValue;\n\n if (isFirstDomino && isDouble && openValue === engineValue) {\n return false;\n }\n\n if (isDouble && prevWasDouble) {\n return false;\n }\n\n if (usedTiles.has(tileKey(openValue, value))) {\n return false;\n }\n\n return true;\n}\n","import { FC, useState, useEffect } from 'react';\nimport { DominoHub } from './DominoHub';\nimport { GameState } from '@/game/GameState';\nimport { generateSampleTrains } from '@/game/generateSampleTrains';\nimport { DEFAULT_PIP_COLORS, PipColorMap } from './pipColors';\n\ninterface MexicanTrainGameProps {\n initialState?: GameState;\n width?: number;\n height?: number;\n pipColors?: PipColorMap;\n onPipColorsChange?: (pipColors: PipColorMap | undefined) => void;\n}\n\nconst defaultGameState: GameState = {\n playerCount: 8,\n trains: [],\n engineValue: 12,\n};\n\nexport const MexicanTrainGame: FC<MexicanTrainGameProps> = ({\n initialState = defaultGameState,\n width = 1200,\n height = 800,\n pipColors: pipColorsProp,\n onPipColorsChange,\n}) => {\n const [gameState, setGameState] = useState<GameState>(initialState);\n const [layoutStyle, setLayoutStyle] = useState<'offset' | 'linear'>('offset');\n const [chickenFeet, setChickenFeet] = useState(false);\n const [pipColorsInternal, setPipColorsInternal] = useState<\n PipColorMap | undefined\n >(undefined);\n const pipColors = pipColorsProp ?? pipColorsInternal;\n const setPipColors = onPipColorsChange ?? setPipColorsInternal;\n const pipColorsEnabled = pipColors !== undefined;\n\n // Center coordinates for the hub\n const centerX = width / 2;\n const centerY = height / 2;\n\n // Generate sample data for better visualization\n const regenerateTrains = (chickenFeetEnabled = chickenFeet) => {\n const sampleTrains = generateSampleTrains(\n gameState.playerCount,\n gameState.engineValue,\n { chickenFeet: chickenFeetEnabled }\n );\n setGameState((prevState) => ({\n ...prevState,\n trains: sampleTrains,\n }));\n };\n\n useEffect(() => {\n regenerateTrains();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const toggleChickenFeet = () => {\n const next = !chickenFeet;\n setChickenFeet(next);\n regenerateTrains(next);\n };\n\n return (\n <div\n style={{\n width: `${width}px`,\n height: `${height}px`,\n position: 'relative',\n backgroundColor: '#1f8a55', // Classic green felt background\n borderRadius: '8px',\n boxShadow: '0 4px 12px rgba(0,0,0,0.2)',\n overflow: 'hidden',\n }}\n >\n <div\n style={{ position: 'absolute', top: '10px', left: '10px', zIndex: 100 }}\n >\n <button\n onClick={() => regenerateTrains()}\n style={{\n padding: '8px 12px',\n backgroundColor: '#fff',\n border: '1px solid #ccc',\n borderRadius: '4px',\n marginRight: '10px',\n cursor: 'pointer',\n }}\n >\n New trains\n </button>\n <button\n onClick={() =>\n setLayoutStyle(layoutStyle === 'offset' ? 'linear' : 'offset')\n }\n style={{\n padding: '8px 12px',\n backgroundColor: '#fff',\n border: '1px solid #ccc',\n borderRadius: '4px',\n marginRight: '10px',\n cursor: 'pointer',\n }}\n >\n Layout: {layoutStyle === 'offset' ? 'Offset' : 'Linear'}\n </button>\n <button\n onClick={toggleChickenFeet}\n style={{\n padding: '8px 12px',\n backgroundColor: chickenFeet ? '#fef3c7' : '#fff',\n border: `1px solid ${chickenFeet ? '#f59e0b' : '#ccc'}`,\n borderRadius: '4px',\n marginRight: '10px',\n cursor: 'pointer',\n }}\n >\n Chicken Feet: {chickenFeet ? 'On' : 'Off'}\n </button>\n <button\n onClick={() =>\n setPipColors(pipColorsEnabled ? undefined : DEFAULT_PIP_COLORS)\n }\n style={{\n padding: '8px 12px',\n backgroundColor: pipColorsEnabled ? '#fef3c7' : '#fff',\n border: `1px solid ${pipColorsEnabled ? '#f59e0b' : '#ccc'}`,\n borderRadius: '4px',\n cursor: 'pointer',\n }}\n >\n Pip Colors: {pipColorsEnabled ? 'On' : 'Off'}\n </button>\n </div>\n\n {/* Game information */}\n <div\n style={{\n position: 'absolute',\n top: '10px',\n right: '10px',\n zIndex: 100,\n backgroundColor: 'rgba(255,255,255,0.8)',\n padding: '8px',\n borderRadius: '4px',\n fontSize: '14px',\n }}\n >\n <div>Engine: Double-{gameState.engineValue}</div>\n <div>Players: {gameState.playerCount}</div>\n </div>\n\n <DominoHub\n playerCount={gameState.playerCount}\n centerX={centerX}\n centerY={centerY}\n radius={80}\n engineValue={gameState.engineValue}\n trains={gameState.trains}\n layoutStyle={layoutStyle}\n tableWidth={width}\n tableHeight={height}\n pipColors={pipColors}\n />\n </div>\n );\n};\n\nexport default MexicanTrainGame;\n","import { DominoValue } from '@/game/DominoValue';\nimport { TrainBranch } from '@/game/TrainData';\nimport {\n DOMINO_HEIGHT,\n DOMINO_WIDTH,\n TrainLayoutEntry,\n TrainLayoutStyle,\n TrainSegment,\n nextPerpOffset,\n outwardPerpSign,\n stepAlongTrain,\n tilesOverlap,\n trainDirection,\n trainPerpendicular,\n} from '@/app/trainLayout';\n\nexport interface LayoutValidationIssue {\n code: string;\n message: string;\n index?: number;\n}\n\nexport interface LayoutValidationResult {\n valid: boolean;\n issues: LayoutValidationIssue[];\n}\n\nconst DEFAULT_TOLERANCE = 1;\n\nexport function projectOnTrainAxis(\n dx: number,\n dy: number,\n angle: number\n): number {\n const { dirX, dirY } = trainDirection(angle);\n return dx * dirX + dy * dirY;\n}\n\nexport function projectOnPerpendicularAxis(\n dx: number,\n dy: number,\n angle: number\n): number {\n const { perpX, perpY } = trainPerpendicular(angle);\n return dx * perpX + dy * perpY;\n}\n\nexport function validateDominoChain(dominoes: readonly DominoValue[]): LayoutValidationResult {\n const issues: LayoutValidationIssue[] = [];\n\n for (let i = 1; i < dominoes.length; i++) {\n if (dominoes[i].value1 !== dominoes[i - 1].value2) {\n issues.push({\n code: 'chain-break',\n message: `Domino ${i} does not connect to domino ${i - 1}`,\n index: i,\n });\n }\n }\n\n for (let i = 1; i < dominoes.length; i++) {\n const prevIsDouble = dominoes[i - 1].value1 === dominoes[i - 1].value2;\n const currentIsDouble = dominoes[i].value1 === dominoes[i].value2;\n if (prevIsDouble && currentIsDouble) {\n issues.push({\n code: 'consecutive-doubles',\n message: `Consecutive doubles at index ${i - 1} and ${i}`,\n index: i,\n });\n }\n }\n\n return { valid: issues.length === 0, issues };\n}\n\nexport interface AxisPosition {\n along: number;\n perp: number;\n}\n\n/**\n * Reconstructs the expected position of every tile in train-axis space\n * (along the train and perpendicular to it), mirroring computeTrainLayout.\n * Positions are relative to the first tile, so only deltas are meaningful.\n */\nexport function expectedAxisLayout(\n layout: readonly TrainLayoutEntry[],\n layoutStyle: TrainLayoutStyle,\n outwardSign: number,\n dominoWidth = DOMINO_WIDTH,\n dominoHeight = DOMINO_HEIGHT\n): AxisPosition[] {\n const perpStep = dominoWidth / 2;\n const isDoubleArr = layout.map((entry) => entry.isDouble);\n\n const positions: AxisPosition[] = [];\n let along = 0;\n let perp = 0;\n let laneSign = 0;\n\n for (let i = 0; i < layout.length; i++) {\n const isDouble = isDoubleArr[i];\n const prevIsDouble = i > 0 && isDoubleArr[i - 1];\n\n if (layoutStyle === 'linear') {\n if (i > 0) {\n along += stepAlongTrain(prevIsDouble, isDouble, dominoWidth, dominoHeight);\n }\n perp = 0;\n } else if (isDouble) {\n // Double stays in the current lane (aligned with the tile it connects to).\n if (i > 0) {\n along += stepAlongTrain(prevIsDouble, true, dominoWidth, dominoHeight);\n }\n // perp unchanged\n } else if (i === 0) {\n laneSign = outwardSign;\n perp = laneSign * perpStep;\n } else if (prevIsDouble) {\n // First tile out of a double stays in the double's lane (mirror).\n along += stepAlongTrain(true, false, dominoWidth, dominoHeight);\n // perp unchanged\n } else {\n along += dominoHeight / 2;\n laneSign = nextPerpOffset(laneSign, outwardSign);\n perp = laneSign * perpStep;\n }\n\n positions.push({ along, perp });\n }\n\n return positions;\n}\n\nexport function validateConsecutiveSpacing(\n layout: readonly TrainLayoutEntry[],\n angle: number,\n layoutStyle: TrainLayoutStyle,\n tolerance = DEFAULT_TOLERANCE,\n outwardSignOverride?: number\n): LayoutValidationResult {\n const issues: LayoutValidationIssue[] = [];\n const outwardSign = outwardSignOverride ?? outwardPerpSign(angle);\n const expected = expectedAxisLayout(layout, layoutStyle, outwardSign);\n\n for (let i = 1; i < layout.length; i++) {\n const prev = layout[i - 1];\n const current = layout[i];\n const dx = current.x - prev.x;\n const dy = current.y - prev.y;\n const along = projectOnTrainAxis(dx, dy, angle);\n const perp = projectOnPerpendicularAxis(dx, dy, angle);\n const expectedAlong = expected[i].along - expected[i - 1].along;\n const expectedPerp = expected[i].perp - expected[i - 1].perp;\n\n if (Math.abs(along - expectedAlong) > tolerance) {\n issues.push({\n code: 'spacing-along-train',\n message: `Along-train spacing between domino ${i - 1} and ${i} is ${along.toFixed(2)}px (expected ${expectedAlong}px)`,\n index: i,\n });\n }\n\n if (Math.abs(perp - expectedPerp) > tolerance) {\n issues.push({\n code: 'spacing-perpendicular',\n message: `Perpendicular spacing between domino ${i - 1} and ${i} is ${perp.toFixed(2)}px (expected ${expectedPerp}px)`,\n index: i,\n });\n }\n }\n\n return { valid: issues.length === 0, issues };\n}\n\nexport function validateNoPairOverlap(\n layout: readonly TrainLayoutEntry[],\n angle: number,\n layoutStyle: TrainLayoutStyle,\n tolerance = DEFAULT_TOLERANCE,\n outwardSignOverride?: number\n): LayoutValidationResult {\n const issues: LayoutValidationIssue[] = [];\n const outwardSign = outwardSignOverride ?? outwardPerpSign(angle);\n const expected = expectedAxisLayout(layout, layoutStyle, outwardSign);\n\n for (let i = 1; i < layout.length; i++) {\n const prev = layout[i - 1];\n const current = layout[i];\n const dx = current.x - prev.x;\n const dy = current.y - prev.y;\n const distance = Math.hypot(dx, dy);\n const expectedAlong = expected[i].along - expected[i - 1].along;\n const expectedPerp = expected[i].perp - expected[i - 1].perp;\n const minDistance = Math.hypot(expectedAlong, expectedPerp) * 0.9;\n\n if (distance + tolerance < minDistance) {\n issues.push({\n code: 'overlap',\n message: `Domino ${i - 1} and ${i} centers are ${distance.toFixed(2)}px apart (minimum ${minDistance.toFixed(2)}px)`,\n index: i,\n });\n }\n }\n\n return { valid: issues.length === 0, issues };\n}\n\nexport function validateTrainLayout(\n layout: readonly TrainLayoutEntry[],\n dominoes: readonly DominoValue[],\n angle: number,\n layoutStyle: TrainLayoutStyle,\n tolerance = DEFAULT_TOLERANCE,\n outwardSignOverride?: number\n): LayoutValidationResult {\n const issues: LayoutValidationIssue[] = [\n ...validateDominoChain(dominoes).issues,\n ...validateConsecutiveSpacing(\n layout,\n angle,\n layoutStyle,\n tolerance,\n outwardSignOverride\n ).issues,\n ...validateNoPairOverlap(\n layout,\n angle,\n layoutStyle,\n tolerance,\n outwardSignOverride\n ).issues,\n ];\n\n if (layout.length !== dominoes.length) {\n issues.push({\n code: 'layout-length',\n message: `Layout length ${layout.length} does not match domino count ${dominoes.length}`,\n });\n }\n\n return { valid: issues.length === 0, issues };\n}\n\n/**\n * Validates the chicken-foot branch tree as data: every chain links up, every\n * foot hangs off a real double, and every toe's first tile matches the double's\n * value. Recurses into nested feet.\n */\nexport function validateChickenFootChain(\n branch: TrainBranch\n): LayoutValidationResult {\n const issues: LayoutValidationIssue[] = [];\n\n const walk = (current: TrainBranch, path: string) => {\n issues.push(\n ...validateDominoChain(current.dominoes).issues.map((issue) => ({\n ...issue,\n message: `[${path}] ${issue.message}`,\n }))\n );\n\n if (!current.feet) {\n return;\n }\n\n for (const key of Object.keys(current.feet)) {\n const hostIndex = Number(key);\n const host = current.dominoes[hostIndex];\n const toes = current.feet[hostIndex] ?? [];\n\n if (!host) {\n issues.push({\n code: 'foot-host-missing',\n message: `[${path}] Foot references missing tile ${hostIndex}`,\n });\n continue;\n }\n\n if (host.value1 !== host.value2) {\n issues.push({\n code: 'foot-host-not-double',\n message: `[${path}] Foot host tile ${hostIndex} is not a double`,\n });\n }\n\n if (toes.length > 2) {\n issues.push({\n code: 'foot-too-many-toes',\n message: `[${path}] Double ${hostIndex} has ${toes.length} side toes (max 2; the center toe is the main line)`,\n });\n }\n\n toes.forEach((toe, toeIndex) => {\n const first = toe.dominoes[0];\n if (first && first.value1 !== host.value1) {\n issues.push({\n code: 'foot-connection',\n message: `[${path}] Toe ${toeIndex} on double ${hostIndex} starts with ${first.value1} but the double is ${host.value1}`,\n });\n }\n walk(toe, `${path}.${hostIndex}.${toeIndex}`);\n });\n }\n };\n\n walk(branch, 'main');\n return { valid: issues.length === 0, issues };\n}\n\n/**\n * Validates a laid-out chicken-foot tree. Each run's tiles must link up by value\n * and match its tile count; each toe must start the right distance out from its\n * host double (measured along the toe's axis); and — the hard physical rule — no\n * two tiles anywhere in the tree may overlap.\n *\n * The center toe is a centered linear run while the inbound spine is offset, so\n * a single run can mix layout styles; the per-tile overlap test below checks the\n * real constraint directly rather than reconstructing each style's spacing.\n */\nexport function validateTrainTree(\n segments: readonly TrainSegment[],\n tolerance = DEFAULT_TOLERANCE\n): LayoutValidationResult {\n const issues: LayoutValidationIssue[] = [];\n\n segments.forEach((segment, segmentIndex) => {\n issues.push(\n ...validateDominoChain(segment.dominoes).issues.map((issue) => ({\n ...issue,\n message: `[segment ${segmentIndex} @${segment.angle}°] ${issue.message}`,\n }))\n );\n\n if (segment.layout.length !== segment.dominoes.length) {\n issues.push({\n code: 'layout-length',\n message: `[segment ${segmentIndex}] Layout length ${segment.layout.length} does not match domino count ${segment.dominoes.length}`,\n });\n }\n\n if (segment.anchor && segment.layout.length > 0) {\n const first = segment.layout[0];\n const along = projectOnTrainAxis(\n first.x - segment.anchor.x,\n first.y - segment.anchor.y,\n segment.angle\n );\n const expected = DOMINO_HEIGHT / 2;\n if (Math.abs(along - expected) > tolerance) {\n issues.push({\n code: 'foot-anchor',\n message: `[segment ${segmentIndex}] First toe tile sits ${along.toFixed(2)}px from the double along the toe (expected ${expected}px)`,\n index: 0,\n });\n }\n }\n });\n\n // Physical rule: dominoes are solid, so no two tiles may overlap anywhere.\n const tiles = segments.flatMap((segment) => segment.layout);\n for (let i = 0; i < tiles.length; i++) {\n for (let j = i + 1; j < tiles.length; j++) {\n if (tilesOverlap(tiles[i], tiles[j])) {\n issues.push({\n code: 'tile-overlap',\n message: `Tiles ${i} and ${j} overlap`,\n index: j,\n });\n }\n }\n }\n\n return { valid: issues.length === 0, issues };\n}\n\nexport function dominoCorners(\n entry: TrainLayoutEntry,\n dominoWidth = DOMINO_WIDTH,\n dominoHeight = DOMINO_HEIGHT\n): { x: number; y: number }[] {\n const rotation = (entry.rotation * Math.PI) / 180;\n const halfW = dominoWidth / 2;\n const halfH = dominoHeight / 2;\n const localCorners = [\n { x: -halfW, y: -halfH },\n { x: halfW, y: -halfH },\n { x: halfW, y: halfH },\n { x: -halfW, y: halfH },\n ];\n\n return localCorners.map(({ x, y }) => {\n const rotatedX = x * Math.cos(rotation) - y * Math.sin(rotation);\n const rotatedY = x * Math.sin(rotation) + y * Math.cos(rotation);\n return { x: entry.x + rotatedX, y: entry.y + rotatedY };\n });\n}\n","import { DominoValue } from '@/game/DominoValue';\nimport { TrainBranch } from '@/game/TrainData';\nimport { TrainLayoutStyle } from '@/app/trainLayout';\n\nexport interface TrainFixture {\n id: string;\n name: string;\n description: string;\n angle: number;\n dominoes: DominoValue[];\n layoutStyles: TrainLayoutStyle[];\n}\n\nexport interface ChickenFootFixture {\n id: string;\n name: string;\n description: string;\n angle: number;\n branch: TrainBranch;\n layoutStyles: TrainLayoutStyle[];\n}\n\nexport const TRAIN_FIXTURES: TrainFixture[] = [\n {\n id: 'regular-after-double',\n name: 'Regular after double',\n description: 'Double followed by a two-tile offset run',\n angle: 0,\n dominoes: [\n { value1: 12, value2: 6 },\n { value1: 6, value2: 6 },\n { value1: 6, value2: 3 },\n { value1: 3, value2: 1 },\n ],\n layoutStyles: ['linear', 'offset'],\n },\n {\n id: 'double-after-regular',\n name: 'Double after regular',\n description: 'Offset run, a double, then another offset run',\n angle: 0,\n dominoes: [\n { value1: 12, value2: 9 },\n { value1: 9, value2: 4 },\n { value1: 4, value2: 4 },\n { value1: 4, value2: 2 },\n { value1: 2, value2: 7 },\n ],\n layoutStyles: ['linear', 'offset'],\n },\n {\n id: 'double-after-double',\n name: 'Double after double',\n description: 'Offset runs at the head, middle, and tail around two doubles',\n angle: 90,\n dominoes: [\n { value1: 12, value2: 7 },\n { value1: 7, value2: 8 },\n { value1: 8, value2: 8 },\n { value1: 8, value2: 3 },\n { value1: 3, value2: 5 },\n { value1: 5, value2: 5 },\n { value1: 5, value2: 2 },\n { value1: 2, value2: 1 },\n ],\n layoutStyles: ['linear', 'offset'],\n },\n {\n id: 'offset-zigzag',\n name: 'Offset zigzag',\n description: 'Alternating perpendicular tiles without doubles',\n angle: 0,\n dominoes: [\n { value1: 12, value2: 5 },\n { value1: 5, value2: 9 },\n { value1: 9, value2: 2 },\n { value1: 2, value2: 7 },\n { value1: 7, value2: 1 },\n ],\n layoutStyles: ['offset'],\n },\n {\n id: 'horizontal-open',\n name: 'Horizontal train',\n description: 'Rightward train: offset head, double, offset tail',\n angle: 0,\n dominoes: [\n { value1: 5, value2: 12 },\n { value1: 12, value2: 11 },\n { value1: 11, value2: 11 },\n { value1: 11, value2: 6 },\n { value1: 6, value2: 2 },\n ],\n layoutStyles: ['linear', 'offset'],\n },\n {\n id: 'vertical-open',\n name: 'Vertical train',\n description: 'Downward train: offset head, double, offset tail',\n angle: 90,\n dominoes: [\n { value1: 3, value2: 12 },\n { value1: 12, value2: 10 },\n { value1: 10, value2: 10 },\n { value1: 10, value2: 4 },\n { value1: 4, value2: 1 },\n ],\n layoutStyles: ['linear', 'offset'],\n },\n];\n\nexport function getTrainFixture(id: string): TrainFixture | undefined {\n return TRAIN_FIXTURES.find((fixture) => fixture.id === id);\n}\n\nexport const CHICKEN_FOOT_FIXTURES: ChickenFootFixture[] = [\n {\n id: 'single-foot',\n name: 'Single foot',\n description:\n 'A double fans two angled toes (±45°) while the main line continues straight as the center toe',\n angle: 0,\n branch: {\n dominoes: [\n { value1: 12, value2: 6 },\n { value1: 6, value2: 6 },\n { value1: 6, value2: 3 },\n { value1: 3, value2: 1 },\n ],\n feet: {\n 1: [\n {\n dominoes: [\n { value1: 6, value2: 2 },\n { value1: 2, value2: 5 },\n ],\n },\n {\n dominoes: [\n { value1: 6, value2: 4 },\n { value1: 4, value2: 0 },\n ],\n },\n ],\n },\n },\n layoutStyles: ['linear', 'offset'],\n },\n {\n id: 'foot-no-center',\n name: 'Foot at the tail',\n description:\n 'Double ends the main line, so both side toes are present with no straight continuation',\n angle: 0,\n branch: {\n dominoes: [\n { value1: 9, value2: 7 },\n { value1: 7, value2: 7 },\n ],\n feet: {\n 1: [\n {\n dominoes: [\n { value1: 7, value2: 3 },\n { value1: 3, value2: 8 },\n ],\n },\n {\n dominoes: [\n { value1: 7, value2: 5 },\n { value1: 5, value2: 0 },\n ],\n },\n ],\n },\n },\n layoutStyles: ['linear', 'offset'],\n },\n {\n id: 'nested-foot',\n name: 'Nested foot',\n description:\n 'A side toe contains its own double, which sprouts a second-level foot',\n angle: 90,\n branch: {\n dominoes: [\n { value1: 12, value2: 8 },\n { value1: 8, value2: 8 },\n { value1: 8, value2: 3 },\n ],\n feet: {\n 1: [\n {\n dominoes: [\n { value1: 8, value2: 5 },\n { value1: 5, value2: 5 },\n { value1: 5, value2: 2 },\n ],\n feet: {\n 1: [\n {\n dominoes: [\n { value1: 5, value2: 9 },\n { value1: 9, value2: 1 },\n ],\n },\n {\n dominoes: [\n { value1: 5, value2: 4 },\n { value1: 4, value2: 6 },\n ],\n },\n ],\n },\n },\n {\n dominoes: [\n { value1: 8, value2: 1 },\n { value1: 1, value2: 7 },\n ],\n },\n ],\n },\n },\n layoutStyles: ['linear', 'offset'],\n },\n];\n\nexport function getChickenFootFixture(\n id: string\n): ChickenFootFixture | undefined {\n return CHICKEN_FOOT_FIXTURES.find((fixture) => fixture.id === id);\n}\n","/**\n * How a played double must be answered before play continues.\n * - `none`: doubles are ordinary tiles.\n * - `cover`: the double must be answered by one tile on its open end.\n * - `chicken-foot`: the double must grow a full foot (`chickenFoot.toeCount`\n * answers: the straight center continuation plus the angled side toes).\n */\nexport type DoubleObligation = 'none' | 'cover' | 'chicken-foot';\n\nexport interface ChickenFootConfig {\n /**\n * Total toes a double must grow to be satisfied, counting the straight center\n * continuation as one. Stays 3 by default (center + two ±angle side toes).\n */\n toeCount: number;\n /** Angles (degrees, relative to the train) of the side toes (toeCount - 1). */\n sideToeAngles: number[];\n}\n\n/**\n * Every knob that governs legal play. Pass a partial override to {@link resolveRules}\n * to get a fully-populated config; unspecified fields fall back to DEFAULT_RULES.\n */\nexport interface RulesConfig {\n /** Highest pip value in the set (12 → double-12, 91 tiles). */\n maxPips: number;\n /** Value of the starting engine double (defaults to maxPips). */\n engineValue: number;\n /** Forbid a double immediately following a double in a chain. */\n allowConsecutiveDoubles: boolean;\n /** Each physical tile may be placed at most once across all play. */\n requireUniqueTiles: boolean;\n /** A tile may only attach where one of its ends matches the open value. */\n requireSequential: boolean;\n /** Obligation imposed by playing a double. */\n doubleObligation: DoubleObligation;\n chickenFoot: ChickenFootConfig;\n}\n\nexport const DEFAULT_RULES: RulesConfig = {\n maxPips: 12,\n engineValue: 12,\n allowConsecutiveDoubles: false,\n requireUniqueTiles: true,\n requireSequential: true,\n doubleObligation: 'cover',\n chickenFoot: {\n toeCount: 3,\n sideToeAngles: [-45, 45],\n },\n};\n\n/** Number of answers a double needs to be satisfied under the given rules. */\nexport function requiredDoubleAnswers(config: RulesConfig): number {\n switch (config.doubleObligation) {\n case 'chicken-foot':\n return Math.max(1, config.chickenFoot.toeCount);\n case 'cover':\n return 1;\n case 'none':\n default:\n return 0;\n }\n}\n\n/** Number of angled side-toe slots a double exposes (center is the main line). */\nexport function sideToeSlots(config: RulesConfig): number {\n if (config.doubleObligation === 'chicken-foot') {\n return Math.max(0, config.chickenFoot.toeCount - 1);\n }\n // Outside chicken-foot, doubles do not branch; covers go on the main line.\n return 0;\n}\n\n/** Fills in any missing fields from DEFAULT_RULES (engineValue tracks maxPips). */\nexport function resolveRules(overrides: Partial<RulesConfig> = {}): RulesConfig {\n const maxPips = overrides.maxPips ?? DEFAULT_RULES.maxPips;\n return {\n ...DEFAULT_RULES,\n ...overrides,\n maxPips,\n engineValue: overrides.engineValue ?? maxPips,\n chickenFoot: {\n ...DEFAULT_RULES.chickenFoot,\n ...(overrides.chickenFoot ?? {}),\n },\n };\n}\n","import { DominoValue } from '@/game/DominoValue';\nimport { TrainBranch } from '@/game/TrainData';\nimport {\n dominoKey,\n isDouble,\n orientForConnection,\n} from '@/rules/dominoSet';\nimport {\n RulesConfig,\n requiredDoubleAnswers,\n sideToeSlots,\n} from '@/rules/rulesConfig';\n\n/**\n * Locates a branch inside a chicken-foot tree. Empty path = the main line; each\n * step descends into the `toeIndex`-th side toe hanging off the double at\n * `doubleIndex` of the current branch.\n */\nexport type BranchPath = ReadonlyArray<{\n doubleIndex: number;\n toeIndex: number;\n}>;\n\nexport interface OpenEnd {\n path: BranchPath;\n attach: 'run-tail' | 'side-toe';\n /** Pip value a tile must match to attach here. */\n value: number;\n /** For side-toe ends: which double in the branch, and which toe slot. */\n doubleIndex?: number;\n toeSlot?: number;\n /** The tile being attached to is a double (for the no-consecutive rule). */\n attachToDouble: boolean;\n /** This end exists only because an unanswered double must be satisfied. */\n obligation: boolean;\n}\n\nexport interface Move {\n end: OpenEnd;\n tile: DominoValue;\n}\n\nexport type PlacementViolation =\n | 'value-mismatch'\n | 'duplicate-tile'\n | 'consecutive-doubles';\n\nexport interface PlacementResult {\n legal: boolean;\n violations: PlacementViolation[];\n}\n\nexport function getBranchAt(\n root: TrainBranch,\n path: BranchPath\n): TrainBranch | undefined {\n let current: TrainBranch | undefined = root;\n for (const step of path) {\n current = current?.feet?.[step.doubleIndex]?.[step.toeIndex];\n if (!current) return undefined;\n }\n return current;\n}\n\ninterface DoubleStatus {\n path: BranchPath;\n doubleIndex: number;\n value: number;\n hasCenter: boolean;\n sideToes: number;\n answers: number;\n}\n\nfunction walkBranches(\n branch: TrainBranch,\n path: BranchPath,\n visit: (branch: TrainBranch, path: BranchPath) => void\n): void {\n visit(branch, path);\n if (!branch.feet) return;\n for (const key of Object.keys(branch.feet)) {\n const doubleIndex = Number(key);\n branch.feet[doubleIndex].forEach((toe, toeIndex) => {\n walkBranches(toe, [...path, { doubleIndex, toeIndex }], visit);\n });\n }\n}\n\nfunction collectDoubles(root: TrainBranch): DoubleStatus[] {\n const out: DoubleStatus[] = [];\n walkBranches(root, [], (branch, path) => {\n branch.dominoes.forEach((domino, doubleIndex) => {\n if (domino.value1 !== domino.value2) return;\n const hasCenter = doubleIndex < branch.dominoes.length - 1;\n const sideToes = branch.feet?.[doubleIndex]?.length ?? 0;\n out.push({\n path,\n doubleIndex,\n value: domino.value1,\n hasCenter,\n sideToes,\n answers: (hasCenter ? 1 : 0) + sideToes,\n });\n });\n });\n return out;\n}\n\n/** Doubles that still owe answers under the current rules. */\nexport function getUnsatisfiedDoubles(\n root: TrainBranch,\n config: RulesConfig\n): DoubleStatus[] {\n const required = requiredDoubleAnswers(config);\n if (required <= 0) return [];\n return collectDoubles(root).filter((d) => d.answers < required);\n}\n\n/** Every key of every tile already placed in the tree (for uniqueness checks). */\nexport function collectPlayedKeys(root: TrainBranch): Set<string> {\n const keys = new Set<string>();\n walkBranches(root, [], (branch) => {\n for (const domino of branch.dominoes) {\n keys.add(dominoKey(domino));\n }\n });\n return keys;\n}\n\n/**\n * All places a tile may legally attach to this train, honoring double\n * obligations. When a double is unanswered (and the rules require answers),\n * only that double's open slots are offered until it is satisfied.\n */\nexport function getOpenEnds(\n root: TrainBranch,\n startValue: number,\n config: RulesConfig\n): OpenEnd[] {\n if (root.dominoes.length === 0) {\n return [\n {\n path: [],\n attach: 'run-tail',\n value: startValue,\n attachToDouble: true, // a train starts off the engine double\n obligation: false,\n },\n ];\n }\n\n const unsatisfied = getUnsatisfiedDoubles(root, config);\n\n if (config.doubleObligation !== 'none' && unsatisfied.length > 0) {\n const slots = sideToeSlots(config);\n const ends: OpenEnd[] = [];\n\n for (const status of unsatisfied) {\n const branch = getBranchAt(root, status.path);\n if (!branch) continue;\n\n // Center continuation: only available if the double is the run's tail.\n if (!status.hasCenter && status.doubleIndex === branch.dominoes.length - 1) {\n ends.push({\n path: status.path,\n attach: 'run-tail',\n value: status.value,\n attachToDouble: true,\n obligation: true,\n });\n }\n\n if (status.sideToes < slots) {\n ends.push({\n path: status.path,\n attach: 'side-toe',\n value: status.value,\n doubleIndex: status.doubleIndex,\n toeSlot: status.sideToes,\n attachToDouble: true,\n obligation: true,\n });\n }\n }\n\n return ends;\n }\n\n // No active obligation: the growing tip of every branch is open.\n const ends: OpenEnd[] = [];\n walkBranches(root, [], (branch, path) => {\n const last = branch.dominoes[branch.dominoes.length - 1];\n if (!last) return;\n ends.push({\n path,\n attach: 'run-tail',\n value: last.value2,\n attachToDouble: isDouble(last),\n obligation: false,\n });\n });\n return ends;\n}\n\nexport function evaluatePlacement(\n tile: DominoValue,\n end: OpenEnd,\n playedKeys: ReadonlySet<string>,\n config: RulesConfig\n): PlacementResult {\n const violations: PlacementViolation[] = [];\n\n const oriented = orientForConnection(tile, end.value);\n if (config.requireSequential && !oriented) {\n violations.push('value-mismatch');\n }\n\n if (config.requireUniqueTiles && playedKeys.has(dominoKey(tile))) {\n violations.push('duplicate-tile');\n }\n\n if (!config.allowConsecutiveDoubles && end.attachToDouble && isDouble(tile)) {\n violations.push('consecutive-doubles');\n }\n\n return { legal: violations.length === 0, violations };\n}\n\n/** Every legal (open end × hand tile) move for this train. */\nexport function getLegalMoves(\n root: TrainBranch,\n startValue: number,\n hand: readonly DominoValue[],\n playedKeys: ReadonlySet<string>,\n config: RulesConfig\n): Move[] {\n const ends = getOpenEnds(root, startValue, config);\n const moves: Move[] = [];\n for (const end of ends) {\n for (const tile of hand) {\n if (evaluatePlacement(tile, end, playedKeys, config).legal) {\n moves.push({ end, tile });\n }\n }\n }\n return moves;\n}\n\nfunction updateBranchAt(\n branch: TrainBranch,\n path: BranchPath,\n updater: (branch: TrainBranch) => TrainBranch\n): TrainBranch {\n if (path.length === 0) {\n return updater(branch);\n }\n const [step, ...rest] = path;\n const toes = branch.feet?.[step.doubleIndex] ?? [];\n const updatedToes = toes.map((toe, index) =>\n index === step.toeIndex ? updateBranchAt(toe, rest, updater) : toe\n );\n return {\n ...branch,\n feet: { ...branch.feet, [step.doubleIndex]: updatedToes },\n };\n}\n\n/**\n * Returns a new tree with `move` applied. The tile is oriented so its matching\n * end connects. Does not validate; call {@link evaluatePlacement} first (or use\n * {@link playMove}).\n */\nexport function applyMove(\n root: TrainBranch,\n move: Move,\n _config?: RulesConfig\n): TrainBranch {\n const oriented =\n orientForConnection(move.tile, move.end.value) ?? { ...move.tile };\n\n return updateBranchAt(root, move.end.path, (branch) => {\n if (move.end.attach === 'run-tail') {\n return { ...branch, dominoes: [...branch.dominoes, oriented] };\n }\n\n const doubleIndex = move.end.doubleIndex ?? 0;\n const slot = move.end.toeSlot ?? branch.feet?.[doubleIndex]?.length ?? 0;\n const existing = branch.feet?.[doubleIndex]\n ? [...branch.feet[doubleIndex]]\n : [];\n existing[slot] = { dominoes: [oriented] };\n return {\n ...branch,\n feet: { ...branch.feet, [doubleIndex]: existing },\n };\n });\n}\n\nexport interface PlayMoveResult {\n ok: boolean;\n board: TrainBranch;\n violations: PlacementViolation[];\n}\n\n/** Validates a move against the rules and applies it only if legal. */\nexport function playMove(\n root: TrainBranch,\n move: Move,\n config: RulesConfig\n): PlayMoveResult {\n const result = evaluatePlacement(\n move.tile,\n move.end,\n collectPlayedKeys(root),\n config\n );\n if (!result.legal) {\n return { ok: false, board: root, violations: result.violations };\n }\n return { ok: true, board: applyMove(root, move, config), violations: [] };\n}\n"],"names":["GRID_POSITIONS","resolvePipPosition","cell","grid","Pip","row","col","gridSize","color","hollow","top","left","positionStyle","jsx","PIP_LAYOUTS","getPipLayout","value","DEFAULT_PIP_COLORS","PIP_COLORS","mergePipColors","overrides","resolvePipStyle","pipColors","getPipStyle","pipProps","pipColor","style","PipPattern","layout","Fragment","index","DominoHalf","DoubleTwelve","value1","value2","width","height","backgroundColor","borderColor","rotation","val1","val2","jsxs","DOMINO_WIDTH","DOMINO_HEIGHT","CHICKEN_FOOT_TOE_ANGLES","halfExtentAlongTrain","isDouble","dominoWidth","dominoHeight","stepAlongTrain","fromIsDouble","toIsDouble","trainDirection","angle","angleRad","trainPerpendicular","dirX","dirY","orientDominoValues","dominoes","layoutStyle","oriented","domino","i","prevValue","outwardPerpSign","nextPerpOffset","current","outwardSign","computeTrainLayout","startX","startY","leadGap","outwardSignInput","hubIndex","perpX","perpY","orientedDominoes","isHub","laneByIndex","currentX","currentY","perpOffset","laneSign","perpStep","setPerpOffset","target","delta","prevIsDouble","shift","tileCorners","entry","r","cos","sin","hw","hh","x","y","projectionGap","a","b","axis","aMin","aMax","bMin","bMax","p","d","tilesOverlap","epsilon","ca","cb","corners","q","ex","ey","len","overlapsAny","tile","others","other","TOE_PUSH_STEP","TOE_PUSH_MAX_STEPS","computeTrainTree","branch","depth","anchor","placed","pushAxis","minPushSteps","segmentOutward","buildLayout","originX","originY","appliedAnchor","k","trial","segments","key","hostIndex","host","toes","toeIndex","toe","toeOffset","sideSign","toeAngle","toePerp","outward","cornerX","cornerY","flattenSegments","segment","getTrainLayoutBounds","padding","halfExtent","minX","minY","maxX","maxY","DominoTrain","trainData","tableWidth","tableHeight","centerX","centerY","trainLayout","useMemo","showMarker","DominoHub","playerCount","radius","engineValue","trains","slots","hubSize","_","radians","t","tileKey","dominoKey","tileHasValue","otherEnd","orientForConnection","connectingValue","generateDominoSet","maxPips","tiles","dominoSetSize","n","generateSampleTrains","options","usedTiles","playerId","dominoCount","openValue","prevWasDouble","j","pickNextValue","feet","buildFeet","doubleValue","buildToe","startValue","length","next","pickNonDouble","candidates","isFirstDomino","isValidNextValue","defaultGameState","MexicanTrainGame","initialState","pipColorsProp","onPipColorsChange","gameState","setGameState","useState","setLayoutStyle","chickenFeet","setChickenFeet","pipColorsInternal","setPipColorsInternal","setPipColors","pipColorsEnabled","regenerateTrains","chickenFeetEnabled","sampleTrains","prevState","useEffect","toggleChickenFeet","DEFAULT_TOLERANCE","projectOnTrainAxis","dx","dy","projectOnPerpendicularAxis","validateDominoChain","issues","currentIsDouble","expectedAxisLayout","isDoubleArr","positions","along","perp","validateConsecutiveSpacing","tolerance","outwardSignOverride","expected","prev","expectedAlong","expectedPerp","validateNoPairOverlap","distance","minDistance","validateTrainLayout","validateChickenFootChain","walk","path","issue","first","validateTrainTree","segmentIndex","TRAIN_FIXTURES","CHICKEN_FOOT_FIXTURES","DEFAULT_RULES","requiredDoubleAnswers","config","sideToeSlots","resolveRules","getBranchAt","root","step","walkBranches","visit","doubleIndex","collectDoubles","out","hasCenter","sideToes","getUnsatisfiedDoubles","required","collectPlayedKeys","keys","getOpenEnds","unsatisfied","ends","status","last","evaluatePlacement","end","playedKeys","violations","getLegalMoves","hand","moves","updateBranchAt","updater","rest","updatedToes","applyMove","move","_config","slot","existing","playMove","result"],"mappings":"wIAUMA,GAGF,CACF,MAAO,CAAE,KAAM,CAAC,GAAI,GAAI,EAAE,EAAG,KAAM,CAAC,GAAI,GAAI,EAAE,EAAG,KAAM,KAAA,EACvD,MAAO,CAAE,KAAM,CAAC,GAAI,GAAI,EAAE,EAAG,KAAM,CAAC,GAAI,GAAI,GAAI,EAAE,EAAG,KAAM,KAAA,EAC3D,MAAO,CAAE,KAAM,CAAC,GAAI,GAAI,GAAI,EAAE,EAAG,KAAM,CAAC,GAAI,GAAI,EAAE,EAAG,KAAM,KAAA,CAC7D,EAEO,SAASC,GAAmBC,EAKjC,CACA,MAAMC,EAAOH,GAAeE,EAAK,QAAQ,EAEzC,MAAO,CACL,IAAKA,EAAK,KAAO,GAAGC,EAAK,KAAKD,EAAK,GAAG,CAAC,IACvC,KAAMA,EAAK,MAAQ,GAAGC,EAAK,KAAKD,EAAK,GAAG,CAAC,IACzC,MAAOC,EAAK,KACZ,OAAQA,EAAK,IAAA,CAEjB,CCpBO,MAAMC,GAAoB,CAAC,CAChC,IAAAC,EACA,IAAAC,EACA,SAAAC,EACA,MAAAC,EACA,OAAAC,EACA,IAAAC,EACA,KAAAC,CACF,IAAM,CACJ,MAAMC,EAAgBX,GAAmB,CAAE,IAAAI,EAAK,IAAAC,EAAK,SAAAC,EAAU,IAAAG,EAAK,KAAAC,EAAM,EAE1E,OACEE,EAAAA,IAAC,MAAA,CACC,cAAY,MACZ,WAAUR,EACV,WAAUC,EACV,YAAWC,EACX,MAAO,CACL,SAAU,WACV,gBAAiBE,EAAS,cAAgBD,EAC1C,OAAQC,EAAS,iBAAmB,OACpC,aAAc,MACd,UAAW,wBACX,UAAWA,EAAS,OAAY,8BAChC,GAAGG,CAAA,CACL,CAAA,CAGN,ECtCaE,GAAwD,CACnE,EAAG,CAAA,EACH,EAAG,CAAC,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,MAAO,EACvC,EAAG,CACD,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,EAC5B,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,CAAM,EAEpC,EAAG,CACD,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,EAC5B,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,EAC5B,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,CAAM,EAEpC,EAAG,CACD,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,EAC5B,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,EAC5B,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,EAC5B,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,CAAM,EAEpC,EAAG,CACD,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,EAC5B,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,EAC5B,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,EAC5B,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,EAC5B,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,CAAM,EAEpC,EAAG,CACD,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,EAC5B,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,EAC5B,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,EAC5B,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,EAC5B,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,EAC5B,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,CAAM,EAEpC,EAAG,CACD,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,EAC5B,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,EAC5B,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,EAC5B,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,EAC5B,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,EAC5B,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,EAC5B,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,CAAM,EAEpC,EAAG,CACD,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,EAC5B,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,EAC5B,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,EAC5B,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU,KAAA,EAC5B,CAAE,IAAK,EAAG,IAAK,EAAG,SAAU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"}
@@ -0,0 +1,24 @@
1
+ export { DoubleTwelve } from './app/DoubleTwelve';
2
+ export type { DoubleTwelveProps } from './app/DoubleTwelve';
3
+ export { MexicanTrainGame } from './app/MexicanTrainGame';
4
+ export { DominoHub } from './app/DominoHub';
5
+ export { DominoTrain } from './app/DominoTrain';
6
+ export { DEFAULT_PIP_COLORS, PIP_COLORS, mergePipColors, resolvePipStyle, getPipStyle, } from './app/pipColors';
7
+ export type { PipColorMap, PipColorStyle } from './app/pipColors';
8
+ export { PIP_LAYOUTS, getPipLayout } from './app/pipLayouts';
9
+ export { resolvePipPosition } from './app/pipGrid';
10
+ export type { PipGridSize, PipLayoutCell } from './app/pipGrid';
11
+ export { computeTrainLayout, computeTrainTree, flattenSegments, stepAlongTrain, outwardPerpSign, getTrainLayoutBounds, tileCorners, tilesOverlap, CHICKEN_FOOT_TOE_ANGLES, } from './app/trainLayout';
12
+ export type { TrainLayoutBounds } from './app/trainLayout';
13
+ export type { TrainLayoutEntry, TrainLayoutStyle, TrainSegment, ComputeTrainLayoutInput, ComputeTrainTreeInput, } from './app/trainLayout';
14
+ export { validateTrainLayout, validateTrainTree, validateChickenFootChain, } from './harness/layoutValidation';
15
+ export type { LayoutValidationResult } from './harness/layoutValidation';
16
+ export { TRAIN_FIXTURES, CHICKEN_FOOT_FIXTURES } from './harness/trainFixtures';
17
+ export type { GameState } from './game/GameState';
18
+ export type { TrainData, TrainBranch } from './game/TrainData';
19
+ export type { DominoValue } from './game/DominoValue';
20
+ export { tileKey, dominoKey, isDouble, tileHasValue, otherEnd, orientForConnection, generateDominoSet, dominoSetSize, } from './rules/dominoSet';
21
+ export { DEFAULT_RULES, resolveRules, requiredDoubleAnswers, sideToeSlots, } from './rules/rulesConfig';
22
+ export type { RulesConfig, ChickenFootConfig, DoubleObligation, } from './rules/rulesConfig';
23
+ export { getOpenEnds, getUnsatisfiedDoubles, getBranchAt, collectPlayedKeys, evaluatePlacement, getLegalMoves, applyMove, playMove, } from './rules/placement';
24
+ export type { OpenEnd, Move, BranchPath, PlacementResult, PlacementViolation, PlayMoveResult, } from './rules/placement';