2019-07-06 20:59:27 +00:00
|
|
|
class LayoutLink {
|
2019-07-07 21:27:55 +00:00
|
|
|
constructor(from, to, nodesByPos, linksByPos) {
|
2019-07-06 20:59:27 +00:00
|
|
|
this.from_ = from;
|
|
|
|
|
this.to_ = to;
|
|
|
|
|
this.nodesByPos_ = nodesByPos;
|
2019-07-07 21:27:55 +00:00
|
|
|
this.linksByPos_ = linksByPos;
|
2019-07-06 20:59:27 +00:00
|
|
|
this.bfs();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bfs() {
|
|
|
|
|
// TODO: give more thought to birdirectional search
|
2019-07-07 21:36:39 +00:00
|
|
|
// TODO: make diagonals cost more
|
2019-07-06 20:59:27 +00:00
|
|
|
|
|
|
|
|
let cheapestCostByPos = new StringMap();
|
|
|
|
|
|
2019-07-07 22:30:49 +00:00
|
|
|
// shortcut to save the lookup
|
|
|
|
|
let cheapestCostToGoal = null;
|
2019-07-06 20:59:27 +00:00
|
|
|
|
|
|
|
|
// BFS work queue
|
2019-07-07 22:28:38 +00:00
|
|
|
let queue = new MinHeap((a) => a.cost);
|
|
|
|
|
queue.push({
|
|
|
|
|
path: [Array.from(this.from_.pos)],
|
|
|
|
|
cost: 0,
|
|
|
|
|
});
|
2019-07-06 20:59:27 +00:00
|
|
|
|
2019-07-07 21:45:15 +00:00
|
|
|
let iter = 0;
|
2019-07-07 22:28:38 +00:00
|
|
|
for (let next = queue.pop(); next; next = queue.pop()) {
|
2019-07-07 21:45:15 +00:00
|
|
|
++iter;
|
2019-07-06 20:59:27 +00:00
|
|
|
let pos = next.path[next.path.length - 1];
|
|
|
|
|
|
2019-07-07 21:27:55 +00:00
|
|
|
let prevCost = cheapestCostByPos.get(pos);
|
|
|
|
|
if (prevCost && prevCost <= next.cost) {
|
2019-07-06 20:59:27 +00:00
|
|
|
// Reached a previous pos via a higher- or equal-cost path
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
cheapestCostByPos.set(pos, next);
|
|
|
|
|
|
|
|
|
|
if (pos[0] == this.to_.pos[0] && pos[1] == this.to_.pos[1]) {
|
|
|
|
|
this.path = next.path;
|
2019-07-07 22:32:35 +00:00
|
|
|
break;
|
2019-07-06 20:59:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//// Calculate cost for next hop
|
|
|
|
|
let newCost = next.cost;
|
|
|
|
|
|
|
|
|
|
// Any hop has cost
|
|
|
|
|
newCost += 1;
|
|
|
|
|
|
2019-07-07 21:27:55 +00:00
|
|
|
// Overlapping links have cost
|
|
|
|
|
if (this.linksByPos_.has(pos)) {
|
|
|
|
|
newCost += 2;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-06 20:59:27 +00:00
|
|
|
if (this.nodesByPos_.has(pos)) {
|
|
|
|
|
// Traversing nodes has higher cost
|
|
|
|
|
newCost += 5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (let xOff of [-1, 0, 1]) {
|
|
|
|
|
for (let yOff of [-1, 0, 1]) {
|
|
|
|
|
if (xOff == 0 && yOff == 0) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
let newPos = [pos[0] + xOff, pos[1] + yOff];
|
|
|
|
|
let newPath = Array.from(next.path);
|
|
|
|
|
newPath.push(newPos);
|
|
|
|
|
queue.push({
|
|
|
|
|
cost: newCost,
|
|
|
|
|
path: newPath,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-07-07 21:27:55 +00:00
|
|
|
|
|
|
|
|
for (let hop of this.path) {
|
2019-07-07 21:47:16 +00:00
|
|
|
getOrSet(this.linksByPos_, hop, []).push(this);
|
2019-07-07 21:27:55 +00:00
|
|
|
}
|
2019-07-07 21:45:15 +00:00
|
|
|
|
|
|
|
|
console.log(iter);
|
2019-07-06 20:59:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Mapping to lines.svg clock-style numbering
|
|
|
|
|
outPointLookup = new StringMap([
|
|
|
|
|
[[ 0,-1], 0],
|
|
|
|
|
[[ 1,-1], 1],
|
|
|
|
|
[[ 1, 0], 2],
|
|
|
|
|
[[ 1, 1], 3],
|
|
|
|
|
[[ 0, 1], 4],
|
|
|
|
|
[[-1, 1], 5],
|
|
|
|
|
[[-1, 0], 6],
|
|
|
|
|
[[-1,-1], 7],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
getOutPoint(from, to) {
|
|
|
|
|
let offset = [
|
|
|
|
|
to[0] - from[0],
|
|
|
|
|
to[1] - from[1],
|
|
|
|
|
];
|
|
|
|
|
return this.outPointLookup.get(offset);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getInPoint(from, to) {
|
|
|
|
|
return (this.getOutPoint(from, to) + 4) % 8;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getSteps() {
|
|
|
|
|
let steps = [];
|
|
|
|
|
|
|
|
|
|
steps.push({
|
|
|
|
|
type: 'line',
|
|
|
|
|
pos: Array.from(this.path[0]),
|
|
|
|
|
cls: 's' + this.getOutPoint(this.path[0], this.path[1]),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
for (let i = 1; i < this.path.length - 1; ++i) {
|
|
|
|
|
let inPoint = this.getInPoint(this.path[i - 1], this.path[i]);
|
|
|
|
|
let outPoint = this.getOutPoint(this.path[i], this.path[i + 1]);
|
|
|
|
|
steps.push({
|
|
|
|
|
type: 'line',
|
|
|
|
|
pos: Array.from(this.path[i]),
|
|
|
|
|
cls: `i${inPoint}o${outPoint}`,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
steps.push({
|
|
|
|
|
type: 'line',
|
|
|
|
|
pos: Array.from(this.path[this.path.length - 1]),
|
|
|
|
|
cls: 's' + this.getInPoint(
|
|
|
|
|
this.path[this.path.length - 2], this.path[this.path.length - 1]),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return steps;
|
|
|
|
|
}
|
|
|
|
|
}
|