Skip to content

Commit

Permalink
Add backtracking and fix all consume key tests
Browse files Browse the repository at this point in the history
  • Loading branch information
IntelOrca committed Sep 21, 2024
1 parent bcf1daf commit ac6c68b
Show file tree
Hide file tree
Showing 7 changed files with 288 additions and 54 deletions.
3 changes: 2 additions & 1 deletion IntelOrca.Biohazard.BioRand.Tests/TestRouting.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public void Basic()

AssertKeyOnce(route, key0, item0a, item0b);
AssertKeyOnce(route, key1, item0a, item0b, item1a);
Assert.Equal((RouteSolverResult)0, route.Solve());
}
}

Expand Down Expand Up @@ -319,8 +320,8 @@ public void SingleUseKey_RouteOrderMatters_Flexible()

var route = builder.GenerateRoute(i);

AssertKeyOnce(route, key1, item3a, item3b);
Assert.True(route.AllNodesVisited);
AssertKeyOnce(route, key1, item3a, item3b);
}
}

Expand Down
2 changes: 2 additions & 0 deletions IntelOrca.Biohazard.BioRand/Routing/Edge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,7 @@ public Edge(Node node, EdgeFlags flags)
Node = node;
Flags = flags;
}

public override string ToString() => $"{Node} Flags = {Flags}";
}
}
2 changes: 1 addition & 1 deletion IntelOrca.Biohazard.BioRand/Routing/OneToManyDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public ImmutableOneToManyDictionary<TOne, TMany> Add(TOne key, TMany value)
}
else
{
newValueToKeys = _valueToKeys.Add(value, ImmutableHashSet<TOne>.Empty);
newValueToKeys = _valueToKeys.Add(value, ImmutableHashSet.Create(key));
}
return new ImmutableOneToManyDictionary<TOne, TMany>(newKeyToValue, newValueToKeys);
}
Expand Down
2 changes: 2 additions & 0 deletions IntelOrca.Biohazard.BioRand/Routing/Route.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,5 +101,7 @@ void Visit(Node n)
}
}
}

public RouteSolverResult Solve() => RouteSolver.Default.Solve(this);
}
}
145 changes: 93 additions & 52 deletions IntelOrca.Biohazard.BioRand/Routing/RouteFinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Runtime.InteropServices;

