-
Notifications
You must be signed in to change notification settings - Fork 77
/
Copy pathPathFinding.cs
executable file
·160 lines (143 loc) · 6.09 KB
/
PathFinding.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/**
* Provide simple path-finding algorithm with tile prices support.
* Based on code and tutorial by Sebastian Lague (https://www.youtube.com/channel/UCmtyQOKKmrMVaKuRXz02jbQ).
*
* Author: Ronen Ness.
* Since: 2016.
*/
using System.Collections.Generic;
namespace NesScripts.Controls.PathFind
{
/// <summary>
/// Main class to find the best path to walk from A to B.
///
/// Usage example:
/// Grid grid = new Grid(width, height, tiles_costs);
/// List<Point> path = Pathfinding.FindPath(grid, from, to);
/// </summary>
public class Pathfinding
{
/// <summary>
/// Different ways to calculate path distance.
/// </summary>
public enum DistanceType
{
/// <summary>
/// The "ordinary" straight-line distance between two points.
/// </summary>
Euclidean,
/// <summary>
/// Distance without diagonals, only horizontal and/or vertical path lines.
/// </summary>
Manhattan
}
/// <summary>
/// Find a path between two points.
/// </summary>
/// <param name="grid">Grid to search.</param>
/// <param name="startPos">Starting position.</param>
/// <param name="targetPos">Ending position.</param>
/// <param name="distance">The type of distance, Euclidean or Manhattan.</param>
/// <param name="ignorePrices">If true, will ignore tile price (how much it "cost" to walk on).</param>
/// <returns>List of points that represent the path to walk.</returns>
public static List<Point> FindPath(Grid grid, Point startPos, Point targetPos, DistanceType distance = DistanceType.Euclidean, bool ignorePrices = false)
{
// find path
List<Node> nodes_path = _ImpFindPath(grid, startPos, targetPos, distance, ignorePrices);
// convert to a list of points and return
List<Point> ret = new List<Point>();
if (nodes_path != null)
{
foreach (Node node in nodes_path)
{
ret.Add(new Point(node.gridX, node.gridY));
}
}
return ret;
}
/// <summary>
/// Internal function that implements the path-finding algorithm.
/// </summary>
/// <param name="grid">Grid to search.</param>
/// <param name="startPos">Starting position.</param>
/// <param name="targetPos">Ending position.</param>
/// <param name="distance">The type of distance, Euclidean or Manhattan.</param>
/// <param name="ignorePrices">If true, will ignore tile price (how much it "cost" to walk on).</param>
/// <returns>List of grid nodes that represent the path to walk.</returns>
private static List<Node> _ImpFindPath(Grid grid, Point startPos, Point targetPos, DistanceType distance = DistanceType.Euclidean, bool ignorePrices = false)
{
Node startNode = grid.nodes[startPos.x, startPos.y];
Node targetNode = grid.nodes[targetPos.x, targetPos.y];
List<Node> openSet = new List<Node>();
HashSet<Node> closedSet = new HashSet<Node>();
openSet.Add(startNode);
while (openSet.Count > 0)
{
Node currentNode = openSet[0];
for (int i = 1; i < openSet.Count; i++)
{
if (openSet[i].fCost < currentNode.fCost || openSet[i].fCost == currentNode.fCost && openSet[i].hCost < currentNode.hCost)
{
currentNode = openSet[i];
}
}
openSet.Remove(currentNode);
closedSet.Add(currentNode);
if (currentNode == targetNode)
{
return RetracePath(grid, startNode, targetNode);
}
foreach (Node neighbour in grid.GetNeighbours(currentNode, distance))
{
if (!neighbour.walkable || closedSet.Contains(neighbour))
{
continue;
}
int newMovementCostToNeighbour = currentNode.gCost + GetDistance(currentNode, neighbour) * (ignorePrices ? 1 : (int)(10.0f * neighbour.price));
if (newMovementCostToNeighbour < neighbour.gCost || !openSet.Contains(neighbour))
{
neighbour.gCost = newMovementCostToNeighbour;
neighbour.hCost = GetDistance(neighbour, targetNode);
neighbour.parent = currentNode;
if (!openSet.Contains(neighbour))
openSet.Add(neighbour);
}
}
}
return null;
}
/// <summary>
/// Retrace path between two points.
/// </summary>
/// <param name="grid">Grid to work on.</param>
/// <param name="startNode">Starting node.</param>
/// <param name="endNode">Ending (target) node.</param>
/// <returns>Retraced path between nodes.</returns>
private static List<Node> RetracePath(Grid grid, Node startNode, Node endNode)
{
List<Node> path = new List<Node>();
Node currentNode = endNode;
while (currentNode != startNode)
{
path.Add(currentNode);
currentNode = currentNode.parent;
}
path.Reverse();
return path;
}
/// <summary>
/// Get distance between two nodes.
/// </summary>
/// <param name="nodeA">First node.</param>
/// <param name="nodeB">Second node.</param>
/// <returns>Distance between nodes.</returns>
private static int GetDistance(Node nodeA, Node nodeB)
{
int dstX = System.Math.Abs(nodeA.gridX - nodeB.gridX);
int dstY = System.Math.Abs(nodeA.gridY - nodeB.gridY);
return (dstX > dstY) ?
14 * dstY + 10 * (dstX - dstY) :
14 * dstX + 10 * (dstY - dstX);
}
}
}