From 39066900773e7857faf2d02e7ed51b71d219d97d Mon Sep 17 00:00:00 2001 From: godosa Date: Tue, 6 Oct 2026 23:39:36 +0200 Subject: godosa-engine: initial public history --- src/Godosa.Core.Tests/Runs/RouteLegsTests.cs | 116 +++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 src/Godosa.Core.Tests/Runs/RouteLegsTests.cs (limited to 'src/Godosa.Core.Tests/Runs/RouteLegsTests.cs') diff --git a/src/Godosa.Core.Tests/Runs/RouteLegsTests.cs b/src/Godosa.Core.Tests/Runs/RouteLegsTests.cs new file mode 100644 index 0000000..31295ab --- /dev/null +++ b/src/Godosa.Core.Tests/Runs/RouteLegsTests.cs @@ -0,0 +1,116 @@ +using Godosa.Core.Runs; +using Xunit; + +namespace Godosa.Core.Tests.Runs; + +public class RouteLegsTests +{ + // 5×5 grid, rows top to bottom; '#' blocked, 's' soft (sealed by the game's scripts, opened later). + private static Func Grid(params string[] rows) => (x, y) => rows[y][(int)x] is '#' or 's'; + private static Func Soft(params string[] rows) => (x, y) => rows[y][(int)x] == 's'; + + [Fact] + public void Path_GoesRoundAWall_StartExcluded() + { + var blocked = Grid( + ".#...", + ".#...", + ".#...", + ".....", + "....."); + // From 0,0 the only way past the wall is under it: down the left column to row 3, then up-right diagonally. + var path = RouteLegs.Path((0, 0), (2, 0), 5, 5, blocked); + Assert.NotNull(path); + Assert.Equal((2, 0), path![^1]); + Assert.DoesNotContain((0L, 0L), path); + Assert.Equal(6, path.Count); // 0,1 0,2 1,3 2,2 2,1 2,0 + Assert.All(path, c => Assert.False(blocked(c.X, c.Y))); + } + + [Fact] + public void Path_NoWay_Null() + { + var blocked = Grid( + "..#..", + "..#..", + "..#..", + "..#..", + "..#.."); + Assert.Null(RouteLegs.Path((0, 0), (4, 0), 5, 5, blocked)); + } + + // The game's router takes a leg only within 2 cells (Chebyshev): from each stop the farthest accepted path cell. + [Fact] + public void Legs_FarthestAcceptedStopEachTime() + { + var path = new List<(long X, long Y)> { (1, 0), (2, 0), (3, 0), (4, 0), (5, 0), (6, 0), (7, 0) }; + var legs = RouteLegs.Legs((0, 0), path, (a, b) => Math.Max(Math.Abs(a.X - b.X), Math.Abs(a.Y - b.Y)) <= 2); + Assert.Equal([(2L, 0L), (4L, 0L), (6L, 0L), (7L, 0L)], legs); + } + + [Fact] + public void Legs_RouterAcceptsNothing_NullWithTheStuckStop() + { + var path = new List<(long X, long Y)> { (1, 0), (2, 0) }; + Assert.Null(RouteLegs.Legs((0, 0), path, (a, b) => b == (1, 0), out var stuck)); + Assert.Equal((1, 0), stuck); + } + + // Closed off only by a sealed (soft) cell: the seals a way would cross once open, start to goal. + [Fact] + public void Seals_ListedInOrder() + { + string[] rows = + [ + "..s..", + "#####", + ".....", + ".....", + ".....", + ]; + Assert.Null(RouteLegs.Path((0, 0), (4, 0), 5, 5, Grid(rows))); + Assert.Equal([(2L, 0L)], RouteLegs.Seals((0, 0), (4, 0), 5, 5, Grid(rows), Soft(rows))); + Assert.Empty(RouteLegs.Seals((0, 0), (4, 4), 5, 5, Grid(rows), Soft(rows))); // walled, not sealed + } + + [Fact] + public void Seals_CappedAndNoneWhenOpenAlready() + { + string[] twice = + [ + ".s.s.", + "#####", + ".....", + ".....", + ".....", + ]; + Assert.Equal([(1L, 0L), (3L, 0L)], RouteLegs.Seals((0, 0), (4, 0), 5, 5, Grid(twice), Soft(twice))); + Assert.Equal([(1L, 0L)], RouteLegs.Seals((0, 0), (4, 0), 5, 5, Grid(twice), Soft(twice), max: 1)); + // Open the long way round (row 2): a seal on the short way is no reason to wait. + string[] detour = + [ + "..s..", + "#.#.#", + ".....", + ".....", + ".....", + ]; + Assert.Empty(RouteLegs.Seals((0, 0), (4, 0), 5, 5, Grid(detour), Soft(detour))); + } + + // Hops: walk to a portal, click it, arrive at its exit; BFS over portal exits. + [Fact] + public void Hops_ThroughTwoPortals() + { + // Three rooms on a line, x 0–9 / 10–19 / 20–29 (walking stays inside a room); portal A in room 1 at 5 → 12, B in + // room 2 at 18 → 25; a dead-end portal C in room 1 at 2 → 40. + static int Room(long x) => (int)(x / 10); + bool Reaches(long from, long to) => Room(from) == Room(to); + var portals = new[] { ("A", 5L, 12L), ("B", 18L, 25L), ("C", 2L, 40L) }; + var hops = RouteLegs.Hops(1L, 27L, portals, p => p.Item2, p => p.Item3, Reaches); + Assert.NotNull(hops); + Assert.Equal(["A", "B"], hops!.Select(p => p.Item1)); + Assert.Null(RouteLegs.Hops(1L, 35L, portals, p => p.Item2, p => p.Item3, Reaches)); + Assert.Empty(RouteLegs.Hops(1L, 7L, portals, p => p.Item2, p => p.Item3, Reaches)!); + } +} -- cgit