namespace IntelOrca.Biohazard.BioRand.Routing
{
Expand All @@ -17,13 +18,15 @@ public RouteFinder(int? seed = null)

public Route Find(Graph input)
{
var m = input.ToMermaid();

var state = new State(input);
state = DoSubgraph(state, input.Start, first: true, _rng);
return GetRoute(state);
}

private static Route GetRoute(State state)
{
return new Route(
input,
state.Input,
state.Next.Count == 0,
state.ItemToKey,
string.Join("\n", state.Log));
Expand All @@ -50,35 +53,78 @@ private static State DoSubgraph(State state, IEnumerable<Node> start, bool first
foreach (var v in toVisit)
state = state.VisitNode(v);

state = DoPass(state, rng);
return Fulfill(state, rng);
}

private static State Fulfill(State state, Random rng)
{
state = Expand(state);
if (!ValidateState(state))
return state;

var subGraphs = state.OneWay.ToArray();
foreach (var n in subGraphs)
var checklist = GetChecklist(state);
var requiredKeys = Shuffle(rng, checklist
.SelectMany(x => x.Need)
.Select(x => x.Node)
.Distinct());
var states = new List<State>();
foreach (var key in requiredKeys)
{
state = DoSubgraph(state, new[] { n }, first: false, rng);
var allEdges = checklist
.Where(x => x.Need.All(x => x.Node == key))
.SelectMany(x => x.Need)
.ToArray();
var multipleRequired = allEdges.Any(x => (x.Flags & EdgeFlags.Consume) != 0);
var need = multipleRequired ? allEdges.Length : 1;
var available = Shuffle(rng, state.SpareItems.Where(x => x.Group == key.Group));
if (need == 1)
{
foreach (var a in available)
{
states.Add(state.PlaceKey(a, key));
}
}
else if (available.Length >= need)
{
var newState = state;
for (var i = 0; i < need; i++)
{
newState = newState.PlaceKey(available[i], key);
}
states.Add(newState);
}
}

return state;
}

private static State DoPass(State state, Random rng)
{
while (true)
if (states.Count == 0)
{
state = Expand(state);
var newState = Fulfill(state, rng);
if (newState == state)
break;
state = newState;
var subGraphs = state.OneWay.ToArray();
foreach (var n in subGraphs)
{
state = DoSubgraph(state, new[] { n }, first: false, rng);
}
return state;
}
else
{
State? firstState = null;
foreach (var s in states)
{
var finalState = Fulfill(s, rng);
if (finalState.Next.Count == 0 && finalState.OneWay.Count == 0)
{
return finalState;
}
firstState ??= finalState;
}
return firstState!;
}
return state;
}

private static State Expand(State state)
{
while (true)
{
var (newState, satisfied) = TakeNextNodes(state, IsSatisfied);
var (newState, satisfied) = TakeNextNodes(state);
if (satisfied.Length == 0)
break;

Expand All @@ -98,29 +144,13 @@ private static State Expand(State state)
return state;
}

private static State Fulfill(State state, Random rng)
{
var checklist = GetChecklist(state);
var requiredKeys = Shuffle(rng, checklist.SelectMany(x => x.Need).Distinct());
foreach (var key in requiredKeys)
{
// Find an item for this key
var available = Shuffle(rng, state.SpareItems.Where(x => x.Group == key.Group));
if (available.Length != 0)
{
return state.PlaceKey(available[0], key);
}
}
return state;
}

private static (State, Node[]) TakeNextNodes(State state, Func<State, Node, bool> predicate)
private static (State, Node[]) TakeNextNodes(State state)
{
var result = new List<Node>();
while (true)
{
var next = state.Next.ToArray();
var index = Array.FindIndex(next, x => predicate(state, x));
var index = Array.FindIndex(next, x => IsSatisfied(state, x));
if (index == -1)
break;

Expand Down Expand Up @@ -175,19 +205,24 @@ private static ImmutableArray<ChecklistItem> GetChecklist(State state)
private static ChecklistItem GetChecklistItem(State state, Node node)
{
var haveList = new List<Node>();
var missingList = new List<Node>();
var missingList = new List<Edge>();
var requiredKeys = GetRequiredKeys(state, node)
.GroupBy(x => x)
.Select(x => (x.Key, x.Count()))
.GroupBy(x => x.Node)
.ToArray();

foreach (var (key, need) in requiredKeys)
foreach (var edges in requiredKeys)
{
var key = edges.Key;
EdgeFlags flags = 0;
foreach (var edge in edges)
flags |= edge.Flags;

var need = edges.Count();
var have = state.Keys.GetCount(key);

var missing = Math.Max(0, need - have);
for (var i = 0; i < missing; i++)
missingList.Add(key);
missingList.Add(new Edge(key, flags));

var progress = Math.Min(have, need);
for (var i = 0; i < progress; i++)
Expand All @@ -197,13 +232,19 @@ private static ChecklistItem GetChecklistItem(State state, Node node)
return new ChecklistItem(node, haveList.ToImmutableArray(), missingList.ToImmutableArray());
}

private static bool ValidateState(State state)
{
var flags = RouteSolver.Default.Solve(GetRoute(state));
return (flags & RouteSolverResult.PotentialSoftlock) == 0;
}

private sealed class ChecklistItem
{
public Node Destination { get; }
public ImmutableArray<Node> Have { get; }
public ImmutableArray<Node> Need { get; }
public ImmutableArray<Edge> Need { get; }

public ChecklistItem(Node destination, ImmutableArray<Node> have, ImmutableArray<Node> need)
public ChecklistItem(Node destination, ImmutableArray<Node> have, ImmutableArray<Edge> need)
{
Destination = destination;
Have = have;
Expand Down Expand Up @@ -246,9 +287,9 @@ private static bool IsSatisfied(State state, Node node)
}
}

private static Node[] GetRequiredKeys(State state, Node node)
private static Edge[] GetRequiredKeys(State state, Node node)
{
var leaves = new List<Node>();
var leaves = new List<Edge>();
GetRequiredKeys(node);
return leaves.ToArray();

Expand All @@ -257,12 +298,12 @@ void GetRequiredKeys(Node c)
if (state.Visited.Contains(c))
return;

if (c.Kind == NodeKind.Key)
leaves.Add(c);

foreach (var r in c.Requires.Select(x => x.Node))
foreach (var r in c.Requires)
{
GetRequiredKeys(r);
if (r.Node.Kind == NodeKind.Key)
{
leaves.Add(r);
}
}
}
}
Expand Down
Loading

0 comments on commit ac6c68b

Please sign in to comment.