test: extend coverage of testBehavior
This commit is contained in:
1 parent
8103be3f96
commit
e7781bb5ca
1 file changed
+162
@@ -313,3 +313,165 @@ VF_TEST(timeLimitCutsOffAChildThatNeverSettles) {
|
||||
CHECK_EQ(runs, 4);
|
||||
}
|
||||
|
||||
VF_TEST(parallelSettlesOnItsSuccessAndFailureThresholds) {
|
||||
int slowRuns = 0;
|
||||
int slowProgress = 0;
|
||||
|
||||
auto wide = std::unique_ptr<ParallelNode>(new ParallelNode("parallel", 2, 2));
|
||||
wide->add(constantAction("ok", BehaviorStatus::Success, nullptr));
|
||||
wide->add(slowAction("slow", 2, &slowRuns, &slowProgress));
|
||||
wide->add(constantAction("nope", BehaviorStatus::Failure, nullptr));
|
||||
|
||||
BehaviorTree tree(std::move(wide));
|
||||
BehaviorContext ctx;
|
||||
|
||||
// One success, one failure, one still running: neither threshold is met.
|
||||
CHECK(tree.tick(ctx) == BehaviorStatus::Running);
|
||||
CHECK(tree.tick(ctx) == BehaviorStatus::Running);
|
||||
// The slow child succeeds on its third visit, which is the second success.
|
||||
CHECK(tree.tick(ctx) == BehaviorStatus::Success);
|
||||
CHECK_EQ(slowRuns, 3);
|
||||
|
||||
auto doomed = std::unique_ptr<ParallelNode>(new ParallelNode("parallel", 3, 2));
|
||||
doomed->add(constantAction("no", BehaviorStatus::Failure, nullptr));
|
||||
doomed->add(constantAction("alsoNo", BehaviorStatus::Failure, nullptr));
|
||||
doomed->add(constantAction("yes", BehaviorStatus::Success, nullptr));
|
||||
BehaviorTree unlucky(std::move(doomed));
|
||||
CHECK(unlucky.tick(ctx) == BehaviorStatus::Failure);
|
||||
}
|
||||
|
||||
VF_TEST(anOverDeepTreeIsRejectedInsteadOfOverflowingTheStack) {
|
||||
std::unique_ptr<BehaviorNode> node = constantAction("leaf", BehaviorStatus::Success, nullptr);
|
||||
for (int level = 0; level < maxBehaviorDepth + 12; ++level) {
|
||||
node = std::unique_ptr<BehaviorNode>(new InverterNode("wrap", std::move(node)));
|
||||
}
|
||||
|
||||
BehaviorTree tree(std::move(node));
|
||||
CHECK(!tree.structureValid());
|
||||
CHECK(tree.stats().depth > maxBehaviorDepth);
|
||||
CHECK_EQ(tree.stats().nodeCount, maxBehaviorDepth + 13);
|
||||
|
||||
BehaviorContext ctx;
|
||||
CHECK(tree.tick(ctx) == BehaviorStatus::Failure);
|
||||
CHECK_EQ(ctx.tick, 0);
|
||||
CHECK_EQ(tree.tickCount(), 0);
|
||||
}
|
||||
|
||||
VF_TEST(aTreeExactlyAtTheDepthLimitStillRuns) {
|
||||
std::unique_ptr<BehaviorNode> node = constantAction("leaf", BehaviorStatus::Success, nullptr);
|
||||
for (int level = 0; level < maxBehaviorDepth - 1; ++level) {
|
||||
node = std::unique_ptr<BehaviorNode>(new InverterNode("wrap", std::move(node)));
|
||||
}
|
||||
|
||||
BehaviorTree tree(std::move(node));
|
||||
CHECK_EQ(tree.stats().depth, maxBehaviorDepth);
|
||||
CHECK(tree.structureValid());
|
||||
|
||||
BehaviorContext ctx;
|
||||
tree.tick(ctx);
|
||||
CHECK_EQ(ctx.tick, 1);
|
||||
}
|
||||
|
||||
VF_TEST(theTraceRecordsEveryNodeVisitedInOrder) {
|
||||
auto root = std::unique_ptr<SequenceNode>(new SequenceNode("root"));
|
||||
root->add(constantAction("first", BehaviorStatus::Success, nullptr));
|
||||
root->add(constantAction("second", BehaviorStatus::Failure, nullptr));
|
||||
root->add(constantAction("third", BehaviorStatus::Success, nullptr));
|
||||
|
||||
BehaviorTree tree(std::move(root));
|
||||
BehaviorContext ctx;
|
||||
ctx.traceEnabled = true;
|
||||
tree.tick(ctx);
|
||||
|
||||
std::vector<std::string> expected{"root", "first", "second"};
|
||||
CHECK_EQ(ctx.trace.size(), expected.size());
|
||||
for (std::size_t i = 0; i < expected.size() && i < ctx.trace.size(); ++i) {
|
||||
CHECK_EQ(ctx.trace[i], expected[i]);
|
||||
}
|
||||
}
|
||||
|
||||
VF_TEST(theHostileTreePathsToThePlayerAndLandsRepeatedBlows) {
|
||||
World world = flatWorld();
|
||||
BehaviorAgent agent = makeAgent(Species::Zombie, {2.5f, 1.0f, 2.5f});
|
||||
|
||||
BehaviorContext ctx = freshContext(&world);
|
||||
ctx.agent = &agent;
|
||||
ctx.playerPresent = true;
|
||||
ctx.playerPosition = {10.5f, 1.0f, 2.5f};
|
||||
|
||||
BehaviorTree tree = makeHostileHunterTree();
|
||||
CHECK(tree.structureValid());
|
||||
|
||||
float startDistance = behaviorHorizontalDistance(agent.position, ctx.playerPosition);
|
||||
for (int step = 0; step < 120; ++step) tree.tick(ctx);
|
||||
|
||||
CHECK(behaviorHorizontalDistance(agent.position, ctx.playerPosition) < startDistance);
|
||||
CHECK(behaviorHorizontalDistance(agent.position, ctx.playerPosition) <=
|
||||
speciesStats(Species::Zombie).attackRange);
|
||||
CHECK(agent.attacksLanded >= 2);
|
||||
CHECK(ctx.blackboard.flag("engaged"));
|
||||
CHECK_NEAR(ctx.blackboard.number("lastAttackDamage"),
|
||||
speciesStats(Species::Zombie).attackDamage, 1e-6f);
|
||||
CHECK(agent.routeGoal == behaviorCellOf(ctx.playerPosition));
|
||||
CHECK(agent.routePlannedTick > 0);
|
||||
}
|
||||
|
||||
VF_TEST(theHostileTreeWalksHomeOnceTheTargetIsGone) {
|
||||
World world = flatWorld();
|
||||
BehaviorAgent agent = makeAgent(Species::Zombie, {9.5f, 1.0f, 2.5f});
|
||||
agent.homePosition = {2.5f, 1.0f, 2.5f};
|
||||
|
||||
BehaviorContext ctx = freshContext(&world);
|
||||
ctx.agent = &agent;
|
||||
ctx.playerPresent = false;
|
||||
|
||||
BehaviorTree tree = makeHostileHunterTree();
|
||||
for (int step = 0; step < 120; ++step) tree.tick(ctx);
|
||||
|
||||
CHECK(behaviorHorizontalDistance(agent.position, agent.homePosition) <=
|
||||
ctx.tuning.homeReturnRadius);
|
||||
CHECK_EQ(agent.attacksLanded, 0);
|
||||
}
|
||||
|
||||
VF_TEST(theAnimalTreeRunsAwayFromAnEmptyHandedPlayer) {
|
||||
World world = grassWorld();
|
||||
BehaviorAgent agent = makeAgent(Species::Sheep, {0.5f, 1.0f, 0.5f});
|
||||
|
||||
BehaviorContext ctx = freshContext(&world);
|
||||
ctx.agent = &agent;
|
||||
ctx.playerPresent = true;
|
||||
ctx.playerPosition = {2.5f, 1.0f, 0.5f};
|
||||
ctx.playerHeldItem = ItemId::None;
|
||||
|
||||
BehaviorTree tree = makeGrazingAnimalTree();
|
||||
float before = behaviorHorizontalDistance(agent.position, ctx.playerPosition);
|
||||
for (int step = 0; step < 20; ++step) tree.tick(ctx);
|
||||
|
||||
CHECK(behaviorHorizontalDistance(agent.position, ctx.playerPosition) > before + 2.0f);
|
||||
CHECK_EQ(agent.breedCount, 0);
|
||||
}
|
||||
|
||||
VF_TEST(theAnimalTreeFollowsHeldFoodAndBreedsOnlyOncePerCooldown) {
|
||||
World world = grassWorld();
|
||||
BehaviorAgent agent = makeAgent(Species::Sheep, {0.5f, 1.0f, 0.5f});
|
||||
|
||||
BehaviorContext ctx = freshContext(&world);
|
||||
ctx.agent = &agent;
|
||||
ctx.playerPresent = true;
|
||||
ctx.playerPosition = {6.5f, 1.0f, 0.5f};
|
||||
ctx.playerHeldItem = breedingFoodFor(Species::Sheep);
|
||||
ctx.matePresent = true;
|
||||
ctx.matePosition = {5.0f, 1.0f, 0.5f};
|
||||
|
||||
BehaviorTree tree = makeGrazingAnimalTree();
|
||||
|
||||
std::vector<long long> breedTicks;
|
||||
int lastCount = 0;
|
||||
for (int step = 0; step < 400; ++step) {
|
||||
tree.tick(ctx);
|
||||
if (agent.breedCount != lastCount) {
|
||||
lastCount = agent.breedCount;
|
||||
breedTicks.push_back(ctx.tick);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user