Browse Source
1. Makes `path.cpp` concerned solely with the pathfinding algorithm. 2. Turns `path_test` into a standalone test.pull/7673/head
18 changed files with 286 additions and 231 deletions
@ -0,0 +1,88 @@
|
||||
#include "levels/tile_properties.hpp" |
||||
|
||||
#include "engine/direction.hpp" |
||||
#include "engine/path.h" |
||||
#include "engine/point.hpp" |
||||
#include "gendung.h" |
||||
#include "objects.h" |
||||
|
||||
namespace devilution { |
||||
|
||||
bool IsTileNotSolid(Point position) |
||||
{ |
||||
if (!InDungeonBounds(position)) { |
||||
return false; |
||||
} |
||||
|
||||
return !TileHasAny(position, TileProperties::Solid); |
||||
} |
||||
|
||||
bool IsTileSolid(Point position) |
||||
{ |
||||
if (!InDungeonBounds(position)) { |
||||
return false; |
||||
} |
||||
|
||||
return TileHasAny(position, TileProperties::Solid); |
||||
} |
||||
|
||||
bool IsTileWalkable(Point position, bool ignoreDoors) |
||||
{ |
||||
Object *object = FindObjectAtPosition(position); |
||||
if (object != nullptr) { |
||||
if (ignoreDoors && object->isDoor()) { |
||||
return true; |
||||
} |
||||
if (object->_oSolidFlag) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
return IsTileNotSolid(position); |
||||
} |
||||
|
||||
bool IsTileOccupied(Point position) |
||||
{ |
||||
if (!InDungeonBounds(position)) { |
||||
return true; // OOB positions are considered occupied.
|
||||
} |
||||
|
||||
if (IsTileSolid(position)) { |
||||
return true; |
||||
} |
||||
if (dMonster[position.x][position.y] != 0) { |
||||
return true; |
||||
} |
||||
if (dPlayer[position.x][position.y] != 0) { |
||||
return true; |
||||
} |
||||
if (IsObjectAtPosition(position)) { |
||||
return true; |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
bool CanStep(Point startPosition, Point destinationPosition) |
||||
{ |
||||
// These checks are written as if working backwards from the destination to the source, given
|
||||
// both tiles are expected to be adjacent this doesn't matter beyond being a bit confusing
|
||||
bool rv = true; |
||||
switch (GetPathDirection(startPosition, destinationPosition)) { |
||||
case 5: // Stepping north
|
||||
rv = IsTileNotSolid(destinationPosition + Direction::SouthWest) && IsTileNotSolid(destinationPosition + Direction::SouthEast); |
||||
break; |
||||
case 6: // Stepping east
|
||||
rv = IsTileNotSolid(destinationPosition + Direction::SouthWest) && IsTileNotSolid(destinationPosition + Direction::NorthWest); |
||||
break; |
||||
case 7: // Stepping south
|
||||
rv = IsTileNotSolid(destinationPosition + Direction::NorthEast) && IsTileNotSolid(destinationPosition + Direction::NorthWest); |
||||
break; |
||||
case 8: // Stepping west
|
||||
rv = IsTileNotSolid(destinationPosition + Direction::SouthEast) && IsTileNotSolid(destinationPosition + Direction::NorthEast); |
||||
break; |
||||
} |
||||
return rv; |
||||
} |
||||
|
||||
} // namespace devilution
|
||||
@ -0,0 +1,32 @@
|
||||
#pragma once |
||||
|
||||
#include "engine/point.hpp" |
||||
|
||||
namespace devilution { |
||||
|
||||
[[nodiscard]] bool IsTileNotSolid(Point position); |
||||
[[nodiscard]] bool IsTileSolid(Point position); |
||||
|
||||
/**
|
||||
* @brief Checks the position is solid or blocked by an object |
||||
*/ |
||||
[[nodiscard]] bool IsTileWalkable(Point position, bool ignoreDoors = false); |
||||
|
||||
/**
|
||||
* @brief Checks if the position contains an object, player, monster, or solid dungeon piece |
||||
*/ |
||||
[[nodiscard]] bool IsTileOccupied(Point position); |
||||
|
||||
/**
|
||||
* @brief check if stepping from a given position to a neighbouring tile cuts a corner. |
||||
* |
||||
* If you step from A to B, both Xs need to be clear: |
||||
* |
||||
* AX |
||||
* XB |
||||
* |
||||
* @return true if step is allowed |
||||
*/ |
||||
[[nodiscard]] bool CanStep(Point startPosition, Point destinationPosition); |
||||
|
||||
} // namespace devilution
|
||||
@ -0,0 +1,103 @@
|
||||
#include "levels/tile_properties.hpp" |
||||
|
||||
#include <gmock/gmock.h> |
||||
#include <gtest/gtest.h> |
||||
|
||||
#include "levels/dun_tile.hpp" |
||||
#include "levels/gendung.h" |
||||
#include "objdat.h" |
||||
#include "objects.h" |
||||
|
||||
namespace devilution { |
||||
namespace { |
||||
|
||||
TEST(TilePropertiesTest, Solid) |
||||
{ |
||||
dPiece[5][5] = 0; |
||||
SOLData[0] = TileProperties::Solid; |
||||
EXPECT_TRUE(IsTileSolid({ 5, 5 })) << "Solid in-bounds tiles are solid"; |
||||
EXPECT_FALSE(IsTileNotSolid({ 5, 5 })) << "IsTileNotSolid returns the inverse of IsTileSolid for in-bounds tiles"; |
||||
|
||||
dPiece[6][6] = 1; |
||||
SOLData[1] = TileProperties::None; |
||||
EXPECT_FALSE(IsTileSolid({ 6, 6 })) << "Non-solid in-bounds tiles are not solid"; |
||||
EXPECT_TRUE(IsTileNotSolid({ 6, 6 })) << "IsTileNotSolid returns the inverse of IsTileSolid for in-bounds tiles"; |
||||
|
||||
EXPECT_FALSE(IsTileSolid({ -1, 1 })) << "Out of bounds tiles are not solid"; // this reads out of bounds in the current code and may fail unexpectedly
|
||||
EXPECT_FALSE(IsTileNotSolid({ -1, 1 })) << "Out of bounds tiles are also not not solid"; |
||||
} |
||||
|
||||
TEST(TilePropertiesTest, Walkable) |
||||
{ |
||||
dPiece[5][5] = 0; |
||||
SOLData[0] = TileProperties::Solid; // Doing this manually to save running through the code in gendung.cpp
|
||||
EXPECT_FALSE(IsTileWalkable({ 5, 5 })) << "Tile which is marked as solid should be considered blocked"; |
||||
EXPECT_FALSE(IsTileWalkable({ 5, 5 }, true)) << "Solid non-door tiles remain unwalkable when ignoring doors"; |
||||
|
||||
SOLData[0] = TileProperties::None; |
||||
EXPECT_TRUE(IsTileWalkable({ 5, 5 })) << "Non-solid tiles are walkable"; |
||||
EXPECT_TRUE(IsTileWalkable({ 5, 5 }, true)) << "Non-solid tiles remain walkable when ignoring doors"; |
||||
|
||||
dObject[5][5] = 1; |
||||
Objects[0]._oSolidFlag = true; |
||||
EXPECT_FALSE(IsTileWalkable({ 5, 5 })) << "Tile occupied by a solid object is unwalkable"; |
||||
EXPECT_FALSE(IsTileWalkable({ 5, 5 }, true)) << "Tile occupied by a solid non-door object are unwalkable when ignoring doors"; |
||||
|
||||
Objects[0]._otype = _object_id::OBJ_L1LDOOR; |
||||
EXPECT_FALSE(IsTileWalkable({ 5, 5 })) << "Tile occupied by a door which is marked as solid should be considered blocked"; |
||||
EXPECT_TRUE(IsTileWalkable({ 5, 5 }, true)) << "Tile occupied by a door is considered walkable when ignoring doors"; |
||||
|
||||
Objects[0]._oSolidFlag = false; |
||||
EXPECT_TRUE(IsTileWalkable({ 5, 5 })) << "Tile occupied by an open door is walkable"; |
||||
EXPECT_TRUE(IsTileWalkable({ 5, 5 }, true)) << "Tile occupied by a door is considered walkable when ignoring doors"; |
||||
|
||||
SOLData[0] = TileProperties::Solid; |
||||
EXPECT_FALSE(IsTileWalkable({ 5, 5 })) << "Solid tiles occupied by an open door remain unwalkable"; |
||||
EXPECT_TRUE(IsTileWalkable({ 5, 5 }, true)) << "Solid tiles occupied by an open door become walkable when ignoring doors"; |
||||
} |
||||
|
||||
TEST(TilePropertiesTest, CanStepTest) |
||||
{ |
||||
dPiece[0][0] = 0; |
||||
dPiece[0][1] = 0; |
||||
dPiece[1][0] = 0; |
||||
dPiece[1][1] = 0; |
||||
SOLData[0] = TileProperties::None; |
||||
EXPECT_TRUE(CanStep({ 0, 0 }, { 1, 1 })) << "A step in open space is free of solid pieces"; |
||||
EXPECT_TRUE(CanStep({ 1, 1 }, { 0, 0 })) << "A step in open space is free of solid pieces"; |
||||
EXPECT_TRUE(CanStep({ 1, 0 }, { 0, 1 })) << "A step in open space is free of solid pieces"; |
||||
EXPECT_TRUE(CanStep({ 0, 1 }, { 1, 0 })) << "A step in open space is free of solid pieces"; |
||||
|
||||
SOLData[1] = TileProperties::Solid; |
||||
dPiece[1][0] = 1; |
||||
EXPECT_TRUE(CanStep({ 0, 1 }, { 1, 0 })) << "Can path to a destination which is solid"; |
||||
EXPECT_TRUE(CanStep({ 1, 0 }, { 0, 1 })) << "Can path from a starting position which is solid"; |
||||
EXPECT_TRUE(CanStep({ 0, 1 }, { 1, 1 })) << "Stepping in a cardinal direction ignores solid pieces"; |
||||
EXPECT_TRUE(CanStep({ 1, 0 }, { 1, 1 })) << "Stepping in a cardinal direction ignores solid pieces"; |
||||
EXPECT_TRUE(CanStep({ 0, 0 }, { 1, 0 })) << "Stepping in a cardinal direction ignores solid pieces"; |
||||
EXPECT_TRUE(CanStep({ 1, 1 }, { 1, 0 })) << "Stepping in a cardinal direction ignores solid pieces"; |
||||
|
||||
EXPECT_FALSE(CanStep({ 0, 0 }, { 1, 1 })) << "Can't cut a solid corner"; |
||||
EXPECT_FALSE(CanStep({ 1, 1 }, { 0, 0 })) << "Can't cut a solid corner"; |
||||
dPiece[0][1] = 1; |
||||
EXPECT_FALSE(CanStep({ 0, 0 }, { 1, 1 })) << "Can't walk through the boundary between two corners"; |
||||
EXPECT_FALSE(CanStep({ 1, 1 }, { 0, 0 })) << "Can't walk through the boundary between two corners"; |
||||
dPiece[1][0] = 0; |
||||
EXPECT_FALSE(CanStep({ 0, 0 }, { 1, 1 })) << "Can't cut a solid corner"; |
||||
EXPECT_FALSE(CanStep({ 1, 1 }, { 0, 0 })) << "Can't cut a solid corner"; |
||||
dPiece[0][1] = 0; |
||||
|
||||
dPiece[0][0] = 1; |
||||
EXPECT_FALSE(CanStep({ 1, 0 }, { 0, 1 })) << "Can't cut a solid corner"; |
||||
EXPECT_FALSE(CanStep({ 0, 1 }, { 1, 0 })) << "Can't cut a solid corner"; |
||||
dPiece[1][1] = 1; |
||||
EXPECT_FALSE(CanStep({ 1, 0 }, { 0, 1 })) << "Can't walk through the boundary between two corners"; |
||||
EXPECT_FALSE(CanStep({ 0, 1 }, { 1, 0 })) << "Can't walk through the boundary between two corners"; |
||||
dPiece[0][0] = 0; |
||||
EXPECT_FALSE(CanStep({ 1, 0 }, { 0, 1 })) << "Can't cut a solid corner"; |
||||
EXPECT_FALSE(CanStep({ 0, 1 }, { 1, 0 })) << "Can't cut a solid corner"; |
||||
dPiece[1][1] = 0; |
||||
} |
||||
|
||||
} // namespace
|
||||
} // namespace devilution
|
||||
Loading…
Reference in new issue