hexagrid
Version:
Hexagonal grid with fast lookup
239 lines • 7.68 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const direction_1 = require("./direction");
const cubecoordinates_1 = require("./cubecoordinates");
class Grid {
constructor(...hexes) {
this.hexes = new Map();
this.push(...hexes);
}
get size() { return this.hexes.size; }
/**
* Merge other grids into the current grid
*
* If any hex in the new grids overlap with hexes in the current grid,
* the older hexes are overwritten, similarly to what happens with `Object.assign`.
* @param grids grids to merge into the current grid
*/
merge(...grids) {
const [thisHexes, ...otherHexes] = [this, ...grids].map(grid => Array.from(grid.values()));
this.hexes.clear();
this.push(...thisHexes.concat(...otherHexes));
return this;
}
/**
* Adds a bunch of hexes to the grid
* @param hexes
*/
push(...hexes) {
for (const hex of hexes) {
this.hexes.set(`${hex.q}x${hex.r}`, hex);
}
}
get(coord) {
return this.hexes.get(`${coord.q}x${coord.r}`);
}
neighbour(coord, direction) {
return this.get(cubecoordinates_1.CubeCoordinates.translated(coord, direction));
}
neighbours(center, directions = direction_1.Direction.all) {
const ret = [];
for (const direction of direction_1.Direction.list()) {
if (direction & directions) {
const hex = this.get(cubecoordinates_1.CubeCoordinates.translated(center, direction));
if (hex) {
ret.push(hex);
}
}
}
return ret;
}
/**
* Get the list of hexes forming the shortest path between two hexes (included)
*
* Using A*
*
*/
path(coord1, coord2) {
const hex1 = this.get(coord1);
const hex2 = this.get(coord2);
if (!hex1 || !hex2) {
return undefined;
}
// const testPath = this.easyPath(coord1, coord2);
// if (testPath) {
// return testPath;
// }
const destCoord = hex2.toString();
const allPaths = {};
const toExpand = {};
let toExpandNext = [];
const addPath = (path) => {
const [last] = path.slice(-1);
const minDist = cubecoordinates_1.CubeCoordinates.distance(last, hex2);
toExpand[minDist] = toExpand[minDist] || {};
toExpand[minDist][last.toString()] = path;
allPaths[last.toString()] = path;
};
const readyNextIteration = () => {
let minDistance = Number.POSITIVE_INFINITY;
for (const key of Object.keys(toExpand)) {
if (+key < minDistance) {
minDistance = +key;
}
}
if (minDistance < Number.POSITIVE_INFINITY) {
toExpandNext = Object.values(toExpand[minDistance]);
delete toExpand[minDistance];
}
else {
toExpandNext = [];
}
};
addPath([hex1]);
readyNextIteration();
while (!(destCoord in allPaths) && toExpandNext.length > 0) {
for (const path of toExpandNext) {
const hex = path[path.length - 1];
for (const neighbour of this.neighbours(hex)) {
if (allPaths[neighbour.toString()]) {
continue;
}
addPath([...path, neighbour]);
}
}
readyNextIteration();
}
return allPaths[destCoord];
}
/**
* Shortest path between two coordinates, stopping when obstacle
* @param hex1
* @param hex2
*/
easyPath(coord1, coord2) {
const hex1 = this.get(coord1);
const hex2 = this.get(coord2);
if (!hex1 || !hex2) {
return undefined;
}
const path = [hex1];
let currentHex = hex1;
while (currentHex.q !== hex2.q || currentHex.r !== hex2.r) {
currentHex = this.neighbour(currentHex, cubecoordinates_1.CubeCoordinates.direction(currentHex, hex2));
if (!currentHex) {
return undefined;
}
path.push(currentHex);
}
return path;
}
/**
* Distance between two hexes. -1 if not possible
*
*/
distance(hex1, hex2) {
const path = this.path(hex1, hex2);
return (path || []).length - 1;
}
/**
* Removes a hex by its coordinates. Returns whether there
* was a hex removed
*
* @param q
* @param r
*/
remove({ q, r }) {
return this.hexes.delete(`${q}x${r}`);
}
/**
* Rotates the whole grid X times to the left, relative to center.
*
* Each rotation is 60°
*
* @param times
* @param center The origin if not given
*/
rotateLeft(times = 1, center) {
this.hexes.forEach(hex => hex.rotateLeft(times, center));
this.recalibrate();
return this;
}
/**
* Rotates the whole grid X times to the right, relative to center.
*
* Each rotation is 60°
*
* @param times
* @param center The origin if not given
*/
rotateRight(times = 1, center) {
this.hexes.forEach(hex => hex.rotateRight(times, center));
this.recalibrate();
return this;
}
/**
* Separate the hexes given into groups.
*
* Each hex in a group can travel through to other
* members of its group by going through only members
* of its group.
*
* @param hexes
*/
groups(hexes) {
const hexSet = new Set(hexes);
const groups = [];
for (const hex of hexes) {
// If the hex is already in a group
if ((() => {
for (const group of groups) {
if (group.has(hex)) {
return true;
}
}
})()) {
continue;
}
const newGroup = new Set([hex]);
let toExplore = new Set([hex]);
let nextToExplore = new Set();
groups.push(newGroup);
while (toExplore.size > 0) {
for (const hex of toExplore) {
for (const nb of this.neighbours(hex)) {
if (newGroup.has(nb)) {
continue;
}
if (!hexSet.has(nb)) {
continue;
}
newGroup.add(nb);
nextToExplore.add(nb);
}
}
toExplore = nextToExplore;
nextToExplore = new Set();
}
}
return groups;
}
/**
* Makes sure the underlying storage of Hexes is coherent, if
* any of their coordinates was changed since they were added
*/
recalibrate() {
const array = Array.from(this.values());
this.hexes.clear();
this.push(...array);
return this;
}
values() {
return this.hexes.values();
}
toJSON() {
return Array.from(this.values());
}
}
exports.default = Grid;
//# sourceMappingURL=grid.js.map