Iterators are now working far more reliably and have more extensive tests that randomize inputs and fuzz them to check they keep working.

main
Zed A. Shaw 10 months ago
parent 8e470df554
commit 70cd963e5c
  1. 23
      lights.cpp
  2. 5
      map.cpp
  3. 79
      matrix.cpp
  4. 15
      matrix.hpp
  5. 29
      pathing.cpp
  6. 77
      scratchpad/matrixittest.cpp
  7. 4
      status.txt
  8. 42
      tests/lighting.cpp
  9. 99
      tests/matrix.cpp
  10. 26
      tests/worldbuilder.cpp
  11. 1
      worldbuilder.cpp

@ -7,19 +7,24 @@ using std::vector;
namespace lighting { namespace lighting {
void LightRender::render_light(LightSource source, Point at) { void LightRender::render_light(LightSource source, Point at) {
Point min, max; Point min, max;
light_box(source, at, min, max);
clear_light_target(at); clear_light_target(at);
vector<Point> has_light; vector<Point> has_light;
for(size_t y = min.y; y <= max.y; ++y) { matrix::in_box it{$lightmap, at.x, at.y, (size_t)source.distance};
auto &light_row = $lightmap[y]; light_box(source, at, min, max);
auto &path_row = $paths.$paths[y];
for(size_t x = min.x; x <= max.x; ++x) { dbc::check(it.x+1 == min.x, "box min x different");
if(path_row[x] != WALL_PATH_LIMIT) { dbc::check(it.y == min.y, "box min y different");
light_row[x] = light_level(source.strength, x, y); dbc::check(it.right == max.x + 1, "box max.x/right different");
has_light.push_back({x,y}); 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});
} }
} }

@ -14,7 +14,7 @@ Map::Map(size_t width, size_t height) :
$width(width), $width(width),
$height(height), $height(height),
$walls(height, matrix::Row(width, INV_WALL)), $walls(height, matrix::Row(width, INV_WALL)),
$paths(height, width) $paths(width, height)
{} {}
Map::Map(Matrix &walls, Pathing &paths) : Map::Map(Matrix &walls, Pathing &paths) :
@ -26,6 +26,7 @@ Map::Map(Matrix &walls, Pathing &paths) :
} }
void Map::make_paths() { void Map::make_paths() {
INVARIANT();
$paths.compute_paths($walls); $paths.compute_paths($walls);
} }
@ -159,6 +160,8 @@ bool Map::INVARIANT() {
check($walls.size() == height(), "walls wrong height"); check($walls.size() == height(), "walls wrong height");
check($walls[0].size() == width(), "walls wrong width"); 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) { for(auto room : $rooms) {
check(int(room.x) >= 0 && int(room.y) >= 0, check(int(room.x) >= 0 && int(room.y) >= 0,

@ -3,7 +3,24 @@
#include <fmt/core.h> #include <fmt/core.h>
using namespace fmt; 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 { namespace matrix {
@ -14,10 +31,9 @@ namespace matrix {
} }
bool each_cell::next() { bool each_cell::next() {
x++; x = next_x(x, width);
x *= (x < width); y = next_y(x, y);
y = y + (x == 0); return at_end(y, height);
return y < height;
} }
each_row::each_row(Matrix &mat) : each_row::each_row(Matrix &mat) :
@ -28,26 +44,59 @@ namespace matrix {
} }
bool each_row::next() { bool each_row::next() {
x++; x = next_x(x, width);
x *= (x < width); y = next_y(x, y);
y = y + (x == 0); row = end_row(x, width);
row = x == width - 1; return at_end(y, height);
cell = y < height ? $mat[y][x] : -1; }
return 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) { void dump(const std::string &msg, Matrix &map, int show_x, int show_y) {
println("----------------- {}", msg); println("----------------- {}", msg);
for(each_row it{map}; it.next();) { 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) { if(int(it.x) == show_x && int(it.y) == show_y) {
print("{:x}<", it.cell); print("{:x}<", cell);
} else if(it.cell == WALL_PATH_LIMIT) { } else if(cell == WALL_PATH_LIMIT) {
print("# "); print("# ");
} else if(it.cell > 15) { } else if(cell > 15) {
print("* "); print("* ");
} else { } else {
print("{:x} ", it.cell); print("{:x} ", cell);
} }
if(it.row) print("\n"); if(it.row) print("\n");

@ -12,7 +12,6 @@ namespace matrix {
size_t y = ~0; size_t y = ~0;
size_t width = 0; size_t width = 0;
size_t height = 0; size_t height = 0;
int cell = 0;
each_cell(Matrix &mat); each_cell(Matrix &mat);
bool next(); bool next();
@ -24,13 +23,25 @@ namespace matrix {
size_t y = ~0; size_t y = ~0;
size_t width = 0; size_t width = 0;
size_t height = 0; size_t height = 0;
int cell = 0;
bool row = false; bool row = false;
each_row(Matrix &mat); each_row(Matrix &mat);
bool next(); 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. * Just a quick thing to reset a matrix to a value.
*/ */

@ -6,17 +6,20 @@
using std::vector; using std::vector;
inline void add_neighbors(PointList &neighbors, Matrix &closed, size_t y, size_t x, size_t w, size_t h) { inline void add_neighbors(PointList &neighbors, Matrix &closed, size_t y, size_t x, size_t w, size_t h) {
vector<size_t> rows{y - 1, y, y + 1}; dbc::check(h == closed.size(), "given height and closed height don't match");
vector<size_t> cols{x - 1, x, x + 1}; dbc::check(w == closed[0].size(), "given width and closed width don't match");
for(size_t row : rows) { vector<int> rows{int(y) - 1, int(y), int(y) + 1};
for(size_t col : cols) { vector<int> cols{int(x) - 1, int(x), int(x) + 1};
if((0 <= row && row < h) &&
(0 <= col && col < w) && for(int row : rows) {
closed[row][col] == 0) 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; 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) { void Pathing::compute_paths(Matrix &walls) {
INVARIANT(); 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 // Initialize the new array with every pixel at limit distance
matrix::assign($paths, WALL_PATH_LIMIT); matrix::assign($paths, WALL_PATH_LIMIT);

@ -0,0 +1,77 @@
#include <algorithm>
#include <iostream>
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;
}
};

@ -1,5 +1,7 @@
TODAY'S GOAL: TODAY'S GOAL:
* Room should always be found.
* Change the test matrix to be irregular dimensions. * Change the test matrix to be irregular dimensions.
* Study https://github.com/hirdrac/gx_lib/blob/main/gx/Unicode.hh * Study https://github.com/hirdrac/gx_lib/blob/main/gx/Unicode.hh
* Study this https://en.cppreference.com/w/cpp/language/explicit * Study this https://en.cppreference.com/w/cpp/language/explicit
@ -17,6 +19,8 @@ TODAY'S GOAL:
TODO: TODO:
* Make the light directional.
* Hot key for debug view. * Hot key for debug view.
* Refine the event handling to pass most of them to the gui panels and then I can intercept them. * Refine the event handling to pass most of them to the gui panels and then I can intercept them.

@ -10,33 +10,35 @@
using namespace lighting; using namespace lighting;
TEST_CASE("lighting a map works", "[lighting]") { TEST_CASE("lighting a map works", "[lighting]") {
Map map(20,20); for(int i = 0; i < 5; i++) {
WorldBuilder builder(map); Map map(20+i,23+i);
builder.generate(); WorldBuilder builder(map);
builder.generate();
Point light1 = map.place_entity(0); Point light1 = map.place_entity(0);
Point light2 = map.place_entity(1); Point light2 = map.place_entity(1);
LightSource source1{7,1}; LightSource source1{7,1};
LightSource source2{3,2}; 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(light1);
lr.set_light_target(light2); lr.set_light_target(light2);
lr.path_light(map.walls()); lr.path_light(map.walls());
lr.render_light(source1, light1); lr.render_light(source1, light1);
lr.render_light(source2, light2); lr.render_light(source2, light2);
lr.clear_light_target(light1); lr.clear_light_target(light1);
lr.clear_light_target(light2); 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 // confirm light is set at least at and around the two points
REQUIRE(lighting[light1.y][light1.x] == lighting::LEVELS[source1.strength]); REQUIRE(lighting[light1.y][light1.x] == lighting::LEVELS[source1.strength]);
REQUIRE(lighting[light2.y][light2.x] == lighting::LEVELS[source2.strength]); REQUIRE(lighting[light2.y][light2.x] == lighting::LEVELS[source2.strength]);
}
} }

@ -3,6 +3,7 @@
#include <string> #include <string>
#include "config.hpp" #include "config.hpp"
#include "matrix.hpp" #include "matrix.hpp"
#include "rand.hpp"
#include "components.hpp" #include "components.hpp"
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <fstream> #include <fstream>
@ -19,34 +20,104 @@ TEST_CASE("basic matrix iterator", "[matrix]") {
Matrix walls = test["walls"]; 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 // tests going through straight cells but also
// using two iterators on one matrix (or two) // using two iterators on one matrix (or two)
matrix::each_cell cells{walls}; matrix::each_cell cells{walls};
cells.next(); // kick it off cells.next(); // kick it off
size_t row_count = 0;
for(matrix::each_row it{walls}; for(matrix::each_row it{walls};
it.next(); cells.next()) 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<int>(-10,10);
}
}
} }
TEST_CASE("matrix_assign works", "[matrix]") { TEST_CASE("thash matrix iterators", "[matrix]") {
for(int count = 0; count < Random::uniform<int>(10,30); count++) {
size_t width = Random::uniform<size_t>(1, 100);
size_t height = Random::uniform<size_t>(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<size_t>(1, 25);
size_t height = Random::uniform<size_t>(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<size_t>(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<int>(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
}
}
}
} }

@ -10,25 +10,27 @@ using namespace nlohmann;
using std::string; using std::string;
TEST_CASE("bsp algo test", "[builder]") { TEST_CASE("bsp algo test", "[builder]") {
Map map(20, 20); Map map(31, 20);
WorldBuilder builder(map); WorldBuilder builder(map);
builder.generate(); builder.generate();
} }
TEST_CASE("dumping and debugging", "[builder]") { TEST_CASE("pathing", "[builder]") {
Map map(20, 20); Map map(23, 14);
WorldBuilder builder(map); WorldBuilder builder(map);
builder.generate(); builder.generate();
matrix::dump("GENERATED", map.paths()); matrix::dump("WALLS", map.$walls, 0,0);
map.dump(); 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); for(matrix::each_cell it{map.$walls}; it.next();) {
REQUIRE(map.iswall(0,0) == true); 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);
}
}
} }

@ -1,6 +1,7 @@
#include "worldbuilder.hpp" #include "worldbuilder.hpp"
#include "rand.hpp" #include "rand.hpp"
#include <fmt/core.h> #include <fmt/core.h>
#include <iostream>
using namespace fmt; using namespace fmt;