diff --git a/tests/testRail.cpp b/tests/testRail.cpp new file mode 100644 index 0000000..a08f274 --- /dev/null +++ b/tests/testRail.cpp @@ -0,0 +1,205 @@ +#include "testHarness.h" +#include "vf/Rail.h" + +using namespace vf; + +namespace { + +void placeRail(World& world, int x, int y, int z, Block kind = Block::Rail) { + world.setBlock(x, y, z, kind); +} + +} // namespace + +VF_TEST(straightRailInferredFromOppositeNeighbours) { + World world; + placeRail(world, 0, 0, 0); + placeRail(world, 0, 0, 1); + placeRail(world, 0, 0, -1); + CHECK(inferRailShape(world, {0, 0, 0}) == RailShape::NorthSouth); + + World ew; + placeRail(ew, 0, 0, 0); + placeRail(ew, 1, 0, 0); + placeRail(ew, -1, 0, 0); + CHECK(inferRailShape(ew, {0, 0, 0}) == RailShape::EastWest); +} + +VF_TEST(loneRailDefaultsToNorthSouth) { + World world; + placeRail(world, 0, 0, 0); + CHECK(inferRailShape(world, {0, 0, 0}) == RailShape::NorthSouth); +} + +VF_TEST(plainRailCurvesBetweenPerpendicularNeighbours) { + World world; + placeRail(world, 0, 0, 0); + placeRail(world, 0, 0, 1); // south + placeRail(world, 1, 0, 0); // east + CHECK(inferRailShape(world, {0, 0, 0}) == RailShape::CurveSouthEast); +} + +VF_TEST(poweredRailNeverCurves) { + World world; + placeRail(world, 0, 0, 0, Block::PoweredRail); + placeRail(world, 0, 0, 1); // south + placeRail(world, 1, 0, 0); // east + RailShape shape = inferRailShape(world, {0, 0, 0}); + CHECK(shape == RailShape::NorthSouth || shape == RailShape::EastWest); +} + +VF_TEST(ascendingShapeInferredFromRaisedNeighbour) { + World world; + placeRail(world, 0, 0, 0); + placeRail(world, -1, 0, 0); // west, flat + placeRail(world, 1, 1, 0); // east, raised + CHECK(inferRailShape(world, {0, 0, 0}) == RailShape::AscendEast); +} + +VF_TEST(advanceFollowsStraightTrack) { + World world; + for (int x = 0; x <= 3; ++x) placeRail(world, x, 0, 0); + RailStep step = advanceOnRail(world, {1, 0, 0}, RailDir::East); + CHECK(step.valid); + CHECK(step.nextPos == Vec3i(2, 0, 0)); + CHECK(step.exitDir == RailDir::East); +} + +VF_TEST(advanceTurnsThroughCurve) { + World world; + placeRail(world, 0, 0, 0); // curve SE + placeRail(world, 0, 0, 1); // south neighbour + placeRail(world, 1, 0, 0); // east neighbour + RailStep step = advanceOnRail(world, {0, 0, 0}, RailDir::North); + CHECK(step.valid); + CHECK(step.exitDir == RailDir::East); + CHECK(step.nextPos == Vec3i(1, 0, 0)); +} + +VF_TEST(advanceClimbsAndDescendsSlopes) { + World world; + placeRail(world, 0, 0, 0); + placeRail(world, -1, 0, 0); + placeRail(world, 1, 1, 0); + + RailStep up = advanceOnRail(world, {0, 0, 0}, RailDir::East); + CHECK(up.valid); + CHECK(up.nextPos == Vec3i(1, 1, 0)); + + RailStep down = advanceOnRail(world, {1, 1, 0}, RailDir::West); + CHECK(down.valid); + CHECK(down.nextPos == Vec3i(0, 0, 0)); +} + +VF_TEST(advanceDerailsAtTrackEnd) { + World world; + placeRail(world, 0, 0, 0); + placeRail(world, 1, 0, 0); + RailStep step = advanceOnRail(world, {1, 0, 0}, RailDir::East); + CHECK(!step.valid); +} + +VF_TEST(switchedJunctionTakesTheTurn) { + World world; + placeRail(world, 0, 0, 0); + placeRail(world, 0, 0, -1); // north + placeRail(world, 0, 0, 1); // south + placeRail(world, 1, 0, 0); // east branch + CHECK(inferRailShape(world, {0, 0, 0}, false) == RailShape::NorthSouth); + CHECK(inferRailShape(world, {0, 0, 0}, true) == RailShape::CurveSouthEast); +} + +VF_TEST(poweredRailPropagatesWithReachFalloff) { + World world; + std::vector rails; + for (int x = 0; x <= 12; ++x) { + world.setBlock(x, 0, 0, Block::PoweredRail); + rails.push_back({x, 0, 0}); + } + + auto active = activePoweredRails(world, rails, [](const Vec3i& p) { + return p == Vec3i(0, 0, 0); + }); + + CHECK(active.count({0, 0, 0}) == 1); + CHECK(active.count({8, 0, 0}) == 1); + CHECK(active.count({9, 0, 0}) == 0); + CHECK(active.count({12, 0, 0}) == 0); +} + +VF_TEST(poweredRailPropagatesBothDirectionsFromMiddleSource) { + World world; + std::vector rails; + for (int x = -10; x <= 10; ++x) { + world.setBlock(x, 0, 0, Block::PoweredRail); + rails.push_back({x, 0, 0}); + } + + auto active = activePoweredRails(world, rails, [](const Vec3i& p) { + return p == Vec3i(0, 0, 0); + }); + + CHECK(active.count({8, 0, 0}) == 1); + CHECK(active.count({-8, 0, 0}) == 1); + CHECK(active.count({9, 0, 0}) == 0); + CHECK(active.count({-9, 0, 0}) == 0); +} + +VF_TEST(unpoweredRailNetworkStaysInactive) { + World world; + std::vector rails; + for (int x = 0; x <= 5; ++x) { + world.setBlock(x, 0, 0, Block::PoweredRail); + rails.push_back({x, 0, 0}); + } + auto active = activePoweredRails(world, rails, [](const Vec3i&) { return false; }); + CHECK(active.empty()); +} + +VF_TEST(railGraphMeasuresDistanceAlongLine) { + World world; + std::vector cells; + for (int x = 0; x <= 5; ++x) { + placeRail(world, x, 0, 0); + cells.push_back({x, 0, 0}); + } + RailGraph graph; + graph.build(world, cells); + + CHECK_EQ(static_cast(graph.size()), 6); + CHECK(graph.contains({3, 0, 0})); + CHECK(!graph.contains({100, 0, 0})); + CHECK_EQ(graph.distance({0, 0, 0}, {5, 0, 0}), 5); + CHECK_EQ(graph.distance({0, 0, 0}, {100, 0, 0}), -1); + CHECK_EQ(static_cast(graph.neighbours({0, 0, 0}).size()), 1); + CHECK_EQ(static_cast(graph.neighbours({3, 0, 0}).size()), 2); +} + +VF_TEST(railGraphTracesAClosedLoop) { + World world; + std::vector cells = {{0, 0, 0}, {1, 0, 0}, {1, 0, 1}, {0, 0, 1}}; + for (const Vec3i& c : cells) placeRail(world, c.x, c.y, c.z); + + RailGraph graph; + graph.build(world, cells); + + std::vector path = graph.trace(world, {0, 0, 0}, RailDir::North, 20); + CHECK_EQ(static_cast(path.size()), 4); + CHECK_EQ(graph.distance({0, 0, 0}, {1, 0, 1}), 2); + CHECK_EQ(static_cast(graph.neighbours({0, 0, 0}).size()), 2); +} + +VF_TEST(railGraphTraceStopsAtDeadEnd) { + World world; + std::vector cells; + for (int x = 0; x <= 3; ++x) { + placeRail(world, x, 0, 0); + cells.push_back({x, 0, 0}); + } + RailGraph graph; + graph.build(world, cells); + + std::vector path = graph.trace(world, {0, 0, 0}, RailDir::East, 20); + CHECK_EQ(static_cast(path.size()), 4); + CHECK(path.back() == Vec3i(3, 0, 0)); +}