1
0
Fork 0

Codechange: Add SpiralTileSequence to iterate over a tile area the same ways as CircularTileSearch.

pull/14041/head
frosch 2025-04-18 22:48:18 +02:00 committed by frosch
parent b9bd7b2cfe
commit 0dada5a750
5 changed files with 354 additions and 0 deletions

View File

@ -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 */

View File

@ -15,5 +15,6 @@ add_test_files(
test_network_crypto.cpp
test_script_admin.cpp
test_window_desc.cpp
tilearea.cpp
utf8.cpp
)

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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 */