A* Pathfinding Code
Mon. January 17, 2011Categories: C#
Tags: Game logic, Pathfinding
/* * A* Pathfinding Algorithm * Modified from my solution to the "After the Dance Battle" challenge * in the 2011 Facebook Hacker Cup * by Andrew Brown * Last modified: 1/14/11 */ using System; using System.IO; using System.Collections.Generic; namespace Astar { class Pathfinding { public const int UNKNOWN = 99999999; // Returns a list of coordinates corresponding to the minimum number of moves // required to move from start to end in map. public static List<Coordinate> AstarPath(Map map, Coordinate start, Coordinate end) { List<Coordinate> moves = new List<Coordinate>(); List<Node> neighbors; int moves_needed = AstarPrep(map, start, end); if (moves_needed == Pathfinding.UNKNOWN) { throw new Exception("Seems to be an impossible destination!"); } // Work back from the end, follow the path of lower numbers Coordinate curloc = end; while (curloc != start) { moves.Add(curloc); neighbors = map.GetSurroundingNodes(curloc); foreach (Node n in neighbors) { if (n.distance_from_start < map.GetNode(curloc).distance_from_start) { curloc = n; } } } moves.Reverse(); return moves; } // A* meat // Modifies map; returns the number of moves needed to get from start // to end in map. private static int AstarPrep(Map map, Coordinate start, Coordinate end) { List<Node> queue = new List<Node>(); List<Node> history = new List<Node>(); int moves_needed = UNKNOWN; // Start position map.GetNode(start.X, start.Y).distance_from_start = 0; queue.Add(map.GetNode(start.X, start.Y)); while (queue.Count > 0) { // Pop queue Node curloc = queue[queue.Count - 1]; queue.RemoveAt(queue.Count - 1); // Add to history history.Add(curloc); // Neighbors onto queue List<Node> neighbors = map.GetSurroundingNodes(curloc); foreach (Node n in neighbors) { // Neighbor = wall? if (n.value == Map.S_WALL) { history.Add(n); continue; } // Neighbor = end? if (n.value == Map.S_END) { if (curloc.distance_from_start + 1 < moves_needed) { moves_needed = curloc.distance_from_start + 1; n.distance_from_start = curloc.distance_from_start + 1; } history.Add(curloc); continue; } // Update neighbors with min(new, old) distance if (curloc.distance_from_start + 1 < n.distance_from_start) { n.distance_from_start = curloc.distance_from_start + 1; } // Queue if we haven't been there and we aren't queued already if (!history.Contains(n) && !queue.Contains(n)) { queue.Add(n); } } // Next! } if (moves_needed == UNKNOWN) { throw new Exception("Impossible path"); } return moves_needed; } } // Map class modeled for Facebook Hacker Cup "After the Dance Battle" puzzle class Map { public const char S_WALL = 'W'; public const char S_START = 'S'; public const char S_END = 'E'; public const char S_EMPTY = '.'; private int height, width; private char[,] data; private List<Node> nodes; private Coordinate start, end; private List<Coordinate> walls = new List<Coordinate>(); public Map(string raw) { string[] split = raw.Split(' '); try { height = Convert.ToInt32(split[0]); width = Convert.ToInt32(split[1]); data = new char[height, width]; } catch { throw new Exception("Something is horrendously wrong with the input file"); } for (int y = 0; y < height; y++) { string row = split[y + 2]; for (int x = 0; x < width; x++) { data[y, x] = row[x]; switch (row[x]) { case S_START: start = new Coordinate(x, y); break; case S_END: end = new Coordinate(x, y); break; case S_WALL: walls.Add(new Coordinate(x, y)); break; } } } InitNodes(); } public static List<Map> FromFile(string filename) { StreamReader fin = new StreamReader(filename); List<Map> maps = new List<Map>(); int cases = Convert.ToInt32(fin.ReadLine()); for (int c = 0; c < cases; c++) { fin.ReadLine(); // Clear the n between each map string[] line = fin.ReadLine().Split(' '); int height = Convert.ToInt32(line[0]); int width = Convert.ToInt32(line[1]); string raw = String.Format("{0} {1}", height, width); for (int i = 0; i < height; i++) { raw = String.Format("{0} {1}", raw, fin.ReadLine()); } maps.Add(new Map(raw)); } fin.Close(); return maps; } void InitNodes() { nodes = new List<Node>(); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { nodes.Add(new Node(this, x, y)); } } } public void PrintDebugDistances() { for (int i = 0; i < nodes.Count; i++) { string symbol = nodes[i].distance_from_start.ToString(); // Empty (0) nodes if (nodes[i].distance_from_start == Pathfinding.UNKNOWN) { symbol = S_EMPTY.ToString(); } // Special nodes switch (nodes[i].value) { case S_WALL: case S_END: symbol = nodes[i].value.ToString(); break; } Console.Write("{0,2} {1}", symbol, ((i + 1) % width == 0 ? "n" : "")); } } public List<Node> GetSurroundingNodes(Coordinate loc) { List<Node> surrounding = new List<Node>(); int base_index = loc.Y * width + loc.X; // Up if (loc.Y > 0) { surrounding.Add(GetNode(loc.X, loc.Y - 1)); } // Right if (loc.X + 1 < width) { surrounding.Add(GetNode(loc.X + 1, loc.Y)); } // Left if (loc.X > 0) { surrounding.Add(GetNode(loc.X - 1, loc.Y)); } // Down if (loc.Y + 1 < height) { surrounding.Add(GetNode(loc.X, loc.Y + 1)); } return surrounding; } public Node GetNode(int x, int y) { return nodes[y * width + x]; } public Node GetNode(Coordinate c) { return GetNode(c.X, c.Y); } #region Accessors/Mutators public Coordinate Start { get { return start; } set { start = value; } } public Coordinate End { get { return end; } set { end = value; } } public List<Coordinate> Walls { get { return walls; } } public List<Node> Nodes { get { return nodes; } } public char[,] Data { get { return data; } } #endregion } class Coordinate { private int x, y; public Coordinate(int x, int y) { this.x = x; this.y = y; } public int X { get { return x; } set { x = value; } } public int Y { get { return y; } set { y = value; } } public override string ToString() { return String.Format("({0}, {1})", x, y); } public static bool operator ==(Coordinate a, Coordinate b) { return (a.X == b.X && a.Y == b.Y); } public static bool operator !=(Coordinate a, Coordinate b) { return !(a == b); } public override bool Equals(object obj) { return base.Equals(obj); } public override int GetHashCode() { return base.GetHashCode(); } } class Node : Coordinate { public char value; public int distance_from_start; public Node(Map m, int x, int y) : base(x, y) { distance_from_start = Pathfinding.UNKNOWN; value = m.Data[y, x]; } public Node(Map m, Coordinate c) : this(m, c.X, c.Y) { } } }