diff --git a/tests/testPathfind.cpp b/tests/testPathfind.cpp new file mode 100644 index 0000000..064dcbf --- /dev/null +++ b/tests/testPathfind.cpp @@ -0,0 +1,102 @@ +#include "testHarness.h" +#include "vf/Pathfind.h" + +using namespace vf; + +namespace { + +void floor(World& world, int y, int x0, int z0, int x1, int z1) { + for (int x = x0; x <= x1; ++x) + for (int z = z0; z <= z1; ++z) + world.setBlock(x, y, z, Block::Stone); +} + +void pillar(World& world, int x, int z, int y0, int y1) { + for (int y = y0; y <= y1; ++y) world.setBlock(x, y, z, Block::Stone); +} + +bool contiguous(const std::vector& path) { + for (std::size_t i = 1; i < path.size(); ++i) { + int dx = std::abs(path[i].x - path[i - 1].x); + int dz = std::abs(path[i].z - path[i - 1].z); + if (dx + dz != 1) return false; // exactly one horizontal step apart + } + return true; +} + +} // namespace + +VF_TEST(pathStraightLineAcrossOpenFloor) { + World world; + floor(world, 0, 0, 0, 8, 8); + std::vector path = findPath(world, {0, 1, 0}, {5, 1, 0}); + CHECK_EQ(path.size(), static_cast(6)); // 5 steps + start + CHECK(path.front() == Vec3i(0, 1, 0)); + CHECK(path.back() == Vec3i(5, 1, 0)); + CHECK(contiguous(path)); +} + +VF_TEST(pathRoutesAroundWallThroughGap) { + World world; + floor(world, 0, 0, 0, 8, 8); + for (int z = 0; z <= 6; ++z) pillar(world, 4, z, 1, 2); // wall x=4, gap at z=7,8 + std::vector path = findPath(world, {1, 1, 1}, {7, 1, 1}); + CHECK(!path.empty()); + CHECK(path.back() == Vec3i(7, 1, 1)); + CHECK(contiguous(path)); + CHECK(path.size() > 7); // longer than the blocked straight line + for (const Vec3i& cell : path) + CHECK(!world.solidAt(cell.x, cell.y, cell.z)); // never inside the wall +} + +VF_TEST(pathReturnsEmptyWhenGoalSealed) { + World world; + floor(world, 0, 0, 0, 8, 8); + pillar(world, 4, 5, 1, 2); + pillar(world, 6, 5, 1, 2); + pillar(world, 5, 4, 1, 2); + pillar(world, 5, 6, 1, 2); // seal all four neighbours of {5,1,5} + std::vector path = findPath(world, {1, 1, 1}, {5, 1, 5}); + CHECK(path.empty()); +} + +VF_TEST(pathFoundWhenSealBreached) { + World world; + floor(world, 0, 0, 0, 8, 8); + pillar(world, 4, 5, 1, 2); + pillar(world, 6, 5, 1, 2); + pillar(world, 5, 4, 1, 2); + // {5,1,6} left open -> reachable + std::vector path = findPath(world, {1, 1, 1}, {5, 1, 5}); + CHECK(!path.empty()); + CHECK(path.back() == Vec3i(5, 1, 5)); +} + +VF_TEST(pathClimbsSingleStep) { + World world; + floor(world, 0, 0, 0, 8, 8); + floor(world, 1, 4, 0, 8, 8); // raised terrace from x=4 + std::vector path = findPath(world, {0, 1, 0}, {6, 2, 0}); + CHECK(!path.empty()); + CHECK(path.front() == Vec3i(0, 1, 0)); + CHECK(path.back() == Vec3i(6, 2, 0)); + CHECK(contiguous(path)); +} + +VF_TEST(pathTwoHighWallIsImpassable) { + World world; + floor(world, 0, 0, 0, 8, 8); + for (int z = 0; z <= 8; ++z) pillar(world, 4, z, 1, 3); // full 3-high wall, no gap + std::vector path = findPath(world, {1, 1, 1}, {7, 1, 1}); + CHECK(path.empty()); +} + +VF_TEST(pathIsDeterministic) { + World world; + floor(world, 0, 0, 0, 8, 8); + for (int z = 0; z <= 6; ++z) pillar(world, 4, z, 1, 2); + std::vector a = findPath(world, {1, 1, 1}, {7, 1, 1}); + std::vector b = findPath(world, {1, 1, 1}, {7, 1, 1}); + CHECK(a.size() == b.size()); + for (std::size_t i = 0; i < a.size(); ++i) CHECK(a[i] == b[i]); +}