diff --git a/map.hpp b/map.hpp index 0ca250a..b74f4dd 100644 --- a/map.hpp +++ b/map.hpp @@ -31,7 +31,6 @@ typedef std::vector MatrixRow; typedef std::vector Matrix; void dump_map(const std::string &msg, Matrix &map); -void add_neighbors(Matrix &closed, size_t j, size_t i); class Map { public: @@ -78,6 +77,7 @@ public: bool neighbors(Point &out, bool up); bool inmap(size_t x, size_t y); bool iswall(size_t x, size_t y); + void pathing_for(Matrix &input_map, Matrix &path_for); void make_paths(); void set_target(const Point &at, int value=0); @@ -86,6 +86,7 @@ public: Point map_to_camera(const Point &loc, const Point &cam_orig); Point center_camera(const Point &around, size_t view_x, size_t view_y); + void reset_light(); void set_light_target(const Point &at, int value=0); void clear_light_target(const Point &at); diff --git a/meson.build b/meson.build index a08d553..ee41494 100644 --- a/meson.build +++ b/meson.build @@ -30,6 +30,7 @@ runtests = executable('runtests', [ 'save.cpp', 'panel.cpp', 'render.cpp', + 'pathing.cpp', 'tests/fsm.cpp', 'tests/dbc.cpp', 'tests/map.cpp', @@ -42,6 +43,7 @@ runtests = executable('runtests', [ 'tests/render.cpp', 'tests/panel.cpp', 'tests/sound.cpp', + 'tests/pathing.cpp', ], dependencies: dependencies) @@ -60,6 +62,7 @@ roguish = executable('roguish', [ 'config.cpp', 'save.cpp', 'panel.cpp', + 'pathing.cpp', ], dependencies: dependencies) diff --git a/pathing.cpp b/pathing.cpp new file mode 100644 index 0000000..1ddd800 --- /dev/null +++ b/pathing.cpp @@ -0,0 +1,92 @@ +#include "pathing.hpp" +#include + +using std::vector; + +inline void add_neighbors(PointList &neighbors, Matrix &closed, size_t y, size_t x) { + size_t h = closed.size(); + size_t w = closed[0].size(); + 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) + { + closed[row][col] = 1; + neighbors.push_back({.x=col, .y=row}); + } + } + } +} + +inline void matrix_assign(Matrix &out, int new_value) { + for(auto &row : out) { + row.assign(row.size(), new_value); + } +} + +void Pathing::compute_paths(Matrix &walls) { + INVARIANT(); + // Initialize the new array with every pixel at limit distance + // NOTE: this is normally ones() * limit + int limit = $limit == 0 ? $height * $width : $limit; + matrix_assign($paths, limit); + + Matrix closed = walls; + PointList starting_pixels; + PointList open_pixels; + + // First pass: Add starting pixels and put them in closed + for(size_t counter = 0; counter < $height * $width; counter++) { + size_t x = counter % $width; // BUG: is this right? + size_t y = counter / $width; + if($input[y][x] == 0) { + $paths[y][x] = 0; + closed[y][x] = 1; + starting_pixels.push_back({.x=x,.y=y}); + } + } + + // Second pass: Add border to open + for(auto sp : starting_pixels) { + add_neighbors(open_pixels, closed, sp.y, sp.x); + } + + // Third pass: Iterate filling in the open list + int counter = 1; // leave this here so it's available below + for(; counter < limit && !open_pixels.empty(); ++counter) { + PointList next_open; + for(auto sp : open_pixels) { + $paths[sp.y][sp.x] = counter; + add_neighbors(next_open, closed, sp.y, sp.x); + } + open_pixels = next_open; + } + + // Last pass: flood last pixels + for(auto sp : open_pixels) { + $paths[sp.y][sp.x] = counter; + } +} + +void Pathing::set_target(const Point &at, int value) { + $input[at.y][at.x] = 0; +} + +void Pathing::clear_target(const Point &at) { + $input[at.y][at.x] = 1; +} + +bool Pathing::INVARIANT() { + using dbc::check; + + check($paths.size() == $height, "paths wrong height"); + check($paths[0].size() == $width, "paths wrong width"); + check($input.size() == $height, "input wrong height"); + check($input[0].size() == $width, "input wrong width"); + + return true; +} diff --git a/pathing.hpp b/pathing.hpp new file mode 100644 index 0000000..0a4d288 --- /dev/null +++ b/pathing.hpp @@ -0,0 +1,25 @@ +#pragma once +#include "map.hpp" +#include "point.hpp" + +class Pathing { +public: + int $limit; + size_t $width; + size_t $height; + Matrix $paths; + Matrix $input; + + Pathing(size_t width, size_t height, int limit) : + $limit(limit), + $width(width), + $height(height), + $paths(height, MatrixRow(width, 1)), + $input(height, MatrixRow(width, 1)) + {} + + void compute_paths(Matrix &walls); + void set_target(const Point &at, int value=0); + void clear_target(const Point &at); + bool INVARIANT(); +}; diff --git a/tests/pathing.cpp b/tests/pathing.cpp new file mode 100644 index 0000000..0bfe72e --- /dev/null +++ b/tests/pathing.cpp @@ -0,0 +1,34 @@ +#include +#include +#include +#include +#include "pathing.hpp" + +using namespace fmt; +using namespace nlohmann; +using std::string; + +json load_test_pathing(const string &fname) { + std::ifstream infile(fname); + return json::parse(infile); +} + +TEST_CASE("dijkstra algo test", "[pathing]") { + json data = load_test_pathing("./tests/dijkstra.json"); + + for(auto &test : data) { + Matrix expected = test["expected"]; + Matrix walls = test["walls"]; + int limit = test["limit"]; + + Pathing pathing(walls[0].size(), walls.size(), limit); + + pathing.$input = test["input"]; + + REQUIRE(pathing.INVARIANT()); + pathing.compute_paths(walls); + + REQUIRE(pathing.INVARIANT()); + REQUIRE(pathing.$paths == expected); + } +}