Mostly cleaned up world get to handle more rooms and paths, but rando_rect needs to be actually random.

main
Zed A. Shaw 1 week ago
parent c19c53b15b
commit e9277bf052
  1. 2
      .gdbinit
  2. 2
      dbc.cpp
  3. 43
      map.cpp
  4. 4
      map.hpp
  5. 60
      matrix.hpp
  6. 4
      status.txt
  7. 6
      tests/lighting.cpp
  8. 25
      tests/map.cpp
  9. 74
      tests/matrix.cpp
  10. 73
      worldbuilder.cpp
  11. 1
      worldbuilder.hpp

@ -7,4 +7,4 @@ set pagination off
break abort break abort
#break _invalid_parameter_noinfo #break _invalid_parameter_noinfo
#break _invalid_parameter #break _invalid_parameter
# catch throw catch throw

@ -2,7 +2,7 @@
#include <iostream> #include <iostream>
void dbc::log(const string &message) { void dbc::log(const string &message) {
std::cerr << message << std::endl; std::cerr << "!!!!!!!!!!" << message << std::endl;
} }
void dbc::sentinel(const string &message) { void dbc::sentinel(const string &message) {

@ -44,18 +44,20 @@ void Map::clear_target(const Point &at) {
$paths.clear_target(at); $paths.clear_target(at);
} }
Point Map::place_entity(size_t room_index) { bool Map::place_entity(size_t room_index, Point &out) {
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[room_index]; Room &start = $rooms[room_index];
size_t size = std::max(start.width, start.height); for(matrix::rando_rect it{$walls, start.x, start.y, start.width, start.height}; it.next();) {
for(matrix::box it{$walls, start.x, start.y, size}; it.next();) { if(!iswall(it.x, it.y)) {
if(!iswall(it.x, it.y)) return {it.x, it.y}; out.x = it.x;
out.y = it.y;
return true;
}
} }
dbc::sentinel("DIDN'T FIND AN OPEN SPACE!"); return false;
return {start.x, start.y};
} }
bool Map::iswall(size_t x, size_t y) { bool Map::iswall(size_t x, size_t y) {
@ -205,3 +207,32 @@ void Map::expand() {
$paths = Pathing($width, $height); $paths = Pathing($width, $height);
$tiles = TileMap($width, $height); $tiles = TileMap($width, $height);
} }
void Map::add_room(Room &room) {
// println(">>ADDING ROOM x/y={},{}; w/h={},{}; map={},{}",
// room.x, room.y, room.width, room.height, $width, $height);
room.x++;
room.y++;
room.width--;
room.height--;
if(room.x + room.width >= $width) {
// fix the width
room.x--;
}
if(room.y + room.height >= $height) {
// fix the height
room.y--;
}
$rooms.push_back(room);
}
void Map::invert_space() {
for(matrix::each_cell it{$walls}; it.next();) {
int is_wall = !$walls[it.y][it.x];
$walls[it.y][it.x] = is_wall;
}
}

@ -53,7 +53,7 @@ public:
Room &room(size_t at) { return $rooms[at]; } Room &room(size_t at) { return $rooms[at]; }
size_t room_count() { return $rooms.size(); } size_t room_count() { return $rooms.size(); }
Point place_entity(size_t room_index); bool place_entity(size_t room_index, Point &out);
bool inmap(size_t x, size_t y); bool inmap(size_t x, size_t y);
bool iswall(size_t x, size_t y); bool iswall(size_t x, size_t y);
bool can_move(Point move_to); bool can_move(Point move_to);
@ -73,4 +73,6 @@ public:
bool INVARIANT(); bool INVARIANT();
void load_tiles(); void load_tiles();
void add_room(Room &room);
void invert_space();
}; };

@ -3,8 +3,12 @@
#include <queue> #include <queue>
#include <string> #include <string>
#include <array> #include <array>
#include <numeric>
#include <algorithm>
#include <fmt/core.h> #include <fmt/core.h>
#include "point.hpp" #include "point.hpp"
#include "rand.hpp"
#include "dbc.hpp"
namespace matrix { namespace matrix {
using std::vector, std::queue, std::array; using std::vector, std::queue, std::array;
@ -324,4 +328,60 @@ namespace matrix {
}; };
using circle = circle_t<Matrix>; using circle = circle_t<Matrix>;
template<typename MAT>
struct rectangle_t {
int x;
int y;
int left;
int right;
int width;
int height;
int bottom;
rectangle_t(MAT &mat, size_t start_x, size_t start_y, size_t width, size_t height) :
left(start_x),
width(width),
height(height)
{
size_t h = matrix::height(mat);
size_t w = matrix::width(mat);
y = start_y - 1;
x = left - 1; // must be -1 for next()
right = min(start_x + width, w);
y = start_y;
bottom = min(start_y + height, h);
}
bool next() {
x = next_x(x, right);
y = next_y(x, y);
x = max(x, left);
return at_end(y, bottom);
}
};
using rectangle = rectangle_t<Matrix>;
template<typename MAT>
struct rando_rect_t {
int x;
int y;
rectangle_t<MAT> it;
rando_rect_t(MAT &mat, size_t start_x, size_t start_y, size_t width, size_t height) :
it{mat, start_x, start_y, width, height}
{
}
bool next() {
bool done = it.next();
x = it.x;
y = it.y;
return done;
}
};
using rando_rect = rando_rect_t<Matrix>;
} }

@ -1,5 +1,9 @@
TODAY'S GOAL: TODAY'S GOAL:
* UNKNOWN COLLISION TYPE 6
* Either reduce the amount of size_t or use amit's suggestion of 0u since small sized unsigned can be casted to size_t.
* https://github.com/Ericsson/codechecker?tab=readme-ov-file * https://github.com/Ericsson/codechecker?tab=readme-ov-file
* Goblins will be in the world and not move or are already dead. * Goblins will be in the world and not move or are already dead.
* https://pkl-lang.org/ * https://pkl-lang.org/

@ -13,9 +13,11 @@ TEST_CASE("lighting a map works", "[lighting]") {
Map map(20,23); Map map(20,23);
WorldBuilder builder(map); WorldBuilder builder(map);
builder.generate_map(); builder.generate_map();
Point light1, light2;
REQUIRE(map.place_entity(0, light1));
REQUIRE(map.place_entity(1, light1));
Point light1 = map.place_entity(0);
Point light2 = map.place_entity(1);
LightSource source1{6, 1.0}; LightSource source1{6, 1.0};
LightSource source2{4,3}; LightSource source2{4,3};

@ -30,6 +30,31 @@ TEST_CASE("camera control", "[map]") {
REQUIRE(translation.y == 2); REQUIRE(translation.y == 2);
} }
TEST_CASE("map placement test", "[map:placement]") {
for(int i = 0; i < 50; i++) {
size_t width = Random::uniform<size_t>(9, 21);
size_t height = Random::uniform<size_t>(13, 25);
Map map(width, height);
WorldBuilder builder(map);
builder.generate_rooms();
map.invert_space();
for(size_t rnum = 0; rnum < map.room_count(); rnum++) {
Room &room = map.room(rnum);
Point pos;
REQUIRE(map.place_entity(rnum, pos));
// matrix::dump("ROOM PLACEMENT TEST", map.walls(), pos.x, pos.y);
REQUIRE(!map.iswall(pos.x, pos.y));
REQUIRE(pos.x >= room.x);
REQUIRE(pos.y >= room.y);
REQUIRE(pos.x <= room.x + room.width);
REQUIRE(pos.y <= room.y + room.height);
}
}
}
TEST_CASE("dijkstra algo test", "[map]") { TEST_CASE("dijkstra algo test", "[map]") {
json data = load_test_data("./tests/dijkstra.json"); json data = load_test_data("./tests/dijkstra.json");

@ -194,7 +194,8 @@ TEST_CASE("prototype flood algorithm", "[matrix:flood]") {
if(map.room_count() < 2) continue; if(map.room_count() < 2) continue;
Point start = map.place_entity(map.room_count() / 2); Point start;
REQUIRE(map.place_entity(map.room_count() / 2, start));
map.set_target(start); map.set_target(start);
map.make_paths(); map.make_paths();
Matrix result = map.paths(); Matrix result = map.paths();
@ -286,7 +287,8 @@ TEST_CASE("viewport iterator", "[matrix:viewport]") {
size_t view_width = width/2; size_t view_width = width/2;
size_t view_height = height/2; size_t view_height = height/2;
Point player = map.place_entity(1); Point player;
REQUIRE(map.place_entity(1, player));
Point start = map.center_camera(player, view_width, view_height); Point start = map.center_camera(player, view_width, view_height);
size_t end_x = std::min(view_width, map.width() - start.x); size_t end_x = std::min(view_width, map.width() - start.x);
@ -300,3 +302,71 @@ TEST_CASE("viewport iterator", "[matrix:viewport]") {
} }
} }
} }
TEST_CASE("random rectangle", "[matrix:rando_rect]") {
for(int i = 0; i < 10; i++) {
size_t width = Random::uniform<size_t>(9, 21);
size_t height = Random::uniform<size_t>(13, 25);
Map map(width, height);
WorldBuilder builder(map);
builder.generate_rooms();
map.invert_space();
auto wall_copy = map.walls();
for(size_t rnum = 0; rnum < map.room_count(); rnum++) {
Room &room = map.room(rnum);
Point pos;
for(matrix::rando_rect it{map.walls(), room.x, room.y, room.width, room.height}; it.next();)
{
if(map.iswall(it.x, it.y)) {
matrix::dump("BAD RECTANGLE SPOT", map.walls(), it.x, it.y);
}
REQUIRE(!map.iswall(it.x, it.y));
REQUIRE(size_t(it.x) >= room.x);
REQUIRE(size_t(it.y) >= room.y);
REQUIRE(size_t(it.x) <= room.x + room.width);
REQUIRE(size_t(it.y) <= room.y + room.height);
wall_copy[it.y][it.x] = wall_copy[it.y][it.x] + 5;
}
}
matrix::dump("WALLS FILLED", wall_copy);
}
}
TEST_CASE("standard rectangle", "[matrix:rectangle]") {
for(int i = 0; i < 20; i++) {
size_t width = Random::uniform<size_t>(9, 21);
size_t height = Random::uniform<size_t>(13, 25);
Map map(width, height);
WorldBuilder builder(map);
builder.generate_rooms();
map.invert_space();
auto wall_copy = map.walls();
for(size_t rnum = 0; rnum < map.room_count(); rnum++) {
Room &room = map.room(rnum);
Point pos;
for(matrix::rectangle it{map.walls(), room.x, room.y, room.width, room.height}; it.next();)
{
if(map.iswall(it.x, it.y)) {
matrix::dump("BAD RECTANGLE SPOT", map.walls(), it.x, it.y);
}
REQUIRE(!map.iswall(it.x, it.y));
REQUIRE(size_t(it.x) >= room.x);
REQUIRE(size_t(it.y) >= room.y);
REQUIRE(size_t(it.x) <= room.x + room.width);
REQUIRE(size_t(it.y) <= room.y + room.height);
wall_copy[it.y][it.x] = wall_copy[it.y][it.x] + 5;
}
}
matrix::dump("WALLS FILLED", wall_copy);
}
}

@ -53,9 +53,10 @@ void WorldBuilder::add_door(Room &room) {
} }
void WorldBuilder::partition_map(Room &cur, int depth) { void WorldBuilder::partition_map(Room &cur, int depth) {
if(cur.width >= 5 && cur.width <= 10 && if(cur.width >= 3 && cur.width <= 6 &&
cur.height >= 5 && cur.height <= 10) { cur.height >= 3 && cur.height <= 6)
$map.$rooms.push_back(cur); {
$map.add_room(cur);
return; return;
} }
@ -97,9 +98,11 @@ void WorldBuilder::update_door(Point &at, int wall_or_space) {
void WorldBuilder::stylize_room(int room, string tile_name, float size) { void WorldBuilder::stylize_room(int room, string tile_name, float size) {
Point center = $map.place_entity(room); Point pos_out;
bool placed = $map.place_entity(room, pos_out);
dbc::check(placed, "failed to place style in room");
for(matrix::circle it{$map.$walls, center, size}; it.next();) { for(matrix::circle it{$map.$walls, pos_out, size}; it.next();) {
for(int x = it.left; x < it.right; x++) { for(int x = it.left; x < it.right; x++) {
if(!$map.iswall(x, it.y)) { if(!$map.iswall(x, it.y)) {
$map.$tiles.set_tile(x, it.y, tile_name); $map.$tiles.set_tile(x, it.y, tile_name);
@ -108,22 +111,24 @@ void WorldBuilder::stylize_room(int room, string tile_name, float size) {
} }
} }
void WorldBuilder::generate_map() { void WorldBuilder::generate_rooms() {
PointList holes;
Room root{ Room root{
.x = 0, .x = 0,
.y = 0, .y = 0,
.width = $map.$width, .width = $map.$width,
.height = $map.$height .height = $map.$height
}; };
// BUG: depth should be configurable // BUG: depth should be configurable
partition_map(root, 10); partition_map(root, 10);
place_rooms(); place_rooms();
dbc::check($map.room_count() > 0, "map generated zero rooms, map too small?"); dbc::check($map.room_count() > 0, "map generated zero rooms, map too small?");
}
void WorldBuilder::generate_map() {
generate_rooms();
PointList holes;
for(size_t i = 0; i < $map.$rooms.size() - 1; i++) { for(size_t i = 0; i < $map.$rooms.size() - 1; i++) {
tunnel_doors(holes, $map.$rooms[i], $map.$rooms[i+1]); tunnel_doors(holes, $map.$rooms[i], $map.$rooms[i+1]);
} }
@ -133,14 +138,21 @@ void WorldBuilder::generate_map() {
// place all the holes // place all the holes
for(auto hole : holes) { for(auto hole : holes) {
$map.$walls[hole.y][hole.x] = INV_SPACE;
if(!matrix::inbounds($map.$walls, hole.x, hole.y)) {
matrix::dump("MAP BEFORE CRASH", $map.$walls, hole.x, hole.y);
auto err = fmt::format("invalid hold target {},{} map is only {},{}",
hole.x, hole.y, matrix::width($map.$walls),
matrix::height($map.$walls));
dbc::sentinel(err);
} }
for(matrix::each_cell it{$map.$walls}; it.next();) { $map.$walls[hole.y][hole.x] = INV_SPACE;
int is_wall = !$map.$walls[it.y][it.x];
$map.$walls[it.y][it.x] = is_wall;
} }
$map.invert_space();
$map.expand(); $map.expand();
$map.load_tiles(); $map.load_tiles();
@ -159,9 +171,11 @@ void WorldBuilder::generate_map() {
DinkyECS::Entity place_item(DinkyECS::World &world, Map &game_map, std::string name, int in_room) { DinkyECS::Entity place_item(DinkyECS::World &world, Map &game_map, std::string name, int in_room) {
auto &config = world.get_the<GameConfig>(); auto &config = world.get_the<GameConfig>();
auto item = world.entity(); auto item = world.entity();
auto pos = game_map.place_entity(in_room); Point pos_out;
bool placed = game_map.place_entity(in_room, pos_out);
dbc::check(placed, "failed to randomly place item in room");
json item_data = config.items[name]; json item_data = config.items[name];
world.set<Position>(item, {pos.x+1, pos.y+1}); world.set<Position>(item, {pos_out.x+1, pos_out.y+1});
if(item_data["inventory_count"] > 0) { if(item_data["inventory_count"] > 0) {
world.set<InventoryItem>(item, {item_data["inventory_count"], item_data}); world.set<InventoryItem>(item, {item_data["inventory_count"], item_data});
@ -178,7 +192,9 @@ DinkyECS::Entity place_combatant(DinkyECS::World &world, Map &game_map, std::str
auto &config = world.get_the<GameConfig>(); auto &config = world.get_the<GameConfig>();
auto enemy = world.entity(); auto enemy = world.entity();
auto enemy_data = config.enemies[name]; auto enemy_data = config.enemies[name];
auto pos = game_map.place_entity(in_room); Point pos;
bool placed = game_map.place_entity(in_room, pos);
dbc::check(placed, "failed to place combatant in room");
world.set<Position>(enemy, {pos}); world.set<Position>(enemy, {pos});
if(enemy_data.contains("components")) { if(enemy_data.contains("components")) {
@ -250,11 +266,8 @@ void WorldBuilder::make_room(size_t origin_x, size_t origin_y, size_t w, size_t
void WorldBuilder::place_rooms() { void WorldBuilder::place_rooms() {
for(auto &cur : $map.$rooms) { for(auto &cur : $map.$rooms) {
cur.x += WORLDBUILD_SHRINK; // println("ROOM x/y={},{}; w/h={},{}; map={},{}",
cur.y += WORLDBUILD_SHRINK; // cur.x, cur.y, cur.width, cur.height, $map.$width, $map.$height);
cur.width -= WORLDBUILD_SHRINK * 2;
cur.height -= WORLDBUILD_SHRINK * 2;
add_door(cur); add_door(cur);
make_room(cur.x, cur.y, cur.width, cur.height); make_room(cur.x, cur.y, cur.width, cur.height);
} }
@ -281,24 +294,34 @@ inline bool random_path(Map &map, PointList &holes, Point src, Point target) {
return target_found; return target_found;
} }
inline void straight_path(PointList &holes, Point src, Point target) { inline void straight_path(Map &map, PointList &holes, Point src, Point target) {
for(matrix::line dig{src, target}; dig.next();) { for(matrix::line dig{src, target}; dig.next();) {
holes.push_back({size_t(dig.x), size_t(dig.y)}); holes.push_back({size_t(dig.x), size_t(dig.y)});
holes.push_back({size_t(dig.x+1), size_t(dig.y)}); Point expand{(size_t)dig.x+1, (size_t)dig.y};
if(map.inmap(expand.x, expand.y)) {
// BUG? should really just move doors away from the edge
holes.push_back(expand);
}
} }
} }
void WorldBuilder::tunnel_doors(PointList &holes, Room &src, Room &target) { void WorldBuilder::tunnel_doors(PointList &holes, Room &src, Room &target) {
int path_type = Random::uniform<int>(0, 3); int path_type = Random::uniform<int>(0, 3);
switch(path_type) { switch(path_type) {
case 0: case 0:
// for now do 25% as simple straight paths // for now do 25% as simple straight paths
straight_path(holes, src.exit, target.entry); straight_path($map, holes, src.exit, target.entry);
break;
case 1:
// for now do 25% as simple straight paths
straight_path($map, holes, src.exit, target.entry);
break; break;
default: default:
// then do the rest as random with fallback // then do the rest as random with fallback
if(!random_path($map, holes, src.exit, target.entry)) { if(!random_path($map, holes, src.exit, target.entry)) {
straight_path(holes, src.exit, target.entry); straight_path($map, holes, src.exit, target.entry);
} }
} }
} }

@ -17,6 +17,7 @@ class WorldBuilder {
void tunnel_doors(PointList &holes, Room &src, Room &target); void tunnel_doors(PointList &holes, Room &src, Room &target);
void update_door(Point &at, int wall_or_space); void update_door(Point &at, int wall_or_space);
void stylize_room(int room, string tile_name, float size); void stylize_room(int room, string tile_name, float size);
void generate_rooms();
void generate_map(); void generate_map();
void place_entities(DinkyECS::World &world); void place_entities(DinkyECS::World &world);
void generate(DinkyECS::World &world); void generate(DinkyECS::World &world);

Loading…
Cancel
Save