Browse Source

Added Formal Unit Testing Tool, Slight Refactoring

- Added Catch to make my formalize my Unit Testing code. It's very convenient and has nice output :)
- Added some helper functions for PlanStep() to eliminate some copypasta. I'll likely be doing more of this
combatComponentRefactor
Macoy Madson 6 years ago
parent
commit
37d1661ad7
  1. 3
      .gitmodules
  2. 1
      README.md
  3. 129
      src/ai/htn/HTNPlanner.cpp
  4. 34
      src/ai/htn/HTNTasks.cpp
  5. 20
      src/ai/htn/HTNTasks.hpp
  6. 4
      src/project/galavantSublime/galavant.sublime-project
  7. 51
      src/unitTesting/HTN_test.cpp
  8. 1
      thirdParty/Catch

3
.gitmodules

@ -1,3 +1,6 @@
[submodule "thirdParty/flatbuffers"]
path = thirdParty/flatbuffers
url = https://github.com/google/flatbuffers.git
[submodule "thirdParty/Catch"]
path = thirdParty/Catch
url = https://github.com/philsquared/Catch.git

1
README.md

@ -30,6 +30,7 @@ Galavant uses features of C++11.
The following libraries are required by Galavant and included in /thirdParty:
- [OpenSimplexNoise](https://gist.github.com/tombsar/716134ec71d1b8c1b530), created by Arthur Tombs (public domain)
- [Flatbuffers](https://github.com/google/flatbuffers), created by Google/Fun Propulsion Labs (Apache License, v2.0)
- [Catch](https://github.com/philsquared/Catch), created by various contributors/a dude named Travis (Boost Software License)
## License

129
src/ai/htn/HTNPlanner.cpp

@ -7,6 +7,39 @@
namespace Htn
{
bool DecomposeGoalTask(GoalDecompositionStack& decompositionStack, GoalTask* goalTask,
int methodIndex, ParameterList& parameters, WorldState& state)
{
GoalDecomposition decomposition;
// GoalTask* goalTask = currentTask->GetGoal();
decomposition.DecomposedGoalTask = goalTask;
decomposition.MethodIndex = methodIndex;
// How the hell are parameters off of goals used? Fuck
// Well, a method could be restricted to a single Primitive Task or a
// CompoundTask
decomposition.Parameters = parameters;
decomposition.InitialState = state;
// Perform the decomposition
if (goalTask->DecomposeMethodAtIndex(decomposition.CallList, decomposition.MethodIndex,
decomposition.Parameters))
{
decompositionStack.push_back(decomposition);
return true;
}
return false;
}
bool DecomposeCompoundTask(TaskCallList& compoundDecompositions, CompoundTask* compoundTask,
TaskArguments& taskArguments)
{
if (!compoundTask->StateMeetsPreconditions(taskArguments))
return false;
return compoundTask->Decompose(compoundDecompositions, taskArguments);
}
Planner::Status Planner::PlanStep(void)
{
// When the stack is empty, find a goal task to push onto the task or add tasks as per usual
@ -54,25 +87,11 @@ Planner::Status Planner::PlanStep(void)
{
if (DebugPrint)
std::cout << "Goal\n";
GoalTask* goalTask = currentTask->Goal;
GoalDecomposition decomposition;
decomposition.DecomposedGoalTask = goalTask;
decomposition.MethodIndex = 0; // -- CHANGE for stacked
// How the hell are parameters off of goals used? Fuck
// Well, a method could be restricted to a single Primitive Task or a
// CompoundTask
decomposition.Parameters = currentTaskCall.Parameters;
decomposition.InitialState = StacklessState; // -- CHANGE for stacked
// Perform the decomposition
if (!goalTask->DecomposeMethodAtIndex(decomposition.CallList,
decomposition.MethodIndex,
decomposition.Parameters))
return Status::Failed_NoPossiblePlan;
GoalTask* goalTask = currentTask->GetGoal();
DecompositionStack.push_back(decomposition);
if (DebugPrint)
std::cout << "DecompositionStack.push_back(decomposition);\n";
if (!DecomposeGoalTask(DecompositionStack, goalTask, 0,
currentTaskCall.Parameters, StacklessState))
return Status::Failed_NoPossiblePlan;
if (BreakOnStackPush)
{
@ -89,7 +108,7 @@ Planner::Status Planner::PlanStep(void)
{
if (DebugPrint)
std::cout << "Primitive\n";
PrimitiveTask* primitiveTask = currentTask->Primitive;
PrimitiveTask* primitiveTask = currentTask->GetPrimitive();
if (!primitiveTask->StateMeetsPreconditions(taskArguments))
return Status::Failed_NoPossiblePlan;
@ -111,17 +130,15 @@ Planner::Status Planner::PlanStep(void)
{
if (DebugPrint)
std::cout << "Compound\n";
CompoundTask* compoundTask = currentTask->Compound;
if (!compoundTask->StateMeetsPreconditions(taskArguments))
return Status::Failed_NoPossiblePlan;
if (!compoundTask->Decompose(compoundDecompositions, taskArguments))
CompoundTask* compoundTask = currentTask->GetCompound();
if (!DecomposeCompoundTask(compoundDecompositions, compoundTask, taskArguments))
return Status::Failed_NoPossiblePlan;
currentTaskCallIter = WorkingCallList.erase(currentTaskCallIter);
// we need to push our decomposition to our call list, but we're iterating on
// it. Break out of the loop and tack it on there
// it. By pushing our decomposition to compoundDecompositions, we break out of
// the loop and tack it on there
}
break;
default:
@ -207,44 +224,22 @@ Planner::Status Planner::PlanStep(void)
{
if (DebugPrint)
std::cout << "Goal\n";
GoalTask* goalTask = currentTask->Goal;
GoalDecomposition decomposition;
decomposition.DecomposedGoalTask = goalTask;
decomposition.MethodIndex = 0;
// How the hell are parameters off of goals used? Fuck
// Well, a method could be restricted to a single Primitive Task or a
// CompoundTask
decomposition.Parameters = currentTaskCall.Parameters;
decomposition.InitialState = currentStackFrame.WorkingState;
// Perform the decomposition
if (!goalTask->DecomposeMethodAtIndex(decomposition.CallList,
decomposition.MethodIndex,
decomposition.Parameters))
return Status::Failed_NoPossiblePlan;
GoalTask* goalTask = currentTask->GetGoal();
// TODO erase ahead of time because fuck
// Strange things are afoot when we push to stack
currentStackFrame.CallList.erase(currentTaskCallIter);
if (DebugPrint)
// Perform the decomposition. This function only fails if the method at method
// index doesn't exist. This means that the goal task has no alternative options
if (!DecomposeGoalTask(DecompositionStack, goalTask, 0,
currentTaskCall.Parameters,
currentStackFrame.WorkingState))
{
std::cout << "----Fullstack working lists [PRE PUSH BACK]\n";
std::cout << "[0]\n";
printTaskCallList(WorkingCallList);
int i = 1;
for (GoalDecomposition& stackFrame : DecompositionStack)
{
std::cout << "[" << i++ << "]\n";
printTaskCallList(stackFrame.CallList);
}
std::cout << "----\n";
methodFailed = true;
break;
}
DecompositionStack.push_back(decomposition);
if (DebugPrint)
std::cout << "DecompositionStack.push_back(decomposition);\n";
// This code is wrong. I'm not sure why.
// Pushing to the stack invalidates our currentStackFrame reference; update it
// What the actual fuck (updating the ref doesn't fix the fucking thing)
@ -252,19 +247,6 @@ Planner::Status Planner::PlanStep(void)
<< &(*(DecompositionStack.end() - 1)) << "\n";
currentStackFrameIter = DecompositionStack.end() - 1;
currentStackFrame = *currentStackFrameIter;*/
if (DebugPrint)
{
std::cout << "----Fullstack working lists [POST PUSH BACK]\n";
std::cout << "[0]\n";
printTaskCallList(WorkingCallList);
int i = 1;
for (GoalDecomposition& stackFrame : DecompositionStack)
{
std::cout << "[" << i++ << "]\n";
printTaskCallList(stackFrame.CallList);
}
std::cout << "----\n";
}
if (BreakOnStackPush)
{
@ -281,7 +263,7 @@ Planner::Status Planner::PlanStep(void)
{
if (DebugPrint)
std::cout << "Primitive\n";
PrimitiveTask* primitiveTask = currentTask->Primitive;
PrimitiveTask* primitiveTask = currentTask->GetPrimitive();
if (!primitiveTask->StateMeetsPreconditions(taskArguments))
{
methodFailed = true;
@ -305,14 +287,9 @@ Planner::Status Planner::PlanStep(void)
{
if (DebugPrint)
std::cout << "Compound\n";
CompoundTask* compoundTask = currentTask->Compound;
if (!compoundTask->StateMeetsPreconditions(taskArguments))
{
methodFailed = true;
break;
}
CompoundTask* compoundTask = currentTask->GetCompound();
if (!compoundTask->Decompose(compoundDecompositions, taskArguments))
if (!DecomposeCompoundTask(compoundDecompositions, compoundTask, taskArguments))
{
methodFailed = true;
break;

34
src/ai/htn/HTNTasks.cpp

@ -1,5 +1,8 @@
#include "HTNTasks.hpp"
#include <cassert>
#include <iostream>
namespace Htn
{
/*Task::Task(void)
@ -44,12 +47,10 @@ void GoalTask::SetMethods(TaskList* newMethods)
CompoundTask::CompoundTask(void)
{
// Type = TaskType::Compound;
}
PrimitiveTask::PrimitiveTask(void)
{
// Type = TaskType::Primitive;
}
CompoundTask::~CompoundTask(void)
@ -63,25 +64,40 @@ PrimitiveTask::~PrimitiveTask(void)
Task::Task(GoalTask* goal)
{
Goal = goal;
Type = TaskType::Goal;
}
Task::Task(CompoundTask* compound)
{
Compound = compound;
Type = TaskType::Compound;
}
Task::Task(PrimitiveTask* primitive)
{
Primitive = primitive;
Type = TaskType::Primitive;
}
TaskType Task::GetType(void) const
{
if (Goal)
return TaskType::Goal;
if (Compound)
return TaskType::Compound;
if (Primitive)
return TaskType::Primitive;
return TaskType::None;
return Type;
}
GoalTask* Task::GetGoal(void)
{
assert(Type == TaskType::Goal);
return Goal;
}
CompoundTask* Task::GetCompound(void)
{
assert(Type == TaskType::Compound);
return Compound;
}
PrimitiveTask* Task::GetPrimitive(void)
{
assert(Type == TaskType::Primitive);
return Primitive;
}
std::ostream& operator<<(std::ostream& os, const Task& task)

20
src/ai/htn/HTNTasks.hpp

@ -82,19 +82,29 @@ protected:
// Instead of the commented code, just use a simple struct which stores all types of tasks but
// only allow only one thing to be filled in for it
// TODO: Make members private and require accessors?
struct Task
{
GoalTask* Goal = nullptr;
CompoundTask* Compound = nullptr;
PrimitiveTask* Primitive = nullptr;
Task(void) = delete;
Task(GoalTask* goal);
Task(CompoundTask* compound);
Task(PrimitiveTask* primitive);
TaskType GetType(void) const;
GoalTask* GetGoal(void);
CompoundTask* GetCompound(void);
PrimitiveTask* GetPrimitive(void);
friend std::ostream& operator<<(std::ostream& os, const Task& task);
private:
union
{
GoalTask* Goal;
CompoundTask* Compound;
PrimitiveTask* Primitive;
};
TaskType Type;
};
std::ostream& operator<<(std::ostream& os, const Task& task);

4
src/project/galavantSublime/galavant.sublime-project

@ -5,12 +5,12 @@
"path": "../../../../galavant",
"name": "Galavant",
"folder_exclude_patterns": ["project"],
"file_include_patterns": ["*.c", "*.cpp", "*.h", "*.hpp", "*.txt", "Jam*"]
"file_include_patterns": ["*.c", "*.cpp", "*.h", "*.hpp", "*.txt", "Jam*", "*.md"]
},
{
"path": "../../../../galavant-unreal/GalavantUnreal/Source",
"name": "Source - Galavant Unreal",
"file_include_patterns": ["*.c", "*.cpp", "*.h", "*.hpp", "*.txt", "*.cs"]
"file_include_patterns": ["*.c", "*.cpp", "*.h", "*.hpp", "*.txt", "*.cs", "*.md"]
},
{
"path": "../../../gamedata",

51
src/unitTesting/HTN_test.cpp

@ -1,5 +1,8 @@
#include <iostream>
#define CATCH_CONFIG_MAIN
#include "../../thirdParty/Catch/single_include/catch.hpp"
#include "../ai/htn/HTNTypes.hpp"
#include "../ai/htn/HTNTasks.hpp"
#include "../ai/htn/HTNPlanner.hpp"
@ -103,14 +106,9 @@ public:
}
};
int main()
TEST_CASE("Hierarchical Task Networks Planner")
{
// Test parameters
Htn::Parameter testParam = {Htn::Parameter::ParamType::Int, 123};
// Todo: add getters/setters (settor constructors too)
std::cout << testParam.FloatValue << "\n";
Htn::ParameterList params;
params.push_back(testParam);
@ -143,7 +141,7 @@ int main()
Htn::Task nestedGoalTaskTask(&nestedGoalTask);
Htn::TaskCall nestedGoalTaskCall = {&nestedGoalTaskTask, params};
// Compounds and primitives, but no goals
SECTION("Compounds and primitives, but no goals")
{
std::cout << "TEST: Compounds and primitives, but no goals\n\n";
Htn::Planner testPlan;
@ -161,11 +159,11 @@ int main()
break;
}
std::cout << "\n\nFinal Plan length: " << testPlan.FinalCallList.size()
<< (testPlan.FinalCallList.size() == 3 ? " (Pass)" : " (Fail)") << "\n\n";
REQUIRE(testPlan.FinalCallList.size() == 3);
std::cout << "\n\nFinal Plan length: " << testPlan.FinalCallList.size() << "\n\n";
}
// One goal (one stack frame)
SECTION("One goal (one stack frame)")
{
std::cout << "TEST: One goal (one stack frame)\n\n";
Htn::Planner testPlan;
@ -179,13 +177,13 @@ int main()
break;
}
std::cout << "\n\nFinal Plan length: " << testPlan.FinalCallList.size()
<< (testPlan.FinalCallList.size() == 1 ? " (Pass)" : " (Fail)") << "\n";
REQUIRE(testPlan.FinalCallList.size() == 1);
std::cout << "\n\nFinal Plan length: " << testPlan.FinalCallList.size() << "\n";
printTaskCallList(testPlan.FinalCallList);
std::cout << "\n\n";
}
// Nested goal (two stack frames)
SECTION("Nested goal (two stack frames)")
{
std::cout << "TEST: Nested goal (two stack frames)\n\n";
Htn::Planner testPlan;
@ -200,13 +198,13 @@ int main()
break;
}
std::cout << "\n\nFinal Plan length: " << testPlan.FinalCallList.size()
<< (testPlan.FinalCallList.size() == 2 ? " (Pass)" : " (Fail)") << "\n";
REQUIRE(testPlan.FinalCallList.size() == 2);
std::cout << "\n\nFinal Plan length: " << testPlan.FinalCallList.size() << "\n";
printTaskCallList(testPlan.FinalCallList);
std::cout << "\n\n";
}
// Failed decomposition of first goal method (one stack frame)
SECTION("Failed decomposition of first goal method (one stack frame)")
{
std::cout << "TEST: Failed decomposition of first goal method (one stack frame)\n\n";
// Goal task setup (first task fails)
@ -228,13 +226,13 @@ int main()
break;
}
std::cout << "\n\nFinal Plan length: " << testPlan.FinalCallList.size()
<< (testPlan.FinalCallList.size() == 1 ? " (Pass)" : " (Fail)") << "\n";
REQUIRE(testPlan.FinalCallList.size() == 1);
std::cout << "\n\nFinal Plan length: " << testPlan.FinalCallList.size() << "\n";
printTaskCallList(testPlan.FinalCallList);
std::cout << "\n\n";
}
// Proper state change test (task with dependencies on another task being run before it)
SECTION("Proper state change test (task with dependencies on another task being run before it)")
{
std::cout << "TEST: Proper state change test (task with dependencies on another task being "
"run before it)\n\n";
@ -251,14 +249,15 @@ int main()
break;
}
std::cout << "\n\nFinal Plan length: " << testPlan.FinalCallList.size()
<< (testPlan.FinalCallList.size() == 2 ? " (Pass)" : " (Fail)") << "\n";
REQUIRE(testPlan.FinalCallList.size() == 2);
std::cout << "\n\nFinal Plan length: " << testPlan.FinalCallList.size() << "\n";
printTaskCallList(testPlan.FinalCallList);
std::cout << "\n\n";
}
// Proper state change test (task with dependencies on another task being run before it) (This
// time, fail!)
SECTION(
"Proper state change test (task with dependencies on another task being run before it) "
"(This time, fail!)")
{
std::cout << "TEST: Proper state change test (task with dependencies on another task being "
"run before it) (This time, fail!)\n\n";
@ -276,11 +275,9 @@ int main()
break;
}
std::cout << "\n\nFinal Plan length: " << testPlan.FinalCallList.size()
<< (testPlan.FinalCallList.size() == 0 ? " (Pass)" : " (Fail)") << "\n";
REQUIRE(testPlan.FinalCallList.size() == 0);
std::cout << "\n\nFinal Plan length: " << testPlan.FinalCallList.size() << "\n";
printTaskCallList(testPlan.FinalCallList);
std::cout << "\n\n";
}
return 0;
}

1
thirdParty/Catch

@ -0,0 +1 @@
Subproject commit e12fc4aca07c3116a2bf8e09b1595b3cfec8fd39
Loading…
Cancel
Save