@hayro_o7/labyrinth 0.0.1
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.
- package/README.md +243 -0
- package/dist/algorithms/astar.d.ts +2 -0
- package/dist/algorithms/astar.js +105 -0
- package/dist/algorithms/dijkstra.d.ts +2 -0
- package/dist/algorithms/dijkstra.js +81 -0
- package/dist/assets/favicon.svg +1 -0
- package/dist/components/Labyrinth.svelte +370 -0
- package/dist/components/Labyrinth.svelte.d.ts +19 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -0
- package/dist/labyrinth-generator.d.ts +2 -0
- package/dist/labyrinth-generator.js +106 -0
- package/dist/types.d.ts +56 -0
- package/dist/types.js +1 -0
- package/dist/utils/priority-queue.d.ts +7 -0
- package/dist/utils/priority-queue.js +26 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# Labyrinth
|
|
2
|
+
|
|
3
|
+
A Svelte package for visualizing graph pathfinding algorithms with interactive maze generation and solving.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🎯 **Algorithm Visualization**: Step-by-step visualization of Dijkstra's and A* pathfinding algorithms
|
|
8
|
+
- 🌀 **Maze Generation**: Generates perfect mazes using Recursive Backtracking algorithm
|
|
9
|
+
- 🎨 **Interactive Component**: Fully customizable Svelte component with play/pause controls
|
|
10
|
+
- 📊 **Graph-based**: Uses proper graph data structures for efficient pathfinding
|
|
11
|
+
- 🔧 **TypeScript**: Fully typed for excellent developer experience
|
|
12
|
+
- âš¡ **Configurable**: Adjustable maze size, cell size, and animation speed
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install labyrinth
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Basic Example
|
|
23
|
+
|
|
24
|
+
```svelte
|
|
25
|
+
<script>
|
|
26
|
+
import { generateLabyrinth, Labyrinth } from 'labyrinth';
|
|
27
|
+
|
|
28
|
+
const graph = generateLabyrinth(20, 20);
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<Labyrinth {graph} algorithm="astar" />
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Advanced Example
|
|
35
|
+
|
|
36
|
+
```svelte
|
|
37
|
+
<script>
|
|
38
|
+
import { generateLabyrinth, Labyrinth } from 'labyrinth';
|
|
39
|
+
import type { Graph, AlgorithmType, ColorScheme } from 'labyrinth';
|
|
40
|
+
|
|
41
|
+
let width = 25;
|
|
42
|
+
let height = 25;
|
|
43
|
+
let algorithm: AlgorithmType = 'dijkstra';
|
|
44
|
+
|
|
45
|
+
const graph = generateLabyrinth(width, height);
|
|
46
|
+
|
|
47
|
+
const customColors: ColorScheme = {
|
|
48
|
+
start: '#10b981',
|
|
49
|
+
end: '#dc2626',
|
|
50
|
+
path: '#8b5cf6',
|
|
51
|
+
wall: '#374151'
|
|
52
|
+
};
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<Labyrinth
|
|
56
|
+
{graph}
|
|
57
|
+
{algorithm}
|
|
58
|
+
cellSize={30}
|
|
59
|
+
animationSpeed={50}
|
|
60
|
+
autoPlay={false}
|
|
61
|
+
showGrid={true}
|
|
62
|
+
colors={customColors}
|
|
63
|
+
/>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## API Reference
|
|
67
|
+
|
|
68
|
+
### `generateLabyrinth(width: number, height: number): Graph`
|
|
69
|
+
|
|
70
|
+
Generates a perfect maze (exactly one path between any two points) using the Recursive Backtracking algorithm.
|
|
71
|
+
|
|
72
|
+
**Parameters:**
|
|
73
|
+
- `width`: Number of cells horizontally
|
|
74
|
+
- `height`: Number of cells vertically
|
|
75
|
+
|
|
76
|
+
**Returns:** A `Graph` object representing the maze
|
|
77
|
+
|
|
78
|
+
### `Labyrinth` Component Props
|
|
79
|
+
|
|
80
|
+
| Prop | Type | Default | Description |
|
|
81
|
+
|------|------|---------|-------------|
|
|
82
|
+
| `graph` | `Graph` | **required** | The maze graph to visualize |
|
|
83
|
+
| `algorithm` | `'dijkstra' \| 'astar'` | `'astar'` | Pathfinding algorithm to use |
|
|
84
|
+
| `cellSize` | `number` | `30` | Size of each cell in pixels |
|
|
85
|
+
| `wallThickness` | `number` | `2` | Thickness of walls in pixels |
|
|
86
|
+
| `startNode` | `string` | `'0,0'` | Starting node ID (format: "x,y") |
|
|
87
|
+
| `endNode` | `string` | `'{width-1},{height-1}'` | Ending node ID (format: "x,y"), auto-computed from graph size |
|
|
88
|
+
| `autoPlay` | `boolean` | `false` | Start animation automatically |
|
|
89
|
+
| `animationSpeed` | `number` | `50` | Delay between steps in milliseconds |
|
|
90
|
+
| `showGrid` | `boolean` | `true` | Show grid lines |
|
|
91
|
+
| `legend` | `boolean` | `true` | Toggle the legend panel |
|
|
92
|
+
| `stepCount` | `boolean` | `true` | Toggle the step counter |
|
|
93
|
+
| `colors` | `ColorScheme` | default theme | Custom color scheme (see below) |
|
|
94
|
+
|
|
95
|
+
### Algorithms
|
|
96
|
+
|
|
97
|
+
#### Dijkstra's Algorithm
|
|
98
|
+
- Explores all nodes systematically
|
|
99
|
+
- Guarantees shortest path
|
|
100
|
+
- Works well for unweighted graphs
|
|
101
|
+
- Time complexity: O((V+E) log V)
|
|
102
|
+
|
|
103
|
+
#### A* Algorithm
|
|
104
|
+
- Uses Manhattan distance heuristic
|
|
105
|
+
- More efficient than Dijkstra for pathfinding
|
|
106
|
+
- Explores fewer nodes by prioritizing promising paths
|
|
107
|
+
- Guarantees shortest path with admissible heuristic
|
|
108
|
+
|
|
109
|
+
### Functions
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
// Generate a maze
|
|
113
|
+
import { generateLabyrinth } from 'labyrinth';
|
|
114
|
+
const graph = generateLabyrinth(20, 20);
|
|
115
|
+
|
|
116
|
+
// Run algorithms programmatically
|
|
117
|
+
import { dijkstra, astar } from 'labyrinth';
|
|
118
|
+
const result = dijkstra(graph, '0,0', '19,19');
|
|
119
|
+
const result2 = astar(graph, '0,0', '19,19');
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Color Customization
|
|
123
|
+
|
|
124
|
+
You can customize all colors used in the visualization by passing a `colors` object:
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import type { ColorScheme } from 'labyrinth';
|
|
128
|
+
|
|
129
|
+
const customColors: ColorScheme = {
|
|
130
|
+
start: '#10b981', // Start node color
|
|
131
|
+
end: '#dc2626', // End node color
|
|
132
|
+
current: '#f59e0b', // Current node being explored
|
|
133
|
+
visiting: '#fef3c7', // Nodes in the frontier
|
|
134
|
+
visited: '#e5e7eb', // Already visited nodes
|
|
135
|
+
path: '#8b5cf6', // Final path color
|
|
136
|
+
background: '#ffffff', // Cell background
|
|
137
|
+
wall: '#374151', // Wall color
|
|
138
|
+
grid: '#e5e7eb', // Grid line color
|
|
139
|
+
legend: '#f3f4f6', // Legend background
|
|
140
|
+
legendtext: '#1f2937', // Legend text color
|
|
141
|
+
buttons: '#0ea5e9', // Button background
|
|
142
|
+
buttonshover: '#0284c7', // Button hover background
|
|
143
|
+
buttonsdisabled: '#94a3b8', // Disabled button background
|
|
144
|
+
buttonstext: '#ffffff' // Button text color
|
|
145
|
+
};
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
All color properties are optional - only specify the ones you want to override.
|
|
149
|
+
|
|
150
|
+
### Types
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
interface Graph {
|
|
154
|
+
nodes: Map<string, GraphNode>;
|
|
155
|
+
width: number;
|
|
156
|
+
height: number;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
interface GraphNode {
|
|
160
|
+
id: string;
|
|
161
|
+
x: number;
|
|
162
|
+
y: number;
|
|
163
|
+
neighbors: string[];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
interface AlgorithmResult {
|
|
167
|
+
path: string[];
|
|
168
|
+
steps: PathStep[];
|
|
169
|
+
found: boolean;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
interface PathStep {
|
|
173
|
+
nodeId: string;
|
|
174
|
+
type: 'visiting' | 'visited' | 'path' | 'current';
|
|
175
|
+
distance?: number;
|
|
176
|
+
heuristic?: number;
|
|
177
|
+
fScore?: number;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
interface ColorScheme {
|
|
181
|
+
start?: string;
|
|
182
|
+
end?: string;
|
|
183
|
+
current?: string;
|
|
184
|
+
visiting?: string;
|
|
185
|
+
visited?: string;
|
|
186
|
+
path?: string;
|
|
187
|
+
background?: string;
|
|
188
|
+
wall?: string;
|
|
189
|
+
grid?: string;
|
|
190
|
+
legend?: string;
|
|
191
|
+
legendtext?: string;
|
|
192
|
+
buttons?: string;
|
|
193
|
+
buttonshover?: string;
|
|
194
|
+
buttonsdisabled?: string;
|
|
195
|
+
buttonstext?: string;
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Development
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
# Install dependencies
|
|
203
|
+
npm install
|
|
204
|
+
|
|
205
|
+
# Start dev server with demo
|
|
206
|
+
npm run dev
|
|
207
|
+
|
|
208
|
+
# Build the package
|
|
209
|
+
npm run package
|
|
210
|
+
|
|
211
|
+
# Type check
|
|
212
|
+
npm run check
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## How It Works
|
|
216
|
+
|
|
217
|
+
### Maze Generation
|
|
218
|
+
The package uses **Recursive Backtracking** (a DFS-based algorithm) to generate perfect mazes:
|
|
219
|
+
1. Start at a random cell and mark it as visited
|
|
220
|
+
2. While there are unvisited neighbors:
|
|
221
|
+
- Choose a random unvisited neighbor
|
|
222
|
+
- Remove the wall between current cell and chosen neighbor
|
|
223
|
+
- Recursively visit the neighbor
|
|
224
|
+
3. Backtrack when stuck
|
|
225
|
+
|
|
226
|
+
This guarantees a maze with exactly one path between any two points.
|
|
227
|
+
|
|
228
|
+
### Pathfinding Visualization
|
|
229
|
+
The component visualizes the algorithm's execution step-by-step:
|
|
230
|
+
- **Green**: Start node
|
|
231
|
+
- **Red**: End node
|
|
232
|
+
- **Orange**: Current node being explored
|
|
233
|
+
- **Yellow**: Nodes in the frontier (being considered)
|
|
234
|
+
- **Gray**: Visited nodes
|
|
235
|
+
- **Blue**: Final shortest path
|
|
236
|
+
|
|
237
|
+
## License
|
|
238
|
+
|
|
239
|
+
MIT
|
|
240
|
+
|
|
241
|
+
## Contributing
|
|
242
|
+
|
|
243
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { PriorityQueue } from '../utils/priority-queue';
|
|
2
|
+
function parseKey(key) {
|
|
3
|
+
const [x, y] = key.split(',').map(Number);
|
|
4
|
+
return { x, y };
|
|
5
|
+
}
|
|
6
|
+
function manhattanDistance(id1, id2) {
|
|
7
|
+
const pos1 = parseKey(id1);
|
|
8
|
+
const pos2 = parseKey(id2);
|
|
9
|
+
return Math.abs(pos1.x - pos2.x) + Math.abs(pos1.y - pos2.y);
|
|
10
|
+
}
|
|
11
|
+
export function astar(graph, startId, endId) {
|
|
12
|
+
const gScore = new Map();
|
|
13
|
+
const fScore = new Map();
|
|
14
|
+
const previous = new Map();
|
|
15
|
+
const visited = new Set();
|
|
16
|
+
const steps = [];
|
|
17
|
+
const pq = new PriorityQueue();
|
|
18
|
+
for (const nodeId of graph.nodes.keys()) {
|
|
19
|
+
gScore.set(nodeId, Infinity);
|
|
20
|
+
fScore.set(nodeId, Infinity);
|
|
21
|
+
previous.set(nodeId, null);
|
|
22
|
+
}
|
|
23
|
+
const h = manhattanDistance(startId, endId);
|
|
24
|
+
gScore.set(startId, 0);
|
|
25
|
+
fScore.set(startId, h);
|
|
26
|
+
pq.enqueue(startId, h);
|
|
27
|
+
while (!pq.isEmpty()) {
|
|
28
|
+
const currentId = pq.dequeue();
|
|
29
|
+
if (!currentId)
|
|
30
|
+
break;
|
|
31
|
+
if (visited.has(currentId))
|
|
32
|
+
continue;
|
|
33
|
+
visited.add(currentId);
|
|
34
|
+
const currentG = gScore.get(currentId) ?? Infinity;
|
|
35
|
+
const currentF = fScore.get(currentId) ?? Infinity;
|
|
36
|
+
const currentH = manhattanDistance(currentId, endId);
|
|
37
|
+
steps.push({
|
|
38
|
+
nodeId: currentId,
|
|
39
|
+
type: 'current',
|
|
40
|
+
distance: currentG,
|
|
41
|
+
heuristic: currentH,
|
|
42
|
+
fScore: currentF
|
|
43
|
+
});
|
|
44
|
+
if (currentId === endId) {
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
const currentNode = graph.nodes.get(currentId);
|
|
48
|
+
if (!currentNode)
|
|
49
|
+
continue;
|
|
50
|
+
for (const neighborId of currentNode.neighbors) {
|
|
51
|
+
if (visited.has(neighborId))
|
|
52
|
+
continue;
|
|
53
|
+
const tentativeGScore = currentG + 1;
|
|
54
|
+
const oldGScore = gScore.get(neighborId) ?? Infinity;
|
|
55
|
+
if (tentativeGScore < oldGScore) {
|
|
56
|
+
previous.set(neighborId, currentId);
|
|
57
|
+
gScore.set(neighborId, tentativeGScore);
|
|
58
|
+
const heuristic = manhattanDistance(neighborId, endId);
|
|
59
|
+
const newFScore = tentativeGScore + heuristic;
|
|
60
|
+
fScore.set(neighborId, newFScore);
|
|
61
|
+
pq.enqueue(neighborId, newFScore);
|
|
62
|
+
steps.push({
|
|
63
|
+
nodeId: neighborId,
|
|
64
|
+
type: 'visiting',
|
|
65
|
+
distance: tentativeGScore,
|
|
66
|
+
heuristic,
|
|
67
|
+
fScore: newFScore
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
steps.push({
|
|
72
|
+
nodeId: currentId,
|
|
73
|
+
type: 'visited',
|
|
74
|
+
distance: currentG,
|
|
75
|
+
heuristic: currentH,
|
|
76
|
+
fScore: currentF
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
const path = reconstructPath(previous, startId, endId);
|
|
80
|
+
for (const nodeId of path) {
|
|
81
|
+
steps.push({
|
|
82
|
+
nodeId,
|
|
83
|
+
type: 'path'
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
path,
|
|
88
|
+
steps,
|
|
89
|
+
found: path.length > 0 && path[path.length - 1] === endId
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function reconstructPath(previous, startId, endId) {
|
|
93
|
+
const path = [];
|
|
94
|
+
let current = endId;
|
|
95
|
+
while (current !== null) {
|
|
96
|
+
path.unshift(current);
|
|
97
|
+
if (current === startId)
|
|
98
|
+
break;
|
|
99
|
+
current = previous.get(current) ?? null;
|
|
100
|
+
}
|
|
101
|
+
if (path[0] !== startId) {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
return path;
|
|
105
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { PriorityQueue } from '../utils/priority-queue';
|
|
2
|
+
export function dijkstra(graph, startId, endId) {
|
|
3
|
+
const distances = new Map();
|
|
4
|
+
const previous = new Map();
|
|
5
|
+
const visited = new Set();
|
|
6
|
+
const steps = [];
|
|
7
|
+
const pq = new PriorityQueue();
|
|
8
|
+
for (const nodeId of graph.nodes.keys()) {
|
|
9
|
+
distances.set(nodeId, Infinity);
|
|
10
|
+
previous.set(nodeId, null);
|
|
11
|
+
}
|
|
12
|
+
distances.set(startId, 0);
|
|
13
|
+
pq.enqueue(startId, 0);
|
|
14
|
+
while (!pq.isEmpty()) {
|
|
15
|
+
const currentId = pq.dequeue();
|
|
16
|
+
if (!currentId)
|
|
17
|
+
break;
|
|
18
|
+
if (visited.has(currentId))
|
|
19
|
+
continue;
|
|
20
|
+
visited.add(currentId);
|
|
21
|
+
steps.push({
|
|
22
|
+
nodeId: currentId,
|
|
23
|
+
type: 'current',
|
|
24
|
+
distance: distances.get(currentId)
|
|
25
|
+
});
|
|
26
|
+
if (currentId === endId) {
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
const currentNode = graph.nodes.get(currentId);
|
|
30
|
+
if (!currentNode)
|
|
31
|
+
continue;
|
|
32
|
+
const currentDistance = distances.get(currentId) ?? Infinity;
|
|
33
|
+
for (const neighborId of currentNode.neighbors) {
|
|
34
|
+
if (visited.has(neighborId))
|
|
35
|
+
continue;
|
|
36
|
+
const newDistance = currentDistance + 1;
|
|
37
|
+
const oldDistance = distances.get(neighborId) ?? Infinity;
|
|
38
|
+
if (newDistance < oldDistance) {
|
|
39
|
+
distances.set(neighborId, newDistance);
|
|
40
|
+
previous.set(neighborId, currentId);
|
|
41
|
+
pq.enqueue(neighborId, newDistance);
|
|
42
|
+
steps.push({
|
|
43
|
+
nodeId: neighborId,
|
|
44
|
+
type: 'visiting',
|
|
45
|
+
distance: newDistance
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
steps.push({
|
|
50
|
+
nodeId: currentId,
|
|
51
|
+
type: 'visited',
|
|
52
|
+
distance: distances.get(currentId)
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const path = reconstructPath(previous, startId, endId);
|
|
56
|
+
for (const nodeId of path) {
|
|
57
|
+
steps.push({
|
|
58
|
+
nodeId,
|
|
59
|
+
type: 'path'
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
path,
|
|
64
|
+
steps,
|
|
65
|
+
found: path.length > 0 && path[path.length - 1] === endId
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function reconstructPath(previous, startId, endId) {
|
|
69
|
+
const path = [];
|
|
70
|
+
let current = endId;
|
|
71
|
+
while (current !== null) {
|
|
72
|
+
path.unshift(current);
|
|
73
|
+
if (current === startId)
|
|
74
|
+
break;
|
|
75
|
+
current = previous.get(current) ?? null;
|
|
76
|
+
}
|
|
77
|
+
if (path[0] !== startId) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
return path;
|
|
81
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Graph, AlgorithmType, PathStep } from '../types';
|
|
3
|
+
import { dijkstra } from '../algorithms/dijkstra';
|
|
4
|
+
import { astar } from '../algorithms/astar';
|
|
5
|
+
|
|
6
|
+
import type { ColorScheme } from '../types';
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
graph: Graph;
|
|
10
|
+
cellSize?: number;
|
|
11
|
+
wallThickness?: number;
|
|
12
|
+
algorithm?: AlgorithmType;
|
|
13
|
+
startNode?: string;
|
|
14
|
+
endNode?: string;
|
|
15
|
+
autoPlay?: boolean;
|
|
16
|
+
legend?: boolean;
|
|
17
|
+
stepCount?: boolean;
|
|
18
|
+
animationSpeed?: number;
|
|
19
|
+
showGrid?: boolean;
|
|
20
|
+
colors?: ColorScheme;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let {
|
|
24
|
+
graph,
|
|
25
|
+
cellSize = 30,
|
|
26
|
+
wallThickness = 2,
|
|
27
|
+
algorithm = 'astar',
|
|
28
|
+
startNode = '0,0',
|
|
29
|
+
endNode,
|
|
30
|
+
autoPlay = false,
|
|
31
|
+
legend = true,
|
|
32
|
+
stepCount = true,
|
|
33
|
+
animationSpeed = 50,
|
|
34
|
+
showGrid = true,
|
|
35
|
+
colors
|
|
36
|
+
}: Props = $props();
|
|
37
|
+
|
|
38
|
+
const defaultColors: Required<ColorScheme> = {
|
|
39
|
+
start: '#22c55e',
|
|
40
|
+
end: '#ef4444',
|
|
41
|
+
current: '#f59e0b',
|
|
42
|
+
visiting: '#fef3c7',
|
|
43
|
+
visited: '#e5e7eb',
|
|
44
|
+
path: '#3b82f6',
|
|
45
|
+
background: '#ffffff',
|
|
46
|
+
wall: '#1f2937',
|
|
47
|
+
grid: '#e5e7eb',
|
|
48
|
+
legend: '#f9fafb',
|
|
49
|
+
legendtext: '#1f2937',
|
|
50
|
+
buttons: '#3b82f6',
|
|
51
|
+
buttonshover: '#2563eb',
|
|
52
|
+
buttonsdisabled: '#9ca3af',
|
|
53
|
+
buttonstext: '#ffffff'
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const colorScheme = $derived({ ...defaultColors, ...colors });
|
|
57
|
+
const computedEndNode = $derived(endNode ?? `${graph.width - 1},${graph.height - 1}`);
|
|
58
|
+
const cssVars = $derived(
|
|
59
|
+
`--lab-buttons:${colorScheme.buttons};
|
|
60
|
+
--lab-buttonstext:${colorScheme.buttonstext};
|
|
61
|
+
--lab-buttonshover:${colorScheme.buttonshover};
|
|
62
|
+
--lab-buttonsdisabled:${colorScheme.buttonsdisabled};
|
|
63
|
+
--lab-legend:${colorScheme.legend};
|
|
64
|
+
--lab-legend-text:${colorScheme.legendtext};`
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
let currentStepIndex = $state(0);
|
|
68
|
+
let isPlaying = $state(false);
|
|
69
|
+
let steps: PathStep[] = $state([]);
|
|
70
|
+
let intervalId: number | null = null;
|
|
71
|
+
|
|
72
|
+
const visitedNodes = $derived(new Set(
|
|
73
|
+
steps.slice(0, currentStepIndex).filter(s => s.type === 'visited').map(s => s.nodeId)
|
|
74
|
+
));
|
|
75
|
+
|
|
76
|
+
const visitingNodes = $derived(new Set(
|
|
77
|
+
steps.slice(0, currentStepIndex).filter(s => s.type === 'visiting').map(s => s.nodeId)
|
|
78
|
+
));
|
|
79
|
+
|
|
80
|
+
const currentNode = $derived(
|
|
81
|
+
steps[currentStepIndex - 1]?.type === 'current' ? steps[currentStepIndex - 1]?.nodeId : null
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const pathNodes = $derived(new Set(
|
|
85
|
+
steps.slice(0, currentStepIndex).filter(s => s.type === 'path').map(s => s.nodeId)
|
|
86
|
+
));
|
|
87
|
+
|
|
88
|
+
function runAlgorithm() {
|
|
89
|
+
const result = algorithm === 'dijkstra'
|
|
90
|
+
? dijkstra(graph, startNode, computedEndNode)
|
|
91
|
+
: astar(graph, startNode, computedEndNode);
|
|
92
|
+
|
|
93
|
+
steps = result.steps;
|
|
94
|
+
currentStepIndex = 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
$effect(() => {
|
|
98
|
+
algorithm;
|
|
99
|
+
reset();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
function play() {
|
|
103
|
+
if (steps.length === 0) {
|
|
104
|
+
runAlgorithm();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
isPlaying = true;
|
|
108
|
+
intervalId = window.setInterval(() => {
|
|
109
|
+
if (currentStepIndex < steps.length) {
|
|
110
|
+
currentStepIndex++;
|
|
111
|
+
} else {
|
|
112
|
+
pause();
|
|
113
|
+
}
|
|
114
|
+
}, animationSpeed);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function pause() {
|
|
118
|
+
isPlaying = false;
|
|
119
|
+
if (intervalId !== null) {
|
|
120
|
+
clearInterval(intervalId);
|
|
121
|
+
intervalId = null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function reset() {
|
|
126
|
+
pause();
|
|
127
|
+
currentStepIndex = 0;
|
|
128
|
+
steps = [];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function stepForward() {
|
|
132
|
+
if (steps.length === 0) {
|
|
133
|
+
runAlgorithm();
|
|
134
|
+
}
|
|
135
|
+
if (currentStepIndex < steps.length) {
|
|
136
|
+
currentStepIndex++;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function stepBackward() {
|
|
141
|
+
if (currentStepIndex > 0) {
|
|
142
|
+
currentStepIndex--;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function getCellColor(nodeId: string): string {
|
|
147
|
+
if (nodeId === startNode) return colorScheme.start;
|
|
148
|
+
if (nodeId === computedEndNode) return colorScheme.end;
|
|
149
|
+
if (pathNodes.has(nodeId)) return colorScheme.path;
|
|
150
|
+
if (nodeId === currentNode) return colorScheme.current;
|
|
151
|
+
if (visitedNodes.has(nodeId)) return colorScheme.visited;
|
|
152
|
+
if (visitingNodes.has(nodeId)) return colorScheme.visiting;
|
|
153
|
+
return colorScheme.background;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function getWalls(nodeId: string): { top: boolean; right: boolean; bottom: boolean; left: boolean } {
|
|
157
|
+
const node = graph.nodes.get(nodeId);
|
|
158
|
+
if (!node) return { top: true, right: true, bottom: true, left: true };
|
|
159
|
+
|
|
160
|
+
const { x, y } = node;
|
|
161
|
+
const hasTop = !node.neighbors.includes(`${x},${y - 1}`);
|
|
162
|
+
const hasRight = !node.neighbors.includes(`${x + 1},${y}`);
|
|
163
|
+
const hasBottom = !node.neighbors.includes(`${x},${y + 1}`);
|
|
164
|
+
const hasLeft = !node.neighbors.includes(`${x - 1},${y}`);
|
|
165
|
+
|
|
166
|
+
return { top: hasTop, right: hasRight, bottom: hasBottom, left: hasLeft };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
$effect(() => {
|
|
170
|
+
if (autoPlay && steps.length === 0) {
|
|
171
|
+
play();
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
$effect(() => {
|
|
176
|
+
return () => {
|
|
177
|
+
if (intervalId !== null) {
|
|
178
|
+
clearInterval(intervalId);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const width = $derived(graph.width * cellSize);
|
|
184
|
+
const height = $derived(graph.height * cellSize);
|
|
185
|
+
</script>
|
|
186
|
+
|
|
187
|
+
<div class="labyrinth-container" style={cssVars}>
|
|
188
|
+
<svg
|
|
189
|
+
{width}
|
|
190
|
+
{height}
|
|
191
|
+
class="labyrinth-svg"
|
|
192
|
+
style="border: {wallThickness}px solid {colorScheme.wall};"
|
|
193
|
+
>
|
|
194
|
+
{#each Array.from(graph.nodes.values()) as node}
|
|
195
|
+
{@const walls = getWalls(node.id)}
|
|
196
|
+
{@const x = node.x * cellSize}
|
|
197
|
+
{@const y = node.y * cellSize}
|
|
198
|
+
{@const color = getCellColor(node.id)}
|
|
199
|
+
|
|
200
|
+
<rect
|
|
201
|
+
{x}
|
|
202
|
+
{y}
|
|
203
|
+
width={cellSize}
|
|
204
|
+
height={cellSize}
|
|
205
|
+
fill={color}
|
|
206
|
+
stroke={showGrid ? colorScheme.grid : 'none'}
|
|
207
|
+
stroke-width="0.5"
|
|
208
|
+
/>
|
|
209
|
+
|
|
210
|
+
{#if walls.top}
|
|
211
|
+
<line
|
|
212
|
+
x1={x}
|
|
213
|
+
y1={y}
|
|
214
|
+
x2={x + cellSize}
|
|
215
|
+
y2={y}
|
|
216
|
+
stroke={colorScheme.wall}
|
|
217
|
+
stroke-width={wallThickness}
|
|
218
|
+
/>
|
|
219
|
+
{/if}
|
|
220
|
+
|
|
221
|
+
{#if walls.right}
|
|
222
|
+
<line
|
|
223
|
+
x1={x + cellSize}
|
|
224
|
+
y1={y}
|
|
225
|
+
x2={x + cellSize}
|
|
226
|
+
y2={y + cellSize}
|
|
227
|
+
stroke={colorScheme.wall}
|
|
228
|
+
stroke-width={wallThickness}
|
|
229
|
+
/>
|
|
230
|
+
{/if}
|
|
231
|
+
|
|
232
|
+
{#if walls.bottom}
|
|
233
|
+
<line
|
|
234
|
+
x1={x}
|
|
235
|
+
y1={y + cellSize}
|
|
236
|
+
x2={x + cellSize}
|
|
237
|
+
y2={y + cellSize}
|
|
238
|
+
stroke={colorScheme.wall}
|
|
239
|
+
stroke-width={wallThickness}
|
|
240
|
+
/>
|
|
241
|
+
{/if}
|
|
242
|
+
|
|
243
|
+
{#if walls.left}
|
|
244
|
+
<line
|
|
245
|
+
x1={x}
|
|
246
|
+
y1={y}
|
|
247
|
+
x2={x}
|
|
248
|
+
y2={y + cellSize}
|
|
249
|
+
stroke={colorScheme.wall}
|
|
250
|
+
stroke-width={wallThickness}
|
|
251
|
+
/>
|
|
252
|
+
{/if}
|
|
253
|
+
{/each}
|
|
254
|
+
</svg>
|
|
255
|
+
|
|
256
|
+
<div class="controls">
|
|
257
|
+
<button onclick={play} disabled={isPlaying}>Play</button>
|
|
258
|
+
<button onclick={pause} disabled={!isPlaying}>Pause</button>
|
|
259
|
+
<button onclick={reset}>Reset</button>
|
|
260
|
+
<button onclick={stepBackward} disabled={isPlaying || currentStepIndex === 0}>Step Back</button>
|
|
261
|
+
<button onclick={stepForward} disabled={isPlaying || currentStepIndex === steps.length}>Step Forward</button>
|
|
262
|
+
{#if stepCount}
|
|
263
|
+
<span class="step-counter">
|
|
264
|
+
Step: {currentStepIndex} / {steps.length}
|
|
265
|
+
</span>
|
|
266
|
+
{/if}
|
|
267
|
+
</div>
|
|
268
|
+
|
|
269
|
+
{#if legend}
|
|
270
|
+
<div class="legend">
|
|
271
|
+
<div class="legend-item">
|
|
272
|
+
<div class="legend-color" style="background-color: {colorScheme.start};"></div>
|
|
273
|
+
<span>Start</span>
|
|
274
|
+
</div>
|
|
275
|
+
<div class="legend-item">
|
|
276
|
+
<div class="legend-color" style="background-color: {colorScheme.end};"></div>
|
|
277
|
+
<span>End</span>
|
|
278
|
+
</div>
|
|
279
|
+
<div class="legend-item">
|
|
280
|
+
<div class="legend-color" style="background-color: {colorScheme.current};"></div>
|
|
281
|
+
<span>Current</span>
|
|
282
|
+
</div>
|
|
283
|
+
<div class="legend-item">
|
|
284
|
+
<div class="legend-color" style="background-color: {colorScheme.visiting};"></div>
|
|
285
|
+
<span>Visiting</span>
|
|
286
|
+
</div>
|
|
287
|
+
<div class="legend-item">
|
|
288
|
+
<div class="legend-color" style="background-color: {colorScheme.visited};"></div>
|
|
289
|
+
<span>Visited</span>
|
|
290
|
+
</div>
|
|
291
|
+
<div class="legend-item">
|
|
292
|
+
<div class="legend-color" style="background-color: {colorScheme.path};"></div>
|
|
293
|
+
<span>Path</span>
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
{/if}
|
|
297
|
+
</div>
|
|
298
|
+
|
|
299
|
+
<style>
|
|
300
|
+
.labyrinth-container {
|
|
301
|
+
display: flex;
|
|
302
|
+
flex-direction: column;
|
|
303
|
+
gap: 1rem;
|
|
304
|
+
align-items: center;
|
|
305
|
+
padding: 1rem;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.labyrinth-svg {
|
|
309
|
+
background-color: white;
|
|
310
|
+
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.controls {
|
|
314
|
+
display: flex;
|
|
315
|
+
gap: 0.5rem;
|
|
316
|
+
align-items: center;
|
|
317
|
+
flex-wrap: wrap;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.controls button {
|
|
321
|
+
padding: 0.5rem 1rem;
|
|
322
|
+
border: none;
|
|
323
|
+
border-radius: 0.375rem;
|
|
324
|
+
cursor: pointer;
|
|
325
|
+
font-weight: 500;
|
|
326
|
+
transition: background-color 0.2s;
|
|
327
|
+
background-color: var(--lab-buttons);
|
|
328
|
+
color: var(--lab-buttonstext);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.controls button:hover:not(:disabled) {
|
|
332
|
+
background-color: var(--lab-buttonshover);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.controls button:disabled {
|
|
336
|
+
background-color: var(--lab-buttonsdisabled);
|
|
337
|
+
cursor: not-allowed;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.step-counter {
|
|
341
|
+
padding: 0.5rem 1rem;
|
|
342
|
+
border-radius: 0.375rem;
|
|
343
|
+
font-weight: 500;
|
|
344
|
+
background-color: var(--lab-legend);
|
|
345
|
+
color: var(--lab-legend-text);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.legend {
|
|
349
|
+
display: flex;
|
|
350
|
+
gap: 1rem;
|
|
351
|
+
flex-wrap: wrap;
|
|
352
|
+
padding: 0.75rem;
|
|
353
|
+
border-radius: 0.5rem;
|
|
354
|
+
background-color: var(--lab-legend);
|
|
355
|
+
color: var(--lab-legend-text);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.legend-item {
|
|
359
|
+
display: flex;
|
|
360
|
+
align-items: center;
|
|
361
|
+
gap: 0.5rem;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.legend-color {
|
|
365
|
+
width: 1.5rem;
|
|
366
|
+
height: 1.5rem;
|
|
367
|
+
border-radius: 0.25rem;
|
|
368
|
+
border: 1px solid var(--lab-legend-text);
|
|
369
|
+
}
|
|
370
|
+
</style>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Graph, AlgorithmType } from '../types';
|
|
2
|
+
import type { ColorScheme } from '../types';
|
|
3
|
+
interface Props {
|
|
4
|
+
graph: Graph;
|
|
5
|
+
cellSize?: number;
|
|
6
|
+
wallThickness?: number;
|
|
7
|
+
algorithm?: AlgorithmType;
|
|
8
|
+
startNode?: string;
|
|
9
|
+
endNode?: string;
|
|
10
|
+
autoPlay?: boolean;
|
|
11
|
+
legend?: boolean;
|
|
12
|
+
stepCount?: boolean;
|
|
13
|
+
animationSpeed?: number;
|
|
14
|
+
showGrid?: boolean;
|
|
15
|
+
colors?: ColorScheme;
|
|
16
|
+
}
|
|
17
|
+
declare const Labyrinth: import("svelte").Component<Props, {}, "">;
|
|
18
|
+
type Labyrinth = ReturnType<typeof Labyrinth>;
|
|
19
|
+
export default Labyrinth;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { generateLabyrinth } from './labyrinth-generator';
|
|
2
|
+
export { dijkstra } from './algorithms/dijkstra';
|
|
3
|
+
export { astar } from './algorithms/astar';
|
|
4
|
+
export { default as Labyrinth } from './components/Labyrinth.svelte';
|
|
5
|
+
export type { Graph, GraphNode, Point, Cell, PathStep, AlgorithmResult, AlgorithmType, ColorScheme } from './types';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
function createKey(x, y) {
|
|
2
|
+
return `${x},${y}`;
|
|
3
|
+
}
|
|
4
|
+
function parseKey(key) {
|
|
5
|
+
const [x, y] = key.split(',').map(Number);
|
|
6
|
+
return { x, y };
|
|
7
|
+
}
|
|
8
|
+
export function generateLabyrinth(width, height) {
|
|
9
|
+
const cells = [];
|
|
10
|
+
for (let y = 0; y < height; y++) {
|
|
11
|
+
cells[y] = [];
|
|
12
|
+
for (let x = 0; x < width; x++) {
|
|
13
|
+
cells[y][x] = {
|
|
14
|
+
x,
|
|
15
|
+
y,
|
|
16
|
+
walls: {
|
|
17
|
+
top: true,
|
|
18
|
+
right: true,
|
|
19
|
+
bottom: true,
|
|
20
|
+
left: true
|
|
21
|
+
},
|
|
22
|
+
visited: false
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const stack = [];
|
|
27
|
+
const startCell = cells[0][0];
|
|
28
|
+
startCell.visited = true;
|
|
29
|
+
stack.push(startCell);
|
|
30
|
+
while (stack.length > 0) {
|
|
31
|
+
const current = stack[stack.length - 1];
|
|
32
|
+
const neighbors = getUnvisitedNeighbors(current, cells, width, height);
|
|
33
|
+
if (neighbors.length > 0) {
|
|
34
|
+
const next = neighbors[Math.floor(Math.random() * neighbors.length)];
|
|
35
|
+
removeWall(current, next);
|
|
36
|
+
next.visited = true;
|
|
37
|
+
stack.push(next);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
stack.pop();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return cellsToGraph(cells, width, height);
|
|
44
|
+
}
|
|
45
|
+
function getUnvisitedNeighbors(cell, cells, width, height) {
|
|
46
|
+
const neighbors = [];
|
|
47
|
+
const { x, y } = cell;
|
|
48
|
+
if (y > 0 && !cells[y - 1][x].visited)
|
|
49
|
+
neighbors.push(cells[y - 1][x]);
|
|
50
|
+
if (x < width - 1 && !cells[y][x + 1].visited)
|
|
51
|
+
neighbors.push(cells[y][x + 1]);
|
|
52
|
+
if (y < height - 1 && !cells[y + 1][x].visited)
|
|
53
|
+
neighbors.push(cells[y + 1][x]);
|
|
54
|
+
if (x > 0 && !cells[y][x - 1].visited)
|
|
55
|
+
neighbors.push(cells[y][x - 1]);
|
|
56
|
+
return neighbors;
|
|
57
|
+
}
|
|
58
|
+
function removeWall(cell1, cell2) {
|
|
59
|
+
const dx = cell2.x - cell1.x;
|
|
60
|
+
const dy = cell2.y - cell1.y;
|
|
61
|
+
if (dx === 1) {
|
|
62
|
+
cell1.walls.right = false;
|
|
63
|
+
cell2.walls.left = false;
|
|
64
|
+
}
|
|
65
|
+
else if (dx === -1) {
|
|
66
|
+
cell1.walls.left = false;
|
|
67
|
+
cell2.walls.right = false;
|
|
68
|
+
}
|
|
69
|
+
else if (dy === 1) {
|
|
70
|
+
cell1.walls.bottom = false;
|
|
71
|
+
cell2.walls.top = false;
|
|
72
|
+
}
|
|
73
|
+
else if (dy === -1) {
|
|
74
|
+
cell1.walls.top = false;
|
|
75
|
+
cell2.walls.bottom = false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function cellsToGraph(cells, width, height) {
|
|
79
|
+
const nodes = new Map();
|
|
80
|
+
for (let y = 0; y < height; y++) {
|
|
81
|
+
for (let x = 0; x < width; x++) {
|
|
82
|
+
const cell = cells[y][x];
|
|
83
|
+
const nodeId = createKey(x, y);
|
|
84
|
+
const neighbors = [];
|
|
85
|
+
if (!cell.walls.top && y > 0) {
|
|
86
|
+
neighbors.push(createKey(x, y - 1));
|
|
87
|
+
}
|
|
88
|
+
if (!cell.walls.right && x < width - 1) {
|
|
89
|
+
neighbors.push(createKey(x + 1, y));
|
|
90
|
+
}
|
|
91
|
+
if (!cell.walls.bottom && y < height - 1) {
|
|
92
|
+
neighbors.push(createKey(x, y + 1));
|
|
93
|
+
}
|
|
94
|
+
if (!cell.walls.left && x > 0) {
|
|
95
|
+
neighbors.push(createKey(x - 1, y));
|
|
96
|
+
}
|
|
97
|
+
nodes.set(nodeId, {
|
|
98
|
+
id: nodeId,
|
|
99
|
+
x,
|
|
100
|
+
y,
|
|
101
|
+
neighbors
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return { nodes, width, height };
|
|
106
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface Point {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
}
|
|
5
|
+
export interface Cell {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
walls: {
|
|
9
|
+
top: boolean;
|
|
10
|
+
right: boolean;
|
|
11
|
+
bottom: boolean;
|
|
12
|
+
left: boolean;
|
|
13
|
+
};
|
|
14
|
+
visited: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface GraphNode {
|
|
17
|
+
id: string;
|
|
18
|
+
x: number;
|
|
19
|
+
y: number;
|
|
20
|
+
neighbors: string[];
|
|
21
|
+
}
|
|
22
|
+
export interface Graph {
|
|
23
|
+
nodes: Map<string, GraphNode>;
|
|
24
|
+
width: number;
|
|
25
|
+
height: number;
|
|
26
|
+
}
|
|
27
|
+
export interface PathStep {
|
|
28
|
+
nodeId: string;
|
|
29
|
+
type: 'visiting' | 'visited' | 'path' | 'current';
|
|
30
|
+
distance?: number;
|
|
31
|
+
heuristic?: number;
|
|
32
|
+
fScore?: number;
|
|
33
|
+
}
|
|
34
|
+
export interface AlgorithmResult {
|
|
35
|
+
path: string[];
|
|
36
|
+
steps: PathStep[];
|
|
37
|
+
found: boolean;
|
|
38
|
+
}
|
|
39
|
+
export type AlgorithmType = 'dijkstra' | 'astar';
|
|
40
|
+
export interface ColorScheme {
|
|
41
|
+
start?: string;
|
|
42
|
+
end?: string;
|
|
43
|
+
current?: string;
|
|
44
|
+
visiting?: string;
|
|
45
|
+
visited?: string;
|
|
46
|
+
path?: string;
|
|
47
|
+
background?: string;
|
|
48
|
+
wall?: string;
|
|
49
|
+
grid?: string;
|
|
50
|
+
legend?: string;
|
|
51
|
+
legendtext?: string;
|
|
52
|
+
buttons?: string;
|
|
53
|
+
buttonshover?: string;
|
|
54
|
+
buttonsdisabled?: string;
|
|
55
|
+
buttonstext?: string;
|
|
56
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export class PriorityQueue {
|
|
2
|
+
items = [];
|
|
3
|
+
enqueue(element, priority) {
|
|
4
|
+
const item = { element, priority };
|
|
5
|
+
let added = false;
|
|
6
|
+
for (let i = 0; i < this.items.length; i++) {
|
|
7
|
+
if (item.priority < this.items[i].priority) {
|
|
8
|
+
this.items.splice(i, 0, item);
|
|
9
|
+
added = true;
|
|
10
|
+
break;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
if (!added) {
|
|
14
|
+
this.items.push(item);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
dequeue() {
|
|
18
|
+
return this.items.shift()?.element;
|
|
19
|
+
}
|
|
20
|
+
isEmpty() {
|
|
21
|
+
return this.items.length === 0;
|
|
22
|
+
}
|
|
23
|
+
size() {
|
|
24
|
+
return this.items.length;
|
|
25
|
+
}
|
|
26
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hayro_o7/labyrinth",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "vite dev",
|
|
7
|
+
"build": "vite build && npm run package",
|
|
8
|
+
"preview": "vite preview",
|
|
9
|
+
"package": "svelte-kit sync && svelte-package && publint",
|
|
10
|
+
"prepublishOnly": "npm run package",
|
|
11
|
+
"prepare": "svelte-kit sync || echo ''",
|
|
12
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
13
|
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
|
|
14
|
+
},
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"svelte": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"!dist/**/*.test.*",
|
|
24
|
+
"!dist/**/*.spec.*"
|
|
25
|
+
],
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"svelte": "^5.0.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@sveltejs/adapter-auto": "^7.0.0",
|
|
31
|
+
"@sveltejs/kit": "^2.49.1",
|
|
32
|
+
"@sveltejs/package": "^2.3.7",
|
|
33
|
+
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
|
34
|
+
"@tailwindcss/vite": "^4.1.17",
|
|
35
|
+
"publint": "^0.2.12",
|
|
36
|
+
"svelte": "^5.45.6",
|
|
37
|
+
"svelte-check": "^4.3.4",
|
|
38
|
+
"tailwindcss": "^4.1.17",
|
|
39
|
+
"typescript": "^5.9.3",
|
|
40
|
+
"vite": "^7.2.6"
|
|
41
|
+
},
|
|
42
|
+
"svelte": "./dist/index.js",
|
|
43
|
+
"types": "./dist/index.d.ts"
|
|
44
|
+
}
|