Initial commit

This commit is contained in:
Astatin3
2024-04-30 22:07:50 -06:00
commit 8565caa62a
8463 changed files with 4915934 additions and 0 deletions
+46
View File
@@ -0,0 +1,46 @@
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <catch2/generators/catch_generators.hpp>
#include <botcraft/Game/Physics/AABB.hpp>
using namespace Botcraft;
TEST_CASE("Constructor")
{
AABB aabb(Vector3<double>(0.5, 0.5, 0.5), Vector3<double>(0.5, 0.5, 0.5));
REQUIRE_THAT(aabb.GetMin().x, Catch::Matchers::WithinAbs(0.0, 1e-8));
REQUIRE_THAT(aabb.GetMin().y, Catch::Matchers::WithinAbs(0.0, 1e-8));
REQUIRE_THAT(aabb.GetMin().z, Catch::Matchers::WithinAbs(0.0, 1e-8));
REQUIRE_THAT(aabb.GetMax().x, Catch::Matchers::WithinAbs(1.0, 1e-8));
REQUIRE_THAT(aabb.GetMax().y, Catch::Matchers::WithinAbs(1.0, 1e-8));
REQUIRE_THAT(aabb.GetMax().z, Catch::Matchers::WithinAbs(1.0, 1e-8));
}
TEST_CASE("Translate")
{
AABB aabb(Vector3<double>(0.5, 0.5, 0.5), Vector3<double>(0.5, 0.5, 0.5));
aabb.Translate(Vector3<double>(1.0, 1.0, 1.0));
REQUIRE_THAT(aabb.GetMin().x, Catch::Matchers::WithinAbs(1.0, 1e-8));
REQUIRE_THAT(aabb.GetMin().y, Catch::Matchers::WithinAbs(1.0, 1e-8));
REQUIRE_THAT(aabb.GetMin().z, Catch::Matchers::WithinAbs(1.0, 1e-8));
REQUIRE_THAT(aabb.GetMax().x, Catch::Matchers::WithinAbs(2.0, 1e-8));
REQUIRE_THAT(aabb.GetMax().y, Catch::Matchers::WithinAbs(2.0, 1e-8));
REQUIRE_THAT(aabb.GetMax().z, Catch::Matchers::WithinAbs(2.0, 1e-8));
}
TEST_CASE("Inflate")
{
auto inflate = GENERATE(0.1, -0.1, 1e-6, -1e-6);
AABB aabb(Vector3<double>(0.5, 0.5, 0.5), Vector3<double>(0.5, 0.5, 0.5));
aabb.Inflate(inflate);
REQUIRE_THAT(aabb.GetMin().x, Catch::Matchers::WithinAbs(0.0 - inflate, 1e-8));
REQUIRE_THAT(aabb.GetMin().y, Catch::Matchers::WithinAbs(0.0 - inflate, 1e-8));
REQUIRE_THAT(aabb.GetMin().z, Catch::Matchers::WithinAbs(0.0 - inflate, 1e-8));
REQUIRE_THAT(aabb.GetMax().x, Catch::Matchers::WithinAbs(1.0 + inflate, 1e-8));
REQUIRE_THAT(aabb.GetMax().y, Catch::Matchers::WithinAbs(1.0 + inflate, 1e-8));
REQUIRE_THAT(aabb.GetMax().z, Catch::Matchers::WithinAbs(1.0 + inflate, 1e-8));
}
@@ -0,0 +1,655 @@
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_string.hpp>
#include <botcraft/AI/BehaviourTree.hpp>
using namespace Botcraft;
Status TestLeaf(int& c)
{
c += 1;
return Status::Success;
}
Status TestLeafWithArg(int& c, const int i)
{
c += i;
return Status::Success;
}
TEST_CASE("LeafTree")
{
int i = 0;
SECTION("Function")
{
auto tree = Builder<int>()
.leaf(TestLeaf);
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 1);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 2);
}
SECTION("NamedFunction")
{
auto tree = Builder<int>()
.leaf("leaf", TestLeaf);
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 1);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 2);
}
SECTION("FunctionWithArg")
{
auto tree = Builder<int>()
.leaf(TestLeafWithArg, 2);
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 2);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 4);
}
SECTION("NamedFunctionWithArg")
{
auto tree = Builder<int>()
.leaf("leaf", TestLeafWithArg, 2);
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 2);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 4);
}
SECTION("std::bind")
{
auto tree = Builder<int>()
.leaf(std::bind(TestLeafWithArg, std::placeholders::_1, 2));
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 2);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 4);
}
SECTION("Namedstd::bind")
{
auto tree = Builder<int>()
.leaf("leaf", std::bind(TestLeafWithArg, std::placeholders::_1, 2));
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 2);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 4);
}
SECTION("Lambda")
{
auto tree = Builder<int>()
.leaf([](int& i) { i += 1; return Status::Success; });
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 1);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 2);
}
SECTION("NamedLambda")
{
auto tree = Builder<int>()
.leaf("leaf", [](int& i) { i += 1; return Status::Success; });
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 1);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 2);
}
}
template<class Context>
class CustomDecorator : public Decorator<Context>
{
using Decorator<Context>::Decorator;
public:
virtual Status TickImpl(Context& context) const override
{
this->TickChild(context);
return Status::Failure;
}
};
TEST_CASE("Decorator")
{
int i = 0;
SECTION("Inverter")
{
auto tree = Builder<int>()
.inverter().leaf([](int& i) { i += 1; return Status::Success; });
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Failure);
CHECK(i == 1);
CHECK(tree->Tick(i) == Status::Failure);
CHECK(i == 2);
}
SECTION("NamedInverter")
{
auto tree = Builder<int>()
.inverter("inverter").leaf([](int& i) { i += 1; return Status::Success; });
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Failure);
CHECK(i == 1);
}
SECTION("Succeeder")
{
auto tree = Builder<int>()
.succeeder().leaf([](int& i) { i += 1; return Status::Failure; });
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 1);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 2);
}
SECTION("NamedSucceeder")
{
auto tree = Builder<int>()
.succeeder("succeeder").leaf([](int& i) { i += 1; return Status::Failure; });
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 1);
}
SECTION("Repeater")
{
auto tree = Builder<int>()
.repeater(3).leaf([](int& i) { i += 1; return Status::Success; });
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 3);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 6);
i = 0;
tree = Builder<int>()
.repeater(0).leaf([](int& i) { i += 1; return i > 10 ? Status::Success : Status::Failure; });
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 11);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 12);
}
SECTION("NamedRepeater")
{
auto tree = Builder<int>()
.repeater("repeater", 3).leaf([](int& i) { i += 1; return Status::Success; });
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 3);
}
SECTION("CustomDecorator")
{
auto tree = Builder<int>()
.decorator<CustomDecorator<int>>().leaf([](int& i) { i += 1; return Status::Success; });
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Failure);
CHECK(i == 1);
CHECK(tree->Tick(i) == Status::Failure);
CHECK(i == 2);
}
SECTION("NamedCustomDecorator")
{
auto tree = Builder<int>()
.decorator<CustomDecorator<int>>("custom decorator").leaf([](int& i) { i += 1; return Status::Success; });
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Failure);
CHECK(i == 1);
}
}
template<typename Context>
class CustomComposite : public Composite<Context>
{
using Composite<Context>::Composite;
public:
virtual Status TickImpl(Context& context) const override
{
for (size_t i = 0; i < this->GetNumChildren(); ++i)
{
this->TickChild(context, i);
}
return Status::Success;
}
};
TEST_CASE("Composite")
{
int i = 0;
SECTION("Sequence")
{
auto tree = Builder<int>()
.sequence()
.leaf([](int& i) { i += 1; return Status::Success; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Success; }) // Never reached
.end();
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Failure);
CHECK(i == 2);
CHECK(tree->Tick(i) == Status::Failure);
CHECK(i == 4);
i = 0;
tree = Builder<int>()
.sequence()
.leaf([](int& i) { i += 1; return Status::Success; })
.leaf([](int& i) { i += 1; return Status::Success; })
.leaf([](int& i) { i += 1; return Status::Success; })
.end();
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 3);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 6);
}
SECTION("NamedSequence")
{
auto tree = Builder<int>()
.sequence("sequence")
.leaf([](int& i) { i += 1; return Status::Success; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Success; }) // Never reached
.end();
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Failure);
CHECK(i == 2);
}
SECTION("Empty")
{
auto tree = Builder<int>()
.sequence() // empty sequence is useless but valid
.end();
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 0);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 0);
}
SECTION("NamedEmpty")
{
auto tree = Builder<int>()
.sequence("sequence") // empty sequence is useless but valid
.end();
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 0);
}
SECTION("Selector")
{
auto tree = Builder<int>()
.selector()
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Success; })
.leaf([](int& i) { i += 1; return Status::Success; }) // Never reached
.end();
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 3);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 6);
i = 0;
tree = Builder<int>()
.selector()
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.end();
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Failure);
CHECK(i == 4);
CHECK(tree->Tick(i) == Status::Failure);
CHECK(i == 8);
}
SECTION("NamedSelector")
{
auto tree = Builder<int>()
.selector("selector")
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Success; })
.leaf([](int& i) { i += 1; return Status::Success; }) // Never reached
.end();
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 3);
}
SECTION("CustomSelector")
{
auto tree = Builder<int>()
.composite<CustomComposite<int>>()
.leaf([](int& i) { i += 1; return Status::Success; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Success; })
.end();
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 3);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 6);
}
SECTION("NamedCustomSelector")
{
auto tree = Builder<int>()
.composite<CustomComposite<int>>("custom composite")
.leaf([](int& i) { i += 1; return Status::Success; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Success; })
.end();
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 3);
}
}
TEST_CASE("Subtree")
{
auto subtree = Builder<int>("tree")
.sequence()
.leaf([](int& i) { i += 1; return Status::Success; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Success; }) // Never reached
.end();
auto tree = Builder<int>()
.selector()
.tree(subtree)
.tree(subtree)
.leaf([](int& i) { i += 1; return Status::Success; })
.end();
int i = 0;
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 5);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 10);
}
TEST_CASE("Exceptions")
{
int i = 0;
SECTION("Anonymous")
{
auto tree = Builder<int>()
.inverter().sequence()
.selector()
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.succeeder().leaf([](int& i) { i += 1; return Status::Success; })
.end()
.selector()
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.repeater(2).composite<CustomComposite<int>>()
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; throw std::runtime_error("Exception to catch"); return Status::Failure; })
.end()
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Success; })
.end()
.end();
CHECK_FALSE(tree == nullptr);
CHECK_THROWS(tree->Tick(i));
CHECK(i == 11);
try
{
tree->Tick(i);
}
catch (const std::exception& ex)
{
CHECK_THAT(ex.what(), Catch::Matchers::Equals(
std::string("In Inverter\n") +
"In Sequence while Ticking child 1\n" +
"In Selector while Ticking child 2\n" +
"In Repeater\n" +
"In CustomComposite while Ticking child 3\n" +
"Exception to catch")
);
}
}
SECTION("Named")
{
auto tree = Builder<int>("tree")
.inverter("inverter").sequence("sequence")
.selector("selector 0")
.leaf("leaf 00", [](int& i) { i += 1; return Status::Failure; })
.leaf("leaf 01", [](int& i) { i += 1; return Status::Failure; })
.leaf("leaf 02", [](int& i) { i += 1; return Status::Failure; })
.leaf("leaf 03", [](int& i) { i += 1; return Status::Failure; })
.succeeder("succeeder").leaf("leaf 04", [](int& i) { i += 1; return Status::Success; })
.end()
.selector("selector 1")
.leaf("leaf 10", [](int& i) { i += 1; return Status::Failure; })
.leaf("leaf 11", [](int& i) { i += 1; return Status::Failure; })
.repeater("repeater", 2).composite<CustomComposite<int>>("custom composite")
.leaf("leaf 20", [](int& i) { i += 1; return Status::Failure; })
.leaf("leaf 21", [](int& i) { i += 1; return Status::Failure; })
.leaf("leaf 22", [](int& i) { i += 1; return Status::Failure; })
.leaf("leaf 23", [](int& i) { i += 1; throw std::runtime_error("Exception to catch"); return Status::Failure; })
.end()
.leaf("leaf 30", [](int& i) { i += 1; return Status::Failure; })
.leaf("leaf 31", [](int& i) { i += 1; return Status::Success; })
.end()
.end();
CHECK_FALSE(tree == nullptr);
try
{
tree->Tick(i);
}
catch (const std::exception& ex)
{
CHECK_THAT(ex.what(), Catch::Matchers::Equals(
std::string("In tree \"tree\"\n") +
"In \"inverter\" (Inverter)\n" +
"In \"sequence\" (Sequence) while Ticking child 1\n" +
"In \"selector 1\" (Selector) while Ticking child 2\n" +
"In \"repeater\" (Repeater)\n" +
"In \"custom composite\" (CustomComposite) while Ticking child 3\n" +
"In leaf \"leaf 23\"\n" +
"Exception to catch")
);
}
}
}
TEST_CASE("Nested")
{
int i = 0;
SECTION("Composite in Composite")
{
auto tree = Builder<int>()
.sequence()
.selector()
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Success; })
.end()
.selector()
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.composite<CustomComposite<int>>()
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.end()
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Success; })
.end()
.end();
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 11);
}
SECTION("Composite in decorator")
{
auto tree = Builder<int>()
.inverter().selector()
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.inverter().composite<CustomComposite<int>>()
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.end()
.leaf([](int& i) { i += 1; return Status::Failure; })
.leaf([](int& i) { i += 1; return Status::Failure; })
.end();
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 8);
}
SECTION("Decorator in decorator")
{
auto tree = Builder<int>()
.inverter().decorator<CustomDecorator<int>>().repeater(3).leaf([](int& i) { i += 1; return Status::Success; });
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Success);
CHECK(i == 3);
}
SECTION("Decorator in Composite")
{
auto tree = Builder<int>()
.sequence()
.inverter().leaf([](int& i) { i += 1; return Status::Failure; })
.repeater(3).leaf([](int& i) { i += 1; return Status::Success; })
.succeeder().leaf([](int& i) { i += 1; return Status::Failure; })
.decorator<CustomDecorator<int>>().leaf([](int& i) { i += 1; return Status::Success; })
.end();
CHECK_FALSE(tree == nullptr);
CHECK(tree->Tick(i) == Status::Failure);
CHECK(i == 6);
}
}
struct CustomContext
{
void OnNodeStartTick()
{
num_start_tick += 1;
}
void OnNodeEndTick(const Status s)
{
num_end_tick += 1;
if (s == Status::Success)
{
num_success_tick += 1;
}
else if (s == Status::Failure)
{
num_failure_tick += 1;
}
}
void OnNodeTickChild(const size_t index)
{
num_child_tick += 1;
}
int num_start_tick = 0;
int num_end_tick = 0;
int num_success_tick = 0;
int num_failure_tick = 0;
int num_child_tick = 0;
};
TEST_CASE("NodeCallbacks")
{
auto tree = Builder<CustomContext>()
.sequence()
.selector()
.leaf([](CustomContext& c) { return Status::Failure; })
.leaf([](CustomContext& c) { return Status::Failure; })
.leaf([](CustomContext& c) { return Status::Success; })
.leaf([](CustomContext& c) { return Status::Failure; })
.end()
.inverter().leaf([](CustomContext& c) { return Status::Failure; })
.leaf([](CustomContext& c) { return Status::Success; })
.end();
CustomContext context;
const Status s = tree->Tick(context);
CHECK(s == Status::Success);
CHECK(context.num_start_tick == 9);
CHECK(context.num_end_tick == 9);
CHECK(context.num_success_tick == 6);
CHECK(context.num_failure_tick == 3);
CHECK(context.num_child_tick == 8);
}
+79
View File
@@ -0,0 +1,79 @@
#include <catch2/catch_test_macros.hpp>
#include <botcraft/AI/Blackboard.hpp>
using namespace Botcraft;
TEST_CASE("Blackboard Read/Write values")
{
Blackboard blackboard;
REQUIRE_NOTHROW(blackboard.Set("hello", std::string("world")));
REQUIRE_NOTHROW(blackboard.Get<std::string>("hello"));
REQUIRE(blackboard.Get<std::string>("hello") == "world");
REQUIRE_NOTHROW(blackboard.Set("hello", 3));
REQUIRE_THROWS(blackboard.Get<std::string>("hello"));
REQUIRE(blackboard.Get<int>("hello") == 3);
REQUIRE_NOTHROW(blackboard.Copy("hello", "world"));
REQUIRE(blackboard.Get<int>("world") == 3);
REQUIRE_THROWS(blackboard.Copy("hallo", "world"));
REQUIRE_NOTHROW(blackboard.Erase("world"));
REQUIRE_THROWS(blackboard.Get<int>("world"));
NotifyOnEndUseRef<int> wrapped_ref = blackboard.GetRef<int>("hello", 42);
int& ref = wrapped_ref.ref();
REQUIRE(ref == 3);
ref = 42;
REQUIRE(ref == 42);
REQUIRE(blackboard.Get<int>("hello") == 42);
REQUIRE_NOTHROW(blackboard.Reset());
REQUIRE_THROWS(blackboard.Get<int>("hello"));
REQUIRE_NOTHROW(blackboard.Reset({ { "hello", 42 } }));
REQUIRE(blackboard.Get<int>("hello") == 42);
}
class TestBlackboardObserver : public BlackboardObserver
{
public:
void OnReset() override { is_reset = true; }
void OnValueChanged(const std::string& key, const std::any& value) override { is_value_changed = true; }
void OnValueRemoved(const std::string& key) override { is_value_removed = true; }
bool is_reset = false;
bool is_value_changed = false;
bool is_value_removed = false;
};
TEST_CASE("Blackboard notifications")
{
Blackboard blackboard;
TestBlackboardObserver observer;
blackboard.Subscribe(&observer);
REQUIRE_FALSE(observer.is_reset);
REQUIRE_FALSE(observer.is_value_changed);
REQUIRE_FALSE(observer.is_value_removed);
blackboard.Set<std::string>("hello", "world");
REQUIRE(observer.is_value_changed);
observer.is_value_changed = false;
{
const NotifyOnEndUseRef<std::string> wrapped_ref = blackboard.GetRef<std::string>("hello");
std::string& ref = wrapped_ref.ref();
ref = "world!";
}
REQUIRE(blackboard.Get<std::string>("hello") == "world!");
REQUIRE(observer.is_value_changed);
blackboard.Erase("hello");
REQUIRE(observer.is_value_removed);
blackboard.Reset();
REQUIRE(observer.is_reset);
}
+178
View File
@@ -0,0 +1,178 @@
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <botcraft/Game/World/Blockstate.hpp>
using namespace Botcraft;
TEST_CASE("Testing mining time calculation")
{
BlockstateProperties blockstate_properties;
SECTION("air")
{
blockstate_properties.hardness = -1.0f;
blockstate_properties.any_tool_harvest = false;
blockstate_properties.best_tools = {
};
Blockstate blockstate(blockstate_properties);
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::None, ToolMaterial::None), Catch::Matchers::WithinAbs(-1.0, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Axe, ToolMaterial::Wood), Catch::Matchers::WithinAbs(-1.0, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Shears, ToolMaterial::None), Catch::Matchers::WithinAbs(-1.0, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Sword, ToolMaterial::Diamond), Catch::Matchers::WithinAbs(-1.0, 0.04));
}
SECTION("water")
{
blockstate_properties.hardness = 100.0f;
blockstate_properties.any_tool_harvest = false;
blockstate_properties.water = true;
blockstate_properties.best_tools = {
};
Blockstate blockstate(blockstate_properties);
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::None, ToolMaterial::None), Catch::Matchers::WithinAbs(-1.0, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Axe, ToolMaterial::Wood), Catch::Matchers::WithinAbs(-1.0, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Shears, ToolMaterial::None), Catch::Matchers::WithinAbs(-1.0, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Sword, ToolMaterial::Diamond), Catch::Matchers::WithinAbs(-1.0, 0.04));
}
SECTION("obsidian")
{
blockstate_properties.hardness = 50.0f;
blockstate_properties.any_tool_harvest = false;
blockstate_properties.best_tools = {
BestTool {
ToolType::Pickaxe, //tool_type
ToolMaterial::Diamond, //tool_material
1.0f //multiplier
}
};
Blockstate blockstate(blockstate_properties);
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::None, ToolMaterial::None), Catch::Matchers::WithinAbs(250.0, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Axe, ToolMaterial::Wood), Catch::Matchers::WithinAbs(250.0, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Shears, ToolMaterial::None), Catch::Matchers::WithinAbs(250.0, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Shovel, ToolMaterial::Gold), Catch::Matchers::WithinAbs(250.0, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Sword, ToolMaterial::Diamond), Catch::Matchers::WithinAbs(250.0, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Pickaxe, ToolMaterial::Wood), Catch::Matchers::WithinAbs(125.0, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Pickaxe, ToolMaterial::Gold), Catch::Matchers::WithinAbs(20.85, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Pickaxe, ToolMaterial::Diamond), Catch::Matchers::WithinAbs(9.4, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Pickaxe, ToolMaterial::Diamond, 5), Catch::Matchers::WithinAbs(2.25, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Pickaxe, ToolMaterial::Diamond, 0, 2), Catch::Matchers::WithinAbs(6.7, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Pickaxe, ToolMaterial::Diamond, 0, 0, 0, false), Catch::Matchers::WithinAbs(46.9, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Pickaxe, ToolMaterial::Diamond, 0, 0, 0, true, true), Catch::Matchers::WithinAbs(46.9, 0.04));
}
SECTION("ender_chest")
{
BlockstateProperties blockstate_properties;
blockstate_properties.hardness = 22.5f;
blockstate_properties.any_tool_harvest = false;
blockstate_properties.best_tools = {
BestTool {
ToolType::Pickaxe, //tool_type
ToolMaterial::Wood, //tool_material
1.0f //multiplier
}
};
Blockstate blockstate(blockstate_properties);
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::None, ToolMaterial::None), Catch::Matchers::WithinAbs(112.5, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Axe, ToolMaterial::Wood), Catch::Matchers::WithinAbs(112.5, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Shears, ToolMaterial::None), Catch::Matchers::WithinAbs(112.5, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Shovel, ToolMaterial::Stone), Catch::Matchers::WithinAbs(112.5, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Sword, ToolMaterial::Iron), Catch::Matchers::WithinAbs(112.5, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Pickaxe, ToolMaterial::Wood), Catch::Matchers::WithinAbs(16.9, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Pickaxe, ToolMaterial::Gold), Catch::Matchers::WithinAbs(2.85, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Pickaxe, ToolMaterial::Diamond), Catch::Matchers::WithinAbs(4.25, 0.04));
}
SECTION("chest")
{
BlockstateProperties blockstate_properties;
blockstate_properties.hardness = 2.5f;
blockstate_properties.any_tool_harvest = true;
blockstate_properties.best_tools = {
BestTool {
ToolType::Axe, //tool_type
ToolMaterial::Wood, //tool_material
1.0f //multiplier
}
};
Blockstate blockstate(blockstate_properties);
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::None, ToolMaterial::None), Catch::Matchers::WithinAbs(3.75, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Axe, ToolMaterial::Wood), Catch::Matchers::WithinAbs(1.9, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Axe, ToolMaterial::Gold), Catch::Matchers::WithinAbs(0.35, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Shears, ToolMaterial::None), Catch::Matchers::WithinAbs(3.75, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Sword, ToolMaterial::Gold), Catch::Matchers::WithinAbs(3.75, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Shovel, ToolMaterial::Stone), Catch::Matchers::WithinAbs(3.75, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Pickaxe, ToolMaterial::Wood), Catch::Matchers::WithinAbs(3.75, 0.04));
}
SECTION("dirt")
{
BlockstateProperties blockstate_properties;
blockstate_properties.hardness = 0.5f;
blockstate_properties.any_tool_harvest = true;
blockstate_properties.best_tools = {
BestTool {
ToolType::Shovel, //tool_type
ToolMaterial::Wood, //tool_material
1.0f //multiplier
}
};
Blockstate blockstate(blockstate_properties);
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::None, ToolMaterial::None), Catch::Matchers::WithinAbs(0.75, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Axe, ToolMaterial::Wood), Catch::Matchers::WithinAbs(0.75, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Shears, ToolMaterial::None), Catch::Matchers::WithinAbs(0.75, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Sword, ToolMaterial::Gold), Catch::Matchers::WithinAbs(0.75, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Pickaxe, ToolMaterial::Wood), Catch::Matchers::WithinAbs(0.75, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Shovel, ToolMaterial::Wood), Catch::Matchers::WithinAbs(0.4, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Shovel, ToolMaterial::Gold), Catch::Matchers::WithinAbs(0.1, 0.04));
#if PROTOCOL_VERSION > 578 /* > 1.15.2 */
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Shovel, ToolMaterial::Netherite), Catch::Matchers::WithinAbs(0.1, 0.04));
#endif
}
SECTION("leaves")
{
BlockstateProperties blockstate_properties;
blockstate_properties.hardness = 0.2f;
blockstate_properties.any_tool_harvest = true;
blockstate_properties.best_tools = {
BestTool {
ToolType::Shears, //tool_type
ToolMaterial::None, //tool_material
15.0f //multiplier
},
BestTool {
ToolType::Sword, //tool_type
ToolMaterial::None, //tool_material
1.5f //multiplier
},
BestTool {
ToolType::Hoe, //tool_type
ToolMaterial::Wood, //tool_material
1.0f //multiplier
},
};
Blockstate blockstate(blockstate_properties);
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::None, ToolMaterial::None), Catch::Matchers::WithinAbs(0.3, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Shears, ToolMaterial::None), Catch::Matchers::WithinAbs(0.0, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Sword, ToolMaterial::Gold), Catch::Matchers::WithinAbs(0.2, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Sword, ToolMaterial::Diamond), Catch::Matchers::WithinAbs(0.2, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Hoe, ToolMaterial::Wood), Catch::Matchers::WithinAbs(0.15, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Hoe, ToolMaterial::Stone), Catch::Matchers::WithinAbs(0.1, 0.04));
REQUIRE_THAT(blockstate.GetMiningTimeSeconds(ToolType::Hoe, ToolMaterial::Diamond), Catch::Matchers::WithinAbs(0.0, 0.04));
}
}
+18
View File
@@ -0,0 +1,18 @@
#include <catch2/reporters/catch_reporter_event_listener.hpp>
#include <catch2/reporters/catch_reporter_registrars.hpp>
#include <botcraft/Utilities/Logger.hpp>
class testRunListener : public Catch::EventListenerBase
{
public:
using Catch::EventListenerBase::EventListenerBase;
void testRunStarting(Catch::TestRunInfo const&) override
{
// Set log level to warning before starting the tests
Botcraft::Logger::GetInstance().SetLogLevel(Botcraft::LogLevel::Warning);
}
};
CATCH_REGISTER_LISTENER(testRunListener)
+228
View File
@@ -0,0 +1,228 @@
#include <catch2/catch_test_macros.hpp>
#include <botcraft/Game/AssetsManager.hpp>
#include <botcraft/Game/World/World.hpp>
#include <botcraft/Game/World/Biome.hpp>
using namespace Botcraft;
TEST_CASE("Add/Remove chunks")
{
World world = World(false);
#if PROTOCOL_VERSION < 719 /* < 1.16 */
const Dimension dimension = Dimension::Overworld;
#else
const std::string dimension = "minecraft:overworld";
#endif
#if PROTOCOL_VERSION > 756 /* > 1.17.1 */
world.SetDimensionMinY(dimension, 0);
world.SetDimensionHeight(dimension, 256);
#endif
world.SetCurrentDimension(dimension);
REQUIRE(world.GetChunks()->size() == 0);
world.LoadChunk(0, 0, dimension);
REQUIRE(world.GetChunks()->size() == 1);
world.UnloadChunk(0, 0);
REQUIRE(world.GetChunks()->size() == 0);
world.LoadChunk(0, 0, dimension);
world.LoadChunk(0, 1, dimension);
REQUIRE(world.GetChunks()->size() == 2);
world.UnloadAllChunks();
REQUIRE(world.GetChunks()->size() == 0);
}
TEST_CASE("Set/Get blocks")
{
World world = World(false);
#if PROTOCOL_VERSION < 719 /* < 1.16 */
const Dimension dimension = Dimension::Overworld;
#else
const std::string dimension = "minecraft:overworld";
#endif
#if PROTOCOL_VERSION > 756 /* > 1.17.1 */
world.SetDimensionMinY(dimension, 0);
world.SetDimensionHeight(dimension, 256);
#endif
world.SetCurrentDimension(dimension);
#if PROTOCOL_VERSION < 347 /* < 1.13 */
const BlockstateId id = { 1,0 };
#else
const BlockstateId id = 1;
#endif
// Does nothing: chunk not loaded
world.SetBlock(Position(0, 0, 0), id);
REQUIRE(world.GetBlock(Position(0, 0, 0)) == nullptr);
world.LoadChunk(0, 0, dimension);
world.SetBlock(Position(0, 0, 0), id);
REQUIRE(world.GetBlock(Position(0, 0, 0)) != nullptr);
}
TEST_CASE("Set/Get biomes")
{
World world = World(false);
#if PROTOCOL_VERSION < 719 /* < 1.16 */
const Dimension dimension = Dimension::Overworld;
#else
const std::string dimension = "minecraft:overworld";
#endif
#if PROTOCOL_VERSION > 756 /* > 1.17.1 */
world.SetDimensionMinY(dimension, 0);
world.SetDimensionHeight(dimension, 256);
#endif
world.SetCurrentDimension(dimension);
// Does nothing: chunk not loaded
#if PROTOCOL_VERSION < 552 /* < 1.15 */
world.SetBiome(0, 0, 0);
#else
world.SetBiome(0, 0, 0, 0);
#endif
REQUIRE(world.GetBiome(Position(0, 0, 0)) == nullptr);
world.LoadChunk(0, 0, dimension);
#if PROTOCOL_VERSION < 552 /* < 1.15 */
world.SetBiome(0, 0, 0);
#else
world.SetBiome(0, 0, 0, 0);
#endif
REQUIRE(world.GetBiome(Position(0, 0, 0)) != nullptr);
REQUIRE(world.GetBiome(Position(0, 0, 0))->GetName() == AssetsManager::getInstance().GetBiome(0)->GetName());
}
#if USE_GUI
TEST_CASE("Neighbour chunk update")
{
World world = World(false);
#if PROTOCOL_VERSION < 719 /* < 1.16 */
const Dimension dimension = Dimension::Overworld;
#else
const std::string dimension = "minecraft:overworld";
#endif
#if PROTOCOL_VERSION > 756 /* > 1.17.1 */
world.SetDimensionMinY(dimension, 0);
world.SetDimensionHeight(dimension, 256);
#endif
world.SetCurrentDimension(dimension);
world.LoadChunk(0, 0, dimension);
world.LoadChunk(0, 1, dimension);
#if PROTOCOL_VERSION < 347 /* < 1.13 */
const BlockstateId id = { 1,0 };
#else
const BlockstateId id = 1;
#endif
world.SetBlock(Position(0, 0, CHUNK_WIDTH - 1), id);
{
auto world_terrain = world.GetChunks();
REQUIRE(world_terrain->at({ 0,0 }).GetBlock(Position(0, 0, CHUNK_WIDTH - 1)) != nullptr);
REQUIRE(world_terrain->at({ 0,0 }).GetBlock(Position(0, 0, CHUNK_WIDTH - 1))->GetId() == id);
REQUIRE(world_terrain->at({ 0,1 }).GetBlock(Position(0, 0, -1)) != nullptr);
REQUIRE(world_terrain->at({ 0,1 }).GetBlock(Position(0, 0, -1))->GetId() == id);
}
}
#endif
TEST_CASE("Shared world")
{
std::thread thread1 = std::thread([]() {});
std::thread thread2 = std::thread([]() {});
const std::thread::id thread_id1 = thread1.get_id();
const std::thread::id thread_id2 = thread2.get_id();
thread1.join();
thread2.join();
World world = World(true);
#if PROTOCOL_VERSION < 719 /* < 1.16 */
const Dimension dimension = Dimension::Overworld;
#else
const std::string dimension = "minecraft:overworld";
#endif
#if PROTOCOL_VERSION > 756 /* > 1.17.1 */
world.SetDimensionMinY(dimension, 0);
world.SetDimensionHeight(dimension, 256);
#endif
world.SetCurrentDimension(dimension);
REQUIRE(world.GetChunks()->size() == 0);
SECTION("Load same chunk")
{
world.LoadChunk(0, 0, dimension, thread_id1);
world.LoadChunk(0, 0, dimension, thread_id2);
REQUIRE(world.GetChunks()->size() == 1);
world.UnloadChunk(0, 0, thread_id1);
REQUIRE(world.GetChunks()->size() == 1);
world.UnloadChunk(0, 0, thread_id1);
REQUIRE(world.GetChunks()->size() == 1);
world.UnloadChunk(0, 0, thread_id2);
REQUIRE(world.GetChunks()->size() == 0);
}
SECTION("Load different chunks")
{
world.LoadChunk(0, 0, dimension, thread_id1);
world.LoadChunk(0, 1, dimension, thread_id2);
REQUIRE(world.GetChunks()->size() == 2);
world.UnloadAllChunks(thread_id1);
REQUIRE(world.GetChunks()->size() == 1);
world.UnloadAllChunks(thread_id1);
REQUIRE(world.GetChunks()->size() == 1);
world.UnloadAllChunks(thread_id2);
REQUIRE(world.GetChunks()->size() == 0);
}
}
TEST_CASE("Set/Get lights")
{
World world = World(false);
#if PROTOCOL_VERSION < 719 /* < 1.16 */
const Dimension dimension = Dimension::Overworld;
#else
const std::string dimension = "minecraft:overworld";
#endif
#if PROTOCOL_VERSION > 756 /* > 1.17.1 */
world.SetDimensionMinY(dimension, 0);
world.SetDimensionHeight(dimension, 256);
#endif
world.SetCurrentDimension(dimension);
// Does nothing: chunk not loaded
world.SetBlockLight(Position(0, 0, 0), 12);
CHECK(world.GetBlockLight(Position(0, 0, 0)) == 0);
world.SetSkyLight(Position(0, 0, 0), 6);
CHECK(world.GetSkyLight(Position(0, 0, 0)) == 0);
world.LoadChunk(0, 0, dimension);
world.SetBlockLight(Position(0, 0, 0), 12);
CHECK(world.GetBlockLight(Position(0, 0, 0)) == 12);
world.SetBlockLight(Position(1, 0, 0), 6);
CHECK(world.GetBlockLight(Position(0, 0, 0)) == 12);
CHECK(world.GetBlockLight(Position(1, 0, 0)) == 6);
world.SetSkyLight(Position(0, 0, 0), 12);
CHECK(world.GetSkyLight(Position(0, 0, 0)) == 12);
world.SetSkyLight(Position(1, 0, 0), 6);
CHECK(world.GetSkyLight(Position(0, 0, 0)) == 12);
CHECK(world.GetSkyLight(Position(1, 0, 0)) == 6);
}