Refactored the maze functions to be a builder that can do different things to the maze. Also when I hit p in the game it'll save the map to a file. This was extremely hard for no reason.

master
Zed A. Shaw 2 weeks ago
parent 20f03731e5
commit 5f1a453fb4
  1. 2
      gui/fsm.cpp
  2. 14
      gui/map_view.cpp
  3. 1
      gui/map_view.hpp
  4. 3
      map.cpp
  5. 311
      maze.cpp
  6. 26
      maze.hpp
  7. 116
      tests/mazes.cpp
  8. 17
      worldbuilder.cpp

@ -320,6 +320,8 @@ namespace gui {
sound::mute(false); sound::mute(false);
$debug_ui.debug(); $debug_ui.debug();
shaders::reload(); shaders::reload();
dbc::log("save map!");
$map_ui.save_map("map.txt", $main_ui.$compass_dir);
break; break;
case KEY::O: case KEY::O:
autowalking = true; autowalking = true;

@ -10,6 +10,7 @@
#include <codecvt> #include <codecvt>
#include <iostream> #include <iostream>
#include <fmt/xchar.h> #include <fmt/xchar.h>
#include <fstream>
namespace gui { namespace gui {
using namespace components; using namespace components;
@ -58,6 +59,19 @@ namespace gui {
// $gui.debug_layout(window); // $gui.debug_layout(window);
} }
void MapViewUI::save_map(const std::string& outfile, int compass_dir) {
std::wstring map_out = System::draw_map(
$level, $level.map->width(), $level.map->height(), compass_dir);
dbc::check(map_out.size() > 0, "WHAT? printed map has nothing in it.");
std::wofstream out(outfile, std::ios::binary);
std::locale loc(std::locale::classic(), new std::codecvt_utf8<wchar_t>);
out.imbue(loc);
out << map_out;
dbc::check(out.good(), "failed to write map file");
}
void MapViewUI::update() { void MapViewUI::update() {
if($gui.has<Textual>($log_to)) { if($gui.has<Textual>($log_to)) {
auto& text = $gui.get<Textual>($log_to); auto& text = $gui.get<Textual>($log_to);

@ -20,5 +20,6 @@ namespace gui {
void update_level(GameLevel &level); void update_level(GameLevel &level);
void log(std::wstring msg); void log(std::wstring msg);
void update(); void update();
void save_map(const std::string& outfile, int compass_dir);
}; };
} }

@ -45,12 +45,11 @@ void Map::clear_target(const Point &at) {
} }
bool Map::place_entity(size_t room_index, Point &out) { bool Map::place_entity(size_t room_index, Point &out) {
dbc::check($dead_ends.size() != 0, "no dead ends?!");
if($rooms.size() == 0) { if($rooms.size() == 0) {
dbc::log("fucking dead end?");
out = $dead_ends.at(room_index % $dead_ends.size()); out = $dead_ends.at(room_index % $dead_ends.size());
return true; return true;
} else { } else {
dbc::log("fucking fuckng fuck fuck");
dbc::check(room_index < $rooms.size(), "room_index is out of bounds, not enough rooms"); dbc::check(room_index < $rooms.size(), "room_index is out of bounds, not enough rooms");
Room &start = $rooms.at(room_index); Room &start = $rooms.at(room_index);

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

@ -4,16 +4,26 @@
namespace maze { namespace maze {
void init(Matrix& maze);
void hunt_and_kill(Matrix& maze, std::vector<Room>& rooms, std::vector<Point>& dead_ends); struct Builder {
Matrix& $walls;
std::vector<Room>& $rooms;
std::vector<Point>& $dead_ends;
void randomize_rooms(std::vector<Room>& rooms_out, std::vector<Point>& maybe_here); Builder(Map& map) :
$walls(map.$walls), $rooms(map.$rooms), $dead_ends(map.$dead_ends)
{
init();
}
void inner_donut(Matrix& maze, float outer_rad, float inner_rad); void hunt_and_kill();
void inner_box(Matrix& map, size_t outer_size, size_t inner_size);
void divide(Matrix& maze, Point start, Point end); void init();
void randomize_rooms();
void remove_dead_ends(Matrix& maze, std::vector<Point>& dead_ends); 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);
};
} }

@ -9,112 +9,98 @@
using std::string; using std::string;
using matrix::Matrix; using matrix::Matrix;
TEST_CASE("hunt-and-kill", "[mazes]") { TEST_CASE("hunt-and-kill", "[mazes]") {
auto map = matrix::make(21, 21); Map map(21, 21);
std::vector<Room> rooms; maze::Builder maze(map);
std::vector<Point> dead_ends;
maze.hunt_and_kill();
maze.dump("BASIC MAZE");
maze::init(map); maze.randomize_rooms();
maze::hunt_and_kill(map, rooms, dead_ends); maze.hunt_and_kill();
matrix::dump("BASIC MAZE", map);
maze::randomize_rooms(rooms, dead_ends); REQUIRE(map.$dead_ends.size() > 0);
maze::hunt_and_kill(map, rooms, dead_ends); REQUIRE(map.$rooms.size() > 0);
for(auto& room : rooms) { for(auto& room : maze.$rooms) {
for(matrix::box it{map, room.x, room.y, room.width}; for(matrix::box it{maze.$walls, room.x, room.y, room.width};
it.next();) it.next();)
{ {
map[it.y][it.x] = WALL_PATH_LIMIT; maze.$walls[it.y][it.x] = WALL_PATH_LIMIT;
} }
} }
matrix::dump("MAZE WITH ROOMS", map); maze.dump("MAZE WITH ROOMS");
} }
TEST_CASE("hunt-and-kill box", "[mazes]") { TEST_CASE("hunt-and-kill box", "[mazes]") {
auto map = matrix::make(21, 21); Map map(21, 21);
std::vector<Room> rooms; maze::Builder maze(map);
std::vector<Point> dead_ends;
maze::init(map); maze.inner_box(5, 3);
maze::inner_box(map, 5, 3); maze.hunt_and_kill();
maze::hunt_and_kill(map, rooms, dead_ends);
for(auto at : dead_ends) { for(auto at : maze.$dead_ends) {
map[at.y][at.x]=32; maze.$walls[at.y][at.x]=32;
} }
matrix::dump("INNER BOX", map); maze.dump("INNER BOX");
REQUIRE(rooms.size() == 0); REQUIRE(maze.$rooms.size() == 0);
} }
TEST_CASE("hunt-and-kill ring", "[mazes]") { TEST_CASE("hunt-and-kill ring", "[mazes]") {
auto map = matrix::make(21, 21); Map map(21, 21);
std::vector<Room> rooms; maze::Builder maze(map);
std::vector<Point> dead_ends;
maze::init(map); maze.inner_donut(5.5, 3.5);
maze::inner_donut(map, 5.5, 3.5); maze.hunt_and_kill();
maze::hunt_and_kill(map, rooms, dead_ends);
for(auto at : dead_ends) { for(auto at : maze.$dead_ends) {
map[at.y][at.x]=32; maze.$walls[at.y][at.x]=32;
} }
matrix::dump("INNER RING", map); maze.dump("INNER RING");
REQUIRE(rooms.size() == 0); REQUIRE(maze.$rooms.size() == 0);
} }
TEST_CASE("hunt-and-kill fissure", "[mazes]") { TEST_CASE("hunt-and-kill fissure", "[mazes]") {
auto map = matrix::make(21, 21); Map map(21, 21);
std::vector<Room> rooms; maze::Builder maze(map);
std::vector<Point> dead_ends;
maze::init(map); maze.divide({3,3}, {19,18});
maze::divide(map, {3,3}, {19,18}); maze.hunt_and_kill();
maze::hunt_and_kill(map, rooms, dead_ends);
for(auto at : dead_ends) { for(auto at : maze.$dead_ends) {
map[at.y][at.x]=32; maze.$walls[at.y][at.x]=32;
} }
matrix::dump("FISSURE MAZE", map); maze.dump("FISSURE MAZE");
REQUIRE(rooms.size() == 0); REQUIRE(maze.$rooms.size() == 0);
} }
TEST_CASE("hunt-and-kill no-dead-ends", "[mazes]") { TEST_CASE("hunt-and-kill no-dead-ends", "[mazes]") {
auto map = matrix::make(21, 21); Map map(21, 21);
std::vector<Room> rooms; maze::Builder maze(map);
std::vector<Point> dead_ends;
maze::init(map);
maze::hunt_and_kill(map, rooms, dead_ends); maze.hunt_and_kill();
maze::remove_dead_ends(map, dead_ends); maze.remove_dead_ends();
matrix::dump("NO DEAD ENDS", map); maze.dump("NO DEAD ENDS");
} }
TEST_CASE("hunt-and-kill too much", "[mazes]") { TEST_CASE("hunt-and-kill too much", "[mazes]") {
auto map = matrix::make(21, 21); Map map(21, 21);
std::vector<Room> rooms; maze::Builder maze(map);
std::vector<Point> dead_ends;
maze::init(map);
maze::inner_donut(map, 4, 2);
maze::divide(map, {3,3}, {19,18});
auto copy = map;
maze::hunt_and_kill(copy, rooms, dead_ends);
map = copy; maze.hunt_and_kill();
maze::randomize_rooms(rooms, dead_ends); maze.randomize_rooms();
maze::hunt_and_kill(map, rooms, dead_ends); maze.init();
maze.inner_donut(4, 2);
maze.divide({3,3}, {19,18});
maze.hunt_and_kill();
matrix::dump("COMBINED", map); maze.dump("COMBINED");
} }

@ -11,17 +11,20 @@ using namespace fmt;
using namespace components; using namespace components;
void WorldBuilder::generate_map() { void WorldBuilder::generate_map() {
maze::init($map.$walls); maze::Builder maze($map);
maze::hunt_and_kill($map.$walls, $map.$rooms, $map.$dead_ends); size_t x_diff = $map.width() / 4;
maze::randomize_rooms($map.$rooms, $map.$dead_ends); size_t y_diff = $map.height() / 4;
maze::hunt_and_kill($map.$walls, $map.$rooms, $map.$dead_ends);
maze.divide({x_diff, y_diff}, {$map.width() - x_diff, $map.height() - y_diff});
maze.hunt_and_kill();
dbc::check($map.$dead_ends.size() > 0, "world builder/maze builder made a map with no dead ends.");
$map.expand(); $map.expand();
$map.load_tiles(); $map.load_tiles();
} }
DinkyECS::Entity WorldBuilder::configure_entity_in_map(DinkyECS::World &world, json &entity_data, Point pos_out) { DinkyECS::Entity WorldBuilder::configure_entity_in_map(DinkyECS::World &world, json &entity_data, Point pos_out) {
dbc::log(">>>>>>>>>>> ENTER");
auto item = world.entity(); auto item = world.entity();
world.set<Position>(item, {pos_out.x+1, pos_out.y+1}); world.set<Position>(item, {pos_out.x+1, pos_out.y+1});
@ -33,17 +36,14 @@ DinkyECS::Entity WorldBuilder::configure_entity_in_map(DinkyECS::World &world, j
components::configure_entity($components, world, item, entity_data["components"]); components::configure_entity($components, world, item, entity_data["components"]);
} }
dbc::log("<<<<<<<<<<<<< EXIT");
return item; return item;
} }
DinkyECS::Entity WorldBuilder::configure_entity_in_room(DinkyECS::World &world, json &entity_data, int in_room) { DinkyECS::Entity WorldBuilder::configure_entity_in_room(DinkyECS::World &world, json &entity_data, int in_room) {
Point pos_out; Point pos_out;
dbc::log("is it configure_entity_in_map's fault?");
bool placed = $map.place_entity(in_room, pos_out); bool placed = $map.place_entity(in_room, pos_out);
dbc::check(placed, "failed to randomly place item in room"); dbc::check(placed, "failed to randomly place item in room");
auto entity = configure_entity_in_map(world, entity_data, pos_out); auto entity = configure_entity_in_map(world, entity_data, pos_out);
dbc::log("<<<<<<<<<<<<<<<<<<<<<<<<<<< leaving configure_entity_in_room");
return entity; return entity;
} }
@ -118,7 +118,6 @@ void WorldBuilder::place_entities(DinkyECS::World &world) {
if(world.has_the<Player>()) { if(world.has_the<Player>()) {
auto& player = world.get_the<Player>(); auto& player = world.get_the<Player>();
Point pos_out; Point pos_out;
dbc::log("or is it in place_entities placing the player?");
bool placed = $map.place_entity(0, pos_out); bool placed = $map.place_entity(0, pos_out);
dbc::check(placed, "failed to randomly place item in room"); dbc::check(placed, "failed to randomly place item in room");
world.set<Position>(player.entity, {pos_out.x+1, pos_out.y+1}); world.set<Position>(player.entity, {pos_out.x+1, pos_out.y+1});

Loading…
Cancel
Save