import type { AxialCoord, EdgeMask, HexFeature } from './types.js'; import { HexEdge, OPPOSITE_EDGE, ALL_EDGES } from './types.js'; import { getNeighbor } from './coords.js'; import type { HexMap } from './hex-map.js'; /** Create an edge mask from a list of edges */ export function edgeMask(...edges: HexEdge[]): EdgeMask { let mask = 0; for (const e of edges) mask |= (1 << e); return mask; } /** Check if an edge is set in a mask */ export function hasEdge(mask: EdgeMask, edge: HexEdge): boolean { return (mask & (1 << edge)) !== 0; } /** Toggle an edge in a mask */ export function toggleEdge(mask: EdgeMask, edge: HexEdge): EdgeMask { return mask ^ (1 << edge); } /** Set an edge in a mask */ export function setEdge(mask: EdgeMask, edge: HexEdge): EdgeMask { return mask | (1 << edge); } /** Clear an edge from a mask */ export function clearEdge(mask: EdgeMask, edge: HexEdge): EdgeMask { return mask & ~(1 << edge); } /** Count active edges */ export function edgeCount(mask: EdgeMask): number { let n = 0; for (let i = 0; i < 6; i++) { if (mask & (1 << i)) n++; } return n; } /** Get list of active edges */ export function connectedEdges(mask: EdgeMask): HexEdge[] { const result: HexEdge[] = []; for (let i = 0; i < 6; i++) { if (mask & (1 << i)) result.push(i as HexEdge); } return result; } /** Rotate a mask clockwise by N steps (each step = 60 degrees) */ export function rotateMask(mask: EdgeMask, steps: number): EdgeMask { const s = ((steps % 6) + 6) % 6; // Rotate the 6 low bits return ((mask << s) | (mask >> (6 - s))) & 0x3f; } export interface ConstraintAction { coord: AxialCoord; terrainId: string; edge: HexEdge; action: 'add_dead_end'; } /** * After a feature edge is set on a hex, check if the neighbor * needs a corresponding entry. Returns actions to apply. */ export function enforceEdgeConstraints( hexMap: HexMap, coord: AxialCoord, terrainId: string, mask: EdgeMask, ): ConstraintAction[] { const actions: ConstraintAction[] = []; for (const edge of ALL_EDGES) { if (!hasEdge(mask, edge)) continue; const neighbor = getNeighbor(coord, edge); const oppositeEdge = OPPOSITE_EDGE[edge]; const neighborTerrain = hexMap.getTerrain(neighbor); const neighborFeature = neighborTerrain.features.find(f => f.terrainId === terrainId); if (!neighborFeature || !hasEdge(neighborFeature.edgeMask, oppositeEdge)) { actions.push({ coord: neighbor, terrainId, edge: oppositeEdge, action: 'add_dead_end', }); } } return actions; } /** * Apply constraint actions to the hex map. * Adds dead-end features on neighbor hexes. */ export function applyConstraintActions(hexMap: HexMap, actions: ConstraintAction[]): void { for (const action of actions) { const terrain = hexMap.getTerrain(action.coord); const existing = terrain.features.find(f => f.terrainId === action.terrainId); if (existing) { existing.edgeMask = setEdge(existing.edgeMask, action.edge); } else { terrain.features.push({ terrainId: action.terrainId, edgeMask: edgeMask(action.edge), }); } hexMap.setTerrain(action.coord, terrain); } }