maze-test 0.3.0 → 0.4.0

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/dist/index.cjs CHANGED
@@ -390,7 +390,8 @@ function analyzeSolidCellMaze(maze) {
390
390
  const distances = bfsDistances(maze.entry, graph);
391
391
  const degreeValues = [...graph.values()].map((items) => items.length);
392
392
  const edgeCount = degreeValues.reduce((sum, value) => sum + value, 0) / 2;
393
- const mainPath = shortestPath(maze, maze.entry, maze.goal);
393
+ const mainPath = shortestPathInGraph(graph, maze.entry, maze.goal);
394
+ const entryGoalSeparators = separatingCellKeys(graph, maze.entry, maze.goal);
394
395
  const objectCounts = {};
395
396
  for (const object of maze.objects ?? []) {
396
397
  objectCounts[object.type] = (objectCounts[object.type] ?? 0) + 1;
@@ -408,7 +409,7 @@ function analyzeSolidCellMaze(maze) {
408
409
  entryGoalDistance: mainPath.length > 0 ? mainPath.length - 1 : null,
409
410
  doorCount: maze.doors.length,
410
411
  gatingDoorCount: maze.doors.filter(
411
- (door) => shortestPath(maze, maze.entry, maze.goal, door.position).length === 0
412
+ (door) => entryGoalSeparators.has(cellKey(door.position))
412
413
  ).length,
413
414
  objectCounts
414
415
  };
@@ -478,6 +479,9 @@ function validateSolidCellMaze(maze) {
478
479
  function shortestPath(maze, start, goal, excludedCell = null) {
479
480
  if (excludedCell && (sameCell(start, excludedCell) || sameCell(goal, excludedCell))) return [];
480
481
  const graph = adjacency(maze, excludedCell);
482
+ return shortestPathInGraph(graph, start, goal);
483
+ }
484
+ function shortestPathInGraph(graph, start, goal) {
481
485
  const queue = [start];
482
486
  const parent = /* @__PURE__ */ new Map([[cellKey(start), null]]);
483
487
  const values = /* @__PURE__ */ new Map([[cellKey(start), start]]);
@@ -609,12 +613,14 @@ function degreeMap(maze) {
609
613
  }
610
614
  function selectGatingDoorIndices(maze, mainPath, count) {
611
615
  if (count === 0) return [];
612
- const degrees = degreeMap(maze);
616
+ const graph = adjacency(maze);
617
+ const separators = separatingCellKeys(graph, maze.entry, maze.goal);
613
618
  const candidates = [];
614
619
  for (let index = 3; index < mainPath.length - 3; index += 1) {
615
620
  const pathCell = mainPath[index];
616
- if (!pathCell || (degrees.get(cellKey(pathCell)) ?? 0) !== 2) continue;
617
- if (shortestPath(maze, maze.entry, maze.goal, pathCell).length === 0) candidates.push(index);
621
+ if (!pathCell) continue;
622
+ const key = cellKey(pathCell);
623
+ if ((graph.get(key)?.length ?? 0) === 2 && separators.has(key)) candidates.push(index);
618
624
  }
619
625
  if (candidates.length < count) {
620
626
  throw new Error(`Only ${candidates.length} non-bypassable path cells are available for ${count} doors.`);
@@ -633,6 +639,59 @@ function selectGatingDoorIndices(maze, mainPath, count) {
633
639
  if (selected.length !== count) throw new Error(`Could not space ${count} doors along the main path.`);
634
640
  return selected.sort((left, right) => left - right);
635
641
  }
642
+ function separatingCellKeys(graph, start, goal) {
643
+ const startKey = cellKey(start);
644
+ const goalKey = cellKey(goal);
645
+ if (!graph.has(startKey) || !graph.has(goalKey)) return /* @__PURE__ */ new Set();
646
+ const discovered = /* @__PURE__ */ new Map();
647
+ const low = /* @__PURE__ */ new Map();
648
+ const parent = /* @__PURE__ */ new Map([[startKey, null]]);
649
+ const stack = [
650
+ { key: startKey, nextNeighbor: 0 }
651
+ ];
652
+ let order = 1;
653
+ discovered.set(startKey, order);
654
+ low.set(startKey, order);
655
+ while (stack.length > 0) {
656
+ const frame = stack.at(-1);
657
+ if (!frame) break;
658
+ const adjacent = graph.get(frame.key) ?? [];
659
+ const next = adjacent[frame.nextNeighbor];
660
+ if (next) {
661
+ frame.nextNeighbor += 1;
662
+ const nextKey = cellKey(next);
663
+ if (!discovered.has(nextKey)) {
664
+ order += 1;
665
+ discovered.set(nextKey, order);
666
+ low.set(nextKey, order);
667
+ parent.set(nextKey, frame.key);
668
+ stack.push({ key: nextKey, nextNeighbor: 0 });
669
+ continue;
670
+ }
671
+ if (nextKey !== parent.get(frame.key)) {
672
+ low.set(frame.key, Math.min(low.get(frame.key) ?? order, discovered.get(nextKey) ?? order));
673
+ }
674
+ continue;
675
+ }
676
+ stack.pop();
677
+ const parentKey = parent.get(frame.key);
678
+ if (parentKey) {
679
+ low.set(parentKey, Math.min(low.get(parentKey) ?? order, low.get(frame.key) ?? order));
680
+ }
681
+ }
682
+ if (!discovered.has(goalKey)) return /* @__PURE__ */ new Set();
683
+ const separators = /* @__PURE__ */ new Set();
684
+ let childKey = goalKey;
685
+ while (childKey !== startKey) {
686
+ const parentKey = parent.get(childKey);
687
+ if (!parentKey) break;
688
+ if (parentKey !== startKey && (low.get(childKey) ?? Number.POSITIVE_INFINITY) >= (discovered.get(parentKey) ?? Number.NEGATIVE_INFINITY)) {
689
+ separators.add(parentKey);
690
+ }
691
+ childKey = parentKey;
692
+ }
693
+ return separators;
694
+ }
636
695
  function freePathCellBefore(mainPath, preferredIndex, occupied) {
637
696
  for (let distance = 0; distance < mainPath.length; distance += 1) {
638
697
  for (const index of [preferredIndex - distance, preferredIndex + distance]) {