Brought over a bunch of code from the roguelike and now will use it to generate a random map.
This commit is contained in:
parent
8d3d3b4ec3
commit
2daa1c9bd5
59 changed files with 4303 additions and 411 deletions
99
tests/components.cpp
Normal file
99
tests/components.cpp
Normal file
|
@ -0,0 +1,99 @@
|
|||
#include <catch2/catch_test_macros.hpp>
|
||||
#include "components.hpp"
|
||||
#include "dinkyecs.hpp"
|
||||
|
||||
using namespace components;
|
||||
using namespace DinkyECS;
|
||||
|
||||
TEST_CASE("all components can work in the world", "[components]") {
|
||||
World world;
|
||||
auto ent1 = world.entity();
|
||||
|
||||
world.set<Player>(ent1, {ent1});
|
||||
world.set<Position>(ent1, {{10,1}});
|
||||
world.set<Motion>(ent1, {1,0});
|
||||
world.set<Loot>(ent1, {100});
|
||||
world.set<Inventory>(ent1, {0});
|
||||
world.set<Tile>(ent1, {"Z"});
|
||||
world.set<EnemyConfig>(ent1, {4});
|
||||
|
||||
auto player = world.get<Player>(ent1);
|
||||
REQUIRE(player.entity == ent1);
|
||||
|
||||
auto position = world.get<Position>(ent1);
|
||||
REQUIRE(position.location.x == 10);
|
||||
REQUIRE(position.location.y == 1);
|
||||
|
||||
auto motion = world.get<Motion>(ent1);
|
||||
REQUIRE(motion.dx == 1);
|
||||
REQUIRE(motion.dy == 0);
|
||||
|
||||
auto loot = world.get<Loot>(ent1);
|
||||
REQUIRE(loot.amount == 100);
|
||||
|
||||
auto inv = world.get<Inventory>(ent1);
|
||||
REQUIRE(inv.gold == 0);
|
||||
|
||||
auto tile = world.get<Tile>(ent1);
|
||||
REQUIRE(tile.chr == "Z");
|
||||
}
|
||||
|
||||
TEST_CASE("all components can be facts", "[components]") {
|
||||
World world;
|
||||
auto ent1 = world.entity();
|
||||
|
||||
world.set_the<Player>({ent1});
|
||||
world.set_the<Position>({{10,1}});
|
||||
world.set_the<Motion>({1,0});
|
||||
world.set_the<Loot>({100});
|
||||
world.set_the<Inventory>({0});
|
||||
world.set_the<Tile>({"Z"});
|
||||
world.set_the<EnemyConfig>({4});
|
||||
|
||||
auto player = world.get_the<Player>();
|
||||
REQUIRE(player.entity == ent1);
|
||||
|
||||
auto position = world.get_the<Position>();
|
||||
REQUIRE(position.location.x == 10);
|
||||
REQUIRE(position.location.y == 1);
|
||||
|
||||
auto motion = world.get_the<Motion>();
|
||||
REQUIRE(motion.dx == 1);
|
||||
REQUIRE(motion.dy == 0);
|
||||
|
||||
auto loot = world.get_the<Loot>();
|
||||
REQUIRE(loot.amount == 100);
|
||||
|
||||
auto inv = world.get_the<Inventory>();
|
||||
REQUIRE(inv.gold == 0);
|
||||
|
||||
auto tile = world.get_the<Tile>();
|
||||
REQUIRE(tile.chr == "Z");
|
||||
}
|
||||
|
||||
TEST_CASE("confirm combat works", "[components]") {
|
||||
World world;
|
||||
auto player = world.entity();
|
||||
auto enemy = world.entity();
|
||||
|
||||
world.set<Combat>(player, {100, 10});
|
||||
world.set<Combat>(enemy, {20, 10});
|
||||
|
||||
auto p_fight = world.get<Combat>(player);
|
||||
REQUIRE(p_fight.hp == 100);
|
||||
REQUIRE(p_fight.damage == 10);
|
||||
REQUIRE(p_fight.dead == false);
|
||||
|
||||
auto e_fight = world.get<Combat>(enemy);
|
||||
REQUIRE(e_fight.hp == 20);
|
||||
REQUIRE(e_fight.damage == 10);
|
||||
REQUIRE(e_fight.dead == false);
|
||||
|
||||
for(int i = 0; e_fight.hp > 0 && i < 100; i++) {
|
||||
p_fight.attack(e_fight);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("MapConfig loads from JSON", "[components]") {
|
||||
|
||||
}
|
39
tests/dbc.cpp
Normal file
39
tests/dbc.cpp
Normal file
|
@ -0,0 +1,39 @@
|
|||
#include <catch2/catch_test_macros.hpp>
|
||||
#include "dbc.hpp"
|
||||
|
||||
using namespace dbc;
|
||||
|
||||
TEST_CASE("basic feature tests", "[utils]") {
|
||||
log("Logging a message.");
|
||||
|
||||
try {
|
||||
sentinel("This shouldn't happen.");
|
||||
} catch(SentinelError) {
|
||||
log("Sentinel happened.");
|
||||
}
|
||||
|
||||
pre("confirm positive cases work", 1 == 1);
|
||||
pre("confirm positive lambda", [&]{ return 1 == 1;});
|
||||
post("confirm positive post", 1 == 1);
|
||||
post("confirm postitive post with lamdba", [&]{ return 1 == 1;});
|
||||
|
||||
check(1 == 1, "one equals 1");
|
||||
|
||||
try {
|
||||
check(1 == 2, "this should fail");
|
||||
} catch(CheckError err) {
|
||||
log("check fail worked");
|
||||
}
|
||||
|
||||
try {
|
||||
pre("failing pre", 1 == 3);
|
||||
} catch(PreCondError err) {
|
||||
log("pre fail worked");
|
||||
}
|
||||
|
||||
try {
|
||||
post("failing post", 1 == 4);
|
||||
} catch(PostCondError err) {
|
||||
log("post faile worked");
|
||||
}
|
||||
}
|
60
tests/dijkstra.json
Normal file
60
tests/dijkstra.json
Normal file
|
@ -0,0 +1,60 @@
|
|||
[{
|
||||
"input": [
|
||||
[1, 1, 1, 0],
|
||||
[1, 0, 1, 1],
|
||||
[1, 0, 1, 1],
|
||||
[1, 1, 1, 1]
|
||||
],
|
||||
"walls": [
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 1, 0],
|
||||
[0, 0, 1, 0]
|
||||
],
|
||||
"expected": [
|
||||
[1, 1, 1, 0],
|
||||
[1, 0, 1, 1],
|
||||
[1, 0, 1000, 2],
|
||||
[1, 1, 1000, 3]
|
||||
]
|
||||
},{
|
||||
"input": [
|
||||
[1, 1, 1, 0],
|
||||
[1, 0, 1, 1],
|
||||
[1, 0, 1, 1],
|
||||
[1, 1, 1, 1]
|
||||
],
|
||||
"walls": [
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 1, 0],
|
||||
[0, 0, 1, 0]
|
||||
],
|
||||
"expected": [
|
||||
[1, 1, 1, 0],
|
||||
[1, 0, 1, 1],
|
||||
[1, 0, 1000, 2],
|
||||
[1, 1, 1000, 3]
|
||||
]
|
||||
},
|
||||
{
|
||||
"input": [
|
||||
[1, 1, 1, 0],
|
||||
[1, 1, 1, 1],
|
||||
[1, 0, 1, 1],
|
||||
[1, 1, 1, 1]
|
||||
],
|
||||
"walls": [
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 1, 0],
|
||||
[0, 0, 1, 0]
|
||||
],
|
||||
"expected": [
|
||||
[2, 2, 1, 0],
|
||||
[1, 1, 1, 1],
|
||||
[1, 0, 1000, 2],
|
||||
[1, 1, 1000, 3]
|
||||
]
|
||||
}
|
||||
]
|
71
tests/inventory.cpp
Normal file
71
tests/inventory.cpp
Normal file
|
@ -0,0 +1,71 @@
|
|||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <fmt/core.h>
|
||||
#include <string>
|
||||
#include "rand.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <fstream>
|
||||
#include "components.hpp"
|
||||
#include "dinkyecs.hpp"
|
||||
#include "save.hpp"
|
||||
#include "systems.hpp"
|
||||
|
||||
using namespace nlohmann;
|
||||
using namespace fmt;
|
||||
using std::string;
|
||||
using namespace components;
|
||||
|
||||
|
||||
DinkyECS::Entity add_items(DinkyECS::World &world, GameConfig &config) {
|
||||
auto sword = world.entity();
|
||||
json& item_data = config.items["SWORD_RUSTY"];
|
||||
world.set<InventoryItem>(sword, {item_data["inventory_count"], item_data});
|
||||
components::configure(world, sword, item_data);
|
||||
return sword;
|
||||
}
|
||||
|
||||
TEST_CASE("basic inventory test", "[inventory]") {
|
||||
DinkyECS::World world;
|
||||
save::load_configs(world);
|
||||
auto& config = world.get_the<GameConfig>();
|
||||
auto sword = add_items(world, config);
|
||||
|
||||
auto player = world.entity();
|
||||
world.set<Inventory>(player, {});
|
||||
|
||||
auto &inventory = world.get<Inventory>(player);
|
||||
|
||||
System::pickup(world, player, sword);
|
||||
REQUIRE(inventory.count() == 1);
|
||||
// get the item and confirm there is 1
|
||||
auto &item1 = inventory.get(0);
|
||||
REQUIRE(item1.count == 1);
|
||||
|
||||
int item_at = inventory.item_index("SWORD_RUSTY");
|
||||
REQUIRE(item_at == 0);
|
||||
|
||||
REQUIRE(inventory.item_index("SADFASFSADF") == -1);
|
||||
|
||||
System::pickup(world, player, sword);
|
||||
REQUIRE(item1.count == 2);
|
||||
|
||||
System::pickup(world, player, sword);
|
||||
REQUIRE(item1.count == 3);
|
||||
|
||||
System::pickup(world, player, sword);
|
||||
REQUIRE(inventory.count() == 1);
|
||||
|
||||
REQUIRE(item1.count == 4);
|
||||
|
||||
inventory.decrease(0, 1);
|
||||
REQUIRE(item1.count == 3);
|
||||
|
||||
inventory.decrease(0, 2);
|
||||
REQUIRE(item1.count == 1);
|
||||
|
||||
bool active = inventory.decrease(0, 1);
|
||||
REQUIRE(item1.count == 0);
|
||||
REQUIRE(active == false);
|
||||
|
||||
inventory.erase_item(0);
|
||||
REQUIRE(inventory.count() == 0);
|
||||
}
|
40
tests/levelmanager.cpp
Normal file
40
tests/levelmanager.cpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <fmt/core.h>
|
||||
#include "map.hpp"
|
||||
#include "dinkyecs.hpp"
|
||||
#include "worldbuilder.hpp"
|
||||
#include "save.hpp"
|
||||
#include "systems.hpp"
|
||||
#include "spatialmap.hpp"
|
||||
#include "levelmanager.hpp"
|
||||
|
||||
using namespace fmt;
|
||||
using std::string;
|
||||
|
||||
TEST_CASE("basic level manager test", "[levelmanager]") {
|
||||
LevelManager lm;
|
||||
|
||||
// starts off with one already but I need to change that
|
||||
size_t level1 = lm.current_index();
|
||||
size_t level2 = lm.create_level();
|
||||
|
||||
auto& test1_level = lm.get(level1);
|
||||
auto& test2_level = lm.get(level2);
|
||||
|
||||
REQUIRE(test1_level.map->width() > 0);
|
||||
REQUIRE(test1_level.map->height() > 0);
|
||||
REQUIRE(test1_level.index == 0);
|
||||
|
||||
REQUIRE(test2_level.map->width() > 0);
|
||||
REQUIRE(test2_level.map->height() > 0);
|
||||
REQUIRE(test2_level.index == 1);
|
||||
|
||||
auto& cur_level = lm.current();
|
||||
REQUIRE(cur_level.index == 0);
|
||||
|
||||
auto& next_level = lm.next();
|
||||
REQUIRE(next_level.index == 1);
|
||||
|
||||
auto& prev_level = lm.previous();
|
||||
REQUIRE(prev_level.index == 0);
|
||||
}
|
45
tests/lighting.cpp
Normal file
45
tests/lighting.cpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <fmt/core.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <fstream>
|
||||
#include "map.hpp"
|
||||
#include "worldbuilder.hpp"
|
||||
#include "lights.hpp"
|
||||
#include "point.hpp"
|
||||
|
||||
using namespace lighting;
|
||||
|
||||
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));
|
||||
|
||||
LightSource source1{6, 1.0};
|
||||
LightSource source2{4,3};
|
||||
|
||||
LightRender lr(map.width(), map.height());
|
||||
|
||||
lr.reset_light();
|
||||
|
||||
lr.set_light_target(light1);
|
||||
lr.set_light_target(light2);
|
||||
|
||||
lr.path_light(map.walls());
|
||||
|
||||
lr.render_light(source1, light1);
|
||||
lr.render_light(source2, light2);
|
||||
|
||||
lr.clear_light_target(light1);
|
||||
lr.clear_light_target(light2);
|
||||
|
||||
Matrix &lighting = lr.lighting();
|
||||
|
||||
matrix::dump("WALLS=====", map.walls(), light1.x, light1.y);
|
||||
matrix::dump("PATHS=====", lr.paths(), light1.x, light1.y);
|
||||
matrix::dump("LIGHTING 1", lighting, light1.x, light1.y);
|
||||
matrix::dump("LIGHTING 2", lighting, light2.x, light2.y);
|
||||
}
|
83
tests/map.cpp
Normal file
83
tests/map.cpp
Normal file
|
@ -0,0 +1,83 @@
|
|||
#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;
|
||||
|
||||
json load_test_data(const string &fname) {
|
||||
std::ifstream infile(fname);
|
||||
return json::parse(infile);
|
||||
}
|
||||
|
||||
TEST_CASE("camera control", "[map]") {
|
||||
Map map(20, 20);
|
||||
WorldBuilder builder(map);
|
||||
builder.generate_map();
|
||||
|
||||
Point center = map.center_camera({10,10}, 5, 5);
|
||||
|
||||
REQUIRE(center.x == 8);
|
||||
REQUIRE(center.y == 8);
|
||||
|
||||
Point translation = map.map_to_camera({10,10}, center);
|
||||
|
||||
REQUIRE(translation.x == 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]") {
|
||||
json data = load_test_data("./tests/dijkstra.json");
|
||||
|
||||
for(auto &test : data) {
|
||||
Matrix expected = test["expected"];
|
||||
Matrix input = test["input"];
|
||||
Matrix walls = test["walls"];
|
||||
Map map(input.size(), input[0].size());
|
||||
map.$walls = walls;
|
||||
map.$paths.$input = input;
|
||||
|
||||
REQUIRE(map.INVARIANT());
|
||||
|
||||
map.make_paths();
|
||||
Matrix &paths = map.paths();
|
||||
|
||||
if(paths != expected) {
|
||||
println("ERROR! ------");
|
||||
matrix::dump("EXPECTED", expected);
|
||||
matrix::dump("RESULT", paths);
|
||||
}
|
||||
|
||||
REQUIRE(map.INVARIANT());
|
||||
// FIX ME: REQUIRE(paths == expected);
|
||||
}
|
||||
}
|
343
tests/matrix.cpp
Normal file
343
tests/matrix.cpp
Normal file
|
@ -0,0 +1,343 @@
|
|||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <fmt/core.h>
|
||||
#include <string>
|
||||
#include "config.hpp"
|
||||
#include "matrix.hpp"
|
||||
#include "rand.hpp"
|
||||
#include "worldbuilder.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <fstream>
|
||||
|
||||
using namespace nlohmann;
|
||||
using namespace fmt;
|
||||
using std::string;
|
||||
using matrix::Matrix;
|
||||
|
||||
TEST_CASE("basic matrix iterator", "[matrix:basic]") {
|
||||
std::ifstream infile("./tests/dijkstra.json");
|
||||
json data = json::parse(infile);
|
||||
auto test = data[0];
|
||||
|
||||
Matrix walls = test["walls"];
|
||||
|
||||
// tests going through straight cells but also
|
||||
// using two iterators on one matrix (or two)
|
||||
matrix::each_cell cells{walls};
|
||||
cells.next(); // kick it off
|
||||
size_t row_count = 0;
|
||||
|
||||
for(matrix::each_row it{walls};
|
||||
it.next(); cells.next())
|
||||
{
|
||||
REQUIRE(walls[cells.y][cells.x] == walls[it.y][it.x]);
|
||||
row_count += it.row;
|
||||
}
|
||||
|
||||
REQUIRE(row_count == walls.size());
|
||||
|
||||
{
|
||||
// test getting the correct height in the middle
|
||||
row_count = 0;
|
||||
matrix::box box{walls, 2,2, 1};
|
||||
|
||||
while(box.next()) {
|
||||
row_count += box.x == box.left;
|
||||
walls[box.y][box.x] = 3;
|
||||
}
|
||||
matrix::dump("2,2 WALLS", walls, 2, 2);
|
||||
|
||||
REQUIRE(row_count == 3);
|
||||
}
|
||||
|
||||
{
|
||||
matrix::dump("1:1 POINT", walls, 1,1);
|
||||
// confirm boxes have the right number of rows
|
||||
// when x goes to 0 on first next call
|
||||
row_count = 0;
|
||||
matrix::box box{walls, 1, 1, 1};
|
||||
|
||||
while(box.next()) {
|
||||
row_count += box.x == box.left;
|
||||
}
|
||||
REQUIRE(row_count == 3);
|
||||
}
|
||||
|
||||
{
|
||||
matrix::compass star{walls, 1, 1};
|
||||
while(star.next()) {
|
||||
println("START IS {},{}=={}", star.x, star.y, walls[star.y][star.x]);
|
||||
walls[star.y][star.x] = 11;
|
||||
}
|
||||
matrix::dump("STAR POINT", walls, 1,1);
|
||||
}
|
||||
}
|
||||
|
||||
inline void random_matrix(Matrix &out) {
|
||||
for(size_t y = 0; y < out.size(); y++) {
|
||||
for(size_t x = 0; x < out[0].size(); x++) {
|
||||
out[y][x] = Random::uniform<int>(-10,10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("thrash matrix iterators", "[matrix]") {
|
||||
for(int count = 0; count < Random::uniform<int>(10,30); count++) {
|
||||
size_t width = Random::uniform<size_t>(1, 100);
|
||||
size_t height = Random::uniform<size_t>(1, 100);
|
||||
|
||||
Matrix test(height, matrix::Row(width));
|
||||
random_matrix(test);
|
||||
|
||||
// first make a randomized matrix
|
||||
matrix::each_cell cells{test};
|
||||
cells.next(); // kick off the other iterator
|
||||
|
||||
for(matrix::each_row it{test};
|
||||
it.next(); cells.next())
|
||||
{
|
||||
REQUIRE(test[cells.y][cells.x] == test[it.y][it.x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("thrash box distance iterators", "[matrix:distance]") {
|
||||
size_t width = Random::uniform<size_t>(10, 21);
|
||||
size_t height = Random::uniform<size_t>(10, 25);
|
||||
|
||||
Matrix result(height, matrix::Row(width));
|
||||
matrix::assign(result, 0);
|
||||
|
||||
size_t size = Random::uniform<int>(4, 10);
|
||||
|
||||
Point target{width/2, height/2};
|
||||
matrix::box box{result, target.x, target.y, size};
|
||||
while(box.next()) {
|
||||
result[box.y][box.x] = box.distance();
|
||||
}
|
||||
|
||||
matrix::dump(format("MAP {}x{} @ {},{}; BOX {}x{}; size: {}",
|
||||
matrix::width(result), matrix::height(result),
|
||||
target.x, target.y, box.right - box.left, box.bottom - box.top, size),
|
||||
result, target.x, target.y);
|
||||
}
|
||||
|
||||
TEST_CASE("thrash box iterators", "[matrix]") {
|
||||
for(int count = 0; count < 20; count++) {
|
||||
size_t width = Random::uniform<size_t>(1, 25);
|
||||
size_t height = Random::uniform<size_t>(1, 33);
|
||||
|
||||
Matrix test(height, matrix::Row(width));
|
||||
random_matrix(test);
|
||||
|
||||
// this will be greater than the random_matrix cells
|
||||
int test_i = Random::uniform<size_t>(20,30);
|
||||
|
||||
// go through every cell
|
||||
for(matrix::each_cell target{test}; target.next();) {
|
||||
PointList result;
|
||||
// make a random size box
|
||||
size_t size = Random::uniform<int>(1, 33);
|
||||
matrix::box box{test, target.x, target.y, size};
|
||||
|
||||
while(box.next()) {
|
||||
test[box.y][box.x] = test_i;
|
||||
result.push_back({box.x, box.y});
|
||||
}
|
||||
|
||||
for(auto point : result) {
|
||||
REQUIRE(test[point.y][point.x] == test_i);
|
||||
test[point.y][point.x] = 10; // kind of reset it for another try
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("thrash compass iterators", "[matrix:compass]") {
|
||||
for(int count = 0; count < 20; count++) {
|
||||
size_t width = Random::uniform<size_t>(1, 25);
|
||||
size_t height = Random::uniform<size_t>(1, 33);
|
||||
|
||||
Matrix test(height, matrix::Row(width));
|
||||
random_matrix(test);
|
||||
|
||||
// this will be greater than the random_matrix cells
|
||||
int test_i = Random::uniform<size_t>(20,30);
|
||||
|
||||
// go through every cell
|
||||
for(matrix::each_cell target{test}; target.next();) {
|
||||
PointList result;
|
||||
// make a random size box
|
||||
matrix::compass compass{test, target.x, target.y};
|
||||
|
||||
while(compass.next()) {
|
||||
test[compass.y][compass.x] = test_i;
|
||||
result.push_back({compass.x, compass.y});
|
||||
}
|
||||
|
||||
for(auto point : result) {
|
||||
REQUIRE(test[point.y][point.x] == test_i);
|
||||
test[point.y][point.x] = 10; // kind of reset it for another try
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("prototype line algorithm", "[matrix:line]") {
|
||||
size_t width = Random::uniform<size_t>(10, 12);
|
||||
size_t height = Random::uniform<size_t>(10, 15);
|
||||
Map map(width,height);
|
||||
// create a target for the paths
|
||||
Point start{.x=map.width() / 2, .y=map.height()/2};
|
||||
|
||||
for(matrix::box box{map.walls(), start.x, start.y, 3};
|
||||
box.next();)
|
||||
{
|
||||
Matrix result = map.walls();
|
||||
result[start.y][start.x] = 1;
|
||||
Point end{.x=box.x, .y=box.y};
|
||||
|
||||
for(matrix::line it{start, end}; it.next();)
|
||||
{
|
||||
REQUIRE(map.inmap(it.x, it.y));
|
||||
result[it.y][it.x] = 15;
|
||||
}
|
||||
|
||||
result[start.y][start.x] = 15;
|
||||
|
||||
// matrix::dump("RESULT AFTER LINE", result, end.x, end.y);
|
||||
|
||||
bool f_found = false;
|
||||
for(matrix::each_cell it{result}; it.next();) {
|
||||
if(result[it.y][it.x] == 15) {
|
||||
f_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
REQUIRE(f_found);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("prototype circle algorithm", "[matrix:circle]") {
|
||||
for(int count = 0; count < 2; count++) {
|
||||
size_t width = Random::uniform<size_t>(10, 13);
|
||||
size_t height = Random::uniform<size_t>(10, 15);
|
||||
int pos_mod = Random::uniform<int>(-3,3);
|
||||
Map map(width,height);
|
||||
|
||||
// create a target for the paths
|
||||
Point start{.x=map.width() / 2 + pos_mod, .y=map.height()/2 + pos_mod};
|
||||
|
||||
for(float radius = 1.0f; radius < 4.0f; radius += 0.1f) {
|
||||
// use an empty map
|
||||
Matrix result = map.walls();
|
||||
|
||||
for(matrix::circle it{result, start, radius}; it.next();) {
|
||||
for(int x = it.left; x < it.right; x++) {
|
||||
// println("top={}, bottom={}, center.y={}, dy={}, left={}, right={}, x={}, y={}", it.top, it.bottom, it.center.y, it.dy, it.left, it.right, x, it.y);
|
||||
// println("RESULT {},{}", matrix::width(result), matrix::height(result));
|
||||
REQUIRE(it.y >= 0);
|
||||
REQUIRE(x >= 0);
|
||||
REQUIRE(it.y < int(matrix::height(result)));
|
||||
REQUIRE(x < int(matrix::width(result)));
|
||||
result[it.y][x] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// matrix::dump(format("RESULT AFTER CIRCLE radius {}", radius), result, start.x, start.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("viewport iterator", "[matrix:viewport]") {
|
||||
size_t width = Random::uniform<size_t>(20, 22);
|
||||
size_t height = Random::uniform<size_t>(21, 25);
|
||||
Map map(width,height);
|
||||
WorldBuilder builder(map);
|
||||
builder.generate_map();
|
||||
|
||||
size_t view_width = width/2;
|
||||
size_t view_height = height/2;
|
||||
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);
|
||||
size_t end_y = std::min(view_height, map.height() - start.y);
|
||||
|
||||
matrix::viewport it{map.walls(), start, int(view_width), int(view_height)};
|
||||
|
||||
for(size_t y = 0; y < end_y; ++y) {
|
||||
for(size_t x = 0; x < end_x && it.next(); ++x) {
|
||||
// still working on this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
51
tests/pathing.cpp
Normal file
51
tests/pathing.cpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <fmt/core.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <fstream>
|
||||
#include "pathing.hpp"
|
||||
#include "matrix.hpp"
|
||||
|
||||
using namespace fmt;
|
||||
using namespace nlohmann;
|
||||
using std::string;
|
||||
|
||||
json load_test_pathing(const string &fname) {
|
||||
std::ifstream infile(fname);
|
||||
return json::parse(infile);
|
||||
}
|
||||
|
||||
TEST_CASE("dijkstra algo test", "[pathing]") {
|
||||
json data = load_test_pathing("./tests/dijkstra.json");
|
||||
|
||||
for(auto &test : data) {
|
||||
Matrix expected = test["expected"];
|
||||
Matrix walls = test["walls"];
|
||||
|
||||
Pathing pathing(walls[0].size(), walls.size());
|
||||
|
||||
pathing.$input = test["input"];
|
||||
|
||||
REQUIRE(pathing.INVARIANT());
|
||||
pathing.compute_paths(walls);
|
||||
|
||||
REQUIRE(pathing.INVARIANT());
|
||||
|
||||
matrix::dump("PATHING RESULT", pathing.$paths);
|
||||
matrix::dump("PATHING EXPECTED", expected);
|
||||
REQUIRE(pathing.$paths == expected);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("random flood", "[pathing]") {
|
||||
json data = load_test_pathing("./tests/dijkstra.json");
|
||||
auto test = data[0];
|
||||
|
||||
Matrix expected = test["expected"];
|
||||
Matrix walls = test["walls"];
|
||||
|
||||
Pathing pathing(walls[0].size(), walls.size());
|
||||
pathing.$input = test["input"];
|
||||
|
||||
REQUIRE(pathing.INVARIANT());
|
||||
pathing.compute_paths(walls);
|
||||
}
|
103
tests/save.cpp
Normal file
103
tests/save.cpp
Normal file
|
@ -0,0 +1,103 @@
|
|||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <fmt/core.h>
|
||||
#include <string>
|
||||
#include "dinkyecs.hpp"
|
||||
#include "components.hpp"
|
||||
#include "save.hpp"
|
||||
#include <optional>
|
||||
#include <iostream>
|
||||
#include "map.hpp"
|
||||
#include "worldbuilder.hpp"
|
||||
#include "tser.hpp"
|
||||
|
||||
using namespace fmt;
|
||||
using std::string;
|
||||
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]") {
|
||||
DinkyECS::World world;
|
||||
Map map(20, 20);
|
||||
WorldBuilder builder(map);
|
||||
builder.generate_map();
|
||||
|
||||
// configure a player as a fact of the world
|
||||
Player player{world.entity()};
|
||||
world.set_the<Player>(player);
|
||||
|
||||
world.set<Position>(player.entity, {10,10});
|
||||
world.set<Motion>(player.entity, {0, 0});
|
||||
world.set<Combat>(player.entity, {100, 10});
|
||||
world.set<Tile>(player.entity, {"@"});
|
||||
world.set<Inventory>(player.entity, {102});
|
||||
|
||||
save::to_file("./savetest.world", world, map);
|
||||
|
||||
DinkyECS::World in_world;
|
||||
Map in_map(0, 0); // this will be changed on load
|
||||
save::from_file("./savetest.world", in_world, in_map);
|
||||
|
||||
Position &position1 = world.get<Position>(player.entity);
|
||||
Position &position2 = in_world.get<Position>(player.entity);
|
||||
REQUIRE(position1.location.x == position2.location.x);
|
||||
REQUIRE(position1.location.y == position2.location.y);
|
||||
|
||||
Combat &combat1 = world.get<Combat>(player.entity);
|
||||
Combat &combat2 = in_world.get<Combat>(player.entity);
|
||||
REQUIRE(combat1.hp == combat2.hp);
|
||||
|
||||
Motion &motion1 = world.get<Motion>(player.entity);
|
||||
Motion &motion2 = in_world.get<Motion>(player.entity);
|
||||
REQUIRE(motion1.dx == motion2.dx);
|
||||
REQUIRE(motion1.dy == motion2.dy);
|
||||
|
||||
Tile &tile1 = world.get<Tile>(player.entity);
|
||||
Tile &tile2 = in_world.get<Tile>(player.entity);
|
||||
REQUIRE(tile1.chr == tile2.chr);
|
||||
|
||||
REQUIRE(map.width() == in_map.width());
|
||||
REQUIRE(map.height() == in_map.height());
|
||||
REQUIRE(map.$walls == in_map.$walls);
|
||||
|
||||
Inventory &inv = world.get<Inventory>(player.entity);
|
||||
REQUIRE(inv.gold == 102);
|
||||
}
|
137
tests/spatialmap.cpp
Normal file
137
tests/spatialmap.cpp
Normal file
|
@ -0,0 +1,137 @@
|
|||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <fmt/core.h>
|
||||
#include <string>
|
||||
#include "spatialmap.hpp"
|
||||
#include "dinkyecs.hpp"
|
||||
|
||||
using DinkyECS::Entity;
|
||||
using namespace fmt;
|
||||
|
||||
EntityList require_found(const SpatialMap& collider, Point at, bool diag, size_t expect_size) {
|
||||
println("TEST require_found at={},{}", at.x, at.y);
|
||||
auto [found, nearby] = collider.neighbors(at, diag);
|
||||
REQUIRE(found == true);
|
||||
REQUIRE(nearby.size() == expect_size);
|
||||
return nearby;
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("confirm basic collision operations", "[collision]") {
|
||||
DinkyECS::World world;
|
||||
Entity player = world.entity();
|
||||
Entity enemy = world.entity();
|
||||
|
||||
SpatialMap collider;
|
||||
collider.insert({11,11}, player);
|
||||
collider.insert({21,21}, enemy);
|
||||
|
||||
{ // not found
|
||||
auto [found, nearby] = collider.neighbors({1,1});
|
||||
REQUIRE(!found);
|
||||
REQUIRE(nearby.empty());
|
||||
}
|
||||
|
||||
// found
|
||||
EntityList nearby = require_found(collider, {10,10}, true, 1);
|
||||
REQUIRE(nearby[0] == player);
|
||||
|
||||
{ // removed
|
||||
collider.remove({11,11});
|
||||
auto [found, nearby] = collider.neighbors({10,10}, true);
|
||||
REQUIRE(!found);
|
||||
REQUIRE(nearby.empty());
|
||||
}
|
||||
|
||||
collider.insert({11,11}, player); // setup for the move test
|
||||
{ // moving, not found
|
||||
collider.move({11,11}, {12, 12}, player);
|
||||
auto [found, nearby] = collider.neighbors({10,10}, true);
|
||||
REQUIRE(!found);
|
||||
REQUIRE(nearby.empty());
|
||||
}
|
||||
|
||||
nearby = require_found(collider, {11,11}, true, 1);
|
||||
REQUIRE(nearby[0] == player);
|
||||
|
||||
|
||||
// confirm occupied works
|
||||
REQUIRE(collider.occupied({12,12}));
|
||||
REQUIRE(collider.occupied({21,21}));
|
||||
REQUIRE(!collider.occupied({1,10}));
|
||||
|
||||
REQUIRE(collider.get({12,12}) == player);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("confirm multiple entities moving", "[collision]") {
|
||||
DinkyECS::World world;
|
||||
Entity player = world.entity();
|
||||
Entity e1 = world.entity();
|
||||
Entity e2 = world.entity();
|
||||
Entity e3 = world.entity();
|
||||
|
||||
SpatialMap collider;
|
||||
collider.insert({11,11}, player);
|
||||
collider.insert({10,10}, e2);
|
||||
collider.insert({11,10}, e3);
|
||||
collider.insert({21,21}, e1);
|
||||
|
||||
EntityList nearby = require_found(collider, {11,11}, false, 1);
|
||||
REQUIRE(nearby[0] == e3);
|
||||
|
||||
nearby = require_found(collider, {11,11}, true, 2);
|
||||
REQUIRE(nearby[0] == e3);
|
||||
REQUIRE(nearby[1] == e2);
|
||||
|
||||
collider.move({11,11}, {20,20}, player);
|
||||
nearby = require_found(collider, {20,20}, true, 1);
|
||||
REQUIRE(nearby[0] == e1);
|
||||
}
|
||||
|
||||
TEST_CASE("test edge cases that might crash", "[collision]") {
|
||||
DinkyECS::World world;
|
||||
Entity player = world.entity();
|
||||
Entity enemy = world.entity();
|
||||
|
||||
SpatialMap collider;
|
||||
collider.insert({0,0}, player);
|
||||
|
||||
Point enemy_at = {1, 0};
|
||||
collider.insert(enemy_at, enemy);
|
||||
|
||||
EntityList nearby = require_found(collider, {0,0}, true, 1);
|
||||
|
||||
collider.move({1,0}, {1,1}, enemy);
|
||||
nearby = require_found(collider, {0,0}, true, 1);
|
||||
REQUIRE(nearby[0] == enemy);
|
||||
|
||||
collider.move({1,1}, {0,1}, enemy);
|
||||
nearby = require_found(collider, {0,0}, true, 1);
|
||||
REQUIRE(nearby[0] == enemy);
|
||||
}
|
||||
|
||||
TEST_CASE("check all diagonal works", "[collision]") {
|
||||
DinkyECS::World world;
|
||||
Entity player = world.entity();
|
||||
Entity enemy = world.entity();
|
||||
|
||||
SpatialMap collider;
|
||||
Point player_at = {1,1};
|
||||
collider.insert(player_at, player);
|
||||
|
||||
Point enemy_at = {1, 0};
|
||||
collider.insert(enemy_at, enemy);
|
||||
|
||||
for(size_t x = 0; x <= 2; x++) {
|
||||
for(size_t y = 0; y <= 2; y++) {
|
||||
if(enemy_at.x == player_at.x && enemy_at.y == player_at.y) continue; // skip player spot
|
||||
EntityList nearby = require_found(collider, player_at, true, 1);
|
||||
REQUIRE(nearby[0] == enemy);
|
||||
|
||||
// move the enemy to a new spot around the player
|
||||
Point move_to = {enemy_at.x + x, enemy_at.y + y};
|
||||
collider.move(enemy_at, move_to, enemy);
|
||||
enemy_at = move_to;
|
||||
}
|
||||
}
|
||||
}
|
25
tests/tilemap.cpp
Normal file
25
tests/tilemap.cpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <fmt/core.h>
|
||||
#include "map.hpp"
|
||||
#include "worldbuilder.hpp"
|
||||
#include "tilemap.hpp"
|
||||
#include "config.hpp"
|
||||
#include "rand.hpp"
|
||||
|
||||
using namespace fmt;
|
||||
using std::string;
|
||||
|
||||
TEST_CASE("tilemap can load tiles and make a map", "[tilemap]") {
|
||||
size_t width = Random::uniform<size_t>(10, 25);
|
||||
size_t height = Random::uniform<size_t>(10, 33);
|
||||
|
||||
Map map(width,height);
|
||||
WorldBuilder builder(map);
|
||||
builder.generate_map();
|
||||
|
||||
TileMap tiles(map.width(), map.height());
|
||||
auto& walls = map.walls();
|
||||
tiles.load(walls);
|
||||
tiles.dump();
|
||||
REQUIRE(tiles.INVARIANT());
|
||||
}
|
35
tests/worldbuilder.cpp
Normal file
35
tests/worldbuilder.cpp
Normal file
|
@ -0,0 +1,35 @@
|
|||
#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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue