kisch 1.0.2 → 1.0.4

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,113 @@
1
+ export function astar({
2
+ start,
3
+ goal,
4
+ isGoal,
5
+ neighbours,
6
+ cost,
7
+ heuristic = () => 0,
8
+ key,
9
+ stats
10
+ }) {
11
+ if (!key) throw new Error("key(n) is required");
12
+ if (!stats)
13
+ stats={};
14
+
15
+ // Goal resolution
16
+ const goalKey = goal !== undefined ? key(goal) : null;
17
+ const isGoalFn =
18
+ isGoal ||
19
+ ((n) => {
20
+ if (goalKey === null) throw new Error("Provide goal or isGoal");
21
+ return key(n) === goalKey;
22
+ });
23
+
24
+ // Open set (priority queue via simple array for clarity)
25
+ const open = [];
26
+ const push = (node, f) => {
27
+ open.push({ node, f });
28
+ };
29
+ const pop = () => {
30
+ // naive priority queue (can replace with binary heap later)
31
+ let bestIdx = 0;
32
+ for (let i = 1; i < open.length; i++) {
33
+ if (open[i].f < open[bestIdx].f) bestIdx = i;
34
+ }
35
+ const item = open[bestIdx];
36
+ open.splice(bestIdx, 1);
37
+ return item.node;
38
+ };
39
+
40
+ // Caches
41
+ const gScore = new Map(); // key(n) -> best cost
42
+ const cameFrom = new Map(); // key(n) -> previous key
43
+ const nodeCache = new Map(); // key(n) -> node (for reconstruction)
44
+
45
+ // Optional memoization (cheap + safe)
46
+ const heuristicCache = new Map(); // key(n) -> h(n)
47
+ const costCache = new Map(); // "k1|k2" -> cost
48
+
49
+ const getHeuristic = (n) => {
50
+ const k = key(n);
51
+ if (heuristicCache.has(k)) return heuristicCache.get(k);
52
+ const h = heuristic(n);
53
+ heuristicCache.set(k, h);
54
+ return h;
55
+ };
56
+
57
+ const getCost = (a, b) => {
58
+ const ka = key(a);
59
+ const kb = key(b);
60
+ const ck = ka + "|" + kb;
61
+ if (costCache.has(ck)) return costCache.get(ck);
62
+ const c = cost(a, b);
63
+ costCache.set(ck, c);
64
+ return c;
65
+ };
66
+
67
+ const startKey = key(start);
68
+ gScore.set(startKey, 0);
69
+ nodeCache.set(startKey, start);
70
+
71
+ push(start, getHeuristic(start));
72
+
73
+ stats.steps=0;
74
+
75
+ while (open.length > 0) {
76
+ stats.steps++;
77
+ const current = pop();
78
+ const ck = key(current);
79
+
80
+ if (isGoalFn(current)) {
81
+ return reconstructPath(cameFrom, nodeCache, ck);
82
+ }
83
+
84
+ for (const next of neighbours(current)) {
85
+ const nk = key(next);
86
+ nodeCache.set(nk, next);
87
+
88
+ const tentative =
89
+ gScore.get(ck) + getCost(current, next);
90
+
91
+ if (!gScore.has(nk) || tentative < gScore.get(nk)) {
92
+ cameFrom.set(nk, ck);
93
+ gScore.set(nk, tentative);
94
+
95
+ const f = tentative + getHeuristic(next);
96
+ push(next, f);
97
+ }
98
+ }
99
+ }
100
+
101
+ return null; // no path
102
+ }
103
+
104
+ // Helper
105
+ function reconstructPath(cameFrom, nodeCache, currentKey) {
106
+ const path = [];
107
+ while (currentKey) {
108
+ path.push(nodeCache.get(currentKey));
109
+ currentKey = cameFrom.get(currentKey);
110
+ }
111
+ path.reverse();
112
+ return path;
113
+ }
@@ -1,10 +1,19 @@
1
1
  export class Point extends Array {
2
2
  constructor(...a) {
3
- if (a)
3
+ if (a.length==1 &&
4
+ !Array.isArray(a[0]) &&
5
+ Object.hasOwn(a[0],"x") &&
6
+ Object.hasOwn(a[0],"y"))
7
+ super(a[0].x,a[0].y);
8
+
9
+ else if (a)
4
10
  super(...[a].flat(Infinity));
5
11
 
6
12
  else
7
13
  super();
14
+
15
+ if (this.length && (isNaN(this[0]) || isNaN(this[1])))
16
+ throw new Error("NaN point: l="+this.length+" from: "+a);
8
17
  }
9
18
 
10
19
  add(p) {
@@ -15,6 +24,8 @@ export class Point extends Array {
15
24
  }
16
25
 
17
26
  sub(p) {
27
+ //console.log("sub ",this,p);
28
+
18
29
  return Point.from([
19
30
  this[0]-p[0],
20
31
  this[1]-p[1]
@@ -27,10 +38,26 @@ export class Point extends Array {
27
38
 
28
39
  snap(gridSize) {
29
40
  return new Point([
30
- Math.round(this[0]/gridSize)*gridSize,
31
- Math.round(this[1]/gridSize)*gridSize,
41
+ Number((Math.round(this[0]/gridSize)*gridSize).toFixed(2)),
42
+ Number((Math.round(this[1]/gridSize)*gridSize).toFixed(2)),
32
43
  ]);
33
44
  }
45
+
46
+ len() {
47
+ return Math.sqrt(this[0]*this[0]+this[1]*this[1]);
48
+ }
49
+
50
+ rotateDegrees(deg) {
51
+ const rad = deg * Math.PI / 180;
52
+
53
+ const cos = Math.cos(rad);
54
+ const sin = Math.sin(rad);
55
+
56
+ return new Point(
57
+ this[0] * cos - this[1] * sin,
58
+ this[0] * sin + this[1] * cos
59
+ );
60
+ }
34
61
  }
35
62
 
36
63
  export class Rect {
@@ -40,9 +67,14 @@ export class Rect {
40
67
  }
41
68
 
42
69
  static fromCorners(p1, p2) {
70
+ //console.log("p1 ",p1);
71
+ //console.log("p2 ",p2);
72
+
43
73
  p1=new Point(p1);
44
74
  p2=new Point(p2);
45
75
 
76
+ //console.log("created...");
77
+
46
78
  return new Rect(p1,p2.sub(p1));
47
79
  }
48
80
 
@@ -91,6 +123,15 @@ export class Rect {
91
123
  return Rect.fromCorners([left,top],[right,bottom]);
92
124
  }
93
125
 
126
+ includePoint(p) {
127
+ let left=Math.min(this.getLeft(),p[0]);
128
+ let top=Math.min(this.getTop(),p[1]);
129
+ let right=Math.max(this.getRight(),p[0]);
130
+ let bottom=Math.max(this.getBottom(),p[1]);
131
+
132
+ return Rect.fromCorners([left,top],[right,bottom]);
133
+ }
134
+
94
135
  overlaps(other) {
95
136
  return !(
96
137
  this.getRight() <= other.getLeft() ||
@@ -0,0 +1,39 @@
1
+ export function collapsePath(path) {
2
+ if (!path || path.length < 2) return path;
3
+
4
+ const result = [path[0]];
5
+
6
+ let prevDir = null;
7
+
8
+ for (let i = 1; i < path.length - 1; i++) {
9
+ const prev = path[i - 1];
10
+ const cur = path[i];
11
+ const next = path[i + 1];
12
+
13
+ const dir1 = {
14
+ x: cur.x - prev.x,
15
+ y: cur.y - prev.y,
16
+ };
17
+
18
+ const dir2 = {
19
+ x: next.x - cur.x,
20
+ y: next.y - cur.y,
21
+ };
22
+
23
+ const sameDirection =
24
+ dir1.x === dir2.x && dir1.y === dir2.y;
25
+
26
+ // keep only if direction changes
27
+ if (!sameDirection) {
28
+ result.push(cur);
29
+ }
30
+ }
31
+
32
+ result.push(path[path.length - 1]);
33
+
34
+ return result.map(p=>({x: p.x, y: p.y}));
35
+ }
36
+
37
+ export function manhattanDist(x1, y1, x2, y2) {
38
+ return (Math.abs(x2-x1)+Math.abs(y2-y1));
39
+ }
@@ -0,0 +1,33 @@
1
+ export class DeclaredError extends Error {
2
+ constructor(...args) {
3
+ super(...args);
4
+ this.declared=true;
5
+ }
6
+ }
7
+
8
+ export function arrayUnique(array) {
9
+ let result=new Set();
10
+
11
+ for (let item of array)
12
+ result.add(item);
13
+
14
+ return Array.from(result);
15
+ }
16
+
17
+ export function arrayGetMinIndex(array) {
18
+ let min;
19
+ for (let k of Object.keys(array))
20
+ if (min===undefined || Number(k)<min)
21
+ min=Number(k);
22
+
23
+ return min;
24
+ }
25
+
26
+ export function arrayGetMaxIndex(array) {
27
+ let max;
28
+ for (let k of Object.keys(array))
29
+ if (max===undefined || Number(k)>max)
30
+ max=Number(k);
31
+
32
+ return max;
33
+ }
@@ -1,4 +1,5 @@
1
1
  import {spawn} from 'node:child_process';
2
+ import {fileURLToPath} from 'url';
2
3
 
3
4
  export function runCommand(cmd, args = []) {
4
5
  return new Promise((resolve, reject) => {
@@ -15,3 +16,7 @@ export function runCommand(cmd, args = []) {
15
16
  child.on('error', reject);
16
17
  });
17
18
  }
19
+
20
+ export function dirnameFromImportMeta(meta) {
21
+ return fileURLToPath(new URL('.', meta.url));
22
+ }
package/src/js-util.js DELETED
@@ -1,15 +0,0 @@
1
- export class DeclaredError extends Error {
2
- constructor(...args) {
3
- super(...args);
4
- this.declared=true;
5
- }
6
- }
7
-
8
- export function arrayUnique(array) {
9
- let result=new Set();
10
-
11
- for (let item of array)
12
- result.add(item);
13
-
14
- return Array.from(result);
15
- }
@@ -1,176 +0,0 @@
1
- import { Point, pointKey } from "../src/cartesian-math.js";
2
-
3
- function manhattan(a, b) {
4
- return Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]);
5
- }
6
-
7
- function rangeOverlap(a1, a2, b1, b2) {
8
- const minA = Math.min(a1, a2);
9
- const maxA = Math.max(a1, a2);
10
- const minB = Math.min(b1, b2);
11
- const maxB = Math.max(b1, b2);
12
- return maxA > minB && maxB > minA;
13
- }
14
-
15
- /* ───────────────────────── Rect blocking (unchanged) ───────────────────────── */
16
-
17
- export function segmentBlocked(a, b, rects) {
18
- const ax = a[0], ay = a[1];
19
- const bx = b[0], by = b[1];
20
-
21
- for (const r of rects) {
22
- const rx = r.corner[0];
23
- const ry = r.corner[1];
24
- const rw = r.size[0];
25
- const rh = r.size[1];
26
-
27
- // horizontal
28
- if (ay === by) {
29
- if (
30
- ay >= ry && ay <= ry + rh &&
31
- rangeOverlap(ax, bx, rx, rx + rw)
32
- ) return true;
33
- }
34
-
35
- // vertical
36
- if (ax === bx) {
37
- if (
38
- ax >= rx && ax <= rx + rw &&
39
- rangeOverlap(ay, by, ry, ry + rh)
40
- ) return true;
41
- }
42
- }
43
-
44
- return false;
45
- }
46
-
47
- /* ───────────────────────── Line blocking (NEW) ───────────────────────── */
48
-
49
- function similar(a,b) {
50
- return Math.abs(a-b)<0.0000001
51
- }
52
-
53
- function segmentOverlapsLine(a1, a2, b1, b2) {
54
- // horizontal
55
- // if (a1[1] === a2[1] && b1[1] === b2[1] && a1[1] === b1[1]) {
56
- if (similar(a1[1],a2[1]) && similar(b1[1],b2[1]) && similar(a1[1],b1[1])) {
57
- return rangeOverlap(a1[0], a2[0], b1[0], b2[0]);
58
- }
59
-
60
- // vertical
61
- // if (a1[0] === a2[0] && b1[0] === b2[0] && a1[0] === b1[0]) {
62
- if (similar(a1[0],a2[0]) && similar(b1[0],b2[0]) && similar(a1[0],b1[0])) {
63
- return rangeOverlap(a1[1], a2[1], b1[1], b2[1]);
64
- }
65
-
66
- return false;
67
- }
68
-
69
- export function segmentBlockedByLines(a, b, lines) {
70
- for (const { a: l1, b: l2 } of lines) {
71
- if (segmentOverlapsLine(a, b, l1, l2)) {
72
- return true;
73
- }
74
- }
75
- return false;
76
- }
77
-
78
- /* ───────────────────────── Path compression (unchanged) ───────────────────────── */
79
-
80
- function compressPath(points) {
81
- if (points.length <= 2) return points;
82
-
83
- const out = [points[0]];
84
-
85
- for (let i = 1; i < points.length - 1; i++) {
86
- const a = out[out.length - 1];
87
- const b = points[i];
88
- const c = points[i + 1];
89
-
90
- const abx = b[0] - a[0];
91
- const aby = b[1] - a[1];
92
- const bcx = c[0] - b[0];
93
- const bcy = c[1] - b[1];
94
-
95
- if (
96
- (abx === 0 && bcx === 0) ||
97
- (aby === 0 && bcy === 0)
98
- ) continue;
99
-
100
- out.push(b);
101
- }
102
-
103
- out.push(points[points.length - 1]);
104
- return out; //.map(p=>p.snap(2.54));
105
- }
106
-
107
- /* ───────────────────────── Main router ───────────────────────── */
108
-
109
- export function findGridPath({
110
- from,
111
- to,
112
- gridSize,
113
- avoidRects = [],
114
- avoidLines = []
115
- }) {
116
- const open = new Map();
117
- const closed = new Set();
118
-
119
- open.set(pointKey(from), {
120
- point: from,
121
- g: 0,
122
- f: manhattan(from, to),
123
- parent: null
124
- });
125
-
126
- const steps = [
127
- new Point([ gridSize, 0 ]),
128
- new Point([ -gridSize, 0 ]),
129
- new Point([ 0, gridSize ]),
130
- new Point([ 0, -gridSize ])
131
- ];
132
-
133
- while (open.size) {
134
- let current = null;
135
- for (const n of open.values()) {
136
- if (!current || n.f < current.f) current = n;
137
- }
138
-
139
- if (current.point.equals(to)) {
140
- const path = [];
141
- let c = current;
142
- while (c) {
143
- path.push(c.point);
144
- c = c.parent;
145
- }
146
- return compressPath(path.reverse());
147
- }
148
-
149
- open.delete(pointKey(current.point));
150
- closed.add(pointKey(current.point));
151
-
152
- for (const step of steps) {
153
- const next = current.point.add(step);
154
- const key = pointKey(next);
155
-
156
- if (closed.has(key)) continue;
157
- if (segmentBlocked(current.point, next, avoidRects)) continue;
158
- if (segmentBlockedByLines(current.point, next, avoidLines)) continue;
159
-
160
- const g = current.g + gridSize;
161
- const f = g + manhattan(next, to);
162
-
163
- const existing = open.get(key);
164
- if (!existing || g < existing.g) {
165
- open.set(key, {
166
- point: next,
167
- g,
168
- f,
169
- parent: current
170
- });
171
- }
172
- }
173
- }
174
-
175
- throw new Error("No path found");
176
- }
File without changes
File without changes