diff --git a/src/NosCore.PathFinder/NosCore.PathFinder.csproj b/src/NosCore.PathFinder/NosCore.PathFinder.csproj index 76edcc2..3c75461 100644 --- a/src/NosCore.PathFinder/NosCore.PathFinder.csproj +++ b/src/NosCore.PathFinder/NosCore.PathFinder.csproj @@ -12,7 +12,7 @@ https://github.com/NosCoreIO/NosCore.PathFinder.git nostale, noscore, nostale private server source, nostale emulator - 2.1.0 + 2.1.1 false NosCore's PathFinder diff --git a/src/NosCore.PathFinder/Pathfinder/GoalBasedPathfinder.cs b/src/NosCore.PathFinder/Pathfinder/GoalBasedPathfinder.cs index 1204b70..7877993 100644 --- a/src/NosCore.PathFinder/Pathfinder/GoalBasedPathfinder.cs +++ b/src/NosCore.PathFinder/Pathfinder/GoalBasedPathfinder.cs @@ -27,7 +27,7 @@ public GoalBasedPathfinder(IMapGrid mapGrid, IHeuristic heuristic) public GoalBasedPathfinder(IMapGrid mapGrid, IHeuristic heuristic, BrushFire brushfire) : this(mapGrid, heuristic) { - BrushFirecache.Set(brushfire.Origin, brushfire, DateTimeOffset.Now.AddSeconds(10)); + BrushFirecache.Set((mapGrid, brushfire.Origin), brushfire, DateTimeOffset.Now.AddSeconds(10)); } public IEnumerable<(short X, short Y)> FindPath((short X, short Y) start, (short X, short Y) end) @@ -39,10 +39,10 @@ public GoalBasedPathfinder(IMapGrid mapGrid, IHeuristic heuristic, BrushFire bru return list; } - if (!BrushFirecache.TryGetValue(end, out BrushFire brushFire)) + if (!BrushFirecache.TryGetValue((_mapGrid, end), out BrushFire brushFire)) { brushFire = _mapGrid.LoadBrushFire(end, _heuristic); - BrushFirecache.Set(end, brushFire, DateTimeOffset.Now.AddSeconds(10)); + BrushFirecache.Set((_mapGrid, end), brushFire, DateTimeOffset.Now.AddSeconds(10)); } if (!brushFire.Distances.ContainsKey(start)) diff --git a/src/NosCore.PathFinder/Pathfinder/JumpPointSearchPathfinder.cs b/src/NosCore.PathFinder/Pathfinder/JumpPointSearchPathfinder.cs index db7810a..f3c5e77 100644 --- a/src/NosCore.PathFinder/Pathfinder/JumpPointSearchPathfinder.cs +++ b/src/NosCore.PathFinder/Pathfinder/JumpPointSearchPathfinder.cs @@ -59,7 +59,8 @@ public JumpPointSearchPathfinder(IMapGrid mapGrid, IHeuristic heuristic) public IEnumerable<(short X, short Y)> FindPath((short X, short Y) start, (short X, short Y) end) { - if (Cache.TryGetValue((start, end), out IEnumerable<(short X, short Y)>? cachedList)) + // Keyed by grid too: the cache is shared by every pathfinder instance, one per map. + if (Cache.TryGetValue((_mapGrid, start, end), out IEnumerable<(short X, short Y)>? cachedList)) { return cachedList ?? Array.Empty<(short X, short Y)>(); } @@ -67,7 +68,7 @@ public JumpPointSearchPathfinder(IMapGrid mapGrid, IHeuristic heuristic) var path = BuildFullPath(GetJumpList(start, end).ToList()); for (var i = 0; i < path.Count; i++) { - Cache.Set((path[i], end), path.Skip(i), DateTimeOffset.Now.AddSeconds(10)); + Cache.Set((_mapGrid, path[i], end), path.Skip(i + 1), DateTimeOffset.Now.AddSeconds(10)); } return path; } diff --git a/test/NosCore.PathFinder.Tests/JumpPointSearchPathfinderTests.cs b/test/NosCore.PathFinder.Tests/JumpPointSearchPathfinderTests.cs index 087c2dc..884cbc5 100644 --- a/test/NosCore.PathFinder.Tests/JumpPointSearchPathfinderTests.cs +++ b/test/NosCore.PathFinder.Tests/JumpPointSearchPathfinderTests.cs @@ -53,5 +53,51 @@ public void Test_JumpPointSearchPathfinder() TestHelper.VerifyFile("jump-point-search-pathfinder.png", image, listPixel, "Jump Point Search Pathfinder (break at walls)"); } + + [TestMethod] + public void ACachedPathStartsOnTheFirstStepLikeAFreshOne() + { + var pathfinder = new JumpPointSearchPathfinder(Walled(new[] + { + new byte[] { 0, 0, 0, 0, 0, 0 }, + }), new OctileDistanceHeuristic()); + var fresh = pathfinder.FindPath((1, 1), (6, 1)).ToList(); + + var cached = pathfinder.FindPath(fresh[0], (6, 1)).ToList(); + + CollectionAssert.AreEqual(fresh.Skip(1).ToList(), cached); + } + + [TestMethod] + public void TwoMapsDoNotShareCachedPaths() + { + var open = new JumpPointSearchPathfinder(Walled(new[] + { + new byte[] { 0, 0, 0, 0, 0 }, + new byte[] { 0, 0, 0, 0, 0 }, + }), new OctileDistanceHeuristic()); + var blocked = new JumpPointSearchPathfinder(Walled(new[] + { + new byte[] { 0, 0, 1, 0, 0 }, + new byte[] { 0, 0, 0, 0, 0 }, + }), new OctileDistanceHeuristic()); + + var straight = open.FindPath((1, 1), (5, 1)).ToList(); + var around = blocked.FindPath(straight[0], (5, 1)).ToList(); + + Assert.Contains(((short)3, (short)1), straight); + Assert.DoesNotContain(((short)3, (short)1), around, "the blocked map must not reuse the open map's path through its wall"); + Assert.AreEqual(((short)5, (short)1), around.Last()); + } + + private static TestMap Walled(byte[][] inside) + { + var width = inside[0].Length + 2; + var wall = Enumerable.Repeat((byte)1, width).ToArray(); + return new TestMap(new[] { wall } + .Concat(inside.Select(row => new byte[] { 1 }.Concat(row).Append((byte)1).ToArray())) + .Append(wall) + .ToArray()); + } } }