From bb4e79ddda09251ebbc779f0cb483630d13101eb Mon Sep 17 00:00:00 2001 From: Richard Fine Date: Tue, 1 Jan 2019 14:38:29 +0000 Subject: [PATCH] Rework tests to use ride entrances as path targets It turns out that trying to just give a peep a pathfinding goal and then let them loose doesn't work, because every time they reach a junction, the pathfinder has them walk 'aimlessly' instead of pursuing their target. That's why we were seeing some very large step counts in previous tests - they were (eventually) walking onto the target square, but only after lots of wandering around in circles. This commit reworks the test data to contain an actual ride for each test scenario, where the peep will path to the tile in front of the ride entrance. A nice side benefit of this is that the ride names must match the test names, so you can now tell from looking at the rides in the test data which one is used for which test instance. The 'yellow marker' tiles for goal positions are also removed here, as we're deriving goal positions from the ride entrances instead. --- test/tests/Pathfinding.cpp | 117 +++++++++++------- .../testdata/parks/pathfinding-tests.sv6 | Bin 286574 -> 294792 bytes 2 files changed, 69 insertions(+), 48 deletions(-) diff --git a/test/tests/Pathfinding.cpp b/test/tests/Pathfinding.cpp index 1f11a9d9cb..40be195863 100644 --- a/test/tests/Pathfinding.cpp +++ b/test/tests/Pathfinding.cpp @@ -11,6 +11,7 @@ #include #include #include +#include "openrct2/ride/Station.h" using namespace OpenRCT2; @@ -50,7 +51,22 @@ public: } protected: - static bool FindPath(TileCoordsXYZ* pos, const TileCoordsXYZ& goal, int expectedSteps) + + static Ride* FindRideByName(const char* name, int32_t* outRideIndex) + { + Ride* ride; + FOR_ALL_RIDES ((*outRideIndex), ride) + { + char thisName[256]; + format_string(thisName, sizeof(thisName), ride->name, &ride->name_arguments); + if (!_strnicmp(thisName, name, sizeof(thisName))) + return ride; + } + + return nullptr; + } + + static bool FindPath(TileCoordsXYZ* pos, const TileCoordsXYZ& goal, int expectedSteps, int targetRideID) { // Our start position is in tile coordinates, but we need to give the peep spawn // position in actual world coords (32 units per tile X/Y, 8 per Z level). @@ -61,6 +77,12 @@ protected: // use here peep->outside_of_park = 0; + // An earlier iteration of this code just gave peeps a target position to walk to, but it turns out + // that with no actual ride to head towards, when a peep reaches a junction they use the 'aimless' + // pathfinder instead of pursuing their original pathfinding target. So, we always need to give them + // an actual ride to walk to the entrance of. + peep->guest_heading_to_ride_id = targetRideID; + // Pick the direction the peep should initially move in, given the goal position. // This will also store the goal position and initialize pathfinding data for the peep. gPeepPathFindGoalPosition = goal; @@ -112,12 +134,16 @@ protected: static ::testing::AssertionResult AssertIsStartPosition(const char*, const TileCoordsXYZ& location) { - return AssertPositionIsSetUp("Start", 11u, location); - } + const uint32_t expectedSurfaceStyle = 11u; + const uint32_t style = map_get_surface_element_at(location.x, location.y)->AsSurface()->GetSurfaceStyle(); - static ::testing::AssertionResult AssertIsGoalPosition(const char*, const TileCoordsXYZ& location) - { - return AssertPositionIsSetUp("Goal", 9u, location); + if (style != expectedSurfaceStyle) + return ::testing::AssertionFailure() + << "Start location " << location << " should have surface style " << expectedSurfaceStyle + << " but actually has style " << style + << ". Either the test map is not set up correctly, or you got the coordinates wrong."; + + return ::testing::AssertionSuccess(); } static ::testing::AssertionResult AssertIsNotForbiddenPosition(const char*, const TileCoordsXYZ& location) @@ -135,20 +161,6 @@ protected: } private: - static ::testing::AssertionResult AssertPositionIsSetUp( - const char* positionKind, uint32_t expectedSurfaceStyle, const TileCoordsXYZ& location) - { - const uint32_t style = map_get_surface_element_at(location.x, location.y)->AsSurface()->GetSurfaceStyle(); - - if (style != expectedSurfaceStyle) - return ::testing::AssertionFailure() - << positionKind << " location " << location << " should have surface style " << expectedSurfaceStyle - << " but actually has style " << style - << ". Either the test map is not set up correctly, or you got the coordinates wrong."; - - return ::testing::AssertionSuccess(); - } - static std::shared_ptr _context; }; @@ -158,22 +170,15 @@ struct SimplePathfindingScenario { const char* name; TileCoordsXYZ start; - TileCoordsXYZ goal; uint32_t steps; - SimplePathfindingScenario(const char* _name, const TileCoordsXYZ& _start, const TileCoordsXYZ& _goal, int _steps) + SimplePathfindingScenario(const char* _name, const TileCoordsXYZ& _start, int _steps) : name(_name) , start(_start) - , goal(_goal) , steps(_steps) { } - friend std::ostream& operator<<(std::ostream& os, const SimplePathfindingScenario& scenario) - { - return os << scenario.start << " => " << scenario.goal; - } - static std::string ToName(const ::testing::TestParamInfo& param_info) { return param_info.param.name; @@ -189,13 +194,21 @@ TEST_P(SimplePathfindingTest, CanFindPathFromStartToGoal) const SimplePathfindingScenario& scenario = GetParam(); ASSERT_PRED_FORMAT1(AssertIsStartPosition, scenario.start); - ASSERT_PRED_FORMAT1(AssertIsGoalPosition, scenario.goal); - TileCoordsXYZ pos = scenario.start; - const auto succeeded = FindPath(&pos, scenario.goal, scenario.steps) ? ::testing::AssertionSuccess() - : ::testing::AssertionFailure() - << "Failed to find path from " << scenario.start << " to " << scenario.goal << " in " << scenario.steps + int32_t rideIndex; + Ride* ride = FindRideByName(scenario.name, &rideIndex); + ASSERT_NE(ride, nullptr); + + auto entrancePos = ride_get_entrance_location(ride, 0); + TileCoordsXYZ goal = TileCoordsXYZ( + entrancePos.x - TileDirectionDelta[entrancePos.direction].x, + entrancePos.y - TileDirectionDelta[entrancePos.direction].y, + entrancePos.z); + + const auto succeeded = FindPath(&pos, goal, scenario.steps, rideIndex) ? ::testing::AssertionSuccess() + : ::testing::AssertionFailure() + << "Failed to find path from " << scenario.start << " to " << goal << " in " << scenario.steps << " steps; reached " << pos << " before giving up."; EXPECT_TRUE(succeeded); @@ -204,15 +217,15 @@ TEST_P(SimplePathfindingTest, CanFindPathFromStartToGoal) INSTANTIATE_TEST_CASE_P( ForScenario, SimplePathfindingTest, ::testing::Values( - SimplePathfindingScenario("StraightFlat", { 2, 19, 14 }, { 4, 19, 14 }, 24), - SimplePathfindingScenario("SBend", { 2, 17, 14 }, { 4, 16, 14 }, 39), - SimplePathfindingScenario("UBend", { 2, 14, 14 }, { 2, 12, 14 }, 88), - SimplePathfindingScenario("CBend", { 2, 10, 14 }, { 2, 7, 14 }, 133), - SimplePathfindingScenario("TwoEqualRoutes", { 6, 18, 14 }, { 10, 18, 14 }, 819), - SimplePathfindingScenario("TwoUnequalRoutes", { 6, 14, 14 }, { 10, 14, 14 }, 15643), - SimplePathfindingScenario("StraightUpBridge", { 2, 4, 14 }, { 4, 4, 16 }, 24), - SimplePathfindingScenario("StraightUpSlope", { 4, 1, 14 }, { 6, 1, 16 }, 24), - SimplePathfindingScenario("SelfCrossingPath", { 6, 5, 14 }, { 8, 5, 14 }, 213)), + SimplePathfindingScenario("StraightFlat", { 19, 15, 14 }, 24), + SimplePathfindingScenario("SBend", { 15, 12, 14 }, 88), + SimplePathfindingScenario("UBend", { 17, 9, 14 }, 86), + SimplePathfindingScenario("CBend", { 14, 5, 14 }, 164), + SimplePathfindingScenario("TwoEqualRoutes", { 9, 13, 14 }, 87), + SimplePathfindingScenario("TwoUnequalRoutes", { 3, 13, 14 }, 87), + SimplePathfindingScenario("StraightUpBridge", { 12, 15, 14 }, 24), + SimplePathfindingScenario("StraightUpSlope", { 14, 15, 14 }, 24), + SimplePathfindingScenario("SelfCrossingPath", { 6, 5, 14 }, 213)), SimplePathfindingScenario::ToName); class ImpossiblePathfindingTest : public PathfindingTestBase, public ::testing::WithParamInterface @@ -223,17 +236,25 @@ TEST_P(ImpossiblePathfindingTest, CannotFindPathFromStartToGoal) { const SimplePathfindingScenario& scenario = GetParam(); TileCoordsXYZ pos = scenario.start; - ASSERT_PRED_FORMAT1(AssertIsStartPosition, scenario.start); - ASSERT_PRED_FORMAT1(AssertIsGoalPosition, scenario.goal); - EXPECT_FALSE(FindPath(&pos, scenario.goal, 10000)); + int32_t rideIndex; + Ride* ride = FindRideByName(scenario.name, &rideIndex); + ASSERT_NE(ride, nullptr); + + auto entrancePos = ride_get_entrance_location(ride, 0); + TileCoordsXYZ goal = TileCoordsXYZ( + entrancePos.x + TileDirectionDelta[entrancePos.direction].x, + entrancePos.y + TileDirectionDelta[entrancePos.direction].y, + entrancePos.z); + + EXPECT_FALSE(FindPath(&pos, goal, 10000, rideIndex)); } INSTANTIATE_TEST_CASE_P( ForScenario, ImpossiblePathfindingTest, ::testing::Values( - SimplePathfindingScenario("PathWithGap", { 6, 9, 14 }, { 10, 9, 14 }, 10000), - SimplePathfindingScenario("PathWithFences", { 6, 7, 14 }, { 10, 7, 14 }, 10000), - SimplePathfindingScenario("PathWithCliff", { 10, 5, 14 }, { 12, 5, 14 }, 10000)), + SimplePathfindingScenario("PathWithGap", { 1, 6, 14 }, 10000), + SimplePathfindingScenario("PathWithFences", { 11, 6, 14 }, 10000), + SimplePathfindingScenario("PathWithCliff", { 7, 17, 14 }, 10000)), SimplePathfindingScenario::ToName); diff --git a/test/tests/testdata/parks/pathfinding-tests.sv6 b/test/tests/testdata/parks/pathfinding-tests.sv6 index 41c0ab0345b313169b02cfed49083c16bf473635..2fbb02269206d081926c92f95071d01da12d7f0b 100644 GIT binary patch delta 13327 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 s;KqXg1som!GaO*1yrli50On8)DgXcg