Skip to content

Commit 09527a4

Browse files
committed
#35 AStar
Provided simple realization for A* algo. Added 2d Matrix playground for test purposes.
1 parent 69ea5f0 commit 09527a4

File tree

6 files changed

+395
-0
lines changed

6 files changed

+395
-0
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using Algorithms.AStar;
2+
using Algorithms.Tests.AStarTests.TestField;
3+
using FluentAssertions;
4+
using Xunit;
5+
6+
namespace Algorithms.Tests.AStarTests
7+
{
8+
public class AStarTests
9+
{
10+
[Fact]
11+
void AStar_RunOnStable3On3Filed_ReturnCorrectPath()
12+
{
13+
/* The way
14+
* |F.W|
15+
* |W..|
16+
* |WWF|
17+
*/
18+
19+
var matrix = new MatrixField();
20+
var search = new AStarSearch(matrix.Start, matrix.Goal);
21+
var expectedPath = "s.W\nW..\nWWg\n";
22+
23+
search.Run();
24+
var foundPath = matrix.Print(search.GetPath());
25+
26+
foundPath.Should().Be(expectedPath);
27+
}
28+
}
29+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Algorithms.AStar;
4+
5+
namespace Algorithms.Tests.AStarTests.TestField
6+
{
7+
public class MatrixField
8+
{
9+
public MatrixNode[][] Matrix;
10+
11+
public int Width { get { return Matrix.Length; } }
12+
public int Height { get { return Matrix[0].Length; } }
13+
14+
public MatrixNode Start;
15+
public MatrixNode Goal;
16+
17+
/// <example>
18+
/// |S W|
19+
/// |W |
20+
/// |W F|
21+
/// </example>
22+
public MatrixField()
23+
{
24+
Start = new MatrixNode(this, 0, 0, false);
25+
Goal = new MatrixNode(this, 2, 2, false);
26+
27+
Matrix = new MatrixNode[3][];
28+
for (var i = 0; i < 3; i++)
29+
Matrix[i] = new MatrixNode[3];
30+
31+
Matrix[Start.X][Start.Y] = Start;
32+
Matrix[Goal.X][Goal.Y] = Goal;
33+
34+
Matrix[0][1] = new MatrixNode(this, 0, 2, false);
35+
Matrix[0][2] = new MatrixNode(this, 0, 2, true);
36+
37+
Matrix[1][0] = new MatrixNode(this, 1, 0, true);
38+
Matrix[1][1] = new MatrixNode(this, 1, 0, false);
39+
Matrix[1][2] = new MatrixNode(this, 1, 0, false);
40+
41+
Matrix[2][0] = new MatrixNode(this, 2, 0, true);
42+
Matrix[2][1] = new MatrixNode(this, 2, 1, true);
43+
}
44+
45+
public string Print(IEnumerable<IWayNode> path)
46+
{
47+
var output = "";
48+
for (var i = 0; i < Width; i++)
49+
{
50+
for (var j = 0; j < Height; j++)
51+
{
52+
output += Matrix[i][j].Print(Start, Goal, path);
53+
}
54+
output += "\n";
55+
}
56+
return output;
57+
}
58+
}
59+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Algorithms.AStar;
4+
5+
namespace Algorithms.Tests.AStarTests.TestField
6+
{
7+
public class MatrixNode : IWayNode
8+
{
9+
private MatrixField Matrix;
10+
11+
private bool isOpenList = false;
12+
private bool isClosedList = false;
13+
14+
private static int[] childXPos = new int[] { 0, -1, 1, 0, };
15+
private static readonly int[] childYPos = new int[] { -1, 0, 0, 1, };
16+
17+
public int X { get; private set; }
18+
public int Y { get; private set; }
19+
public bool IsWall { get; set; }
20+
21+
public MatrixNode(MatrixField matrix, int x, int y, bool isWall)
22+
{
23+
Matrix = matrix;
24+
X = x;
25+
Y = y;
26+
IsWall = isWall;
27+
}
28+
29+
public IWayNode Parent { get; set; }
30+
31+
public IEnumerable<IWayNode> Children
32+
{
33+
get
34+
{
35+
var children = new List<MatrixNode>();
36+
37+
for (int i = 0; i < childXPos.Length; i++)
38+
{
39+
// skip any nodes out of bounds.
40+
if (X + childXPos[i] >= Matrix.Width || Y + childYPos[i] >= Matrix.Height)
41+
continue;
42+
if (X + childXPos[i] < 0 || Y + childYPos[i] < 0)
43+
continue;
44+
45+
children.Add(Matrix.Matrix[X + childXPos[i]][Y + childYPos[i]]);
46+
}
47+
48+
return children;
49+
}
50+
}
51+
52+
public int TotalCost { get { return MovementCost + EstimatedCost; } }
53+
54+
public int MovementCost { get; private set; }
55+
56+
public int EstimatedCost { get; private set; }
57+
58+
public bool IsOpenNodes(IEnumerable<IWayNode> openNodes)
59+
{
60+
return isOpenList;
61+
}
62+
63+
public void SetOpenNode(bool value)
64+
{
65+
isOpenList = value;
66+
}
67+
68+
public bool IsClosedNodes(IEnumerable<IWayNode> closedNodes)
69+
{
70+
return IsWall || isClosedList;
71+
}
72+
73+
public void SetClosedNode(bool value)
74+
{
75+
isClosedList = value;
76+
}
77+
78+
public void SetMovementCost(IWayNode parent)
79+
{
80+
MovementCost = parent.MovementCost + 1;
81+
}
82+
83+
public void SetEstimatedCost(IWayNode goal)
84+
{
85+
var goalNode = (MatrixNode)goal;
86+
EstimatedCost = Math.Abs(X - goalNode.X) + Math.Abs(Y - goalNode.Y);
87+
}
88+
89+
public bool IsGoal(IWayNode goal)
90+
{
91+
return IsEqual((MatrixNode)goal);
92+
}
93+
94+
public bool IsEqual(MatrixNode node)
95+
{
96+
return (this == node) || (this.X == node.X && this.Y == node.Y);
97+
}
98+
99+
public string Print(MatrixNode start, MatrixNode goal, IEnumerable<IWayNode> path)
100+
{
101+
if (IsWall)
102+
{
103+
return "W";
104+
}
105+
else if (IsEqual(start))
106+
{
107+
return "s";
108+
}
109+
else if (IsEqual(goal))
110+
{
111+
return "g";
112+
}
113+
else if (IsInPath(path))
114+
{
115+
return ".";
116+
}
117+
else
118+
{
119+
return " ";
120+
}
121+
}
122+
123+
private bool IsInPath(IEnumerable<IWayNode> path)
124+
{
125+
foreach (var node in path)
126+
{
127+
if (IsEqual((MatrixNode)node))
128+
return true;
129+
}
130+
return false;
131+
}
132+
}
133+
}

Algorithms/Algorithms/AStar/AStar.cs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
using System.Collections.Generic;
2+
3+
namespace Algorithms.AStar
4+
{
5+
public enum State
6+
{
7+
Searching,
8+
GoalFound,
9+
Failed
10+
}
11+
12+
internal class DuplicateComparer : IComparer<int>
13+
{
14+
public int Compare(int x, int y)
15+
{
16+
return x <= y ? -1 : 1;
17+
}
18+
}
19+
20+
public class AStarSearch
21+
{
22+
private readonly SortedList<int, IWayNode> _openNodes;
23+
private readonly SortedList<int, IWayNode> _closeNodes;
24+
25+
public IEnumerable<IWayNode> OpenNodes => _openNodes.Values;
26+
public IEnumerable<IWayNode> ClosedNodes => _closeNodes.Values;
27+
28+
public IWayNode Current { get; private set; }
29+
public IWayNode Goal { get; private set; }
30+
31+
public int Steps { get; private set; }
32+
33+
public AStarSearch(IWayNode start, IWayNode goal)
34+
{
35+
var duplicateComparer = new DuplicateComparer();
36+
_openNodes = new SortedList<int, IWayNode>(duplicateComparer);
37+
_closeNodes = new SortedList<int, IWayNode>(duplicateComparer);
38+
39+
Reset(start, goal);
40+
}
41+
42+
public void Reset(IWayNode start, IWayNode goal)
43+
{
44+
_openNodes.Clear();
45+
_closeNodes.Clear();
46+
47+
Current = start;
48+
Goal = goal;
49+
50+
_openNodes.Add(Current);
51+
Current.SetOpenNode(true);
52+
}
53+
54+
public State Run()
55+
{
56+
while (true)
57+
{
58+
var state = NextStep();
59+
if (state != State.Searching)
60+
return state;
61+
}
62+
}
63+
64+
private State NextStep()
65+
{
66+
Steps++;
67+
while (true)
68+
{
69+
if (_openNodes.IsEmpty())
70+
{
71+
return State.Failed;
72+
}
73+
74+
Current = _openNodes.Pop();
75+
if (Current.IsClosedNodes(ClosedNodes))
76+
{
77+
continue;
78+
}
79+
80+
break;
81+
}
82+
83+
Current.SetOpenNode(false);
84+
_closeNodes.Add(Current);
85+
Current.SetClosedNode(true);
86+
87+
if (Current.IsGoal(Goal))
88+
{
89+
return State.GoalFound;
90+
}
91+
92+
foreach (var child in Current.Children)
93+
{
94+
if (child.IsOpenNodes(OpenNodes) || child.IsClosedNodes(ClosedNodes))
95+
{
96+
continue;
97+
}
98+
99+
child.Parent = Current;
100+
child.SetMovementCost(Current);
101+
child.SetEstimatedCost(Goal);
102+
103+
_openNodes.Add(child);
104+
child.SetOpenNode(true);
105+
}
106+
107+
return State.Searching;
108+
}
109+
110+
public IEnumerable<IWayNode> GetPath()
111+
{
112+
if (Current != null)
113+
{
114+
var next = Current;
115+
var path = new List<IWayNode>();
116+
while (next != null)
117+
{
118+
path.Add(next);
119+
next = next.Parent;
120+
}
121+
path.Reverse();
122+
return path.ToArray();
123+
}
124+
return null;
125+
}
126+
}
127+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System.Collections.Generic;
2+
3+
namespace Algorithms.AStar
4+
{
5+
internal static class Extensions
6+
{
7+
internal static bool IsEmpty<TKey, TValue>(this SortedList<TKey, TValue> sortedList)
8+
{
9+
return sortedList.Count == 0;
10+
}
11+
12+
internal static void Add(this SortedList<int, IWayNode> sortedList, IWayNode wayNode)
13+
{
14+
sortedList.Add(wayNode.TotalCost, wayNode);
15+
}
16+
17+
internal static IWayNode Pop(this SortedList<int, IWayNode> sortedList)
18+
{
19+
var top = sortedList.Values[0];
20+
sortedList.RemoveAt(0);
21+
return top;
22+
}
23+
}
24+
}

0 commit comments

Comments
 (0)