aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/src/Godosa.Core.Tests/Runs/RouteLegsTests.cs
blob: 31295ab1721bb2546ddb582e7b32f3c12d8ce7cb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
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<long, long, bool> Grid(params string[] rows) => (x, y) => rows[y][(int)x] is '#' or 's';
    private static Func<long, long, bool> 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)!);
    }
}