Skip to content

Instantly share code, notes, and snippets.

@pjmagee
Created January 10, 2024 20:42
Show Gist options
  • Select an option

  • Save pjmagee/88fca76fa778710f5972ba612b16131e to your computer and use it in GitHub Desktop.

Select an option

Save pjmagee/88fca76fa778710f5972ba612b16131e to your computer and use it in GitHub Desktop.
flat nodes and edges
void Main()
{
var g = new Graph();
var p = new Player();
p.Location = g.Nodes[0];
var destination = g.Edges[3];
PathFinder finder = new PathFinder();
finder.FindPath(g, p.Location, destination).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 node1 = new Node() { Name = "N1", Context = context };
var node2 = new Node() { Name = "N2", Context = context };
var node3 = new Node() { Name = "N3", Context = context };
var node4 = new Node() { Name = "N4", Context = context };
var node4_1 = new Node() { Name = "N4N1", Context = context };
var node5 = new Node() { Name = "N5", Context = context };
var edge1 = new Edge() { Name = "N1 -> N2", From = node1, To = node2, Context = context };
var edge2 = new Edge() { Name = "N2 -> N3", From = node2, To = node3, Context = context };
var edge3 = new Edge() { Name = "N3 -> N4", From = node3, To = node4, Context = context };
var edge4 = new Edge() { Name = "N4 -> N5", From = node4, To = node5, Context = context };
var edge5 = new Edge() { Name = "N5 -> NULL", From = node5, To = null, Context = context };
var edge6 = new Edge() { Name = "N4 -> N4N1", From = node4, To = node4_1, Context = context };
Nodes.AddRange(new[] { node1, node2, node3, node4, node4_1, node5 });
Edges.AddRange(new[] { edge1, edge2, edge3, edge4, edge5, edge6 });
}
}
/// <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.From == current && !visited.Contains(edge.To) && !edge.IsBlocked)
{
neighbors.Add(edge);
}
else if (edge.To == current && !visited.Contains(edge.From))
{
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;
}
}
public class Location
{
public Context Context { get; set; }
public string Name { get; set; }
public 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)
{
return this.Name.Equals(other?.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 Equals(Edge? other)
{
return this.Name.Equals(other?.Name);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment