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. 75
      worldbuilder.cpp
  11. 1
      worldbuilder.hpp

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

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

@ -44,18 +44,20 @@ void Map::clear_target(const Point &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");
Room &start = $rooms[room_index];
size_t size = std::max(start.width, start.height);
for(matrix::box it{$walls, start.x, start.y, size}; it.next();) {
if(!iswall(it.x, it.y)) return {it.x, it.y};
for(matrix::rando_rect it{$walls, start.x, start.y, start.width, start.height}; it.next();) {
if(!iswall(it.x, it.y)) {
out.x = it.x;
out.y = it.y;
return true;
}
}
dbc::sentinel("DIDN'T FIND AN OPEN SPACE!");
return {start.x, start.y};
return false;
}
bool Map::iswall(size_t x, size_t y) {
@ -205,3 +207,32 @@ void Map::expand() {
$paths = Pathing($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]; }
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 iswall(size_t x, size_t y);
bool can_move(Point move_to);
@ -73,4 +73,6 @@ public:
bool INVARIANT();
void load_tiles();
void add_room(Room &room);
void invert_space();
};

@ -3,8 +3,12 @@
#include <queue>
#include <string>
#include <array>
#include <numeric>
#include <algorithm>
#include <fmt/core.h>
#include "point.hpp"
#include "rand.hpp"
#include "dbc.hpp"
namespace matrix {
using std::vector, std::queue, std::array;
@ -324,4 +328,60 @@ namespace 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:
* 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
* Goblins will be in the world and not move or are already dead.
* https://pkl-lang.org/

@ -13,9 +13,11 @@ TEST_CASE("lighting a map works", "[lighting]") {
Map map(20,23);
WorldBuilder builder(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 source2{4,3};

@ -30,6 +30,31 @@ TEST_CASE("camera control", "[map]") {
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]") {
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;
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.make_paths();
Matrix result = map.paths();
@ -286,7 +287,8 @@ TEST_CASE("viewport iterator", "[matrix:viewport]") {
size_t view_width = width/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);
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) {
if(cur.width >= 5 && cur.width <= 10 &&
cur.height >= 5 && cur.height <= 10) {
$map.$rooms.push_back(cur);
if(cur.width >= 3 && cur.width <= 6 &&
cur.height >= 3 && cur.height <= 6)
{
$map.add_room(cur);
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) {
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++) {
if(!$map.iswall(x, it.y)) {
$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() {
PointList holes;
void WorldBuilder::generate_rooms() {
Room root{
.x = 0,
.y = 0,
.width = $map.$width,
.height = $map.$height
};
// BUG: depth should be configurable
partition_map(root, 10);
place_rooms();
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++) {
tunnel_doors(holes, $map.$rooms[i], $map.$rooms[i+1]);
}
@ -133,14 +138,21 @@ void WorldBuilder::generate_map() {
// place all the holes
for(auto hole : holes) {
$map.$walls[hole.y][hole.x] = INV_SPACE;
}
for(matrix::each_cell it{$map.$walls}; it.next();) {
int is_wall = !$map.$walls[it.y][it.x];
$map.$walls[it.y][it.x] = is_wall;
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);
}
$map.$walls[hole.y][hole.x] = INV_SPACE;
}
$map.invert_space();
$map.expand();
$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) {
auto &config = world.get_the<GameConfig>();
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];
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) {
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 enemy = world.entity();
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});
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() {
for(auto &cur : $map.$rooms) {
cur.x += WORLDBUILD_SHRINK;
cur.y += WORLDBUILD_SHRINK;
cur.width -= WORLDBUILD_SHRINK * 2;
cur.height -= WORLDBUILD_SHRINK * 2;
// println("ROOM x/y={},{}; w/h={},{}; map={},{}",
// cur.x, cur.y, cur.width, cur.height, $map.$width, $map.$height);
add_door(cur);
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;
}
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();) {
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) {
int path_type = Random::uniform<int>(0, 3);
switch(path_type) {
case 0:
// 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;
default:
// then do the rest as random with fallback
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 update_door(Point &at, int wall_or_space);
void stylize_room(int room, string tile_name, float size);
void generate_rooms();
void generate_map();
void place_entities(DinkyECS::World &world);
void generate(DinkyECS::World &world);

Loading…
Cancel
Save