| 1 | /* Copyright (C) 2023 Wildfire Games.
|
|---|
| 2 | * This file is part of 0 A.D.
|
|---|
| 3 | *
|
|---|
| 4 | * 0 A.D. is free software: you can redistribute it and/or modify
|
|---|
| 5 | * it under the terms of the GNU General Public License as published by
|
|---|
| 6 | * the Free Software Foundation, either version 2 of the License, or
|
|---|
| 7 | * (at your option) any later version.
|
|---|
| 8 | *
|
|---|
| 9 | * 0 A.D. is distributed in the hope that it will be useful,
|
|---|
| 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|---|
| 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|---|
| 12 | * GNU General Public License for more details.
|
|---|
| 13 | *
|
|---|
| 14 | * You should have received a copy of the GNU General Public License
|
|---|
| 15 | * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
|---|
| 16 | */
|
|---|
| 17 |
|
|---|
| 18 | #include "simulation2/system/ComponentTest.h"
|
|---|
| 19 |
|
|---|
| 20 | #include "maths/Matrix3D.h"
|
|---|
| 21 | #include "ps/CStr.h"
|
|---|
| 22 | #include "graphics/Terrain.h"
|
|---|
| 23 | #include "graphics/TerritoryBoundary.h"
|
|---|
| 24 | #include "simulation2/helpers/Grid.h"
|
|---|
| 25 | #include "simulation2/components/ICmpTerritoryManager.h"
|
|---|
| 26 | #include "simulation2/components/ICmpPlayerManager.h"
|
|---|
| 27 | #include "simulation2/components/ICmpTerritoryInfluence.h"
|
|---|
| 28 | #include "simulation2/components/ICmpOwnership.h"
|
|---|
| 29 |
|
|---|
| 30 | class MockPathfinderTerrMan : public ICmpPathfinder
|
|---|
| 31 | {
|
|---|
| 32 | public:
|
|---|
| 33 | DEFAULT_MOCK_COMPONENT()
|
|---|
| 34 |
|
|---|
| 35 | // Test data
|
|---|
| 36 | Grid<NavcellData> m_PassabilityGrid;
|
|---|
| 37 |
|
|---|
| 38 | virtual pass_class_t GetPassabilityClass(const std::string&) const override { return 0; }
|
|---|
| 39 | virtual const Grid<NavcellData>& GetPassabilityGrid() override { return m_PassabilityGrid; }
|
|---|
| 40 |
|
|---|
| 41 | // Irrelevant part of the mock.
|
|---|
| 42 | virtual void GetPassabilityClasses(std::map<std::string, pass_class_t>&) const override {}
|
|---|
| 43 | virtual void GetPassabilityClasses(std::map<std::string, pass_class_t>&, std::map<std::string, pass_class_t>&) const override {}
|
|---|
| 44 | virtual entity_pos_t GetClearance(pass_class_t) const override { return entity_pos_t::FromInt(1); }
|
|---|
| 45 | virtual entity_pos_t GetMaximumClearance() const override { return entity_pos_t::FromInt(1); }
|
|---|
| 46 | virtual const GridUpdateInformation& GetAIPathfinderDirtinessInformation() const override { static GridUpdateInformation gridInfo; return gridInfo; }
|
|---|
| 47 | virtual void FlushAIPathfinderDirtinessInformation() override {}
|
|---|
| 48 | virtual Grid<u16> ComputeShoreGrid(bool = false) override { return Grid<u16> {}; }
|
|---|
| 49 | virtual u32 ComputePathAsync(entity_pos_t, entity_pos_t, const PathGoal&, pass_class_t, entity_id_t) override { return 1; }
|
|---|
| 50 | virtual void ComputePathImmediate(entity_pos_t, entity_pos_t, const PathGoal&, pass_class_t, WaypointPath&) const override {}
|
|---|
| 51 | virtual u32 ComputeShortPathAsync(entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, const PathGoal&, pass_class_t, bool, entity_id_t, entity_id_t) override { return 1; }
|
|---|
| 52 | virtual WaypointPath ComputeShortPathImmediate(const ShortPathRequest&) const override { return WaypointPath(); }
|
|---|
| 53 | virtual void SetDebugPath(entity_pos_t, entity_pos_t, const PathGoal&, pass_class_t) override {}
|
|---|
| 54 | virtual bool IsGoalReachable(entity_pos_t, entity_pos_t, const PathGoal&, pass_class_t) override { return false; }
|
|---|
| 55 | virtual bool CheckMovement(const IObstructionTestFilter&, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, pass_class_t) const override { return false; }
|
|---|
| 56 | virtual ICmpObstruction::EFoundationCheck CheckUnitPlacement(const IObstructionTestFilter&, entity_pos_t, entity_pos_t, entity_pos_t, pass_class_t, bool = false) const override { return ICmpObstruction::FOUNDATION_CHECK_SUCCESS; }
|
|---|
| 57 | virtual ICmpObstruction::EFoundationCheck CheckBuildingPlacement(const IObstructionTestFilter&, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, entity_id_t, pass_class_t) const override { return ICmpObstruction::FOUNDATION_CHECK_SUCCESS; }
|
|---|
| 58 | virtual ICmpObstruction::EFoundationCheck CheckBuildingPlacement(const IObstructionTestFilter&, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, entity_id_t, pass_class_t, bool) const override { return ICmpObstruction::FOUNDATION_CHECK_SUCCESS; }
|
|---|
| 59 | virtual void SetDebugOverlay(bool) override {}
|
|---|
| 60 | virtual void SetHierDebugOverlay(bool) override {}
|
|---|
| 61 | virtual void SendRequestedPaths() override {}
|
|---|
| 62 | virtual void StartProcessingMoves(bool) override {}
|
|---|
| 63 | virtual void UpdateGrid() override {}
|
|---|
| 64 | virtual void GetDebugData(u32&, double&, Grid<u8>&) const override {}
|
|---|
| 65 | virtual void SetAtlasOverlay(bool, pass_class_t = 0) override {}
|
|---|
| 66 | };
|
|---|
| 67 |
|
|---|
| 68 | class MockPlayerMgrTerrMan : public ICmpPlayerManager
|
|---|
| 69 | {
|
|---|
| 70 | public:
|
|---|
| 71 | DEFAULT_MOCK_COMPONENT()
|
|---|
| 72 |
|
|---|
| 73 | int32_t GetNumPlayers() override { return 2; }
|
|---|
| 74 | entity_id_t GetPlayerByID(int32_t id) override { return id + 1; }
|
|---|
| 75 | };
|
|---|
| 76 |
|
|---|
| 77 | class MockTerrInfTerrMan : public ICmpTerritoryInfluence
|
|---|
| 78 | {
|
|---|
| 79 | public:
|
|---|
| 80 | DEFAULT_MOCK_COMPONENT()
|
|---|
| 81 |
|
|---|
| 82 | bool IsRoot() const override { return true; };
|
|---|
| 83 | u16 GetWeight() const override { return 10; };
|
|---|
| 84 | u32 GetRadius() const override { return m_Radius; };
|
|---|
| 85 |
|
|---|
| 86 | u32 m_Radius = 0;
|
|---|
| 87 | };
|
|---|
| 88 |
|
|---|
| 89 | class MockOwnershipTerrMan : public ICmpOwnership
|
|---|
| 90 | {
|
|---|
| 91 | public:
|
|---|
| 92 | DEFAULT_MOCK_COMPONENT()
|
|---|
| 93 |
|
|---|
| 94 | player_id_t GetOwner() const override { return 1; };
|
|---|
| 95 | void SetOwner(player_id_t) override {};
|
|---|
| 96 | void SetOwnerQuiet(player_id_t) override {};
|
|---|
| 97 | };
|
|---|
| 98 |
|
|---|
| 99 | class MockPositionTerrMan : public ICmpPosition
|
|---|
| 100 | {
|
|---|
| 101 | public:
|
|---|
| 102 | DEFAULT_MOCK_COMPONENT()
|
|---|
| 103 |
|
|---|
| 104 | void SetTurretParent(entity_id_t, const CFixedVector3D&) override {}
|
|---|
| 105 | entity_id_t GetTurretParent() const override { return INVALID_ENTITY; }
|
|---|
| 106 | void UpdateTurretPosition() override {}
|
|---|
| 107 | std::set<entity_id_t>* GetTurrets() override { return nullptr; }
|
|---|
| 108 | bool IsInWorld() const override { return true; }
|
|---|
| 109 | void MoveOutOfWorld() override {}
|
|---|
| 110 | void MoveTo(entity_pos_t, entity_pos_t) override {}
|
|---|
| 111 | void MoveAndTurnTo(entity_pos_t, entity_pos_t, entity_angle_t) override {}
|
|---|
| 112 | void JumpTo(entity_pos_t, entity_pos_t) override {}
|
|---|
| 113 | void SetHeightOffset(entity_pos_t) override {}
|
|---|
| 114 | entity_pos_t GetHeightOffset() const override { return entity_pos_t::Zero(); }
|
|---|
| 115 | void SetHeightFixed(entity_pos_t) override {}
|
|---|
| 116 | entity_pos_t GetHeightFixed() const override { return entity_pos_t::Zero(); }
|
|---|
| 117 | entity_pos_t GetHeightAtFixed(entity_pos_t, entity_pos_t) const override { return entity_pos_t::Zero(); }
|
|---|
| 118 | bool IsHeightRelative() const override { return true; }
|
|---|
| 119 | void SetHeightRelative(bool) override {}
|
|---|
| 120 | bool CanFloat() const override { return false; }
|
|---|
| 121 | void SetFloating(bool) override {}
|
|---|
| 122 | void SetActorFloating(bool) override {}
|
|---|
| 123 | void SetConstructionProgress(fixed) override {}
|
|---|
| 124 | CFixedVector3D GetPosition() const override { return m_Pos; }
|
|---|
| 125 | CFixedVector2D GetPosition2D() const override { return CFixedVector2D(m_Pos.X, m_Pos.Z); }
|
|---|
| 126 | CFixedVector3D GetPreviousPosition() const override { return CFixedVector3D(); }
|
|---|
| 127 | CFixedVector2D GetPreviousPosition2D() const override { return CFixedVector2D(); }
|
|---|
| 128 | fixed GetTurnRate() const override { return fixed::Zero(); }
|
|---|
| 129 | void TurnTo(entity_angle_t) override {}
|
|---|
| 130 | void SetYRotation(entity_angle_t) override {}
|
|---|
| 131 | void SetXZRotation(entity_angle_t, entity_angle_t) override {}
|
|---|
| 132 | CFixedVector3D GetRotation() const override { return CFixedVector3D(); }
|
|---|
| 133 | fixed GetDistanceTravelled() const override { return fixed::Zero(); }
|
|---|
| 134 | void GetInterpolatedPosition2D(float, float&, float&, float&) const override {}
|
|---|
| 135 | CMatrix3D GetInterpolatedTransform(float) const override { return CMatrix3D(); }
|
|---|
| 136 |
|
|---|
| 137 | CFixedVector3D m_Pos;
|
|---|
| 138 | };
|
|---|
| 139 |
|
|---|
| 140 |
|
|---|
| 141 | class TestCmpTerritoryManager : public CxxTest::TestSuite
|
|---|
| 142 | {
|
|---|
| 143 | public:
|
|---|
| 144 | void setUp()
|
|---|
| 145 | {
|
|---|
| 146 | CxxTest::setAbortTestOnFail(true);
|
|---|
| 147 | g_VFS = CreateVfs();
|
|---|
| 148 | TS_ASSERT_OK(g_VFS->Mount(L"", DataDir() / "mods" / "_test.sim" / "", VFS_MOUNT_MUST_EXIST));
|
|---|
| 149 | TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir() / "_testcache" / "", 0, VFS_MAX_PRIORITY));
|
|---|
| 150 | CXeromyces::Startup();
|
|---|
| 151 | }
|
|---|
| 152 |
|
|---|
| 153 | void tearDown()
|
|---|
| 154 | {
|
|---|
| 155 | CXeromyces::Terminate();
|
|---|
| 156 | g_VFS.reset();
|
|---|
| 157 | DeleteDirectory(DataDir()/"_testcache");
|
|---|
| 158 | }
|
|---|
| 159 |
|
|---|
| 160 | // Regression test for D5181 / fix for rP27673 issue
|
|---|
| 161 | void test_calculate_territories_uninitialised()
|
|---|
| 162 | {
|
|---|
| 163 | ComponentTestHelper test(*g_ScriptContext);
|
|---|
| 164 | ICmpTerritoryManager* cmp = test.Add<ICmpTerritoryManager>(CID_TerritoryManager, "", SYSTEM_ENTITY);
|
|---|
| 165 |
|
|---|
| 166 | MockPathfinderTerrMan pathfinder;
|
|---|
| 167 | test.AddMock(SYSTEM_ENTITY, IID_Pathfinder, pathfinder);
|
|---|
| 168 |
|
|---|
| 169 | MockPlayerMgrTerrMan playerMan;
|
|---|
| 170 | test.AddMock(SYSTEM_ENTITY, IID_PlayerManager, playerMan);
|
|---|
| 171 |
|
|---|
| 172 | pathfinder.m_PassabilityGrid.resize(ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE * 5, ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE * 5);
|
|---|
| 173 |
|
|---|
| 174 | MockTerrInfTerrMan terrInf;
|
|---|
| 175 | test.AddMock(5, IID_TerritoryInfluence, terrInf);
|
|---|
| 176 | MockOwnershipTerrMan ownership;
|
|---|
| 177 | test.AddMock(5, IID_Ownership, ownership);
|
|---|
| 178 | MockPositionTerrMan position;
|
|---|
| 179 | test.AddMock(5, IID_Position, position);
|
|---|
| 180 |
|
|---|
| 181 | position.m_Pos = CFixedVector3D(
|
|---|
| 182 | entity_pos_t::FromInt(ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE * 5 + ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE / 2),
|
|---|
| 183 | entity_pos_t::FromInt(1),
|
|---|
| 184 | entity_pos_t::FromInt(ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE * 5 + ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE / 2)
|
|---|
| 185 | );
|
|---|
| 186 | terrInf.m_Radius = 1;
|
|---|
| 187 |
|
|---|
| 188 | TS_ASSERT_EQUALS(cmp->GetTerritoryPercentage(0), 0);
|
|---|
| 189 | TS_ASSERT_EQUALS(cmp->GetTerritoryPercentage(1), 4); // 5*5 = 25 -> 1 tile out of 25 is 4%
|
|---|
| 190 | TS_ASSERT_EQUALS(cmp->GetTerritoryPercentage(2), 0);
|
|---|
| 191 |
|
|---|
| 192 | terrInf.m_Radius = ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE * 10;
|
|---|
| 193 |
|
|---|
| 194 | test.HandleMessage(cmp, CMessageTerrainChanged(0, 0, 0, 0), true);
|
|---|
| 195 |
|
|---|
| 196 | TS_ASSERT_EQUALS(cmp->GetTerritoryPercentage(0), 0);
|
|---|
| 197 | TS_ASSERT_EQUALS(cmp->GetTerritoryPercentage(1), 100);
|
|---|
| 198 | TS_ASSERT_EQUALS(cmp->GetTerritoryPercentage(2), 0);
|
|---|
| 199 | }
|
|---|
| 200 |
|
|---|
| 201 | void test_boundaries()
|
|---|
| 202 | {
|
|---|
| 203 | Grid<u8> grid = GetGrid("--------"
|
|---|
| 204 | "777777--"
|
|---|
| 205 | "777777--"
|
|---|
| 206 | "777777--"
|
|---|
| 207 | "--------", 8, 5);
|
|---|
| 208 |
|
|---|
| 209 | std::vector<STerritoryBoundary> boundaries = CTerritoryBoundaryCalculator::ComputeBoundaries(&grid);
|
|---|
| 210 | TS_ASSERT_EQUALS(1U, boundaries.size());
|
|---|
| 211 | TS_ASSERT_EQUALS(18U, boundaries[0].points.size()); // 2x6 + 2x3
|
|---|
| 212 | TS_ASSERT_EQUALS((player_id_t)7, boundaries[0].owner);
|
|---|
| 213 | TS_ASSERT_EQUALS(false, boundaries[0].blinking); // high bits aren't set by GetGrid
|
|---|
| 214 |
|
|---|
| 215 | // assumes NAVCELLS_PER_TERRITORY_TILE is 8; dealt with in TestBoundaryPointsEqual
|
|---|
| 216 | int expectedPoints[][2] = {{ 4, 8}, {12, 8}, {20, 8}, {28, 8}, {36, 8}, {44, 8},
|
|---|
| 217 | {48,12}, {48,20}, {48,28},
|
|---|
| 218 | {44,32}, {36,32}, {28,32}, {20,32}, {12,32}, { 4,32},
|
|---|
| 219 | { 0,28}, { 0,20}, { 0,12}};
|
|---|
| 220 |
|
|---|
| 221 | TestBoundaryPointsEqual(boundaries[0].points, expectedPoints);
|
|---|
| 222 | }
|
|---|
| 223 |
|
|---|
| 224 | void test_nested_boundaries1()
|
|---|
| 225 | {
|
|---|
| 226 | // test case from ticket #918; contains single-tile territories with double borders
|
|---|
| 227 | Grid<u8> grid1 = GetGrid("--------"
|
|---|
| 228 | "-111111-"
|
|---|
| 229 | "-1-1213-"
|
|---|
| 230 | "-111111-"
|
|---|
| 231 | "--------", 8, 5);
|
|---|
| 232 |
|
|---|
| 233 | std::vector<STerritoryBoundary> boundaries = CTerritoryBoundaryCalculator::ComputeBoundaries(&grid1);
|
|---|
| 234 |
|
|---|
| 235 | size_t expectedNumBoundaries = 5;
|
|---|
| 236 | TS_ASSERT_EQUALS(expectedNumBoundaries, boundaries.size());
|
|---|
| 237 |
|
|---|
| 238 | STerritoryBoundary* onesOuter = NULL;
|
|---|
| 239 | STerritoryBoundary* onesInner0 = NULL; // inner border around the neutral tile
|
|---|
| 240 | STerritoryBoundary* onesInner2 = NULL; // inner border around the '2' tile
|
|---|
| 241 | STerritoryBoundary* twosOuter = NULL;
|
|---|
| 242 | STerritoryBoundary* threesOuter = NULL;
|
|---|
| 243 |
|
|---|
| 244 | // expected number of points (!) in the inner boundaries for terrain 1 (there are two with the same size)
|
|---|
| 245 | size_t onesInnerNumExpectedPoints = 4;
|
|---|
| 246 |
|
|---|
| 247 | for (size_t i=0; i<expectedNumBoundaries; i++)
|
|---|
| 248 | {
|
|---|
| 249 | STerritoryBoundary& boundary = boundaries[i];
|
|---|
| 250 | switch (boundary.owner)
|
|---|
| 251 | {
|
|---|
| 252 | case 1:
|
|---|
| 253 | // to figure out which 1-boundary is which, we can use the number of points to distinguish between outer and inner,
|
|---|
| 254 | // and within the inners we can split them by their X value (onesInner0 is the leftmost one, onesInner1 the
|
|---|
| 255 | // rightmost one).
|
|---|
| 256 | if (boundary.points.size() != onesInnerNumExpectedPoints)
|
|---|
| 257 | {
|
|---|
| 258 | TSM_ASSERT_EQUALS("Found multiple outer boundaries for territory owned by player 1", onesOuter, (STerritoryBoundary*) NULL);
|
|---|
| 259 | onesOuter = &boundary;
|
|---|
| 260 | }
|
|---|
| 261 | else
|
|---|
| 262 | {
|
|---|
| 263 | TS_ASSERT_EQUALS(onesInnerNumExpectedPoints, boundary.points.size()); // all inner boundaries are of size 2
|
|---|
| 264 | if (boundary.points[0].X < 24.f)
|
|---|
| 265 | {
|
|---|
| 266 | // leftmost inner boundary, i.e. onesInner0
|
|---|
| 267 | TSM_ASSERT_EQUALS("Found multiple leftmost inner boundaries for territory owned by player 1", onesInner0, (STerritoryBoundary*) NULL);
|
|---|
| 268 | onesInner0 = &boundary;
|
|---|
| 269 | }
|
|---|
| 270 | else
|
|---|
| 271 | {
|
|---|
| 272 | TSM_ASSERT_EQUALS("Found multiple rightmost inner boundaries for territory owned by player 1", onesInner2, (STerritoryBoundary*) NULL);
|
|---|
| 273 | onesInner2 = &boundary;
|
|---|
| 274 | }
|
|---|
| 275 | }
|
|---|
| 276 | break;
|
|---|
| 277 | case 2:
|
|---|
| 278 | TSM_ASSERT_EQUALS("Too many boundaries for territory owned by player 2", twosOuter, (STerritoryBoundary*) NULL);
|
|---|
| 279 | twosOuter = &boundary;
|
|---|
| 280 | break;
|
|---|
| 281 |
|
|---|
| 282 | case 3:
|
|---|
| 283 | TSM_ASSERT_EQUALS("Too many boundaries for territory owned by player 3", threesOuter, (STerritoryBoundary*) NULL);
|
|---|
| 284 | threesOuter = &boundary;
|
|---|
| 285 | break;
|
|---|
| 286 |
|
|---|
| 287 | default:
|
|---|
| 288 | TS_FAIL("Unexpected tile owner");
|
|---|
| 289 | break;
|
|---|
| 290 | }
|
|---|
| 291 | }
|
|---|
| 292 |
|
|---|
| 293 | TS_ASSERT_DIFFERS(onesOuter, (STerritoryBoundary*) NULL);
|
|---|
| 294 | TS_ASSERT_DIFFERS(onesInner0, (STerritoryBoundary*) NULL);
|
|---|
| 295 | TS_ASSERT_DIFFERS(onesInner2, (STerritoryBoundary*) NULL);
|
|---|
| 296 | TS_ASSERT_DIFFERS(twosOuter, (STerritoryBoundary*) NULL);
|
|---|
| 297 | TS_ASSERT_DIFFERS(threesOuter, (STerritoryBoundary*) NULL);
|
|---|
| 298 |
|
|---|
| 299 | TS_ASSERT_EQUALS(onesOuter->points.size(), 20U);
|
|---|
| 300 | TS_ASSERT_EQUALS(onesInner0->points.size(), 4U);
|
|---|
| 301 | TS_ASSERT_EQUALS(onesInner2->points.size(), 4U);
|
|---|
| 302 | TS_ASSERT_EQUALS(twosOuter->points.size(), 4U);
|
|---|
| 303 | TS_ASSERT_EQUALS(threesOuter->points.size(), 4U);
|
|---|
| 304 |
|
|---|
| 305 | int onesOuterExpectedPoints[][2] = {{12, 8}, {20, 8}, {28, 8}, {36, 8}, {44, 8}, {52, 8},
|
|---|
| 306 | {56,12}, {52,16}, {48,20}, {52,24}, {56,28},
|
|---|
| 307 | {52,32}, {44,32}, {36,32}, {28,32}, {20,32}, {12,32},
|
|---|
| 308 | { 8,28}, { 8,20}, { 8,12}};
|
|---|
| 309 | int onesInner0ExpectedPoints[][2] = {{20,24}, {24,20}, {20,16}, {16,20}};
|
|---|
| 310 | int onesInner2ExpectedPoints[][2] = {{36,24}, {40,20}, {36,16}, {32,20}};
|
|---|
| 311 | int twosOuterExpectedPoints[][2] = {{36,16}, {40,20}, {36,24}, {32,20}};
|
|---|
| 312 | int threesOuterExpectedPoints[][2] = {{52,16}, {56,20}, {52,24}, {48,20}};
|
|---|
| 313 |
|
|---|
| 314 | TestBoundaryPointsEqual(onesOuter->points, onesOuterExpectedPoints);
|
|---|
| 315 | TestBoundaryPointsEqual(onesInner0->points, onesInner0ExpectedPoints);
|
|---|
| 316 | TestBoundaryPointsEqual(onesInner2->points, onesInner2ExpectedPoints);
|
|---|
| 317 | TestBoundaryPointsEqual(twosOuter->points, twosOuterExpectedPoints);
|
|---|
| 318 | TestBoundaryPointsEqual(threesOuter->points, threesOuterExpectedPoints);
|
|---|
| 319 | }
|
|---|
| 320 |
|
|---|
| 321 | void test_nested_boundaries2()
|
|---|
| 322 | {
|
|---|
| 323 | Grid<u8> grid1 = GetGrid("-22222-"
|
|---|
| 324 | "-2---2-"
|
|---|
| 325 | "-2-1123"
|
|---|
| 326 | "-2-1123"
|
|---|
| 327 | "-2-2223"
|
|---|
| 328 | "-222333", 7, 6);
|
|---|
| 329 |
|
|---|
| 330 | std::vector<STerritoryBoundary> boundaries = CTerritoryBoundaryCalculator::ComputeBoundaries(&grid1);
|
|---|
| 331 |
|
|---|
| 332 | // There should be two boundaries found for the territory of 2's (one outer and one inner edge), plus two regular
|
|---|
| 333 | // outer edges of the territories of 1's and 3's. The order in which they're returned doesn't matter though, so
|
|---|
| 334 | // we should first detect which one is which.
|
|---|
| 335 | size_t expectedNumBoundaries = 4;
|
|---|
| 336 | TS_ASSERT_EQUALS(expectedNumBoundaries, boundaries.size());
|
|---|
| 337 |
|
|---|
| 338 | STerritoryBoundary* onesOuter = NULL;
|
|---|
| 339 | STerritoryBoundary* twosOuter = NULL;
|
|---|
| 340 | STerritoryBoundary* twosInner = NULL;
|
|---|
| 341 | STerritoryBoundary* threesOuter = NULL;
|
|---|
| 342 |
|
|---|
| 343 | for (size_t i=0; i < expectedNumBoundaries; i++)
|
|---|
| 344 | {
|
|---|
| 345 | STerritoryBoundary& boundary = boundaries[i];
|
|---|
| 346 | switch (boundary.owner)
|
|---|
| 347 | {
|
|---|
| 348 | case 1:
|
|---|
| 349 | TSM_ASSERT_EQUALS("Too many boundaries for territory owned by player 1", onesOuter, (STerritoryBoundary*) NULL);
|
|---|
| 350 | onesOuter = &boundary;
|
|---|
| 351 | break;
|
|---|
| 352 |
|
|---|
| 353 | case 3:
|
|---|
| 354 | TSM_ASSERT_EQUALS("Too many boundaries for territory owned by player 3", threesOuter, (STerritoryBoundary*) NULL);
|
|---|
| 355 | threesOuter = &boundary;
|
|---|
| 356 | break;
|
|---|
| 357 |
|
|---|
| 358 | case 2:
|
|---|
| 359 | // assign twosOuter first, then twosInner last; we'll swap them afterwards if needed
|
|---|
| 360 | if (twosOuter == NULL)
|
|---|
| 361 | twosOuter = &boundary;
|
|---|
| 362 | else if (twosInner == NULL)
|
|---|
| 363 | twosInner = &boundary;
|
|---|
| 364 | else
|
|---|
| 365 | TS_FAIL("Too many boundaries for territory owned by player 2");
|
|---|
| 366 |
|
|---|
| 367 | break;
|
|---|
| 368 |
|
|---|
| 369 | default:
|
|---|
| 370 | TS_FAIL("Unexpected tile owner");
|
|---|
| 371 | break;
|
|---|
| 372 | }
|
|---|
| 373 | }
|
|---|
| 374 |
|
|---|
| 375 | TS_ASSERT_DIFFERS(onesOuter, (STerritoryBoundary*) NULL);
|
|---|
| 376 | TS_ASSERT_DIFFERS(twosOuter, (STerritoryBoundary*) NULL);
|
|---|
| 377 | TS_ASSERT_DIFFERS(twosInner, (STerritoryBoundary*) NULL);
|
|---|
| 378 | TS_ASSERT_DIFFERS(threesOuter, (STerritoryBoundary*) NULL);
|
|---|
| 379 |
|
|---|
| 380 | TS_ASSERT_EQUALS(onesOuter->points.size(), 8U);
|
|---|
| 381 | TS_ASSERT_EQUALS(twosOuter->points.size(), 22U);
|
|---|
| 382 | TS_ASSERT_EQUALS(twosInner->points.size(), 14U);
|
|---|
| 383 | TS_ASSERT_EQUALS(threesOuter->points.size(), 14U);
|
|---|
| 384 |
|
|---|
| 385 | // See if we need to swap the outer and inner edges of the twos territories (uses the extremely simplistic
|
|---|
| 386 | // heuristic of comparing the amount of points to determine which one is the outer one and which one the inner
|
|---|
| 387 | // one (which does happen to work in this case though).
|
|---|
| 388 |
|
|---|
| 389 | if (twosOuter->points.size() < twosInner->points.size())
|
|---|
| 390 | {
|
|---|
| 391 | STerritoryBoundary* tmp = twosOuter;
|
|---|
| 392 | twosOuter = twosInner;
|
|---|
| 393 | twosInner = tmp;
|
|---|
| 394 | }
|
|---|
| 395 |
|
|---|
| 396 | int onesOuterExpectedPoints[][2] = {{28,16}, {36,16}, {40,20}, {40,28}, {36,32}, {28,32}, {24,28}, {24,20}};
|
|---|
| 397 | int twosOuterExpectedPoints[][2] = {{12, 0}, {20, 0}, {28, 0}, {32, 4}, {36, 8}, {44, 8},
|
|---|
| 398 | {48,12}, {48,20}, {48,28}, {48,36}, {48,44},
|
|---|
| 399 | {44,48}, {36,48}, {28,48}, {20,48}, {12,48},
|
|---|
| 400 | { 8,44}, { 8,36}, { 8,28}, { 8,20}, { 8,12}, { 8, 4}};
|
|---|
| 401 | int twosInnerExpectedPoints[][2] = {{20,40}, {28,40}, {36,40}, {40,36}, {40,28}, {40,20}, {36,16},
|
|---|
| 402 | {28,16}, {24,12}, {20, 8}, {16,12}, {16,20}, {16,28}, {16,36}};
|
|---|
| 403 | int threesOuterExpectedPoints[][2] = {{36, 0}, {44, 0}, {52, 0}, {56, 4}, {56,12}, {56,20}, {56,28}, {52,32},
|
|---|
| 404 | {48,28}, {48,20}, {48,12}, {44, 8}, {36, 8}, {32, 4}};
|
|---|
| 405 |
|
|---|
| 406 | TestBoundaryPointsEqual(onesOuter->points, onesOuterExpectedPoints);
|
|---|
| 407 | TestBoundaryPointsEqual(twosOuter->points, twosOuterExpectedPoints);
|
|---|
| 408 | TestBoundaryPointsEqual(twosInner->points, twosInnerExpectedPoints);
|
|---|
| 409 | TestBoundaryPointsEqual(threesOuter->points, threesOuterExpectedPoints);
|
|---|
| 410 | }
|
|---|
| 411 |
|
|---|
| 412 | private:
|
|---|
| 413 | /// Parses a string representation of a grid into an actual Grid structure, such that the (i,j) axes are located in the bottom
|
|---|
| 414 | /// left hand side of the map. Note: leaves all custom bits in the grid values at zero (anything outside
|
|---|
| 415 | /// ICmpTerritoryManager::TERRITORY_PLAYER_MASK).
|
|---|
| 416 | Grid<u8> GetGrid(const std::string& def, u16 w, u16 h)
|
|---|
| 417 | {
|
|---|
| 418 | Grid<u8> grid(w, h);
|
|---|
| 419 | const char* chars = def.c_str();
|
|---|
| 420 |
|
|---|
| 421 | for (u16 y=0; y<h; y++)
|
|---|
| 422 | {
|
|---|
| 423 | for (u16 x=0; x<w; x++)
|
|---|
| 424 | {
|
|---|
| 425 | char gridDefChar = chars[x+y*w];
|
|---|
| 426 | if (gridDefChar == '-')
|
|---|
| 427 | continue;
|
|---|
| 428 |
|
|---|
| 429 | ENSURE('0' <= gridDefChar && gridDefChar <= '9');
|
|---|
| 430 | u8 playerId = gridDefChar - '0';
|
|---|
| 431 | grid.set(x, h-1-y, playerId);
|
|---|
| 432 | }
|
|---|
| 433 | }
|
|---|
| 434 |
|
|---|
| 435 | return grid;
|
|---|
| 436 | }
|
|---|
| 437 |
|
|---|
| 438 | void TestBoundaryPointsEqual(const std::vector<CVector2D>& points, int expectedPoints[][2])
|
|---|
| 439 | {
|
|---|
| 440 | // TODO: currently relies on an exact point match, i.e. expectedPoints must be specified going CCW or CW (depending on
|
|---|
| 441 | // whether we're testing an inner or an outer edge) starting from the exact same point that the algorithm happened to
|
|---|
| 442 | // decide to start the run from. This is an algorithmic detail and is not considered to be part of the specification
|
|---|
| 443 | // of the return value. Hence, this method should also accept 'expectedPoints' to be a cyclically shifted
|
|---|
| 444 | // version of 'points', so that the starting position doesn't need to match exactly.
|
|---|
| 445 | for (size_t i = 0; i < points.size(); i++)
|
|---|
| 446 | {
|
|---|
| 447 | // the input numbers in expectedPoints are defined under the assumption that NAVCELLS_PER_TERRITORY_TILE is 8, so let's include
|
|---|
| 448 | // a scaling factor to protect against that should NAVCELLS_PER_TERRITORY_TILE ever change
|
|---|
| 449 | TS_ASSERT_DELTA(points[i].X, float(expectedPoints[i][0]) * 8.f / ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE, 1e-7);
|
|---|
| 450 | TS_ASSERT_DELTA(points[i].Y, float(expectedPoints[i][1]) * 8.f / ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE, 1e-7);
|
|---|
| 451 | }
|
|---|
| 452 | }
|
|---|
| 453 | };
|
|---|