@hayro_o7/labyrinth 0.0.5 → 0.0.7

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.
@@ -8,9 +8,6 @@
8
8
  goalNodes?: string[];
9
9
  nodeRadius?: number;
10
10
  autoPlay?: boolean;
11
- buttons?: boolean;
12
- legend?: boolean;
13
- stepCount?: boolean;
14
11
  animationSpeed?: number;
15
12
  colors?: ColorScheme;
16
13
  showMultiGoal?: boolean;
@@ -23,9 +20,6 @@
23
20
  goalNodes = [],
24
21
  nodeRadius = 20,
25
22
  autoPlay = false,
26
- buttons = true,
27
- legend = true,
28
- stepCount = true,
29
23
  animationSpeed = 100,
30
24
  colors,
31
25
  showMultiGoal = false,
@@ -134,10 +128,13 @@
134
128
  const result = findOptimalMultiGoalPath(graph, computedStartNode, computedGoalNodes);
135
129
  optimalPath = result.optimalPath;
136
130
 
137
- // Generate visualization steps for the optimal path
131
+ // Generate visualization steps by running BFS for each segment
138
132
  steps = [];
139
- for (const nodeId of optimalPath) {
140
- steps.push({ nodeId, type: 'path' });
133
+ const tour = [computedStartNode, ...computedGoalNodes];
134
+
135
+ for (let i = 0; i < tour.length - 1; i++) {
136
+ const segmentResult = bidirectionalBFS(graph, tour[i], tour[i + 1]);
137
+ steps.push(...segmentResult.steps);
141
138
  }
142
139
  } else {
143
140
  const result = bidirectionalBFS(graph, computedStartNode, computedGoalNodes[0]);
@@ -246,8 +243,12 @@
246
243
  onControls?.(controlApi);
247
244
  });
248
245
 
249
- const svgWidth = 650;
250
- const svgHeight = 650;
246
+ const svgWidth = $derived(
247
+ Math.max(...Array.from(graph.nodes.values()).map(n => n.x)) + 100
248
+ );
249
+ const svgHeight = $derived(
250
+ Math.max(...Array.from(graph.nodes.values()).map(n => n.y)) + 100
251
+ );
251
252
  </script>
252
253
 
253
254
  <div class="graph-container" style={cssVars}>
@@ -306,69 +307,6 @@
306
307
  {/each}
307
308
  </svg>
308
309
 
