// GDnD wiki example // Demonstrates: an A* planner that searches over world states, with actions as the edges // Related pages: [[goal-oriented-action-planning]], [[pathfinding-algorithms]] using System.Collections.Generic; // Pathfinding A* searches over places; this A* searches over world states. // Node = a world state. Edge = an action whose preconditions hold in that state. // g = total action cost so far. h = number of goal facts still unmet. // Returns the cheapest action list, or null when no plan exists. public static class GoapPlanner { private class Node { public WorldState State; public Node Parent; public GoapAction Action; // the action that led here public float G; public float F; } public static List Plan(WorldState start, Dictionary goal, List actions, int maxExpansions = 1000) { var open = new List { new Node { State = start, G = 0, F = start.CountUnmet(goal) } }; var bestCost = new Dictionary { [start.Key()] = 0 }; for (int i = 0; i < maxExpansions && open.Count > 0; i++) { // Take the open node with the lowest f = g + h. A plain list keeps this // readable; a priority queue is the usual upgrade for bigger action sets. Node current = open[0]; foreach (Node n in open) if (n.F < current.F) current = n; open.Remove(current); if (current.State.Satisfies(goal)) return BuildPlan(current); foreach (GoapAction action in actions) { if (!current.State.Satisfies(action.Preconditions)) continue; WorldState next = action.ApplyTo(current.State); float g = current.G + action.Cost; string key = next.Key(); // Skip states we have already reached at least as cheaply. if (bestCost.TryGetValue(key, out float known) && known <= g) continue; bestCost[key] = g; open.Add(new Node { State = next, Parent = current, Action = action, G = g, F = g + next.CountUnmet(goal) }); } } return null; } // Walk back from the goal node to the start, then reverse into execution order. private static List BuildPlan(Node node) { var plan = new List(); for (; node.Action != null; node = node.Parent) plan.Add(node.Action); plan.Reverse(); return plan; } }