BROKEN: Big refactoring happening, so it compiles but game does not run and the tests fail.

master
Zed A. Shaw 4 weeks ago
parent 96efc990c1
commit 9e91c71125
  1. 12
      assets/devices.json
  2. 18
      assets/enemies.json
  3. 26
      assets/items.json
  4. 45
      components.cpp
  5. 20
      components.hpp
  6. 8
      dinkyecs.cpp
  7. 2
      dinkyecs.hpp
  8. 2
      levelmanager.cpp
  9. 1
      levelmanager.hpp
  10. 3
      map.hpp
  11. 2
      meson.build
  12. 3
      point.hpp
  13. 66
      save.cpp
  14. 7
      save.hpp
  15. 4
      tests/dinkyecs.cpp
  16. 8
      tests/inventory.cpp
  17. 9
      tests/lighting.cpp
  18. 17
      tests/map.cpp
  19. 70
      tests/matrix.cpp
  20. 42
      tests/save.cpp
  21. 11
      tests/tilemap.cpp
  22. 35
      tests/worldbuilder.cpp
  23. 220
      tser.hpp
  24. 18
      worldbuilder.cpp
  25. 9
      worldbuilder.hpp

@ -9,8 +9,8 @@
"inventory_count": 0, "inventory_count": 0,
"randomized": false, "randomized": false,
"components": [ "components": [
{"type": "Tile", "config": {"chr": "\u2ac5"}}, {"_type": "Tile", "config": {"chr": "\u2ac5"}},
{"type": "Device", {"_type": "Device",
"config": {"test": true}, "events": ["Events::GUI::STAIRS_DOWN"] "config": {"test": true}, "events": ["Events::GUI::STAIRS_DOWN"]
} }
] ]
@ -24,8 +24,8 @@
"inventory_count": 0, "inventory_count": 0,
"placement": "fixed", "placement": "fixed",
"components": [ "components": [
{"type": "Tile", "config": {"chr": "\u2259"}}, {"_type": "Tile", "config": {"chr": "\u2259"}},
{"type": "Device", {"_type": "Device",
"config": {"test": true}, "events": ["Events::GUI::STAIRS_UP"] "config": {"test": true}, "events": ["Events::GUI::STAIRS_UP"]
} }
] ]
@ -38,8 +38,8 @@
"description": "Spikes stab you from the floor.", "description": "Spikes stab you from the floor.",
"inventory_count": 0, "inventory_count": 0,
"components": [ "components": [
{"type": "Tile", "config": {"chr": "\u1ac7"}}, {"_type": "Tile", "config": {"chr": "\u1ac7"}},
{"type": "Device", {"_type": "Device",
"config": {"test": true}, "events": ["Events::GUI::TRAP"] "config": {"test": true}, "events": ["Events::GUI::TRAP"]
} }
] ]

@ -3,21 +3,21 @@
"foreground": [255, 200, 125], "foreground": [255, 200, 125],
"background": [30, 20, 75], "background": [30, 20, 75],
"components": [ "components": [
{"type": "Tile", "config": {"chr": "\ua66b"}}, {"_type": "Tile", "config": {"chr": "\ua66b"}},
{"type": "Combat", "config": {"hp": 200, "damage": 15}}, {"_type": "Combat", "config": {"hp": 200, "damage": 15}},
{"type": "Motion", "config": {"dx": 0, "dy": 0, "random": false}}, {"_type": "Motion", "config": {"dx": 0, "dy": 0, "random": false}},
{"type": "LightSource", "config": {"strength": 70, "radius": 2}}, {"_type": "LightSource", "config": {"strength": 70, "radius": 2}},
{"type": "EnemyConfig", "config": {"hearing_distance": 5}} {"_type": "EnemyConfig", "config": {"hearing_distance": 5}}
] ]
}, },
"EVIL_EYE": { "EVIL_EYE": {
"foreground": [75, 200, 125], "foreground": [75, 200, 125],
"background": [30, 20, 75], "background": [30, 20, 75],
"components": [ "components": [
{"type": "Tile", "config": {"chr": "\u08ac"}}, {"_type": "Tile", "config": {"chr": "\u08ac"}},
{"type": "Combat", "config": {"hp": 100, "damage": 50}}, {"_type": "Combat", "config": {"hp": 100, "damage": 50}},
{"type": "Motion", "config": {"dx": 0, "dy": 0, "random": false}}, {"_type": "Motion", "config": {"dx": 0, "dy": 0, "random": false}},
{"type": "EnemyConfig", "config": {"hearing_distance": 10}} {"_type": "EnemyConfig", "config": {"hearing_distance": 10}}
] ]
} }
} }