309
- <div class="controls">
310
- {#if buttons}
311
- <button onclick={play} disabled={isPlaying}>Play</button>
312
- <button onclick={pause} disabled={!isPlaying}>Pause</button>
313
- <button onclick={reset}>Reset</button>
314
- <button onclick={stepBackward} disabled={isPlaying || currentStepIndex === 0}>
315
- Step Back
316
- </button>
317
- <button onclick={stepForward} disabled={isPlaying || currentStepIndex === steps.length}>
318
- Step Forward
319
- </button>
320
- {/if}
321
- {#if stepCount}
322
- <span class="step-counter">
323
- Step: {currentStepIndex} / {steps.length}
324
- </span>
325
- {/if}
326
- </div>
327
-
328
- {#if legend}
329
- <div class="legend">
330
- <div class="legend-item">
331
- <div class="legend-color" style="background-color: {colorScheme.start};"></div>
332
- <span>Start</span>
333
- </div>
334
- <div class="legend-item">
335
- <div class="legend-color" style="background-color: {colorScheme.end};"></div>
336
- <span>Goal{computedGoalNodes.length > 1 ? 's' : ''}</span>
337
- </div>
338
- <div class="legend-item">
339
- <div class="legend-color" style="background-color: {colorScheme.current};"></div>
340
- <span>Forward</span>
341
- </div>
342
- <div class="legend-item">
343
- <div class="legend-color" style="background-color: #f97316;"></div>
344
- <span>Backward</span>
345
- </div>
346
- <div class="legend-item">
347
- <div class="legend-color" style="background-color: {colorScheme.visiting};"></div>
348
- <span>Forward Frontier</span>
349
- </div>
350
- <div class="legend-item">
351
- <div class="legend-color" style="background-color: #fed7aa;"></div>
352
- <span>Backward Frontier</span>
353
- </div>
354
- <div class="legend-item">
355
- <div class="legend-color" style="background-color: {colorScheme.visited};"></div>
356
- <span>Forward Visited</span>
357
- </div>
358
- <div class="legend-item">
359
- <div class="legend-color" style="background-color: #fecaca;"></div>
360
- <span>Backward Visited</span>
361
- </div>
362
- <div class="legend-item">
363
- <div class="legend-color" style="background-color: #a855f7;"></div>
364
- <span>Intersection</span>
365
- </div>
366
- <div class="legend-item">
367
- <div class="legend-color" style="background-color: {colorScheme.path};"></div>
368
- <span>Path</span>
369
- </div>
370
- </div>
371
- {/if}
372
310
  </div>
373
311
 
374
312
  <style>
@@ -377,7 +315,6 @@
377
315
  flex-direction: column;
378
316
  gap: 1rem;
379
317
  align-items: center;
380
- padding: 1rem;
381
318
  }
382
319
 
383
320
  .graph-svg {
@@ -386,64 +323,4 @@
386
323
  border: 2px solid #e5e7eb;
387
324
  border-radius: 0.5rem;
388
325
  }
389
-
390
- .controls {
391
- display: flex;
392
- gap: 0.5rem;
393
- align-items: center;
394
- flex-wrap: wrap;
395
- }
396
-
397
- .controls button {
398
- padding: 0.5rem 1rem;
399
- border: none;
400
- border-radius: 0.375rem;
401
- cursor: pointer;
402
- font-weight: 500;
403
- transition: background-color 0.2s;
404
- background-color: var(--graph-buttons);
405
- color: var(--graph-buttonstext);
406
- }
407
-
408
- .controls button:hover:not(:disabled) {
409
- background-color: var(--graph-buttonshover);
410
- }
411
-
412
- .controls button:disabled {
413
- background-color: var(--graph-buttonsdisabled);
414
- cursor: not-allowed;
415
- }
416
-
417
- .step-counter {
418
- padding: 0.5rem 1rem;
419
- border-radius: 0.375rem;
420
- font-weight: 500;
421
- background-color: var(--graph-legend);
422
- color: var(--graph-legend-text);
423
- }
424
-
425
- .legend {
426
- display: flex;
427
- gap: 1rem;
428
- flex-wrap: wrap;
429
- padding: 0.75rem;
430
- border-radius: 0.5rem;
431
- background-color: var(--graph-legend);
432
- color: var(--graph-legend-text);
433
- max-width: 650px;
434
- }
435
-
436
- .legend-item {
437
- display: flex;
438
- align-items: center;
439
- gap: 0.5rem;
440
- font-size: 0.875rem;
441
- }
442
-
443
- .legend-color {
444
- width: 1.5rem;
445
- height: 1.5rem;
446
- border-radius: 0.25rem;
447
- border: 1px solid var(--graph-legend-text);
448
- }
449
326
  </style>
@@ -5,9 +5,6 @@ interface Props {
5
5
  goalNodes?: string[];
6
6
  nodeRadius?: number;
7
7
  autoPlay?: boolean;
8
- buttons?: boolean;
9
- legend?: boolean;
10
- stepCount?: boolean;
11
8
  animationSpeed?: number;
12
9
  colors?: ColorScheme;
13
10
  showMultiGoal?: boolean;
@@ -1,7 +1,15 @@
1
1
  import type { GeneralGraph } from './types';
2
2
  export interface GraphGeneratorOptions {
3
3
  nodeCount: number;
4
- avgDegree: number;
4
+ width?: number;
5
+ height?: number;
6
+ maxConnectionsPerNode?: number;
7
+ connectionRadius?: number;
8
+ minNodeDistance?: number;
5
9
  seed?: number;
6
10
  }
7
11
  export declare function generateRandomGraph(options: GraphGeneratorOptions): GeneralGraph;
12
+ export declare function findOppositeCornerNodes(graph: GeneralGraph): {
13
+ topLeft: string;
14
+ bottomRight: string;
15
+ };
@@ -6,17 +6,31 @@ function seededRandom(seed) {
6
6
  };
7
7
  }
8
8
  export function generateRandomGraph(options) {
9
- const { nodeCount, avgDegree, seed } = options;
9
+ const { nodeCount, width = 600, height = 600, maxConnectionsPerNode = 5, connectionRadius = 150, minNodeDistance = 30, seed } = options;
10
10
  const random = seed !== undefined ? seededRandom(seed) : Math.random;
11
11
  const nodes = new Map();
12
- // Create nodes in a circular layout for better visualization
13
- const radius = 200;
14
- const centerX = 300;
15
- const centerY = 300;
12
+ const padding = 50;
13
+ const nodePositions = [];
14
+ // Generate node positions with minimum distance constraint
16
15
  for (let i = 0; i < nodeCount; i++) {
17
- const angle = (i / nodeCount) * 2 * Math.PI;
18
- const x = centerX + radius * Math.cos(angle);
19
- const y = centerY + radius * Math.sin(angle);
16
+ let x, y;
17
+ let attempts = 0;
18
+ const maxAttempts = 100;
19
+ do {
20
+ x = padding + random() * (width - 2 * padding);
21
+ y = padding + random() * (height - 2 * padding);
22
+ attempts++;
23
+ // Check if position is far enough from existing nodes
24
+ const tooClose = nodePositions.some(pos => {
25
+ const dx = pos.x - x;
26
+ const dy = pos.y - y;
27
+ return Math.sqrt(dx * dx + dy * dy) < minNodeDistance;
28
+ });
29
+ if (!tooClose || attempts >= maxAttempts) {
30
+ break;
31
+ }
32
+ } while (true);
33
+ nodePositions.push({ x, y });
20
34
  nodes.set(`n${i}`, {
21
35
  id: `n${i}`,
22
36
  x,
@@ -24,34 +38,88 @@ export function generateRandomGraph(options) {
24
38
  neighbors: []
25
39
  });
26
40
  }
27
- // Connect nodes to create a connected graph
28
- // First, create a spanning tree to ensure connectivity
29
- const nodeIds = Array.from(nodes.keys());
30
- const connected = new Set([nodeIds[0]]);
31
- const unconnected = new Set(nodeIds.slice(1));
32
- while (unconnected.size > 0) {
33
- const connectedNode = Array.from(connected)[Math.floor(random() * connected.size)];
34
- const unconnectedNode = Array.from(unconnected)[Math.floor(random() * unconnected.size)];
35
- nodes.get(connectedNode).neighbors.push(unconnectedNode);
36
- nodes.get(unconnectedNode).neighbors.push(connectedNode);
37
- connected.add(unconnectedNode);
38
- unconnected.delete(unconnectedNode);
39
- }
40
- // Add additional edges to reach target average degree
41
- const targetEdges = Math.floor((nodeCount * avgDegree) / 2);
42
- const currentEdges = nodeCount - 1; // from spanning tree
43
- const edgesToAdd = targetEdges - currentEdges;
44
- for (let i = 0; i < edgesToAdd; i++) {
45
- const node1Id = nodeIds[Math.floor(random() * nodeIds.length)];
46
- const node2Id = nodeIds[Math.floor(random() * nodeIds.length)];
47
- if (node1Id === node2Id)
41
+ // Connect nodes based on proximity
42
+ const nodeArray = Array.from(nodes.values());
43
+ for (let i = 0; i < nodeArray.length; i++) {
44
+ const node = nodeArray[i];
45
+ // Skip if already at max connections
46
+ if (node.neighbors.length >= maxConnectionsPerNode)
48
47
  continue;
49
- const node1 = nodes.get(node1Id);
50
- const node2 = nodes.get(node2Id);
51
- if (!node1.neighbors.includes(node2Id)) {
52
- node1.neighbors.push(node2Id);
53
- node2.neighbors.push(node1Id);
48
+ // Find nearby nodes
49
+ const distances = [];
50
+ for (let j = 0; j < nodeArray.length; j++) {
51
+ if (i === j)
52
+ continue;
53
+ const other = nodeArray[j];
54
+ // Skip if other node is at max connections
55
+ if (other.neighbors.length >= maxConnectionsPerNode)
56
+ continue;
57
+ // Skip if already connected
58
+ if (node.neighbors.includes(other.id))
59
+ continue;
60
+ const dx = node.x - other.x;
61
+ const dy = node.y - other.y;
62
+ const distance = Math.sqrt(dx * dx + dy * dy);
63
+ if (distance <= connectionRadius) {
64
+ distances.push({ nodeId: other.id, distance });
65
+ }
66
+ }
67
+ // Sort by distance and connect to closest nodes
68
+ distances.sort((a, b) => a.distance - b.distance);
69
+ const connectionsNeeded = Math.min(maxConnectionsPerNode - node.neighbors.length, distances.length);
70
+ for (let k = 0; k < connectionsNeeded; k++) {
71
+ const targetId = distances[k].nodeId;
72
+ const targetNode = nodes.get(targetId);
73
+ // Only connect if target isn't at max
74
+ if (targetNode.neighbors.length < maxConnectionsPerNode) {
75
+ node.neighbors.push(targetId);
76
+ targetNode.neighbors.push(node.id);
77
+ }
78
+ }
79
+ }
80
+ // Ensure all nodes have at least one connection
81
+ for (const node of nodeArray) {
82
+ if (node.neighbors.length === 0) {
83
+ // Find closest node that can accept a connection
84
+ let closestNode = null;
85
+ let closestDistance = Infinity;
86
+ for (const other of nodeArray) {
87
+ if (other.id === node.id)
88
+ continue;
89
+ if (other.neighbors.length >= maxConnectionsPerNode)
90
+ continue;
91
+ const dx = node.x - other.x;
92
+ const dy = node.y - other.y;
93
+ const distance = Math.sqrt(dx * dx + dy * dy);
94
+ if (distance < closestDistance) {
95
+ closestDistance = distance;
96
+ closestNode = other;
97
+ }
98
+ }
99
+ if (closestNode) {
100
+ node.neighbors.push(closestNode.id);
101
+ closestNode.neighbors.push(node.id);
102
+ }
54
103
  }
55
104
  }
56
105
  return { nodes };
57
106
  }
107
+ export function findOppositeCornerNodes(graph) {
108
+ const nodeArray = Array.from(graph.nodes.values());
109
+ let topLeftNode = nodeArray[0];
110
+ let bottomRightNode = nodeArray[0];
111
+ for (const node of nodeArray) {
112
+ // Top-left: minimize x + y
113
+ if (node.x + node.y < topLeftNode.x + topLeftNode.y) {
114
+ topLeftNode = node;
115
+ }
116
+ // Bottom-right: maximize x + y
117
+ if (node.x + node.y > bottomRightNode.x + bottomRightNode.y) {
118
+ bottomRightNode = node;
119
+ }
120
+ }
121
+ return {
122
+ topLeft: topLeftNode.id,
123
+ bottomRight: bottomRightNode.id
124
+ };
125
+ }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { generateLabyrinth } from './labyrinth-generator';
2
- export { generateRandomGraph } from './graph-generator';
2
+ export { generateRandomGraph, findOppositeCornerNodes } from './graph-generator';
3
3
  export { dijkstra } from './algorithms/dijkstra';
4
4
  export { astar } from './algorithms/astar';
5
5
  export { bidirectionalBFS, findOptimalMultiGoalPath } from './algorithms/bidirectional-bfs';
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { generateLabyrinth } from './labyrinth-generator';
2
- export { generateRandomGraph } from './graph-generator';
2
+ export { generateRandomGraph, findOppositeCornerNodes } from './graph-generator';
3
3
  export { dijkstra } from './algorithms/dijkstra';
4
4
  export { astar } from './algorithms/astar';
5
5
  export { bidirectionalBFS, findOptimalMultiGoalPath } from './algorithms/bidirectional-bfs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hayro_o7/labyrinth",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite dev",