hexgrid-algorithms

Complete reference for hexagonal grid mathematics and algorithms optimized for game development.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "hexgrid-algorithms" with this command: npx skills add ccalebcarter/purria-skills/ccalebcarter-purria-skills-hexgrid-algorithms

Hexgrid Algorithms

Complete reference for hexagonal grid mathematics and algorithms optimized for game development.

Coordinate Systems

Cube Coordinates (Recommended)

Three-axis system where q + r + s = 0 always.

interface CubeCoord { q: number; // Column r: number; // Row
s: number; // Derived: s = -q - r }

// Constraint: q + r + s must equal 0 function isValidCube(coord: CubeCoord): boolean { return coord.q + coord.r + coord.s === 0; }

Axial Coordinates (Storage-Efficient)

Two-axis system derived from cube (drop s).

interface AxialCoord { q: number; r: number; }

// Convert to cube function axialToCube(axial: AxialCoord): CubeCoord { return { q: axial.q, r: axial.r, s: -axial.q - axial.r }; }

// Convert from cube function cubeToAxial(cube: CubeCoord): AxialCoord { return { q: cube.q, r: cube.r }; }

Offset Coordinates (Display)

For rendering in standard grid layouts.

// Odd-q offset (pointy-top hexes) function axialToOffset(axial: AxialCoord): { col: number; row: number } { const col = axial.q; const row = axial.r + Math.floor((axial.q - (axial.q & 1)) / 2); return { col, row }; }

function offsetToAxial(col: number, row: number): AxialCoord { const q = col; const r = row - Math.floor((col - (col & 1)) / 2); return { q, r }; }

Core Operations

Distance

function hexDistance(a: CubeCoord, b: CubeCoord): number { return Math.max( Math.abs(a.q - b.q), Math.abs(a.r - b.r), Math.abs(a.s - b.s) ); }

// Or equivalently: function hexDistanceAlt(a: CubeCoord, b: CubeCoord): number { return (Math.abs(a.q - b.q) + Math.abs(a.r - b.r) + Math.abs(a.s - b.s)) / 2; }

Neighbors

const CUBE_DIRECTIONS: CubeCoord[] = [ { q: 1, r: -1, s: 0 }, // East { q: 1, r: 0, s: -1 }, // Southeast { q: 0, r: 1, s: -1 }, // Southwest { q: -1, r: 1, s: 0 }, // West { q: -1, r: 0, s: 1 }, // Northwest { q: 0, r: -1, s: 1 }, // Northeast ];

function getNeighbors(hex: CubeCoord): CubeCoord[] { return CUBE_DIRECTIONS.map(dir => ({ q: hex.q + dir.q, r: hex.r + dir.r, s: hex.s + dir.s })); }

function getNeighbor(hex: CubeCoord, direction: number): CubeCoord { const dir = CUBE_DIRECTIONS[direction]; return { q: hex.q + dir.q, r: hex.r + dir.r, s: hex.s + dir.s }; }

Ring

Get all hexes at exact distance from center.

function hexRing(center: CubeCoord, radius: number): CubeCoord[] { if (radius === 0) return [center];

const results: CubeCoord[] = []; let hex: CubeCoord = { q: center.q + CUBE_DIRECTIONS[4].q * radius, r: center.r + CUBE_DIRECTIONS[4].r * radius, s: center.s + CUBE_DIRECTIONS[4].s * radius };

for (let i = 0; i < 6; i++) { for (let j = 0; j < radius; j++) { results.push({ ...hex }); hex = getNeighbor(hex, i); } }

return results; }

Spiral (All Hexes Within Radius)

function hexSpiral(center: CubeCoord, radius: number): CubeCoord[] { const results: CubeCoord[] = [center];

for (let r = 1; r <= radius; r++) { results.push(...hexRing(center, r)); }

return results; }

Pathfinding

A* for Hex Grids

interface PathNode { coord: CubeCoord; g: number; // Cost from start h: number; // Heuristic to goal f: number; // Total: g + h parent: PathNode | null; }

function hexKey(coord: CubeCoord): string { return ${coord.q},${coord.r}; }

function findPath( start: CubeCoord, goal: CubeCoord, isPassable: (coord: CubeCoord) => boolean, getCost: (coord: CubeCoord) => number = () => 1 ): CubeCoord[] | null { const openSet = new Map<string, PathNode>(); const closedSet = new Set<string>();

const startNode: PathNode = { coord: start, g: 0, h: hexDistance(start, goal), f: hexDistance(start, goal), parent: null };

openSet.set(hexKey(start), startNode);

while (openSet.size > 0) { // Get node with lowest f let current: PathNode | null = null; for (const node of openSet.values()) { if (!current || node.f < current.f) { current = node; } }

if (!current) break;

// Check if reached goal
if (hexKey(current.coord) === hexKey(goal)) {
  const path: CubeCoord[] = [];
  let node: PathNode | null = current;
  while (node) {
    path.unshift(node.coord);
    node = node.parent;
  }
  return path;
}

// Move current to closed set
openSet.delete(hexKey(current.coord));
closedSet.add(hexKey(current.coord));

// Check neighbors
for (const neighbor of getNeighbors(current.coord)) {
  const key = hexKey(neighbor);
  
  if (closedSet.has(key) || !isPassable(neighbor)) {
    continue;
  }
  
  const g = current.g + getCost(neighbor);
  const existing = openSet.get(key);
  
  if (!existing || g &#x3C; existing.g) {
    const node: PathNode = {
      coord: neighbor,
      g,
      h: hexDistance(neighbor, goal),
      f: g + hexDistance(neighbor, goal),
      parent: current
    };
    openSet.set(key, node);
  }
}

}

return null; // No path found }

Spreading Algorithms

Trouble Spread (Farming in Purria)

function spreadTrouble( cluster: TroubleCluster, gridRadius: number, occupiedHexes: Set<string> ): CubeCoord[] { const newHexes: CubeCoord[] = []; const spreadChance = 0.3 + (cluster.severity * 0.1); // 30-80%

for (const hex of cluster.hexCoords) { for (const neighbor of getNeighbors(hex)) { const key = hexKey(neighbor);

  // Skip if already occupied or out of bounds
  if (occupiedHexes.has(key)) continue;
  if (hexDistance({ q: 0, r: 0, s: 0 }, neighbor) > gridRadius) continue;
  
  // Random spread chance
  if (Math.random() &#x3C; spreadChance) {
    newHexes.push(neighbor);
    occupiedHexes.add(key);
  }
}

}

return newHexes; }

Flood Fill

function floodFill( start: CubeCoord, maxDistance: number, isValid: (coord: CubeCoord) => boolean ): CubeCoord[] { const visited = new Set<string>(); const result: CubeCoord[] = []; const queue: { coord: CubeCoord; dist: number }[] = [{ coord: start, dist: 0 }];

while (queue.length > 0) { const { coord, dist } = queue.shift()!; const key = hexKey(coord);

if (visited.has(key) || dist > maxDistance || !isValid(coord)) {
  continue;
}

visited.add(key);
result.push(coord);

for (const neighbor of getNeighbors(coord)) {
  queue.push({ coord: neighbor, dist: dist + 1 });
}

}

return result; }

Rendering

Pixel Coordinates

const HEX_SIZE = 50; // Radius in pixels

// Pointy-top hex function hexToPixel(hex: AxialCoord, size: number = HEX_SIZE): { x: number; y: number } { const x = size * (Math.sqrt(3) * hex.q + Math.sqrt(3) / 2 * hex.r); const y = size * (3 / 2 * hex.r); return { x, y }; }

function pixelToHex(x: number, y: number, size: number = HEX_SIZE): AxialCoord { const q = (Math.sqrt(3) / 3 * x - 1 / 3 * y) / size; const r = (2 / 3 * y) / size; return hexRound({ q, r }); }

// Round fractional hex to nearest hex function hexRound(hex: AxialCoord): AxialCoord { const cube = axialToCube(hex); let rq = Math.round(cube.q); let rr = Math.round(cube.r); let rs = Math.round(cube.s);

const qDiff = Math.abs(rq - cube.q); const rDiff = Math.abs(rr - cube.r); const sDiff = Math.abs(rs - cube.s);

if (qDiff > rDiff && qDiff > sDiff) { rq = -rr - rs; } else if (rDiff > sDiff) { rr = -rq - rs; }

return { q: rq, r: rr }; }

SVG Hex Path

function hexCorners(center: { x: number; y: number }, size: number): { x: number; y: number }[] { const corners: { x: number; y: number }[] = []; for (let i = 0; i < 6; i++) { const angle = (Math.PI / 180) * (60 * i - 30); // Pointy-top corners.push({ x: center.x + size * Math.cos(angle), y: center.y + size * Math.sin(angle) }); } return corners; }

function hexPath(center: { x: number; y: number }, size: number): string { const corners = hexCorners(center, size); return corners.map((c, i) => ${i === 0 ? 'M' : 'L'} ${c.x} ${c.y}).join(' ') + ' Z'; }

Performance Tips

  • Use string keys for Map/Set operations: ${q},${r}

  • Pre-calculate neighbors for static grids

  • Limit pathfinding with max iterations

  • Batch render updates instead of per-hex

  • Use spatial hashing for large grids (chunk into regions)

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

game-engineering-team

No summary provided by upstream source.

Repository SourceNeeds Review
General

react-game-ui

No summary provided by upstream source.

Repository SourceNeeds Review
General

game-assets-team

No summary provided by upstream source.

Repository SourceNeeds Review