test: cover flatWorld and grassWorld

This commit is contained in:
dualfroz committed 2026-09-10 18:58:57 +02:00
1 parent 30b64ccfaa
commit a53c700c17
1 file changed
+158
+158
View File
@@ -0,0 +1,158 @@
#include "testHarness.h"
#include "vf/Behavior.h"
#include <memory>
#include <string>
#include <vector>
using namespace vf;
namespace {
World flatWorld(int halfSpan = 24) {
World world;
for (int x = -halfSpan; x <= halfSpan; ++x) {
for (int z = -halfSpan; z <= halfSpan; ++z) world.setBlock(x, 0, z, Block::Stone);
}
return world;
}
World grassWorld(int halfSpan = 12) {
World world;
for (int x = -halfSpan; x <= halfSpan; ++x) {
for (int z = -halfSpan; z <= halfSpan; ++z) world.setBlock(x, 0, z, Block::Grass);
}
return world;
}
std::unique_ptr<BehaviorNode> constantAction(const char* name, BehaviorStatus status, int* counter) {
return std::unique_ptr<BehaviorNode>(new ActionNode(name, [status, counter](BehaviorContext&) {
if (counter != nullptr) ++(*counter);
return status;
}));
}
std::unique_ptr<BehaviorNode> flagCondition(const char* name, const bool* source) {
return std::unique_ptr<BehaviorNode>(
new ConditionNode(name, [source](BehaviorContext&) { return *source; }));
}
// Reports Running for `runningTicks` calls, then Success once, then restarts.
std::unique_ptr<BehaviorNode> slowAction(const char* name, int runningTicks, int* counter,
int* progress) {
return std::unique_ptr<BehaviorNode>(
new ActionNode(name, [runningTicks, counter, progress](BehaviorContext&) {
++(*counter);
++(*progress);
if (*progress <= runningTicks) return BehaviorStatus::Running;
*progress = 0;
return BehaviorStatus::Success;
}));
}
BehaviorContext freshContext(const World* world) {
BehaviorContext ctx;
ctx.world = world;
return ctx;
}
BehaviorAgent makeAgent(Species species, const Vec3& position) {
BehaviorAgent agent;
agent.species = species;
agent.position = position;
agent.homePosition = position;
const SpeciesStats& stats = speciesStats(species);
agent.health = stats.maxHealth;
agent.maxHealth = stats.maxHealth;
agent.moveStep = 0.25f;
return agent;
}
} // namespace
VF_TEST(blackboardKeepsEachValueTypeInItsOwnDrawer) {
Blackboard board;
board.setNumber("threat", 4.5f);
board.setCount("meals", 3);
board.setFlag("engaged", true);
board.setPoint("lastSeen", Vec3{1.0f, 2.0f, 3.0f});
board.setCell("target", Vec3i{4, 5, 6});
board.setRoute("path", {Vec3i{0, 1, 0}, Vec3i{1, 1, 0}});
CHECK_NEAR(board.number("threat"), 4.5f, 1e-6f);
CHECK_EQ(board.count("meals"), 3);
CHECK(board.flag("engaged"));
CHECK_NEAR(board.point("lastSeen").y, 2.0f, 1e-6f);
CHECK(board.cell("target") == Vec3i(4, 5, 6));
CHECK(board.route("path") != nullptr);
CHECK_EQ(board.route("path")->size(), 2u);
CHECK_EQ(board.entryCount(), 6u);
// A key written as one type is invisible to every other type.
CHECK(!board.hasCount("threat"));
CHECK(!board.hasNumber("meals"));
CHECK(board.route("threat") == nullptr);
CHECK_NEAR(board.number("missing", -1.0f), -1.0f, 1e-6f);
board.eraseNumber("threat");
CHECK(!board.hasNumber("threat"));
CHECK_EQ(board.entryCount(), 5u);
board.eraseRoute("path");
CHECK(!board.hasRoute("path"));
board.clear();
CHECK_EQ(board.entryCount(), 0u);
}
VF_TEST(sequenceStopsAtTheChildThatFailsAndSkipsTheRest) {
int firstRuns = 0;
int lastRuns = 0;
bool gateOpen = false;
auto root = std::unique_ptr<SequenceNode>(new SequenceNode("sequence"));
root->add(constantAction("first", BehaviorStatus::Success, &firstRuns));
root->add(flagCondition("gate", &gateOpen));
root->add(constantAction("last", BehaviorStatus::Success, &lastRuns));
BehaviorTree tree(std::move(root));
BehaviorContext ctx;
CHECK(tree.tick(ctx) == BehaviorStatus::Failure);
CHECK_EQ(firstRuns, 1);
CHECK_EQ(lastRuns, 0);
gateOpen = true;
CHECK(tree.tick(ctx) == BehaviorStatus::Success);
CHECK_EQ(firstRuns, 2);
CHECK_EQ(lastRuns, 1);
}
VF_TEST(selectorTakesTheFirstBranchThatSucceedsAndFailsWhenNoneDo) {
int firstRuns = 0;
int secondRuns = 0;
int thirdRuns = 0;
auto root = std::unique_ptr<SelectorNode>(new SelectorNode("selector"));
root->add(constantAction("first", BehaviorStatus::Failure, &firstRuns));
root->add(constantAction("second", BehaviorStatus::Success, &secondRuns));
root->add(constantAction("third", BehaviorStatus::Success, &thirdRuns));
BehaviorTree tree(std::move(root));
BehaviorContext ctx;
CHECK(tree.tick(ctx) == BehaviorStatus::Success);
CHECK_EQ(firstRuns, 1);
CHECK_EQ(secondRuns, 1);
CHECK_EQ(thirdRuns, 0);
auto allFail = std::unique_ptr<SelectorNode>(new SelectorNode("selector"));
allFail->add(constantAction("a", BehaviorStatus::Failure, nullptr));
allFail->add(constantAction("b", BehaviorStatus::Failure, nullptr));
BehaviorTree hopeless(std::move(allFail));
CHECK(hopeless.tick(ctx) == BehaviorStatus::Failure);
}
VF_TEST(aRunningNodeResumesNextTickInsteadOfRestartingTheSequence) {
int firstRuns = 0;
int slowRuns = 0;
int slowProgress = 0;