My program is:
var maze = MazeFactory.FromPattern(Patterns.Maze3, Patterns.WallSymbol);
var a = maze.GetElementAt(0, 1);
var b = maze.GetElementAt(12, 0);
var printer = new PathPrinter(ConsoleColor.Yellow, ConsoleColor.Gray, ConsoleColor.Black);
Console.WriteLine("Maze");
printer.Print(maze, Path.Empty, Console.CursorLeft, Console.CursorTop);
var ab = new PathFinder(maze, a, b).GetPaths();
var abPath = ab.OrderBy(p => p.Elements.Count()).First();
Console.WriteLine("A -> B");
printer.Print(maze, abPath, Console.CursorLeft, Console.CursorTop);
var ba = new PathFinder(maze, b, a).GetPaths();
var baPath = ba.OrderBy(p => p.Elements.Count()).First();
Console.WriteLine("B -> A");
printer.Print(maze, baPath, Console.CursorLeft, Console.CursorTop);
Output is:
I expect PathFinder.GetPaths() to return a collection of Paths so that I could take the shortest one, but the collection contains only one element.
Answer to any is welcome.
- Why is that?
- Why 'a to b' path and 'b to a' one are different?
- Why is 'a to b' path so long?
- How to get both paths the shortest and the same?
Note: I'm not looking for a new solution, just for fixing up this one.
Details:
I have a class MazeElement:
class MazeElement
{
public bool IsPass { get; }
public int X { get; }
public int Y { get; }
public MazeElement(int x, int y, bool isPass) => (X, Y, IsPass) = (x, y, isPass);
}
MazeElement instances can form a Maze:
class Maze
{
public IReadOnlyList<IReadOnlyList<MazeElement>> Elements { get; }
public int MaxY { get; }
public int MaxX { get; }
public Maze(MazeElement[][] elements) =>
(Elements, MaxY, MaxX) =
(elements, elements.Length - 1, elements[0].Length - 1);
public MazeElement GetElementAt(int x, int y) =>
0 <= x && x <= MaxX && 0 <= y && y <= MaxY ?
Elements[y][x] :
null;
}
and a Path:
class Path
{
public IEnumerable<MazeElement> Elements { get; }
public bool ReachedDestination { get; }
public static Path Empty => new Path(new MazeElement[0], false);
private Path(IEnumerable<MazeElement> elements, bool reachedDestination) => (Elements, ReachedDestination) = (elements, reachedDestination);
public Path Continued(MazeElement next, bool reachedDestination) => new Path(new List<MazeElement>(Elements) { next }, reachedDestination);
public static Path FromStart(MazeElement start) => new Path(new[] { start }, false);
}
I have a MazeFactory that can create a Maze from a set of strings:
static class MazeFactory
{
public static Maze FromPattern(IEnumerable<string> pattern, char wallSymbol) =>
new Maze(pattern.Select((line, y) => line.Select((c, x) => new MazeElement(x, y, c != wallSymbol)).ToArray()).ToArray());
}
and some such sets:
static class Patterns
{
public static readonly char WallSymbol = '#';
public static readonly IReadOnlyList<string> Maze1 = new[]
{
"#########################",
" ###",
"##################### # #",
"# # #",
"# ##################### #",
"# # #",
"# ##################### #",
"# #",
"####################### #"
};
public static readonly IReadOnlyList<string> Maze2 = new[]
{
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" "
};
public static readonly IReadOnlyList<string> Maze3 = new[]
{
"############ ############",
" ###",
"######## ############ # #",
"# # #",
"# ########## ######## # #",
"# # #",
"# ###### ############ # #",
"# #",
"####################### #"
};
}
To display results I have a PathPrinter:
class PathPrinter
{
private static readonly string mazeSymbol = "█";
private static readonly string pathSymbol = "*";
private readonly ConsoleColor wallColor;
private readonly ConsoleColor passColor;
private readonly ConsoleColor pathColor;
public PathPrinter(ConsoleColor wallColor, ConsoleColor passColor, ConsoleColor pathColor) =>
(this.wallColor, this.passColor, this.pathColor) = (wallColor, passColor, pathColor);
public void Print(Maze maze, Path path, int x = -1, int y = -1)
{
x = x == -1 ? Console.CursorLeft : x;
y = y == -1 ? Console.CursorTop : y;
foreach (var line in maze.Elements)
foreach (var e in line)
{
Console.SetCursorPosition(e.X + x, e.Y + y);
Console.ForegroundColor = e.IsPass ? passColor : wallColor;
Console.Write(mazeSymbol);
}
Console.ForegroundColor = pathColor;
foreach (var e in path.Elements)
{
Console.SetCursorPosition(e.X + x, e.Y + y);
Console.BackgroundColor = e.IsPass ? passColor : wallColor;
Console.Write(pathSymbol);
}
Console.SetCursorPosition(0, maze.MaxY + y + 1);
Console.ResetColor();
}
}
Finally I have a PathFinder to find a path in a Maze:
class PathFinder
{
private readonly Maze maze;
private readonly MazeElement start;
private readonly MazeElement end;
private readonly Dictionary<MazeElement, bool> elementIsChecked;
public PathFinder(Maze maze, MazeElement start, MazeElement end) =>
(this.maze, this.start, this.end, elementIsChecked) =
(maze, start, end, maze.Elements.SelectMany(i => i).ToDictionary(i => i, i => false));
public Path[] GetPaths() => FindPath(Path.FromStart(start)).ToArray();
private IEnumerable<Path> FindPath(Path path) =>
GetContinuations(path).Where(next => next != null)
.SelectMany(next => next.ReachedDestination ? new[] { next } : FindPath(next));
private IEnumerable<Path> GetContinuations(Path path)
{
var e = path.Elements.LastOrDefault();
if (e == null)
return new Path[] { null };
return new[]
{
maze.GetElementAt(e.X, e.Y - 1),
maze.GetElementAt(e.X, e.Y + 1),
maze.GetElementAt(e.X - 1, e.Y),
maze.GetElementAt(e.X + 1, e.Y)
}
.Where(i => i != null)
.Select(i => GetContinuedPath(path, i));
}
private Path GetContinuedPath(Path path, MazeElement e)
{
if (e == null || elementIsChecked[e])
return null;
elementIsChecked[e] = true;
if (e.IsPass)
return path.Continued(e, e == end);
return null;
}
}


ab.OrderBy(p => p.Elements.Count()).First()