mirror of https://github.com/OpenTTD/OpenTTD
Codechange: Add SpiralTileSequence to iterate over a tile area the same ways as CircularTileSearch.
parent
b9bd7b2cfe
commit
0dada5a750
|
@ -249,6 +249,7 @@ bool CircularTileSearch(TileIndex *tile, uint size, TestTileOnSearchProc proc, v
|
|||
/* If the length of the side is uneven, the center has to be checked
|
||||
* separately, as the pattern of uneven sides requires to go around the center */
|
||||
if (proc(*tile, user_data)) return true;
|
||||
if (size < 2) return false;
|
||||
|
||||
/* If tile test is not successful, get one tile up,
|
||||
* ready for a test in first circle around center tile */
|
||||
|
|
|
@ -15,5 +15,6 @@ add_test_files(
|
|||
test_network_crypto.cpp
|
||||
test_script_admin.cpp
|
||||
test_window_desc.cpp
|
||||
tilearea.cpp
|
||||
utf8.cpp
|
||||
)
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
|
||||
* OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @file tilearea.cpp Test functionality from tilearea_type. */
|
||||
|
||||
#include "../stdafx.h"
|
||||
|
||||
#include "../3rdparty/catch2/catch.hpp"
|
||||
|
||||
#include "../tilearea_type.h"
|
||||
#include "../map_func.h"
|
||||
|
||||
#include "../safeguards.h"
|
||||
|
||||
struct TileCoord {
|
||||
uint x, y;
|
||||
};
|
||||
|
||||
static void TestSpiralTileSequence(TileCoord center, uint diameter, std::span<TileCoord> expected)
|
||||
{
|
||||
auto tile = TileXY(center.x, center.y);
|
||||
|
||||
std::vector<TileIndex> result;
|
||||
for (auto ti : SpiralTileSequence(tile, diameter)) {
|
||||
result.push_back(ti);
|
||||
}
|
||||
REQUIRE(result.size() == expected.size());
|
||||
for (size_t i = 0; i < result.size(); ++i) {
|
||||
CHECK(TileX(result[i]) == expected[i].x);
|
||||
CHECK(TileY(result[i]) == expected[i].y);
|
||||
}
|
||||
|
||||
result.clear();
|
||||
CircularTileSearch(&tile, diameter,
|
||||
+[](TileIndex tile, void *user_data) -> bool {
|
||||
reinterpret_cast<std::vector<TileIndex> *>(user_data)->push_back(tile);
|
||||
return false;
|
||||
}, &result);
|
||||
REQUIRE(result.size() == expected.size());
|
||||
for (size_t i = 0; i < result.size(); ++i) {
|
||||
CHECK(TileX(result[i]) == expected[i].x);
|
||||
CHECK(TileY(result[i]) == expected[i].y);
|
||||
}
|
||||
}
|
||||
|
||||
static void TestSpiralTileSequence(TileCoord start_north, uint radius, uint w, uint h, std::span<TileCoord> expected)
|
||||
{
|
||||
auto tile = TileXY(start_north.x, start_north.y);
|
||||
|
||||
std::vector<TileIndex> result;
|
||||
for (auto ti : SpiralTileSequence(tile, radius, w, h)) {
|
||||
result.push_back(ti);
|
||||
}
|
||||
REQUIRE(result.size() == expected.size());
|
||||
for (size_t i = 0; i < result.size(); ++i) {
|
||||
CHECK(TileX(result[i]) == expected[i].x);
|
||||
CHECK(TileY(result[i]) == expected[i].y);
|
||||
}
|
||||
|
||||
result.clear();
|
||||
CircularTileSearch(&tile, radius, w, h,
|
||||
+[](TileIndex tile, void *user_data) -> bool {
|
||||
reinterpret_cast<std::vector<TileIndex> *>(user_data)->push_back(tile);
|
||||
return false;
|
||||
}, &result);
|
||||
REQUIRE(result.size() == expected.size());
|
||||
for (size_t i = 0; i < result.size(); ++i) {
|
||||
CHECK(TileX(result[i]) == expected[i].x);
|
||||
CHECK(TileY(result[i]) == expected[i].y);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("SpiralTileSequence - minimum")
|
||||
{
|
||||
Map::Allocate(64, 64);
|
||||
|
||||
TileCoord expected[] = {{63, 63}};
|
||||
TestSpiralTileSequence({63, 63}, 1, expected);
|
||||
TestSpiralTileSequence({63, 63}, 2, expected);
|
||||
TestSpiralTileSequence({63, 63}, 1, 0, 0, expected);
|
||||
TestSpiralTileSequence({63, 63}, 1, 2, 2, expected);
|
||||
}
|
||||
|
||||
TEST_CASE("SpiralTileSequence - odd")
|
||||
{
|
||||
Map::Allocate(64, 64);
|
||||
|
||||
TileCoord expected[] = {
|
||||
{1, 1},
|
||||
{2, 0}, {1, 0}, {0, 0}, {0, 1}, {0, 2}, {1, 2}, {2, 2}, {2, 1},
|
||||
{0, 3}, {1, 3}, {2, 3}, {3, 3}, {3, 2}, {3, 1}, {3, 0},
|
||||
};
|
||||
TestSpiralTileSequence({1, 1}, 5, expected);
|
||||
}
|
||||
|
||||
TEST_CASE("SpiralTileSequence - even")
|
||||
{
|
||||
Map::Allocate(64, 64);
|
||||
|
||||
TileCoord expected[] = {
|
||||
{2, 1}, {1, 1}, {1, 2}, {2, 2},
|
||||
{3, 0}, {2, 0}, {1, 0}, {0, 0}, {0, 1}, {0, 2}, {0, 3}, {1, 3}, {2, 3}, {3, 3}, {3, 2}, {3, 1},
|
||||
{0, 4}, {1, 4}, {2, 4}, {3, 4}, {4, 4}, {4, 3}, {4, 2}, {4, 1}, {4, 0},
|
||||
};
|
||||
TestSpiralTileSequence({1, 1}, 6, expected);
|
||||
TestSpiralTileSequence({1, 1}, 3, 0, 0, expected);
|
||||
}
|
||||
|
||||
TEST_CASE("SpiralTileSequence - zero hole")
|
||||
{
|
||||
Map::Allocate(64, 64);
|
||||
|
||||
TileCoord expected[] = {
|
||||
{5, 2}, {4, 2}, {3, 2}, {2, 2}, {2, 3}, {3, 3}, {4, 3}, {5, 3},
|
||||
{6, 1}, {5, 1}, {4, 1}, {3, 1}, {2, 1}, {1, 1}, {1, 2}, {1, 3}, {1, 4}, {2, 4}, {3, 4}, {4, 4}, {5, 4}, {6, 4}, {6, 3}, {6, 2},
|
||||
};
|
||||
TestSpiralTileSequence({2, 2}, 2, 2, 0, expected);
|
||||
}
|
||||
|
||||
TEST_CASE("SpiralTileSequence - normal hole")
|
||||
{
|
||||
Map::Allocate(64, 64);
|
||||
|
||||
TileCoord expected[] = {
|
||||
{4, 2}, {3, 2}, {2, 2}, {2, 3}, {2, 4}, {2, 5}, {3, 5}, {4, 5}, {4, 4}, {4, 3},
|
||||
};
|
||||
TestSpiralTileSequence({2, 2}, 1, 1, 2, expected);
|
||||
}
|
102
src/tilearea.cpp
102
src/tilearea.cpp
|
@ -295,3 +295,105 @@ TileIterator &DiagonalTileIterator::operator++()
|
|||
}
|
||||
return std::make_unique<OrthogonalTileIterator>(corner1, corner2);
|
||||
}
|
||||
|
||||
/**
|
||||
* See SpiralTileSequence constructor for description.
|
||||
*/
|
||||
SpiralTileIterator::SpiralTileIterator(TileIndex center, uint diameter) :
|
||||
max_radius(diameter / 2),
|
||||
cur_radius(0),
|
||||
dir(DIAGDIR_BEGIN)
|
||||
{
|
||||
assert(diameter > 0);
|
||||
|
||||
if (diameter % 2 == 1) {
|
||||
this->extent.fill(1);
|
||||
this->dir = INVALID_DIAGDIR; // special case for odd diameters, see Increment()
|
||||
this->position = 0;
|
||||
|
||||
this->x = TileX(center);
|
||||
this->y = TileY(center);
|
||||
} else {
|
||||
this->extent.fill(0);
|
||||
this->dir = DIAGDIR_BEGIN;
|
||||
this->InitPosition();
|
||||
|
||||
/* Start with the west corner of the center 2x2 rect */
|
||||
this->x = TileX(center) + 1;
|
||||
this->y = TileY(center);
|
||||
}
|
||||
this->SkipOutsideMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* See SpiralTileSequence constructor for description.
|
||||
*/
|
||||
SpiralTileIterator::SpiralTileIterator(TileIndex start_north, uint radius, uint w, uint h) :
|
||||
max_radius(radius),
|
||||
extent{w, h, w, h},
|
||||
cur_radius(0),
|
||||
dir(DIAGDIR_BEGIN),
|
||||
/* first tile is the west corner */
|
||||
x(TileX(start_north) + w + 1),
|
||||
y(TileY(start_north))
|
||||
{
|
||||
assert(max_radius > 0);
|
||||
this->InitPosition();
|
||||
this->SkipOutsideMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance the internal state until it reaches a valid tile or the end.
|
||||
*/
|
||||
void SpiralTileIterator::SkipOutsideMap()
|
||||
{
|
||||
while (!this->IsEnd() && (this->x >= Map::SizeX() || this->y >= Map::SizeY())) this->Increment();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise "position" after "dir" was changed.
|
||||
*/
|
||||
void SpiralTileIterator::InitPosition()
|
||||
{
|
||||
this->position = this->extent[this->dir] + this->cur_radius * 2 + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance the internal state to the next potential tile.
|
||||
* The tile may be outside the map though.
|
||||
*/
|
||||
void SpiralTileIterator::Increment()
|
||||
{
|
||||
assert(!this->IsEnd());
|
||||
|
||||
/* Special value for first tile in areas with odd diameter */
|
||||
if (this->dir == INVALID_DIAGDIR) {
|
||||
const auto west = TileIndexDiffCByDir(DIR_W);
|
||||
this->x += west.x;
|
||||
this->y += west.y;
|
||||
this->dir = DIAGDIR_BEGIN;
|
||||
this->InitPosition();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Step to the next 'neighbour' in the circular line */
|
||||
const auto diff = TileIndexDiffCByDiagDir(this->dir);
|
||||
this->x += diff.x;
|
||||
this->y += diff.y;
|
||||
--this->position;
|
||||
if (this->position > 0) return;
|
||||
|
||||
/* Corner reached, switch direction */
|
||||
++this->dir;
|
||||
|
||||
if (this->dir == DIAGDIR_END) {
|
||||
/* Jump to next circle */
|
||||
const auto west = TileIndexDiffCByDir(DIR_W);
|
||||
this->x += west.x;
|
||||
this->y += west.y;
|
||||
++this->cur_radius;
|
||||
this->dir = DIAGDIR_BEGIN;
|
||||
}
|
||||
|
||||
this->InitPosition();
|
||||
}
|
||||
|
|
|
@ -253,4 +253,122 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper class for SpiralTileSequence.
|
||||
*/
|
||||
class SpiralTileIterator {
|
||||
public:
|
||||
using value_type = TileIndex;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using pointer = void;
|
||||
using reference = void;
|
||||
|
||||
SpiralTileIterator(TileIndex center, uint diameter);
|
||||
SpiralTileIterator(TileIndex start_north, uint radius, uint w, uint h);
|
||||
|
||||
bool operator==(const SpiralTileIterator &rhs) const { return this->x == rhs.x && this->y == rhs.y; }
|
||||
bool operator==(const std::default_sentinel_t &) const { return this->IsEnd(); }
|
||||
|
||||
TileIndex operator*() const { return TileXY(this->x, this->y); }
|
||||
|
||||
SpiralTileIterator &operator++()
|
||||
{
|
||||
this->Increment();
|
||||
this->SkipOutsideMap();
|
||||
return *this;
|
||||
}
|
||||
|
||||
SpiralTileIterator operator++(int)
|
||||
{
|
||||
SpiralTileIterator result = *this;
|
||||
++*this;
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
/* set by constructor, const afterwards */
|
||||
uint max_radius;
|
||||
std::array<uint, DIAGDIR_END> extent;
|
||||
|
||||
/* mutable iterator state */
|
||||
uint cur_radius;
|
||||
DiagDirection dir;
|
||||
uint position;
|
||||
uint x, y;
|
||||
|
||||
void SkipOutsideMap();
|
||||
void InitPosition();
|
||||
void Increment();
|
||||
|
||||
/**
|
||||
* Test whether the iterator reached the end.
|
||||
*/
|
||||
bool IsEnd() const
|
||||
{
|
||||
return this->cur_radius == this->max_radius && this->dir != INVALID_DIAGDIR;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate TileIndices around a center tile or tile area, with increasing distance.
|
||||
*/
|
||||
class SpiralTileSequence {
|
||||
public:
|
||||
/**
|
||||
* Generate TileIndices for a square area around a center tile.
|
||||
*
|
||||
* The size of the square is given by the length of the edge.
|
||||
* If the size is even, the south extent will be larger than the north extent.
|
||||
*
|
||||
* Example for diameter=4, [ ] is the "center":
|
||||
* 1
|
||||
* 1 1
|
||||
* 1 [0] 1
|
||||
* 1 0 0 1
|
||||
* 1 0 1
|
||||
* 1 1
|
||||
* 1
|
||||
* The sequence starts with the "0" tiles, and continues with the shells around it.
|
||||
*
|
||||
* @param center Center of the square area.
|
||||
* @param diameter Edge length of the square.
|
||||
* @pre diameter > 0
|
||||
* @note This constructor uses a "diameter", unlike the other constructor using a "radius".
|
||||
*/
|
||||
SpiralTileSequence(TileIndex center, uint diameter) : start(center, diameter) {}
|
||||
|
||||
/**
|
||||
* Generate TileIndices for a rectangular area with an optional rectangular hole in the center.
|
||||
* The TileIndices will be sorted by increasing distance from the center (hole).
|
||||
*
|
||||
* Example for radius=2, w=2, h=1, [ ] is "start_north":
|
||||
* 1
|
||||
* 1 1
|
||||
* 1 [0] 1
|
||||
* 1 0 0 1
|
||||
* 1 0 H 0 1
|
||||
* 1 0 H 0 1
|
||||
* 1 0 0 1
|
||||
* 1 0 1
|
||||
* 1 1
|
||||
* 1
|
||||
* The sequence starts with the "0" tiles, and continues with the shells around it.
|
||||
*
|
||||
* @param start_north Tile directly north from the center hole.
|
||||
* @param radius Radial distance between outer rectangle and center hole.
|
||||
* @param w Width of the inner rectangular hole.
|
||||
* @param h Height of the inner rectangular hole.
|
||||
* @pre radius > 0
|
||||
* @note This constructor uses a "radius", unlike the other constructor using a "diameter".
|
||||
*/
|
||||
SpiralTileSequence(TileIndex start_north, uint radius, uint w, uint h) : start(start_north, radius, w, h) {}
|
||||
|
||||
SpiralTileIterator begin() const { return start; }
|
||||
std::default_sentinel_t end() const { return std::default_sentinel_t(); }
|
||||
|
||||
private:
|
||||
SpiralTileIterator start;
|
||||
};
|
||||
|
||||
#endif /* TILEAREA_TYPE_H */
|
||||
|
|
Loading…
Reference in New Issue