Algos directory is setup.

This commit is contained in:
Zed A. Shaw 2026-02-27 12:07:01 -05:00
parent 0064664556
commit b91e9ffaf6
38 changed files with 46 additions and 47 deletions

221
src/algos/maze.cpp Normal file
View file

@ -0,0 +1,221 @@
#include <fmt/core.h>
#include <string>
#include "algos/rand.hpp"
#include "constants.hpp"
#include "algos/maze.hpp"
using std::string;
using matrix::Matrix;
namespace maze {
inline size_t rand(size_t i, size_t j) {
if(i < j) {
return Random::uniform(i, j);
} else if(j < i) {
return Random::uniform(j, i);
} else {
return i;
}
}
inline bool complete(Matrix& maze) {
size_t width = matrix::width(maze);
size_t height = matrix::height(maze);
for(size_t row = 1; row < height; row += 2) {
for(size_t col = 1; col < width; col += 2) {
if(maze[row][col] != 0) return false;
}
}
return true;
}
std::vector<Point> neighborsAB(Matrix& maze, Point on) {
std::vector<Point> result;
std::array<Point, 4> points{{
{on.x, on.y - 2},
{on.x, on.y + 2},
{on.x - 2, on.y},
{on.x + 2, on.y}
}};
for(auto point : points) {
if(matrix::inbounds(maze, point.x, point.y)) {
result.push_back(point);
}
}
return result;
}
std::vector<Point> neighbors(Matrix& maze, Point on) {
std::vector<Point> result;
std::array<Point, 4> points{{
{on.x, on.y - 2},
{on.x, on.y + 2},
{on.x - 2, on.y},
{on.x + 2, on.y}
}};
for(auto point : points) {
if(matrix::inbounds(maze, point.x, point.y)) {
if(maze[point.y][point.x] == WALL_VALUE) {
result.push_back(point);
}
}
}
return result;
}
inline std::pair<Point, Point> find_coord(Matrix& maze) {
size_t width = matrix::width(maze);
size_t height = matrix::height(maze);
for(size_t y = 1; y < height; y += 2) {
for(size_t x = 1; x < width; x += 2) {
if(maze[y][x] == WALL_VALUE) {
auto found = neighborsAB(maze, {x, y});
for(auto point : found) {
if(maze[point.y][point.x] == 0) {
return {{x, y}, point};
}
}
}
}
}
matrix::dump("BAD MAZE", maze);
dbc::sentinel("failed to find coord?");
}
void Builder::randomize_rooms() {
// use those dead ends to randomly place rooms
for(auto at : $dead_ends) {
if(Random::uniform(0,1)) {
size_t offset = Random::uniform(0,1);
Room cur{at.x+offset, at.y+offset, 1, 1};
$rooms.push_back(cur);
}
}
}
void Builder::init() {
matrix::assign($walls, WALL_VALUE);
}
void Builder::divide(Point start, Point end) {
for(matrix::line it{start, end}; it.next();) {
$walls[it.y][it.x] = 0;
$walls[it.y+1][it.x] = 0;
}
}
void Builder::hunt_and_kill(Point on) {
for(auto& room : $rooms) {
for(matrix::box it{$walls, room.x, room.y, room.width}; it.next();) {
$walls[it.y][it.x] = 0;
}
}
while(!complete($walls)) {
auto n = neighbors($walls, on);
if(n.size() == 0) {
$dead_ends.push_back(on);
auto t = find_coord($walls);
on = t.first;
$walls[on.y][on.x] = 0;
size_t row = (on.y + t.second.y) / 2;
size_t col = (on.x + t.second.x) / 2;
$walls[row][col] = 0;
} else {
auto nb = n[rand(size_t(0), n.size() - 1)];
$walls[nb.y][nb.x] = 0;
size_t row = (nb.y + on.y) / 2;
size_t col = (nb.x + on.x) / 2;
$walls[row][col] = 0;
on = nb;
}
}
for(auto at : $dead_ends) {
for(auto& room : $rooms) {
Point room_ul{room.x - room.width - 1, room.y - room.height - 1};
Point room_lr{room.x + room.width - 1, room.y + room.height - 1};
if(at.x >= room_ul.x && at.y >= room_ul.y &&
at.x <= room_lr.x && at.y <= room_lr.y)
{
for(matrix::compass it{$walls, at.x, at.y}; it.next();) {
if($walls[it.y][it.x] == 1) {
$walls[it.y][it.x] = 0;
break;
}
}
}
}
}
}
void Builder::inner_donut(float outer_rad, float inner_rad) {
size_t x = matrix::width($walls) / 2;
size_t y = matrix::height($walls) / 2;
for(matrix::circle it{$walls, {x, y}, outer_rad};
it.next();)
{
for(int x = it.left; x < it.right; x++) {
$walls[it.y][x] = 0;
}
}
for(matrix::circle it{$walls, {x, y}, inner_rad};
it.next();)
{
for(int x = it.left; x < it.right; x++) {
$walls[it.y][x] = 1;
}
}
}
void Builder::inner_box(size_t outer_size, size_t inner_size) {
size_t x = matrix::width($walls) / 2;
size_t y = matrix::height($walls) / 2;
for(matrix::box it{$walls, x, y, outer_size};
it.next();)
{
$walls[it.y][it.x] = 0;
}
for(matrix::box it{$walls, x, y, inner_size};
it.next();)
{
$walls[it.y][it.x] = 1;
}
}
void Builder::remove_dead_ends() {
dbc::check($dead_ends.size() > 0, "you have to run an algo first, no dead_ends to remove");
for(auto at : $dead_ends) {
for(matrix::compass it{$walls, at.x, at.y}; it.next();) {
if($walls[it.y][it.x] == 0) {
int diff_x = at.x - it.x;
int diff_y = at.y - it.y;
$walls[at.y + diff_y][at.x + diff_x] = 0;
}
}
}
}
void Builder::dump(const std::string& msg) {
matrix::dump(msg, $walls);
}
}

