From 70cd963e5ca7de34577e8ca6a02e40db6fd6f5ea Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Sun, 15 Dec 2024 19:38:16 -0500 Subject: [PATCH] Iterators are now working far more reliably and have more extensive tests that randomize inputs and fuzz them to check they keep working. --- lights.cpp | 23 +++++---- map.cpp | 5 +- matrix.cpp | 79 +++++++++++++++++++++++------ matrix.hpp | 15 +++++- pathing.cpp | 29 +++++++---- scratchpad/matrixittest.cpp | 77 +++++++++++++++++++++++++++++ status.txt | 4 ++ tests/lighting.cpp | 42 ++++++++-------- tests/matrix.cpp | 99 +++++++++++++++++++++++++++++++------ tests/worldbuilder.cpp | 26 +++++----- worldbuilder.cpp | 1 + 11 files changed, 317 insertions(+), 83 deletions(-) create mode 100644 scratchpad/matrixittest.cpp diff --git a/lights.cpp b/lights.cpp index 4ec996e..d5c3e68 100644 --- a/lights.cpp +++ b/lights.cpp @@ -7,19 +7,24 @@ using std::vector; namespace lighting { void LightRender::render_light(LightSource source, Point at) { Point min, max; - light_box(source, at, min, max); clear_light_target(at); vector has_light; - for(size_t y = min.y; y <= max.y; ++y) { - auto &light_row = $lightmap[y]; - auto &path_row = $paths.$paths[y]; + matrix::in_box it{$lightmap, at.x, at.y, (size_t)source.distance}; + light_box(source, at, min, max); - for(size_t x = min.x; x <= max.x; ++x) { - if(path_row[x] != WALL_PATH_LIMIT) { - light_row[x] = light_level(source.strength, x, y); - has_light.push_back({x,y}); - } + dbc::check(it.x+1 == min.x, "box min x different"); + dbc::check(it.y == min.y, "box min y different"); + dbc::check(it.right == max.x + 1, "box max.x/right different"); + dbc::check(it.bottom == max.y + 1, "box max.y/bottom different"); + + while(it.next()) { + auto &light_row = $lightmap[it.y]; + auto &path_row = $paths.$paths[it.y]; + + if(path_row[it.x] != WALL_PATH_LIMIT) { + light_row[it.x] = light_level(source.strength, it.x, it.y); + has_light.push_back({it.x, it.y}); } } diff --git a/map.cpp b/map.cpp index 3b0959a..530dea6 100644 --- a/map.cpp +++ b/map.cpp @@ -14,7 +14,7 @@ Map::Map(size_t width, size_t height) : $width(width), $height(height), $walls(height, matrix::Row(width, INV_WALL)), - $paths(height, width) + $paths(width, height) {} Map::Map(Matrix &walls, Pathing &paths) : @@ -26,6 +26,7 @@ Map::Map(Matrix &walls, Pathing &paths) : } void Map::make_paths() { + INVARIANT(); $paths.compute_paths($walls); } @@ -159,6 +160,8 @@ bool Map::INVARIANT() { check($walls.size() == height(), "walls wrong height"); check($walls[0].size() == width(), "walls wrong width"); + check($paths.$width == width(), "in Map paths width don't match map width"); + check($paths.$height == height(), "in Map paths height don't match map height"); for(auto room : $rooms) { check(int(room.x) >= 0 && int(room.y) >= 0, diff --git a/matrix.cpp b/matrix.cpp index 75ab418..cc28b3c 100644 --- a/matrix.cpp +++ b/matrix.cpp @@ -3,7 +3,24 @@ #include using namespace fmt; -using matrix::Matrix; +using std::min, std::max; + +inline size_t next_x(size_t x, size_t width) { + return (x + 1) * ((x + 1) < width); +} + +inline size_t next_y(size_t x, size_t y) { + return y + (x == 0); +} + +inline bool at_end(size_t y, size_t height) { + return y < height; +} + +inline bool end_row(size_t x, size_t width) { + return x == width - 1; +} + namespace matrix { @@ -14,10 +31,9 @@ namespace matrix { } bool each_cell::next() { - x++; - x *= (x < width); - y = y + (x == 0); - return y < height; + x = next_x(x, width); + y = next_y(x, y); + return at_end(y, height); } each_row::each_row(Matrix &mat) : @@ -28,26 +44,59 @@ namespace matrix { } bool each_row::next() { - x++; - x *= (x < width); - y = y + (x == 0); - row = x == width - 1; - cell = y < height ? $mat[y][x] : -1; - return y < height; + x = next_x(x, width); + y = next_y(x, y); + row = end_row(x, width); + return at_end(y, height); + } + + in_box::in_box(Matrix &mat, size_t from_x, size_t from_y, size_t size) { + size_t h = mat.size(); + size_t w = mat[0].size(); + + // keeps it from going below zero + // need extra -1 to compensate for the first next() + left = max(from_x, size) - size; + x = left - 1; // must be -1 for next() + // keeps it from going above width + right = min(from_x + size + 1, w); + + // same for these two + top = max(from_y, size) - size; + y = top - (left == 0); + bottom = min(from_y + size + 1, h); + } + + bool in_box::next() { + // calc next but allow to go to 0 for next + x = next_x(x, right); + // x will go to 0, which signals new line + y = next_y(x, y); // this must go here + // if x==0 then this moves it to min_x + x = max(x, left); + // and done + return at_end(y, bottom); + } + + void in_box::dump() { + println("BOX: x={},y={}; left={},right={}; top={},bottom={}", + x, y, left, right, top, bottom); } void dump(const std::string &msg, Matrix &map, int show_x, int show_y) { println("----------------- {}", msg); for(each_row it{map}; it.next();) { + int cell = map[it.y][it.x]; + if(int(it.x) == show_x && int(it.y) == show_y) { - print("{:x}<", it.cell); - } else if(it.cell == WALL_PATH_LIMIT) { + print("{:x}<", cell); + } else if(cell == WALL_PATH_LIMIT) { print("# "); - } else if(it.cell > 15) { + } else if(cell > 15) { print("* "); } else { - print("{:x} ", it.cell); + print("{:x} ", cell); } if(it.row) print("\n"); diff --git a/matrix.hpp b/matrix.hpp index fb13b01..061194e 100644 --- a/matrix.hpp +++ b/matrix.hpp @@ -12,7 +12,6 @@ namespace matrix { size_t y = ~0; size_t width = 0; size_t height = 0; - int cell = 0; each_cell(Matrix &mat); bool next(); @@ -24,13 +23,25 @@ namespace matrix { size_t y = ~0; size_t width = 0; size_t height = 0; - int cell = 0; bool row = false; each_row(Matrix &mat); bool next(); }; + struct in_box { + size_t x = 0; // these are set in constructor + size_t y = 0; // again, no fancy ~ trick needed + size_t left = 0; + size_t top = 0; + size_t right = 0; + size_t bottom = 0; + + in_box(Matrix &mat, size_t x, size_t y, size_t size); + bool next(); + void dump(); + }; + /* * Just a quick thing to reset a matrix to a value. */ diff --git a/pathing.cpp b/pathing.cpp index 473f206..189108c 100644 --- a/pathing.cpp +++ b/pathing.cpp @@ -6,17 +6,20 @@ using std::vector; inline void add_neighbors(PointList &neighbors, Matrix &closed, size_t y, size_t x, size_t w, size_t h) { - vector rows{y - 1, y, y + 1}; - vector cols{x - 1, x, x + 1}; - - for(size_t row : rows) { - for(size_t col : cols) { - if((0 <= row && row < h) && - (0 <= col && col < w) && - closed[row][col] == 0) - { + dbc::check(h == closed.size(), "given height and closed height don't match"); + dbc::check(w == closed[0].size(), "given width and closed width don't match"); + + vector rows{int(y) - 1, int(y), int(y) + 1}; + vector cols{int(x) - 1, int(x), int(x) + 1}; + + for(int row : rows) { + for(int col : cols) { + if(row < 0 || row >= int(h)) continue; + if(col < 0 || col >= int(w)) continue; + + if(closed[row][col] == 0) { closed[row][col] = 1; - neighbors.push_back({.x=col, .y=row}); + neighbors.push_back({.x=size_t(col), .y=size_t(row)}); } } } @@ -27,6 +30,12 @@ inline void add_neighbors(PointList &neighbors, Matrix &closed, size_t y, size_t */ void Pathing::compute_paths(Matrix &walls) { INVARIANT(); + dbc::check(walls[0].size() == $width, + fmt::format("Pathing::compute_paths called with walls.width={} but paths $width={}", walls[0].size(), $width)); + + dbc::check(walls.size() == $height, + fmt::format("Pathing::compute_paths called with walls.height={} but paths $height={}", walls[0].size(), $height)); + // Initialize the new array with every pixel at limit distance matrix::assign($paths, WALL_PATH_LIMIT); diff --git a/scratchpad/matrixittest.cpp b/scratchpad/matrixittest.cpp new file mode 100644 index 0000000..64302a0 --- /dev/null +++ b/scratchpad/matrixittest.cpp @@ -0,0 +1,77 @@ +#include +#include + +struct ItStep { + int cell; + size_t x; + size_t y; + bool row; +}; + +class MatrixIterator1 { + public: + class iterator + { + public: + Matrix &mat; + size_t x = 0; + size_t y = 0; + + using iterator_category = std::input_iterator_tag; + using value_type = ItStep; + using difference_type = ItStep; + using pointer = ItStep *; + using reference = ItStep; + + explicit iterator(Matrix &_mat) + : mat(_mat) {} + + iterator& operator++() { + x++; + if(x < mat[0].size()) { + return *this; + } else { + x = 0; + y++; + return *this; + } + } + + iterator operator++(int) { + iterator retval = *this; + ++(*this); + return retval; + } + + bool operator==(iterator other) const { + return x == other.x && y == other.y; + } + + reference operator*() const { + return { + .cell = mat[y][x], + .x = x, + .y = y, + .row = x >= mat[0].size() - 1 + }; + } + }; + + Matrix &mat; + + MatrixIterator1(Matrix &mat_) : + mat(mat_) + { + } + + iterator begin() { + return iterator(mat); + } + + iterator end() { + iterator it(mat); + it.y = mat.size(); + it.x = 0; + return it; + } +}; diff --git a/status.txt b/status.txt index 3b4dfac..27e2758 100644 --- a/status.txt +++ b/status.txt @@ -1,5 +1,7 @@ TODAY'S GOAL: +* Room should always be found. + * Change the test matrix to be irregular dimensions. * Study https://github.com/hirdrac/gx_lib/blob/main/gx/Unicode.hh * Study this https://en.cppreference.com/w/cpp/language/explicit @@ -17,6 +19,8 @@ TODAY'S GOAL: TODO: +* Make the light directional. + * Hot key for debug view. * Refine the event handling to pass most of them to the gui panels and then I can intercept them. diff --git a/tests/lighting.cpp b/tests/lighting.cpp index 5a40b37..40049c2 100644 --- a/tests/lighting.cpp +++ b/tests/lighting.cpp @@ -10,33 +10,35 @@ using namespace lighting; TEST_CASE("lighting a map works", "[lighting]") { - Map map(20,20); - WorldBuilder builder(map); - builder.generate(); + for(int i = 0; i < 5; i++) { + Map map(20+i,23+i); + WorldBuilder builder(map); + builder.generate(); - Point light1 = map.place_entity(0); - Point light2 = map.place_entity(1); - LightSource source1{7,1}; - LightSource source2{3,2}; + Point light1 = map.place_entity(0); + Point light2 = map.place_entity(1); + LightSource source1{7,1}; + LightSource source2{3,2}; - LightRender lr(map.width(), map.height()); + LightRender lr(map.width(), map.height()); - lr.reset_light(); + lr.reset_light(); - lr.set_light_target(light1); - lr.set_light_target(light2); + lr.set_light_target(light1); + lr.set_light_target(light2); - lr.path_light(map.walls()); + lr.path_light(map.walls()); - lr.render_light(source1, light1); - lr.render_light(source2, light2); + lr.render_light(source1, light1); + lr.render_light(source2, light2); - lr.clear_light_target(light1); - lr.clear_light_target(light2); + lr.clear_light_target(light1); + lr.clear_light_target(light2); - const auto &lighting = lr.lighting(); + const auto &lighting = lr.lighting(); - // confirm light is set at least at and around the two points - REQUIRE(lighting[light1.y][light1.x] == lighting::LEVELS[source1.strength]); - REQUIRE(lighting[light2.y][light2.x] == lighting::LEVELS[source2.strength]); + // confirm light is set at least at and around the two points + REQUIRE(lighting[light1.y][light1.x] == lighting::LEVELS[source1.strength]); + REQUIRE(lighting[light2.y][light2.x] == lighting::LEVELS[source2.strength]); + } } diff --git a/tests/matrix.cpp b/tests/matrix.cpp index bb07bc1..554c1d6 100644 --- a/tests/matrix.cpp +++ b/tests/matrix.cpp @@ -3,6 +3,7 @@ #include #include "config.hpp" #include "matrix.hpp" +#include "rand.hpp" #include "components.hpp" #include #include @@ -19,34 +20,104 @@ TEST_CASE("basic matrix iterator", "[matrix]") { Matrix walls = test["walls"]; - matrix::dump("ITERATOR DUMP", walls); - - println("VS matrix::each_row ------"); - - for(matrix::each_row it{walls}; it.next();) { - REQUIRE(walls[it.y][it.x] == it.cell); - print("{} ", it.cell); - if(it.row) print("\n"); - } - // tests going through straight cells but also // using two iterators on one matrix (or two) matrix::each_cell cells{walls}; cells.next(); // kick it off + size_t row_count = 0; for(matrix::each_row it{walls}; it.next(); cells.next()) { - REQUIRE(walls[cells.y][cells.x] == it.cell); + REQUIRE(walls[cells.y][cells.x] == walls[it.y][it.x]); + row_count += it.row; + } + + REQUIRE(row_count == walls.size()); + + { + // test getting the correct height in the middle + row_count = 0; + matrix::in_box box{walls, 2,2, 1}; + + while(box.next()) { + row_count += box.x == box.left; + walls[box.y][box.x] = 3; + } + matrix::dump("2,2 WALLS", walls, 2, 2); + + REQUIRE(row_count == 3); + } + + { + matrix::dump("1:1 POINT", walls, 1,1); + // confirm boxes have the right number of rows + // when x goes to 0 on first next call + row_count = 0; + matrix::in_box box{walls, 1, 1, 1}; + + while(box.next()) { + row_count += box.x == box.left; + } + REQUIRE(row_count == 3); } +} - println("END TEST============="); +inline void random_matrix(Matrix &out) { + for(size_t y = 0; y < out.size(); y++) { + for(size_t x = 0; x < out[0].size(); x++) { + out[y][x] = Random::uniform(-10,10); + } + } } -TEST_CASE("matrix_assign works", "[matrix]") { +TEST_CASE("thash matrix iterators", "[matrix]") { + for(int count = 0; count < Random::uniform(10,30); count++) { + size_t width = Random::uniform(1, 100); + size_t height = Random::uniform(1, 100); + + Matrix test(width, matrix::Row(height)); + random_matrix(test); + + // first make a randomized matrix + matrix::each_cell cells{test}; + cells.next(); // kick off the other iterator + for(matrix::each_row it{test}; + it.next(); cells.next()) + { + REQUIRE(test[cells.y][cells.x] == test[it.y][it.x]); + } + } } -TEST_CASE("matrix_dump works", "[matrix]") { +TEST_CASE("thrash box iterators", "[matrix]") { + for(int count = 0; count < 20; count++) { + size_t width = Random::uniform(1, 25); + size_t height = Random::uniform(1, 33); + + Matrix test(height, matrix::Row(width)); + random_matrix(test); + + // this will be greater than the random_matrix cells + int test_i = Random::uniform(20,30); + // go through every cell + for(matrix::each_cell target{test}; target.next();) { + PointList result; + // make a random size box + size_t size = Random::uniform(1, 33); + matrix::in_box box{test, target.x, target.y, size}; + + while(box.next()) { + test[box.y][box.x] = test_i; + result.push_back({box.x, box.y}); + } + + for(auto point : result) { + REQUIRE(test[point.y][point.x] == test_i); + test[point.y][point.x] = 10; // kind of reset it for another try + } + } + } } diff --git a/tests/worldbuilder.cpp b/tests/worldbuilder.cpp index c96513e..45e60ed 100644 --- a/tests/worldbuilder.cpp +++ b/tests/worldbuilder.cpp @@ -10,25 +10,27 @@ using namespace nlohmann; using std::string; TEST_CASE("bsp algo test", "[builder]") { - Map map(20, 20); + Map map(31, 20); WorldBuilder builder(map); builder.generate(); } -TEST_CASE("dumping and debugging", "[builder]") { - Map map(20, 20); +TEST_CASE("pathing", "[builder]") { + Map map(23, 14); WorldBuilder builder(map); builder.generate(); - matrix::dump("GENERATED", map.paths()); - map.dump(); -} + matrix::dump("WALLS", map.$walls, 0,0); + println("wall at 0,0=={}", map.$walls[0][0]); -TEST_CASE("pathing", "[builder]") { - Map map(20, 20); - WorldBuilder builder(map); - builder.generate(); - REQUIRE(map.can_move({0,0}) == false); - REQUIRE(map.iswall(0,0) == true); + for(matrix::each_cell it{map.$walls}; it.next();) { + if(map.$walls[it.y][it.x] == WALL_VALUE) { + REQUIRE(map.iswall(it.x, it.y) == true); + REQUIRE(map.can_move({it.x, it.y}) == false); + } else { + REQUIRE(map.iswall(it.x, it.y) == false); + REQUIRE(map.can_move({it.x, it.y}) == true); + } + } } diff --git a/worldbuilder.cpp b/worldbuilder.cpp index b926969..d78cd5d 100644 --- a/worldbuilder.cpp +++ b/worldbuilder.cpp @@ -1,6 +1,7 @@ #include "worldbuilder.hpp" #include "rand.hpp" #include +#include using namespace fmt;