using System; using System.Collections; namespace Holo.Virtual.Rooms.Pathfinding { /// /// A node class for doing pathfinding on a 2-dimensional map /// public class AStarNode2D : AStarNode { #region Properties /// /// The X-coordinate of the node /// public int X { get { return FX; } } private int FX; /// /// The Y-coordinate of the node /// public int Y { get { return FY; } } private int FY; #endregion #region Constructors /// /// Constructor for a node in a 2-dimensional map /// /// Parent of the node /// Goal node /// Accumulative cost /// X-coordinate /// Y-coordinate public AStarNode2D(AStarNode AParent,AStarNode AGoalNode,double ACost,int AX, int AY) : base(AParent,AGoalNode,ACost) { FX = AX; FY = AY; } #endregion #region Private Methods /// /// Adds a successor to a list if it is not impassible or the parent node /// /// List of successors /// X-coordinate /// Y-coordinate private void AddSuccessor(ArrayList ASuccessors,int AX,int AY) { int CurrentCost = MainClass.GetMap(AX,AY); if(CurrentCost == -1) { return; } AStarNode2D NewNode = new AStarNode2D(this,GoalNode,Cost + CurrentCost,AX,AY); if(NewNode.IsSameState(Parent)) { return; } ASuccessors.Add(NewNode); } #endregion #region Overidden Methods /// /// Determines wheather the current node is the same state as the on passed. /// /// AStarNode to compare the current node to /// Returns true if they are the same state public override bool IsSameState(AStarNode ANode) { if(ANode == null) { return false; } return ((((AStarNode2D)ANode).X == FX) && (((AStarNode2D)ANode).Y == FY)); } /// /// Calculates the estimated cost for the remaining trip to the goal. /// public override void Calculate() { if(GoalNode != null) { double xd = FX - ((AStarNode2D)GoalNode).X; double yd = FY - ((AStarNode2D)GoalNode).Y; // "Euclidean distance" - Used when search can move at any angle. //GoalEstimate = Math.Sqrt((xd*xd) + (yd*yd)); // "Manhattan Distance" - Used when search can only move vertically and // horizontally. //GoalEstimate = Math.Abs(xd) + Math.Abs(yd); // "Diagonal Distance" - Used when the search can move in 8 directions. GoalEstimate = Math.Max(Math.Abs(xd),Math.Abs(yd)); } else { GoalEstimate = 0; } } /// /// Gets all successors nodes from the current node and adds them to the successor list /// /// List in which the successors will be added public override void GetSuccessors(ArrayList ASuccessors) { ASuccessors.Clear(); AddSuccessor(ASuccessors,FX-1,FY ); AddSuccessor(ASuccessors,FX-1,FY-1); AddSuccessor(ASuccessors,FX ,FY-1); AddSuccessor(ASuccessors,FX+1,FY-1); AddSuccessor(ASuccessors,FX+1,FY ); AddSuccessor(ASuccessors,FX+1,FY+1); AddSuccessor(ASuccessors,FX ,FY+1); AddSuccessor(ASuccessors,FX-1,FY+1); } /// /// Prints information about the current node /// public override void PrintNodeInfo() { Console.WriteLine("X:\t{0}\tY:\t{1}\tCost:\t{2}\tEst:\t{3}\tTotal:\t{4}",FX,FY,Cost,GoalEstimate,TotalCost); } #endregion } /// /// Test class for doing A* pathfinding on a 2D map. /// class MainClass { #region Test Maps static int[,] Map = { { 1,-1, 1, 1, 1,-1, 1, 1, 1, 1 }, { 1,-1, 1,-1, 1,-1, 1, 1, 1, 1 }, { 1,-1, 1,-1, 1,-1, 1, 1, 1, 1 }, { 1,-1, 1,-1, 1,-1, 1, 1, 1, 1 }, { 1,-1, 1,-1, 1,-1, 1, 1, 1, 1 }, { 1,-1, 1,-1, 1,-1, 1, 1, 1, 1 }, { 1,-1, 1,-1, 1,-1, 1, 1, 1, 1 }, { 1,-1, 1,-1, 1,-1, 1, 1, 1, 1 }, { 1,-1, 1,-1, 1,-1, 1, 2, 1, 1 }, { 1, 1, 1,-1, 1, 1, 2, 3, 2, 1 } }; // static int[,] Map = { // { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, // { 1, 1, 1, 1, 1, 1, 1, 1,-1, 1 }, // { 1, 1, 1, 1, 1, 1, 1, 1,-1, 1 }, // { 1, 1, 1, 1, 1, 1, 1, 1,-1, 1 }, // { 1, 1, 1, 1, 1, 1, 1, 1,-1, 1 }, // { 1, 1, 1, 1, 1, 1, 1, 1,-1, 1 }, // { 1, 1, 1, 1, 1, 1, 1, 1,-1, 1 }, // { 1, 1, 1, 1, 1, 1, 1, 1,-1, 1 }, // { 1,-1,-1,-1,-1,-1,-1,-1,-1, 1 }, // { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 } // }; // static int[,] Map = { // { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, // { 1, 1, 1, 1, 1, 2, 1, 1, 1, 1 }, // { 1, 1, 1, 1, 2, 3, 2, 1, 1, 1 }, // { 1, 1, 1, 2, 3, 4, 3, 2, 1, 1 }, // { 1, 1, 2, 3, 4, 5, 4, 3, 2, 1 }, // { 1, 1, 1, 2, 3, 4, 3, 2, 1, 1 }, // { 1, 1, 1, 1, 2, 3, 2, 1, 1, 1 }, // { 1, 1, 1, 1, 1, 2, 1, 1, 1, 1 }, // { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, // { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 } // }; #endregion #region Public Methods /// /// Gets movement cost from the 2-dimensional map /// /// X-coordinate /// Y-coordinate /// Returns movement cost at the specified point in the map static public int GetMap(int x,int y) { if((x < 0) || (x > 9)) return(-1); if((y < 0) || (y > 9)) return(-1); return(Map[y,x]); } /// /// Prints the solution /// /// The list that holds the solution static public void PrintSolution(ArrayList ASolution) { for(int j=0;j<10;j++) { for(int i=0;i<10;i++) { bool solution = false; foreach(AStarNode2D n in ASolution) { AStarNode2D tmp = new AStarNode2D(null,null,0,i,j); solution = n.IsSameState(tmp); if(solution) break; } if(solution) Console.Write("S "); else if(MainClass.GetMap(i,j) == -1) Console.Write("X "); else Console.Write(". "); } Console.WriteLine(""); } } /// /// The main entry point for the application. /// [STAThread] static void Main(string[] args) { Console.WriteLine("Starting..."); Games.Pathfinding.AStar astar = new Games.Pathfinding.AStar(); AStarNode2D GoalNode = new AStarNode2D(null,null,0,9,9); AStarNode2D StartNode = new AStarNode2D(null,GoalNode,0,0,0); StartNode.GoalNode = GoalNode; astar.FindPath(StartNode,GoalNode); PrintSolution(astar.Solution); Console.ReadLine(); } #endregion } }