Created
January 10, 2024 23:55
-
-
Save pjmagee/8ec574b7181ae0c77cf3762786748fd4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| void Main() | |
| { | |
| var g = new Graph(); | |
| var p = new Player(); | |
| p.Location = g.Edges[3]; | |
| var destination = g.Nodes[0]; | |
| PathFinder finder = new PathFinder(); | |
| var path = finder.FindPath(g, p.Location, destination); | |
| new | |
| { | |
| Start = p.Location, | |
| End = destination, | |
| Path = path | |
| } | |
| .Dump(); | |
| } | |
| public class Graph | |
| { | |
| public List<Node> Nodes { get; } = new(); | |
| public List<Edge> Edges { get; } = new(); | |
| public Graph() | |
| { | |
| var context = new Context(){ Name = "Starting zone" }; | |
| var n1 = new Node() { Name = "N1", Context = context }; | |
| var n2 = new Node() { Name = "N2", Context = context }; | |
| var n3 = new Node() { Name = "N3", Context = context }; | |
| var n4 = new Node() { Name = "N4", Context = context }; | |
| var n5 = new Node() { Name = "N5", Context = context }; | |
| var e12 = new Edge() { From = n1, To = n2, Context = context, IsBidirectional = true }; | |
| var e23 = new Edge() { From = n2, To = n3, Context = context, IsBidirectional = true }; | |
| var e34 = new Edge() { From = n3, To = n4, Context = context, IsBidirectional = true }; | |
| var e45 = new Edge() { From = n4, To = n5, Context = context, IsBidirectional = true }; | |
| var e54 = new Edge() { From = n5, To = n4, Context = context, IsBidirectional = true }; | |
| Nodes.AddRange(new[] { n1, n2, n3, n4, n5 }); | |
| Edges.AddRange(new[] { e12, e23, e34, e54 }); | |
| } | |
| } | |
| /// <summary> | |
| /// Context about the Location, is it a building, an area, etc. | |
| /// A context can be shared by nodes and edges (A location) | |
| /// </summary> | |
| public class Context | |
| { | |
| public string Name { get; set; } | |
| } | |
| public class Player | |
| { | |
| public Location? Location { get; set; } | |
| } | |
| public class PathFinder | |
| { | |
| public List<Location> FindPath(Graph graph, Location start, Location goal) | |
| { | |
| var queue = new Queue<Location>(); | |
| var visited = new HashSet<Location>(); | |
| var cameFrom = new Dictionary<Location, Location>(); | |
| if (start == null || goal == null) | |
| { | |
| throw new ArgumentNullException("Start or goal location is null."); | |
| } | |
| queue.Enqueue(start); | |
| visited.Add(start); | |
| cameFrom[start] = null; // The start location came from nowhere. | |
| while (queue.Count > 0) | |
| { | |
| var current = queue.Dequeue(); | |
| if (current.Equals(goal)) | |
| { | |
| return ReconstructPath(cameFrom, current); | |
| } | |
| foreach (var neighbor in GetNeighbors(graph, current, visited)) | |
| { | |
| if (!visited.Contains(neighbor)) | |
| { | |
| queue.Enqueue(neighbor); | |
| visited.Add(neighbor); | |
| cameFrom[neighbor] = current; | |
| } | |
| } | |
| } | |
| return null; // Path not found | |
| } | |
| private IEnumerable<Location> GetNeighbors(Graph graph, Location current, HashSet<Location> visited) | |
| { | |
| var neighbors = new List<Location>(); | |
| foreach (var edge in graph.Edges) | |
| { | |
| if(edge.IsBidirectional && !edge.IsBlocked) | |
| { | |
| if (edge.From == current && !visited.Contains(edge.To) || edge.To == current && !visited.Contains(edge.From)) | |
| { | |
| neighbors.Add(edge); | |
| } | |
| } | |
| else if(!edge.IsBidirectional && !edge.IsBlocked) | |
| { | |
| if (edge.From == current && !visited.Contains(edge.To)) | |
| { | |
| neighbors.Add(edge); | |
| } | |
| } | |
| } | |
| if (current is Edge e) | |
| { | |
| if (!visited.Contains(e.From)) | |
| { | |
| neighbors.Add(e.From); // Add the node where the edge starts | |
| } | |
| if (!visited.Contains(e.To)) | |
| { | |
| neighbors.Add(e.To); // Add the node where the edge ends | |
| } | |
| } | |
| return neighbors; | |
| } | |
| private List<Location> ReconstructPath(Dictionary<Location, Location> cameFrom, Location? current) | |
| { | |
| var path = new List<Location>(); | |
| while (current != null) | |
| { | |
| path.Insert(0, current); | |
| current = cameFrom.TryGetValue(current, out var previous) ? previous : null; | |
| } | |
| return path; | |
| } | |
| } | |
| static object ToDump(object input) | |
| { | |
| if (input is Context c) | |
| { | |
| return c.Name; | |
| } | |
| if (input is Node n) | |
| { | |
| return n.Name; | |
| } | |
| if (input is Edge e) | |
| { | |
| return e.Name; | |
| } | |
| if(input is List<Location> l) | |
| { | |
| return l.Select(l => l); | |
| } | |
| return input; | |
| } | |
| public class Location | |
| { | |
| public Context Context { get; set; } | |
| public virtual string Name { get; set; } | |
| public virtual string Description { get; set; } | |
| public string Type => this.GetType().Name; | |
| } | |
| /// <summary> | |
| /// A node is similar to an Edge, except can have Child Nodes. | |
| /// An Edge, cannot have Child edges. | |
| /// </summary> | |
| public class Node : Location, IEquatable<Node> | |
| { | |
| /// <summary> | |
| /// Optional edges (more for organisation and structure purposes) | |
| /// </summary> | |
| public List<Edge> Edges { get; set; } = new(); | |
| public bool Equals(Node? other) => Name.Equals(other?.Name); | |
| public override string ToString() => $"{Type}:{Name}"; | |
| } | |
| /// <summary> | |
| /// An edge is a type of location that connects two other locations together | |
| /// Similar to a Graph of Nodes/Vertices and Edges/Connections | |
| /// The difference is that, a player can also be located at an Edge | |
| /// An edge in this case, can be anything from a path, ladder, street or even another building, but provides semantics to how two Nodes are related. | |
| /// </summary> | |
| public class Edge : Location, IEquatable<Edge> | |
| { | |
| /// <summary> | |
| /// Where this edge begins | |
| /// </summary> | |
| public Location From { get; init; } | |
| /// <summary> | |
| /// It is possible for an edge to lead to another edge | |
| /// Generally, an Edge would link two 'Nodes' however, it's possible that an Edge could lead to another Edge | |
| /// </summary> | |
| public Location? To { get; init; } | |
| /// <summary> | |
| /// Sometimes, an Edge may be blocked due to various factors: Player level, skill, environment, quest, dynamic event etc. | |
| /// </summary> | |
| public bool IsBlocked { get; init; } | |
| public bool IsBidirectional { get; init; } = false; | |
| public override string ToString() => Name; | |
| public override string Name | |
| { | |
| get | |
| { | |
| if (IsBidirectional) | |
| { | |
| return $"{From.Name} <-> {To.Name}"; | |
| } | |
| return $"{From} -> {To}"; | |
| } | |
| } | |
| public bool Equals(Edge? other) => Name.Equals(other?.Name); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment