| 231 | * a node, and edges connect adjacent walkable cells with weights based on terrain type. |
| 232 | */ |
| 233 | const parseTerrain = (ascii: string): Terrain => { |
| 234 | const lines = ascii.trim().split("\n").map((line) => line.trim()).filter((line) => line.length > 0) |
| 235 | if (lines.length === 0) { |
| 236 | throw new Error("Terrain must have at least one line") |
| 237 | } |
| 238 | |
| 239 | const width = lines[0].length |
| 240 | const height = lines.length |
| 241 | // Validate all lines have same width |
| 242 | for (const line of lines) { |
| 243 | if (line.length !== width) { |
| 244 | throw new Error("All terrain lines must have the same width") |
| 245 | } |
| 246 | } |
| 247 | |
| 248 | const grid = lines.map((line) => line.split("")) |
| 249 | const nodes = new Map<`${number}:${number}`, Graph.NodeIndex>() |
| 250 | const coordinates = new Map<Graph.NodeIndex, `${number}:${number}`>() |
| 251 | const graph = Graph.directed<TerrainNode, number>((mutable) => { |
| 252 | // First pass: create all nodes |
| 253 | for (let y = 0; y < height; y++) { |
| 254 | for (let x = 0; x < width; x++) { |
| 255 | const char = lines[y][x] |
| 256 | const weight = weights.get(char) ?? 1 |
| 257 | if (weight === Infinity) { |
| 258 | // Skip impassable terrain |
| 259 | continue |
| 260 | } |
| 261 | |
| 262 | const node = new TerrainNode({ x, y, type: char, weight }) |
| 263 | const index = Graph.addNode(mutable, node) |
| 264 | nodes.set(`${x}:${y}`, index) |
| 265 | coordinates.set(index, `${x}:${y}`) |
| 266 | } |
| 267 | } |
| 268 | |
| 269 | // Second pass: create edges between adjacent walkable cells |
| 270 | for (let y = 0; y < height; y++) { |
| 271 | for (let x = 0; x < width; x++) { |
| 272 | const char = lines[y][x] |
| 273 | const weight = weights.get(char) ?? 1 |
| 274 | if (weight === Infinity) { |
| 275 | // Skip impassable terrain |
| 276 | continue |
| 277 | } |
| 278 | |
| 279 | const node = nodes.get(`${x}:${y}`) |
| 280 | if (node === undefined) { |
| 281 | continue |
| 282 | } |
| 283 | |
| 284 | // Check 4-directional adjacency (up, down, left, right) |
| 285 | const directions = [ |
| 286 | [0, -1], // up |
| 287 | [0, 1], // down |
| 288 | [-1, 0], // left |
| 289 | [1, 0] // right |
| 290 | ] as const |