@ -7,8 +7,8 @@
"description": "A torch that barely lights the way. You wonder if it'd be better to not see the person who murders you.", "description": "A torch that barely lights the way. You wonder if it'd be better to not see the person who murders you.",
"inventory_count": 1, "inventory_count": 1,
"components": [ "components": [
{"type": "LightSource", "config": {"strength": 70, "radius": 2.0}}, {"_type": "LightSource", "config": {"strength": 70, "radius": 2.0}},
{"type": "Tile", "config": {"chr": "\u0f08"}} {"_type": "Tile", "config": {"chr": "\u0f08"}}
] ]
}, },
"SWORD_RUSTY": { "SWORD_RUSTY": {
@ -19,8 +19,8 @@
"description": "A sword left to rot in a deep hole where it acquired a patina of dirt and tetanus. You aren't sure if it's more deadly for you to hold it or for the people you stab with it.", "description": "A sword left to rot in a deep hole where it acquired a patina of dirt and tetanus. You aren't sure if it's more deadly for you to hold it or for the people you stab with it.",
"inventory_count": 1, "inventory_count": 1,
"components": [ "components": [
{"type": "Weapon", "config": {"damage": 15}}, {"_type": "Weapon", "config": {"damage": 15}},
{"type": "Tile", "config": {"chr": "\u1e37"}} {"_type": "Tile", "config": {"chr": "\u1e37"}}
] ]
}, },
"SWORD_LIGHT_AND_FLAME": { "SWORD_LIGHT_AND_FLAME": {
@ -31,9 +31,9 @@
"description": "A sword so powerful, a great man from the Land of The Rising Sun thrust it into the ocean of Nerf to chill its effects.", "description": "A sword so powerful, a great man from the Land of The Rising Sun thrust it into the ocean of Nerf to chill its effects.",
"inventory_count": 1, "inventory_count": 1,
"components": [ "components": [
{"type": "LightSource", "config": {"strength": 70, "radius": 1.8}}, {"_type": "LightSource", "config": {"strength": 70, "radius": 1.8}},
{"type": "Tile", "config": {"chr": "\u0236"}}, {"_type": "Tile", "config": {"chr": "\u0236"}},
{"type": "Weapon", "config": {"damage": 30}} {"_type": "Weapon", "config": {"damage": 30}}
] ]
}, },
"CHEST_SMALL": { "CHEST_SMALL": {
@ -43,8 +43,8 @@
"background": [150, 100, 189], "background": [150, 100, 189],
"description": "A small chest of gold. You wonder who would leave something like this around.", "description": "A small chest of gold. You wonder who would leave something like this around.",
"components": [ "components": [
{"type": "Tile", "config": {"chr": "\uaaea"}}, {"_type": "Tile", "config": {"chr": "\uaaea"}},
{"type": "Loot", "config": {"amount": 10}} {"_type": "Loot", "config": {"amount": 10}}
], ],
"inventory_count": 1 "inventory_count": 1
}, },
@ -56,8 +56,8 @@
"description": "A torch on a wall you can't pick up.", "description": "A torch on a wall you can't pick up.",
"inventory_count": 0, "inventory_count": 0,
"components": [ "components": [
{"type": "Tile", "config": {"chr": "\u077e"}}, {"_type": "Tile", "config": {"chr": "\u077e"}},
{"type": "LightSource", "config": {"strength": 60, "radius": 1.8}} {"_type": "LightSource", "config": {"strength": 60, "radius": 1.8}}
] ]
}, },
"POTION_HEALING_SMALL": { "POTION_HEALING_SMALL": {
@ -68,8 +68,8 @@
"description": "A small healing potion.", "description": "A small healing potion.",
"inventory_count": 1, "inventory_count": 1,
"components": [ "components": [
{"type": "Tile", "config": {"chr": "\u03eb"}}, {"_type": "Tile", "config": {"chr": "\u03eb"}},
{"type": "Curative", "config": {"hp": 20}} {"_type": "Curative", "config": {"hp": 20}}
] ]
} }
} }

@ -1,45 +0,0 @@
#include "components.hpp"
namespace components {
void configure(DinkyECS::World &world, DinkyECS::Entity entity, json& entity_data) {
for(auto &comp : entity_data["components"]) {
json& config = comp["config"];
const string comp_type = comp["type"];
if(comp_type == "Weapon") {
world.set<Weapon>(entity, {config["damage"]});
} else if(comp_type == "LightSource") {
world.set<LightSource>(entity, {config["strength"], config["radius"]});
} else if(comp_type == "Loot") {
world.set<Loot>(entity, {config["amount"]});
} else if(comp_type == "Tile") {
world.set<Tile>(entity, {
config["chr"],
entity_data["foreground"][0],
entity_data["foreground"][1],
entity_data["foreground"][2],
entity_data["background"][0],
entity_data["background"][1],
entity_data["background"][2]});
} else if(comp_type == "EnemyConfig") {
world.set<EnemyConfig>(entity, {config["hearing_distance"]});
} else if(comp_type == "Combat") {
world.set<Combat>(entity, {config["hp"], config["damage"]});
} else if(comp_type == "Curative") {
world.set<Curative>(entity, {config["hp"]});
} else if(comp_type == "Motion") {
world.set<Motion>(entity, {config["dx"], config["dy"], config["random"]});
} else if(comp_type == "Device") {
Device device{.config=config, .events={}};
device.configure_events(comp["events"]);
world.set<Device>(entity, device);
} else {
dbc::sentinel(fmt::format("ITEM COMPONENT TYPE MISSING: {}",
std::string(comp_type)));
}
// json config variable dies
}
}
}