29
src/algos/maze.hpp Normal file
View file

@ -0,0 +1,29 @@
#pragma once
#include "algos/matrix.hpp"
#include "map.hpp"
namespace maze {
struct Builder {
Matrix& $walls;
std::vector<Room>& $rooms;
std::vector<Point>& $dead_ends;
Builder(Map& map) :
$walls(map.$walls), $rooms(map.$rooms), $dead_ends(map.$dead_ends)
{
init();
}
void hunt_and_kill(Point on={1,1});
void init();
void randomize_rooms();
void inner_donut(float outer_rad, float inner_rad);
void inner_box(size_t outer_size, size_t inner_size);
void divide(Point start, Point end);
void remove_dead_ends();
void dump(const std::string& msg);
};
}

134
src/algos/pathing.cpp Normal file
View file

@ -0,0 +1,134 @@
#include "constants.hpp"
#include "algos/pathing.hpp"
#include "dbc.hpp"
#include <vector>
using std::vector;
inline void add_neighbors(PointList &neighbors, Matrix &closed, size_t y, size_t x) {
for(matrix::box it{closed, x, y, 1}; it.next();) {
if(closed[it.y][it.x] == 0) {
closed[it.y][it.x] = 1;
neighbors.emplace_back(it.x, it.y);
}
}
}
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);
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;
size_t y = counter / $width;
if($input[y][x] == 0) {
$paths[y][x] = 0;
closed[y][x] = 1;
starting_pixels.emplace_back(x,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 < WALL_PATH_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] = value;
}
void Pathing::clear_target(const Point &at) {
$input[at.y][at.x] = 1;
}
PathingResult Pathing::find_path(Point &out, int direction, bool diag)
{
// get the current dijkstra number
int cur = $paths[out.y][out.x];
int target = cur;
bool found = false;
// a lambda makes it easy to capture what we have to change
auto next_step = [&](size_t x, size_t y) -> bool {
target = $paths[y][x];
// don't go through walls
if(target == WALL_PATH_LIMIT) return false;
int weight = cur - target;
if(weight == direction) {
out = {x, y};
found = true;
// only break if this is a lower path
return true;
} else if(weight == 0) {
out = {x, y};
found = true;
// only found an equal path, keep checking
}
// this says keep going
return false;
};
if(diag) {
for(matrix::box it{$paths, out.x, out.y, 1}; it.next();) {
bool should_stop = next_step(it.x, it.y);
if(should_stop) break;
}
} else {
for(matrix::compass it{$paths, out.x, out.y}; it.next();) {
bool should_stop = next_step(it.x, it.y);
if(should_stop) break;
}
}
if(target == 0) {
return PathingResult::FOUND;
} else if(!found) {
return PathingResult::FAIL;
} else {
return PathingResult::CONTINUE;
}
}
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;
}

40
src/algos/pathing.hpp Normal file
View file

@ -0,0 +1,40 @@
#pragma once
#include "point.hpp"
#include "algos/matrix.hpp"
#include <functional>
using matrix::Matrix;
constexpr const int PATHING_TOWARD=1;
constexpr const int PATHING_AWAY=-1;
enum class PathingResult {
FAIL=0,
FOUND=1,
CONTINUE=2
};
class Pathing {
public:
size_t $width;
size_t $height;
Matrix $paths;
Matrix $input;
Pathing(size_t width, size_t height) :
$width(width),
$height(height),
$paths(height, matrix::Row(width, 1)),
$input(height, matrix::Row(width, 1))
{}
void compute_paths(Matrix &walls);
void set_target(const Point &at, int value=0);
void clear_target(const Point &at);
Matrix &paths() { return $paths; }
Matrix &input() { return $input; }
int distance(Point to) { return $paths[to.y][to.x];}
PathingResult find_path(Point &out, int direction, bool diag);
bool INVARIANT();
};

13
src/algos/rand.cpp Normal file
View file

@ -0,0 +1,13 @@
#include "algos/rand.hpp"
namespace Random {
std::random_device RNG;
std::mt19937 GENERATOR(RNG());
std::chrono::milliseconds milliseconds(int from, int to) {
int tick = Random::uniform_real(float(from), float(to));
return std::chrono::milliseconds{tick};
}
}

31
src/algos/rand.hpp Normal file
View file

@ -0,0 +1,31 @@
#pragma once
#include <random>
#include <chrono>
namespace Random {
extern std::mt19937 GENERATOR;
template<typename T>
T uniform(T from, T to) {
std::uniform_int_distribution<T> rand(from, to);
return rand(GENERATOR);
}
template<typename T>
T uniform_real(T from, T to) {
std::uniform_real_distribution<T> rand(from, to);
return rand(GENERATOR);
}
template<typename T>
T normal(T mean, T stddev) {
std::normal_distribution<T> rand(mean, stddev);
return rand(GENERATOR);
}
std::chrono::milliseconds milliseconds(int from, int to);
}

138
src/algos/spatialmap.cpp Normal file
View file

@ -0,0 +1,138 @@
#include "algos/spatialmap.hpp"
#include <fmt/core.h>
using namespace fmt;
using DinkyECS::Entity;
void SpatialMap::insert(Point pos, Entity ent, bool has_collision) {
if(has_collision) {
dbc::check(!occupied(pos), "attempt to insert an entity with collision in space with collision");
}
$collision.emplace(pos, CollisionData{ent, has_collision});
}
CollisionData SpatialMap::remove(Point pos, Entity ent) {
auto [begin, end] = $collision.equal_range(pos);
for(auto it = begin; it != end; ++it) {
if(it->second.entity == ent) {
// does the it->second go invalid after erase?
auto copy = it->second;
$collision.erase(it);
return copy;
}
}
dbc::sentinel("failed to find entity to remove");
}
void SpatialMap::move(Point from, Point to, Entity ent) {
auto data = remove(from, ent);
insert(to, ent, data.collision);
}
Entity SpatialMap::occupied_by(Point at) const {
auto [begin, end] = $collision.equal_range(at);
for(auto it = begin; it != end; ++it) {
if(it->second.collision) {
return it->second.entity;
}
}
return DinkyECS::NONE;
}
bool SpatialMap::occupied(Point at) const {
return occupied_by(at) != DinkyECS::NONE;
}
bool SpatialMap::something_there(Point at) const {
return $collision.count(at) > 0;
}
Entity SpatialMap::get(Point at) const {
dbc::check($collision.contains(at), "attempt to get entity when none there");
auto [begin, end] = $collision.equal_range(at);
return begin->second.entity;
}
void SpatialMap::find_neighbor(EntityList &result, Point at, int dy, int dx) const {
// don't bother checking for cells out of bounds
if((dx < 0 && at.x <= 0) || (dy < 0 && at.y <= 0)) {
return;
}
Point cell = {at.x + dx, at.y + dy};
auto entity = find(cell, [&](auto data) {
return data.collision;
});
if(entity != DinkyECS::NONE) result.push_back(entity);
}
FoundEntities SpatialMap::neighbors(Point cell, bool diag) const {
EntityList result;
// just unroll the loop since we only check four directions
// this also solves the problem that it was detecting that the cell was automatically included as a "neighbor" but it's not
find_neighbor(result, cell, 0, 1); // north
find_neighbor(result, cell, 0, -1); // south
find_neighbor(result, cell, 1, 0); // east
find_neighbor(result, cell, -1, 0); // west
if(diag) {
find_neighbor(result, cell, 1, -1); // south east
find_neighbor(result, cell, -1, -1); // south west
find_neighbor(result, cell, 1, 1); // north east
find_neighbor(result, cell, -1, 1); // north west
}
return {!result.empty(), result};
}
inline void update_sorted(SortedEntities& sprite_distance, PointEntityMap& table, Point from, int max_dist) {
Point seen{0,0};
float wiggle = 0.0f;
for(const auto &rec : table) {
Point sprite = rec.first;
int inside = (from.x - sprite.x) * (from.x - sprite.x) +
(from.y - sprite.y) * (from.y - sprite.y);
if(from == sprite || rec.second.collision) {
wiggle = 0.0f;
} else if(sprite == seen) {
wiggle += 0.02f;
} else {
wiggle = 0.0f;
seen = sprite;
}
if(inside < max_dist) {
sprite_distance.push_back({inside, rec.second.entity, wiggle});
}
}
}
Entity SpatialMap::find(Point at, std::function<bool(CollisionData)> cb) const {
auto [begin, end] = $collision.equal_range(at);
for(auto it = begin; it != end; ++it) {
if(cb(it->second)) return it->second.entity;
}
return DinkyECS::NONE;
}
void SpatialMap::distance_sorted(SortedEntities& sprite_distance, Point from, int max_dist) {
sprite_distance.clear();
update_sorted(sprite_distance, $collision, from, max_dist);
std::sort(sprite_distance.begin(), sprite_distance.end(), [](auto &a, auto &b) {
return a.dist_square > b.dist_square;
});
}