@ -3,43 +3,38 @@
#include "devices.hpp" #include "devices.hpp"
#include "combat.hpp" #include "combat.hpp"
#include "inventory.hpp" #include "inventory.hpp"
#include "tser.hpp"
#include "config.hpp" #include "config.hpp"
namespace components { namespace components {
struct Player { struct Player {
DinkyECS::Entity entity; DinkyECS::Entity entity;
DEFINE_SERIALIZABLE(Player, entity);
}; };
struct Position { struct Position {
Point location; Point location;
DEFINE_SERIALIZABLE(Position, location);
}; };
struct Motion { struct Motion {
int dx; int dx;
int dy; int dy;
bool random=false; bool random=false;
DEFINE_SERIALIZABLE(Motion, dx, dy);
}; };
struct Loot { struct Loot {
int amount; int amount;
DEFINE_SERIALIZABLE(Loot, amount);
}; };
struct Tile { struct Tile {
std::string chr; std::string chr;
std::array<uint8_t, 3> foreground;
std::array<uint8_t, 3> background;
uint8_t fg_h = 200; uint8_t fg_h = 200;
uint8_t fg_s = 20; uint8_t fg_s = 20;
uint8_t fg_v = 200; uint8_t fg_v = 200;
uint8_t bg_h = 100; uint8_t bg_h = 100;
uint8_t bg_s = 20; uint8_t bg_s = 20;
uint8_t bg_v = 0; uint8_t bg_v = 0;
DEFINE_SERIALIZABLE(Tile, chr);
}; };
struct GameConfig { struct GameConfig {
@ -66,6 +61,13 @@ namespace components {
struct Curative { struct Curative {
int hp = 10; int hp = 10;
}; };
void configure(DinkyECS::World &world, DinkyECS::Entity entity, json& entity_data);
} }
DINKY_HAS_COMPONENT(components::Loot, amount);
DINKY_HAS_COMPONENT(Point, x, y);
DINKY_HAS_COMPONENT(components::Position, location);
DINKY_HAS_COMPONENT(components::Weapon, damage);
DINKY_HAS_COMPONENT(components::Curative, hp);
DINKY_HAS_COMPONENT(components::EnemyConfig, hearing_distance);
DINKY_HAS_COMPONENT(components::Tile, chr, foreground, background);
DINKY_HAS_COMPONENT(components::Motion, dx, dy, random);

@ -1,10 +1,12 @@
#include "dinkyecs.hpp" #include "dinkyecs.hpp"
#include "dbc.hpp"
#include <fmt/core.h>
namespace DinkyECS { namespace DinkyECS {
void configure(World& world, const ComponentMap& component_map, Entity ent, json& data) { void configure(const ComponentMap& component_map, World& world, Entity ent, json& data) {
for (auto &i : data) { for (auto &i : data) {
assert(i.contains("_type") && i["_type"].is_string()); dbc::check(i.contains("_type") && i["_type"].is_string(), fmt::format("component has no _type: {}", data.dump()));
assert(component_map.contains(i["_type"])); dbc::check(component_map.contains(i["_type"]), fmt::format("component_map doesn't have type {}", std::string(i["_type"])));
component_map.at(i["_type"])(world, ent, i); component_map.at(i["_type"])(world, ent, i);
} }
} }

@ -178,6 +178,6 @@ namespace DinkyECS {
}; };
} }
void configure(World& world, const ComponentMap& component_map, Entity ent, json& data); void configure(const ComponentMap& component_map, World& world, Entity ent, json& data);
} }

@ -32,7 +32,7 @@ size_t LevelManager::create_level(shared_ptr<DinkyECS::World> prev_world) {
auto scaling = scale_level(); auto scaling = scale_level();
auto map = make_shared<Map>(scaling.map_width, scaling.map_height); auto map = make_shared<Map>(scaling.map_width, scaling.map_height);
WorldBuilder builder(*map); WorldBuilder builder(*map, $components);
builder.generate(*world); builder.generate(*world);
size_t index = $levels.size(); size_t index = $levels.size();

@ -24,6 +24,7 @@ struct LevelScaling {
class LevelManager { class LevelManager {
public: public:
DinkyECS::ComponentMap $components;
std::vector<GameLevel> $levels; std::vector<GameLevel> $levels;
size_t $current_level = 0; size_t $current_level = 0;

@ -6,7 +6,6 @@
#include <algorithm> #include <algorithm>
#include <fmt/core.h> #include <fmt/core.h>
#include "point.hpp" #include "point.hpp"
#include "tser.hpp"
#include "lights.hpp" #include "lights.hpp"
#include "pathing.hpp" #include "pathing.hpp"
#include "matrix.hpp" #include "matrix.hpp"
@ -22,8 +21,6 @@ struct Room {
size_t height = 0; size_t height = 0;
Point entry{(size_t)-1, (size_t)-1}; Point entry{(size_t)-1, (size_t)-1};
Point exit{(size_t)-1, (size_t)-1}; Point exit{(size_t)-1, (size_t)-1};
DEFINE_SERIALIZABLE(Room, x, y, width, height);
}; };
class Map { class Map {

@ -47,7 +47,6 @@ sources = [
'ansi_parser.cpp', 'ansi_parser.cpp',
'camera.cpp', 'camera.cpp',
'combat.cpp', 'combat.cpp',
'components.cpp',
'config.cpp', 'config.cpp',
'dbc.cpp', 'dbc.cpp',
'devices.cpp', 'devices.cpp',
@ -87,7 +86,6 @@ executable('runtests', sources + [
'tests/pathing.cpp', 'tests/pathing.cpp',
'tests/spatialmap.cpp', 'tests/spatialmap.cpp',
'tests/tilemap.cpp', 'tests/tilemap.cpp',
'tests/worldbuilder.cpp',
], override_options: exe_defaults, ], override_options: exe_defaults,
dependencies: dependencies + [catch2]) dependencies: dependencies + [catch2])

@ -1,6 +1,5 @@
#pragma once #pragma once
#include <vector> #include <vector>
#include "tser.hpp"
struct Point { struct Point {
size_t x = 0; size_t x = 0;
@ -9,8 +8,6 @@ struct Point {
bool operator==(const Point& other) const { bool operator==(const Point& other) const {
return other.x == x && other.y == y; return other.x == x && other.y == y;
} }
DEFINE_SERIALIZABLE(Point, x, y);
}; };
typedef std::vector<Point> PointList; typedef std::vector<Point> PointList;

@ -17,70 +17,16 @@ inline void extract(DinkyECS::World &world, std::map<DinkyECS::Entity, CompT> &i
} }
void save::to_file(fs::path path, DinkyECS::World &world, Map &map) { void save::to_file(fs::path path, DinkyECS::World &world, Map &map) {
SaveData save_data; (void)path;
tser::BinaryArchive archive; (void)world;
(void)map;
save_data.facts.player = world.get_the<Player>();
save_data.map = MapData{
map.$width, map.$height,
map.$rooms, map.$walls};
// BUG: lights aren't saved/restored
extract<Position>(world, save_data.position);
extract<Combat>(world, save_data.combat);
extract<Motion>(world, save_data.motion);
extract<Tile>(world, save_data.tile);
// extract<Inventory>(world, save_data.inventory);
archive.save(save_data);
std::string_view archive_view = archive.get_buffer();
std::ofstream out(path, std::ios::binary);
out << archive_view;
out.flush();
} }
template<typename CompT>
inline void inject(DinkyECS::World &world, std::map<DinkyECS::Entity, CompT> &outof) {
for(auto [entity, value] : outof) {
world.set<CompT>(entity, value);
}
}
void save::from_file(fs::path path, DinkyECS::World &world_out, Map &map_out) { void save::from_file(fs::path path, DinkyECS::World &world_out, Map &map_out) {
tser::BinaryArchive archive(0); (void)path;
dbc::check(fs::exists(path), format("save file does not exist {}", path.string())); (void)world_out;
auto size = fs::file_size(path); (void)map_out;
if(std::ifstream in_file{path, std::ios::binary}) {
std::string in_data(size, '\0');
if(in_file.read(&in_data[0], size)) {
std::string_view in_view(in_data);
archive.initialize(in_view);
} else {
dbc::sentinel(format("wrong size or error reading {}", path.string()));
}
} else {
dbc::sentinel(format("failed to load file {}", path.string()));
}
auto save_data = archive.load<SaveData>();
world_out.set_the<Player>(save_data.facts.player);
inject<Position>(world_out, save_data.position);
inject<Combat>(world_out, save_data.combat);
inject<Motion>(world_out, save_data.motion);
inject<Tile>(world_out, save_data.tile);
// inject<Inventory>(world_out, save_data.inventory);
size_t width = save_data.map.width;
size_t height = save_data.map.height;
Pathing paths(width, height);
map_out = Map(save_data.map.walls, paths);
save::load_configs(world_out);
} }
void save::load_configs(DinkyECS::World &world) { void save::load_configs(DinkyECS::World &world) {

@ -3,7 +3,6 @@
#include "components.hpp" #include "components.hpp"
#include "map.hpp" #include "map.hpp"
#include "dinkyecs.hpp" #include "dinkyecs.hpp"
#include "tser.hpp"
#include <filesystem> #include <filesystem>
#include <string> #include <string>
#include <map> #include <map>
@ -16,14 +15,10 @@ namespace save {
size_t height; size_t height;
std::vector<Room> rooms; std::vector<Room> rooms;
Matrix walls; Matrix walls;
DEFINE_SERIALIZABLE(MapData, width, height, rooms, walls);
}; };
struct Facts { struct Facts {
components::Player player; components::Player player;
DEFINE_SERIALIZABLE(Facts, player);
}; };
struct SaveData { struct SaveData {
@ -35,8 +30,6 @@ namespace save {
std::map<DinkyECS::Entity, components::Combat> combat; std::map<DinkyECS::Entity, components::Combat> combat;
std::map<DinkyECS::Entity, components::Tile> tile; std::map<DinkyECS::Entity, components::Tile> tile;
// std::map<DinkyECS::Entity, components::Inventory> inventory; // std::map<DinkyECS::Entity, components::Inventory> inventory;
DEFINE_SERIALIZABLE(SaveData, facts, map, position, motion, combat, tile);
}; };
void to_file(fs::path path, DinkyECS::World &world, Map &map); void to_file(fs::path path, DinkyECS::World &world, Map &map);

@ -234,8 +234,8 @@ TEST_CASE("test serialization with nlohmann::json", "[ecs-serialize]") {
DinkyECS::Entity ent1 = world.entity(); DinkyECS::Entity ent1 = world.entity();
DinkyECS::Entity ent2 = world.entity(); DinkyECS::Entity ent2 = world.entity();
DinkyECS::configure(world, component_map, ent1, data); DinkyECS::configure(component_map, world, ent1, data);
DinkyECS::configure(world, component_map, ent2, data); DinkyECS::configure(component_map, world, ent2, data);
world.query<Position, Motion>([&](const auto ent, auto &pos, auto &motion) { world.query<Position, Motion>([&](const auto ent, auto &pos, auto &motion) {
fmt::println("entity: {}; position={},{} and motion={},{} motion.random={}", fmt::println("entity: {}; position={},{} and motion={},{} motion.random={}",

@ -15,15 +15,18 @@ using std::string;
using namespace components; using namespace components;
DinkyECS::Entity add_items(DinkyECS::World &world, GameConfig &config) { DinkyECS::Entity add_items(DinkyECS::ComponentMap component_map, DinkyECS::World &world, GameConfig &config) {
auto sword = world.entity(); auto sword = world.entity();
json& item_data = config.items["SWORD_RUSTY"]; json& item_data = config.items["SWORD_RUSTY"];
world.set<InventoryItem>(sword, {item_data["inventory_count"], item_data}); world.set<InventoryItem>(sword, {item_data["inventory_count"], item_data});
components::configure(world, sword, item_data); DinkyECS::configure(component_map, world, sword, item_data);
return sword; return sword;
} }
TEST_CASE("basic inventory test", "[inventory]") { TEST_CASE("basic inventory test", "[inventory]") {
// BUG: rewrite this
REQUIRE(true == false);
/*
DinkyECS::World world; DinkyECS::World world;
save::load_configs(world); save::load_configs(world);
auto& config = world.get_the<GameConfig>(); auto& config = world.get_the<GameConfig>();
@ -68,4 +71,5 @@ TEST_CASE("basic inventory test", "[inventory]") {
inventory.erase_item(0); inventory.erase_item(0);
REQUIRE(inventory.count() == 0); REQUIRE(inventory.count() == 0);
*/
} }

@ -3,16 +3,17 @@
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <fstream> #include <fstream>
#include "map.hpp" #include "map.hpp"
#include "worldbuilder.hpp" #include "levelmanager.hpp"
#include "lights.hpp" #include "lights.hpp"
#include "point.hpp" #include "point.hpp"
using namespace lighting; using namespace lighting;
TEST_CASE("lighting a map works", "[lighting]") { TEST_CASE("lighting a map works", "[lighting]") {
Map map(20,23); LevelManager levels;
WorldBuilder builder(map); GameLevel level = levels.current();
builder.generate_map(); auto &map = *level.map;
Point light1, light2; Point light1, light2;
REQUIRE(map.place_entity(0, light1)); REQUIRE(map.place_entity(0, light1));

@ -3,7 +3,7 @@
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <fstream> #include <fstream>
#include "map.hpp" #include "map.hpp"
#include "worldbuilder.hpp" #include "levelmanager.hpp"
using namespace fmt; using namespace fmt;
using namespace nlohmann; using namespace nlohmann;
@ -15,9 +15,9 @@ json load_test_data(const string &fname) {
} }
TEST_CASE("camera control", "[map]") { TEST_CASE("camera control", "[map]") {
Map map(20, 20); LevelManager levels;
WorldBuilder builder(map); GameLevel level = levels.current();
builder.generate_map(); auto &map = *level.map;
Point center = map.center_camera({10,10}, 5, 5); Point center = map.center_camera({10,10}, 5, 5);
@ -32,11 +32,10 @@ TEST_CASE("camera control", "[map]") {
TEST_CASE("map placement test", "[map:placement]") { TEST_CASE("map placement test", "[map:placement]") {
for(int i = 0; i < 50; i++) { for(int i = 0; i < 50; i++) {
size_t width = Random::uniform<size_t>(9, 21); LevelManager levels;
size_t height = Random::uniform<size_t>(13, 25); GameLevel level = levels.current();
Map map(width, height); auto &map = *level.map;
WorldBuilder builder(map);
builder.generate_rooms();
map.invert_space(); map.invert_space();
for(size_t rnum = 0; rnum < map.room_count(); rnum++) { for(size_t rnum = 0; rnum < map.room_count(); rnum++) {

@ -4,7 +4,7 @@
#include "config.hpp" #include "config.hpp"
#include "matrix.hpp" #include "matrix.hpp"
#include "rand.hpp" #include "rand.hpp"
#include "worldbuilder.hpp" #include "levelmanager.hpp"
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <fstream> #include <fstream>
@ -13,6 +13,12 @@ using namespace fmt;
using std::string; using std::string;
using matrix::Matrix; using matrix::Matrix;
shared_ptr<Map> make_map() {
LevelManager levels;
GameLevel level = levels.current();
return level.map;
}
TEST_CASE("basic matrix iterator", "[matrix:basic]") { TEST_CASE("basic matrix iterator", "[matrix:basic]") {
std::ifstream infile("./tests/dijkstra.json"); std::ifstream infile("./tests/dijkstra.json");
json data = json::parse(infile); json data = json::parse(infile);
@ -252,20 +258,18 @@ TEST_CASE("prototype circle algorithm", "[matrix:circle]") {
TEST_CASE("viewport iterator", "[matrix:viewport]") { TEST_CASE("viewport iterator", "[matrix:viewport]") {
size_t width = Random::uniform<size_t>(20, 22); size_t width = Random::uniform<size_t>(20, 22);
size_t height = Random::uniform<size_t>(21, 25); size_t height = Random::uniform<size_t>(21, 25);
Map map(width,height); shared_ptr<Map> map = make_map();
WorldBuilder builder(map);
builder.generate_map();
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; Point player;
REQUIRE(map.place_entity(1, 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);
size_t end_y = std::min(view_height, map.height() - start.y); size_t end_y = std::min(view_height, map->height() - start.y);
matrix::viewport it{map.walls(), start, int(view_width), int(view_height)}; matrix::viewport it{map->walls(), start, int(view_width), int(view_height)};
for(size_t y = 0; y < end_y; ++y) { for(size_t y = 0; y < end_y; ++y) {
for(size_t x = 0; x < end_x && it.next(); ++x) { for(size_t x = 0; x < end_x && it.next(); ++x) {
@ -276,25 +280,21 @@ TEST_CASE("viewport iterator", "[matrix:viewport]") {
TEST_CASE("random rectangle", "[matrix:rando_rect]") { TEST_CASE("random rectangle", "[matrix:rando_rect]") {
for(int i = 0; i < 10; i++) { for(int i = 0; i < 10; i++) {
size_t width = Random::uniform<size_t>(9, 21); shared_ptr<Map> map = make_map();
size_t height = Random::uniform<size_t>(13, 25); map->invert_space();
Map map(width, height); auto wall_copy = map->walls();
WorldBuilder builder(map);
builder.generate_rooms(); for(size_t rnum = 0; rnum < map->room_count(); rnum++) {
map.invert_space(); Room &room = map->room(rnum);
auto wall_copy = map.walls();
for(size_t rnum = 0; rnum < map.room_count(); rnum++) {
Room &room = map.room(rnum);
Point pos; Point pos;
for(matrix::rando_rect it{map.walls(), room.x, room.y, room.width, room.height}; it.next();) for(matrix::rando_rect it{map->walls(), room.x, room.y, room.width, room.height}; it.next();)
{ {
if(map.iswall(it.x, it.y)) { if(map->iswall(it.x, it.y)) {
matrix::dump("BAD RECTANGLE SPOT", map.walls(), it.x, it.y); matrix::dump("BAD RECTANGLE SPOT", map->walls(), it.x, it.y);
} }
REQUIRE(!map.iswall(it.x, it.y)); REQUIRE(!map->iswall(it.x, it.y));
REQUIRE(size_t(it.x) >= room.x); REQUIRE(size_t(it.x) >= room.x);
REQUIRE(size_t(it.y) >= room.y); REQUIRE(size_t(it.y) >= room.y);
REQUIRE(size_t(it.x) <= room.x + room.width); REQUIRE(size_t(it.x) <= room.x + room.width);
@ -310,25 +310,21 @@ TEST_CASE("random rectangle", "[matrix:rando_rect]") {
TEST_CASE("standard rectangle", "[matrix:rectangle]") { TEST_CASE("standard rectangle", "[matrix:rectangle]") {
for(int i = 0; i < 20; i++) { for(int i = 0; i < 20; i++) {
size_t width = Random::uniform<size_t>(9, 21); shared_ptr<Map> map = make_map();
size_t height = Random::uniform<size_t>(13, 25); map->invert_space();
Map map(width, height); auto wall_copy = map->walls();
WorldBuilder builder(map);
builder.generate_rooms(); for(size_t rnum = 0; rnum < map->room_count(); rnum++) {
map.invert_space(); Room &room = map->room(rnum);
auto wall_copy = map.walls();
for(size_t rnum = 0; rnum < map.room_count(); rnum++) {
Room &room = map.room(rnum);
Point pos; Point pos;
for(matrix::rectangle it{map.walls(), room.x, room.y, room.width, room.height}; it.next();) for(matrix::rectangle it{map->walls(), room.x, room.y, room.width, room.height}; it.next();)
{ {
if(map.iswall(it.x, it.y)) { if(map->iswall(it.x, it.y)) {
matrix::dump("BAD RECTANGLE SPOT", map.walls(), it.x, it.y); matrix::dump("BAD RECTANGLE SPOT", map->walls(), it.x, it.y);
} }
REQUIRE(!map.iswall(it.x, it.y)); REQUIRE(!map->iswall(it.x, it.y));
REQUIRE(size_t(it.x) >= room.x); REQUIRE(size_t(it.x) >= room.x);
REQUIRE(size_t(it.y) >= room.y); REQUIRE(size_t(it.y) >= room.y);
REQUIRE(size_t(it.x) <= room.x + room.width); REQUIRE(size_t(it.x) <= room.x + room.width);

@ -14,47 +14,8 @@ using namespace fmt;
using std::string; using std::string;
using namespace components; using namespace components;
enum class Item : char {
RADAR = 'R',
TRAP = 'T',
ORE = 'O'
};
struct Pixel {
int x = 0;
int y = 0;
DEFINE_SERIALIZABLE(Pixel, x, y);
};
struct Robot {
Pixel point;
std::wstring name;
std::optional<Item> item;
DEFINE_SERIALIZABLE(Robot, point, name, item);
};
TEST_CASE("test using tser for serialization", "[config]") {
auto robot = Robot{ Pixel{3,4}, L"BIG NAME", Item::RADAR};
tser::BinaryArchive archive;
archive.save(robot);
std::string_view archive_view = archive.get_buffer();
tser::BinaryArchive archive2(0);
archive2.initialize(archive_view);
auto loadedRobot = archive2.load<Robot>();
REQUIRE(loadedRobot.point.x == robot.point.x);
REQUIRE(loadedRobot.point.y == robot.point.y);
REQUIRE(loadedRobot.name == robot.name);
REQUIRE(loadedRobot.item == robot.item);
}
TEST_CASE("basic save a world", "[save]") { TEST_CASE("basic save a world", "[save]") {
/*
DinkyECS::World world; DinkyECS::World world;
Map map(20, 20); Map map(20, 20);
WorldBuilder builder(map); WorldBuilder builder(map);
@ -100,4 +61,5 @@ TEST_CASE("basic save a world", "[save]") {
Inventory &inv = world.get<Inventory>(player.entity); Inventory &inv = world.get<Inventory>(player.entity);
REQUIRE(inv.gold == 102); REQUIRE(inv.gold == 102);
*/
} }

@ -1,7 +1,7 @@
#include <catch2/catch_test_macros.hpp> #include <catch2/catch_test_macros.hpp>
#include <fmt/core.h> #include <fmt/core.h>
#include "map.hpp" #include "map.hpp"
#include "worldbuilder.hpp" #include "levelmanager.hpp"
#include "tilemap.hpp" #include "tilemap.hpp"
#include "config.hpp" #include "config.hpp"
#include "rand.hpp" #include "rand.hpp"
@ -10,12 +10,9 @@ using namespace fmt;
using std::string; using std::string;
TEST_CASE("tilemap can load tiles and make a map", "[tilemap]") { TEST_CASE("tilemap can load tiles and make a map", "[tilemap]") {
size_t width = Random::uniform<size_t>(10, 25); LevelManager levels;
size_t height = Random::uniform<size_t>(10, 33); GameLevel level = levels.current();
auto &map = *level.map;
Map map(width,height);
WorldBuilder builder(map);
builder.generate_map();
TileMap tiles(map.width(), map.height()); TileMap tiles(map.width(), map.height());
auto& walls = map.walls(); auto& walls = map.walls();

@ -1,35 +0,0 @@
#include <catch2/catch_test_macros.hpp>
#include <fmt/core.h>
#include <nlohmann/json.hpp>
#include <fstream>
#include "map.hpp"
#include "worldbuilder.hpp"
using namespace fmt;
using namespace nlohmann;
using std::string;
TEST_CASE("bsp algo test", "[builder]") {
Map map(31, 20);
WorldBuilder builder(map);
builder.generate_map();
}
TEST_CASE("pathing", "[builder]") {
Map map(23, 14);
WorldBuilder builder(map);
builder.generate_map();
matrix::dump("WALLS", map.$walls, 0,0);
println("wall at 0,0=={}", map.$walls[0][0]);
for(matrix::each_cell it{map.$walls}; it.next();) {
if(map.$walls[it.y][it.x] == WALL_VALUE) {
REQUIRE(map.iswall(it.x, it.y) == true);
REQUIRE(map.can_move({it.x, it.y}) == false);
} else {
REQUIRE(map.iswall(it.x, it.y) == false);
REQUIRE(map.can_move({it.x, it.y}) == true);
}
}
}

@ -1,220 +0,0 @@
// Licensed under the Boost License <https://opensource.org/licenses/BSL-1.0>.
// SPDX-License-Identifier: BSL-1.0
#pragma once
#include <array>
#include <ostream>
#include <cstring>
#include <string>
#include <string_view>
#include <type_traits>
#include <tuple>
#include <locale>
#include <codecvt>
namespace tser{
//implementation details for C++20 is_detected
namespace detail {
struct ns {
~ns() = delete;
ns(ns const&) = delete;
};
template <class Default, class AlwaysVoid, template<class...> class Op, class... Args>
struct detector {
using value_t = std::false_type;
using type = Default;
};
template <class Default, template<class...> class Op, class... Args>
struct detector<Default, std::void_t<Op<Args...>>, Op, Args...> {
using value_t = std::true_type;
using type = Op<Args...>;
};
template<class T>
struct is_array : std::is_array<T> {};
template<template<typename, size_t> class TArray, typename T, size_t N>
struct is_array<TArray<T, N>> : std::true_type {};
constexpr size_t n_args(char const* c, size_t nargs = 1) {
for (; *c; ++c) if (*c == ',') ++nargs;
return nargs;
}
constexpr size_t str_size(char const* c, size_t strSize = 1) {
for (; *c; ++c) ++strSize;
return strSize;
}
}
// we need a bunch of template metaprogramming for being able to differentiate between different types
template <template<class...> class Op, class... Args>
constexpr bool is_detected_v = detail::detector<detail::ns, void, Op, Args...>::value_t::value;
class BinaryArchive;
template<class T> using has_begin_t = decltype(*std::begin(std::declval<T>()));
template<class T> using has_members_t = decltype(std::declval<T>().members());
template<class T> using has_smaller_t = decltype(std::declval<T>() < std::declval<T>());
template<class T> using has_equal_t = decltype(std::declval<T>() == std::declval<T>());
template<class T> using has_nequal_t = decltype(std::declval<T>() != std::declval<T>());
template<class T> using has_outstream_op_t = decltype(std::declval<std::ostream>() << std::declval<T>());
template<class T> using has_tuple_t = std::tuple_element_t<0, T>;
template<class T> using has_optional_t = decltype(std::declval<T>().has_value());
template<class T> using has_element_t = typename T::element_type;
template<class T> using has_mapped_t = typename T::mapped_type;
template<class T> using has_custom_save_t = decltype(std::declval<T>().save(std::declval<BinaryArchive&>()));
template<class T> using has_free_save_t = decltype(std::declval<const T&>() << std::declval<BinaryArchive&>());
template<class T> constexpr bool is_container_v = is_detected_v<has_begin_t, T>;
template<class T> constexpr bool is_tuple_v = is_detected_v<has_tuple_t, T>;
template<class T> constexpr bool is_tser_t_v = is_detected_v<has_members_t, T>;
template<class T> constexpr bool is_pointer_like_v = std::is_pointer_v<T> || is_detected_v<has_element_t, T> || is_detected_v<has_optional_t, T>;
class BinaryArchive {
std::string m_bytes = std::string(1024, '\0');
size_t m_bufferSize = 0, m_readOffset = 0;
public:
explicit BinaryArchive(const size_t initialSize = 1024) : m_bytes(initialSize, '\0') {}
template<typename T>
explicit BinaryArchive(const T& t) { save(t); }
template<typename T>
void save(const T& t) {
if constexpr (is_detected_v<has_free_save_t, T>) {
operator<<(t,*this);
} else if constexpr (is_detected_v<has_custom_save_t, T>) {
t.save(*this);
} else if constexpr(is_tser_t_v<T>) {
std::apply([&](auto& ... mVal) { (save(mVal), ...); }, t.members());
} else if constexpr(is_tuple_v<T>) {
std::apply([&](auto& ... tVal) { (save(tVal), ...); }, t);
} else if constexpr (is_pointer_like_v<T>) {
save(static_cast<bool>(t));
if (t)
save(*t);
} else if constexpr (is_container_v<T>) {
if constexpr (!detail::is_array<T>::value) {
save(t.size());
}
for (auto& val : t) save(val);
} else {
if (m_bufferSize + sizeof(T) + sizeof(T) / 4 > m_bytes.size()) {
m_bytes.resize((m_bufferSize + sizeof(T)) * 2);
}
std::memcpy(m_bytes.data() + m_bufferSize, std::addressof(t), sizeof(T));
m_bufferSize += sizeof(T);
}
}
template<typename T>
void load(T& t) {
using V = std::decay_t<T>;
if constexpr (is_detected_v<has_free_save_t, V>) {
operator>>(t, *this);
} else if constexpr (is_detected_v<has_custom_save_t, T>) {
t.load(*this);
} else if constexpr (is_tser_t_v<T>) {
std::apply([&](auto& ... mVal) { (load(mVal), ...); }, t.members());
} else if constexpr (is_tuple_v<V>) {
std::apply([&](auto& ... tVal) { (load(tVal), ...); }, t);
} else if constexpr (is_pointer_like_v<T>) {
if constexpr (std::is_pointer_v<T>) {
t = load<bool>() ? (t = new std::remove_pointer_t<T>(), load(*t), t) : nullptr;
} else if constexpr (is_detected_v<has_optional_t, T>) {
t = load<bool>() ? T(load<typename V::value_type>()) : T();
} else { //smart pointer
t = T(load<has_element_t<V>*>());
}
} else if constexpr (is_container_v<T>) {
if constexpr (!detail::is_array<T>::value) {
const auto size = load<decltype(t.size())>();
using VT = typename V::value_type;
for (size_t i = 0; i < size; ++i) {
if constexpr (!is_detected_v<has_mapped_t, V>) {
t.insert(t.end(), load<VT>());
} else {
//we have to special case map, because of the const key
t.emplace(VT{ load<typename V::key_type>(), load<typename V::mapped_type>() });
}
}
} else {
for (auto& val : t) load(val);
}
} else {
std::memcpy(&t, m_bytes.data() + m_readOffset, sizeof(T));
m_readOffset += sizeof(T);
}
}
template<typename T>
T load() {
std::remove_const_t<T> t{}; load(t); return t;
}
template<typename T>
friend BinaryArchive& operator<<(BinaryArchive& ba, const T& t) {
ba.save(t); return ba;
}
template<typename T>
friend BinaryArchive& operator>>(BinaryArchive& ba, T& t) {
ba.load(t); return ba;
}
void reset() {
m_bufferSize = 0;
m_readOffset = 0;
}
void initialize(std::string_view str) {
m_bytes = str;
m_bufferSize = str.size();
m_readOffset = 0;
}
std::string_view get_buffer() const {
return std::string_view(m_bytes.data(), m_bufferSize);
}
};
template<class Base, typename Derived>
std::conditional_t<std::is_const_v<Derived>, const Base, Base>& base(Derived* thisPtr) { return *thisPtr; }
template<typename T>
auto load(std::string_view encoded) { BinaryArchive ba(encoded); return ba.load<T>(); }
}
//this macro defines printing, serialisation and comparision operators (==,!=,<) for custom types
#define DEFINE_SERIALIZABLE(Type, ...) \
inline decltype(auto) members() const { return std::tie(__VA_ARGS__); } \
inline decltype(auto) members() { return std::tie(__VA_ARGS__); } \
static constexpr std::array<char, tser::detail::str_size(#__VA_ARGS__)> _memberNameData = [](){ \
std::array<char, tser::detail::str_size(#__VA_ARGS__)> chars{'\0'}; size_t _idx = 0; constexpr auto* ini(#__VA_ARGS__); \
for (char const* _c = ini; *_c; ++_c, ++_idx) { if(*_c != ',' && *_c != ' ') chars[_idx] = *_c; } return chars;}(); \
static constexpr const char* _typeName = #Type; \
static constexpr std::array<const char*, tser::detail::n_args(#__VA_ARGS__)> _memberNames = \
[](){ std::array<const char*, tser::detail::n_args(#__VA_ARGS__)> out{ }; \
for(size_t _i = 0, nArgs = 0; nArgs < tser::detail::n_args(#__VA_ARGS__) ; ++_i) { \
while(Type::_memberNameData[_i] == '\0') {_i++;} out[nArgs++] = &Type::_memberNameData[_i]; \
while(Type::_memberNameData[++_i] != '\0'); } return out;}();

@ -182,10 +182,10 @@ void WorldBuilder::generate_map() {
} }
DinkyECS::Entity configure_entity_in_map(DinkyECS::World &world, Map &game_map, json &entity_data, int in_room) { DinkyECS::Entity WorldBuilder::configure_entity_in_map(DinkyECS::World &world, json &entity_data, int in_room) {
auto item = world.entity(); auto item = world.entity();
Point pos_out; Point pos_out;
bool placed = game_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");
world.set<Position>(item, {pos_out.x+1, pos_out.y+1}); world.set<Position>(item, {pos_out.x+1, pos_out.y+1});
@ -194,7 +194,7 @@ DinkyECS::Entity configure_entity_in_map(DinkyECS::World &world, Map &game_map,
} }
if(entity_data.contains("components")) { if(entity_data.contains("components")) {
components::configure(world, item, entity_data); DinkyECS::configure($components, world, item, entity_data["components"]);
} }
return item; return item;
} }
@ -235,16 +235,16 @@ void WorldBuilder::randomize_entities(DinkyECS::World &world, GameConfig &config
auto entity_data = entity_db[key]; auto entity_data = entity_db[key];
// pass that to the config as it'll be a generic json // pass that to the config as it'll be a generic json
auto entity = configure_entity_in_map(world, $map, entity_data, room_num); auto entity = configure_entity_in_map(world, entity_data, room_num);
check_player(world, entity); check_player(world, entity);
} }
} }
inline void place_stairs(DinkyECS::World& world, GameConfig& config, Map& map) { void WorldBuilder::place_stairs(DinkyECS::World& world, GameConfig& config) {
auto& device_config = config.devices.json(); auto& device_config = config.devices.json();
auto entity_data = device_config["STAIRS_DOWN"]; auto entity_data = device_config["STAIRS_DOWN"];
int last_room = map.room_count() - 1; int last_room = $map.room_count() - 1;
auto entity = configure_entity_in_map(world, map, entity_data, last_room); auto entity = configure_entity_in_map(world, entity_data, last_room);
check_player(world, entity); check_player(world, entity);
} }
@ -260,7 +260,7 @@ void WorldBuilder::place_entities(DinkyECS::World &world) {
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});
} else { } else {
auto player_data = config.enemies["PLAYER_TILE"]; auto player_data = config.enemies["PLAYER_TILE"];
auto player_ent = configure_entity_in_map(world, $map, player_data, 0); auto player_ent = configure_entity_in_map(world, player_data, 0);
// configure player in the world // configure player in the world
Player player{player_ent}; Player player{player_ent};
world.set_the<Player>(player); world.set_the<Player>(player);
@ -269,7 +269,7 @@ void WorldBuilder::place_entities(DinkyECS::World &world) {
} }
randomize_entities(world, config); randomize_entities(world, config);
place_stairs(world, config, $map); place_stairs(world, config);
} }
void WorldBuilder::generate(DinkyECS::World &world) { void WorldBuilder::generate(DinkyECS::World &world) {

@ -7,8 +7,12 @@
class WorldBuilder { class WorldBuilder {
public: public:
Map& $map; Map& $map;
DinkyECS::ComponentMap& $components;
WorldBuilder(Map &map) : $map(map) { } WorldBuilder(Map &map, DinkyECS::ComponentMap& components) :
$map(map),
$components(components)
{ }
void partition_map(Room &cur, int depth); void partition_map(Room &cur, int depth);
void make_room(size_t origin_y, size_t origin_x, size_t width, size_t height); void make_room(size_t origin_y, size_t origin_x, size_t width, size_t height);
@ -20,8 +24,11 @@ class WorldBuilder {
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_rooms();
void generate_map(); void generate_map();
DinkyECS::Entity configure_entity_in_map(DinkyECS::World &world, nlohmann::json &entity_data, int in_room);
void place_entities(DinkyECS::World &world); void place_entities(DinkyECS::World &world);
void generate(DinkyECS::World &world); void generate(DinkyECS::World &world);
void random_entity(DinkyECS::World &world, components::GameConfig &config); void random_entity(DinkyECS::World &world, components::GameConfig &config);
void randomize_entities(DinkyECS::World &world, components::GameConfig &config); void randomize_entities(DinkyECS::World &world, components::GameConfig &config);
void place_stairs(DinkyECS::World& world, components::GameConfig& config);
}; };

Loading…
Cancel
Save