49
src/algos/spatialmap.hpp Normal file
View file

@ -0,0 +1,49 @@
#pragma once
#include <vector>
#include <unordered_map>
#include "map.hpp"
#include "dinkyecs.hpp"
#include "point.hpp"
struct CollisionData {
DinkyECS::Entity entity = DinkyECS::NONE;
bool collision = false;
};
struct EntityDistance {
int dist_square=0;
DinkyECS::Entity entity=DinkyECS::NONE;
float wiggle=0.0f;
};
// Point's has is in point.hpp
using EntityList = std::vector<DinkyECS::Entity>;
using PointEntityMap = std::unordered_multimap<Point, CollisionData>;
using SortedEntities = std::vector<EntityDistance>;
struct FoundEntities {
bool found;
EntityList nearby;
};
class SpatialMap {
public:
SpatialMap() {}
PointEntityMap $collision;
void insert(Point pos, DinkyECS::Entity obj, bool has_collision);
void move(Point from, Point to, DinkyECS::Entity ent);
// return value is whether the removed thing has collision
CollisionData remove(Point pos, DinkyECS::Entity entity);
DinkyECS::Entity occupied_by(Point pos) const;
bool occupied(Point pos) const;
bool something_there(Point at) const;
DinkyECS::Entity get(Point at) const;
DinkyECS::Entity find(Point at, std::function<bool(CollisionData)> cb) const;
void find_neighbor(EntityList &result, Point at, int dy, int dx) const;
FoundEntities neighbors(Point position, bool diag=false) const;
void distance_sorted(SortedEntities& sorted_sprites, Point from, int max_distance);
size_t size() { return $collision.size(); }
};

11
src/algos/stats.cpp Normal file
View file

@ -0,0 +1,11 @@
#include "algos/stats.hpp"
#include <fmt/core.h>
#include "dbc.hpp"
void Stats::dump(std::string msg)
{
dbc::log(fmt::format("{}: sum: {}, sumsq: {}, n: {}, "
"min: {}, max: {}, mean: {}, stddev: {}",
msg, sum, sumsq, n, min, max, mean(),
stddev()));
}

59
src/algos/stats.hpp Normal file
View file

@ -0,0 +1,59 @@
#pragma once
#include <cmath>
#include <chrono>
struct Stats {
using TimeBullshit = std::chrono::time_point<std::chrono::high_resolution_clock>;
double sum = 0.0;
double sumsq = 0.0;
double n = 0.0;
double min = 0.0;
double max = 0.0;
inline void reset() {
sum = 0.0;
sumsq = 0.0;
n = 0.0;
min = 0.0;
max = 0.0;
}
inline double mean() {
return sum / n;
}
inline double stddev() {
return std::sqrt((sumsq - (sum * sum / n)) / (n - 1));
}
inline void sample(double s) {
sum += s;
sumsq += s * s;
if (n == 0) {
min = s;
max = s;
} else {
if (min > s) min = s;
if (max < s) max = s;
}
n += 1;
}
inline TimeBullshit time_start() {
return std::chrono::high_resolution_clock::now();
}
inline void sample_time(TimeBullshit start) {
auto end = std::chrono::high_resolution_clock::now();
auto elapsed = std::chrono::duration<double>(end - start);
if(elapsed.count() > 0.0) {
sample(1.0/elapsed.count());
}
}
void dump(std::string msg="");
};