Browse Source

Fixed Mysterious HTN Bug

I fixed the bug I was talking about in the previous commit, so it appears all the stack schenanigans are gone. I am still unsure why the bug was occurring, which is perhaps more unsettling than the bug itself.

Added printing Tasks and lists of them, which eases debugging.
combatComponentRefactor
Macoy Madson 6 years ago
parent
commit
c40c65dc79
  1. 548
      src/ai/htn/HTNPlanner.cpp
  2. 43
      src/ai/htn/HTNTasks.cpp
  3. 29
      src/ai/htn/HTNTasks.hpp
  4. 18
      src/unitTesting/HTN_test.cpp

548
src/ai/htn/HTNPlanner.cpp

@ -9,75 +9,81 @@ namespace Htn
{
/*bool DecomposeGoalTask(TaskList& taskList, Task* task, const TaskArguments& taskArguments)
{
GoalTask* goalTask = task->Goal;
return false; // TODO
GoalTask* goalTask = task->Goal;
return false; // TODO
}
bool DecomposeCompoundTask(TaskList& taskList, Task* task, const TaskArguments& taskArguments)
{
CompoundTask* compoundTask = task->Compound;
CompoundTask* compoundTask = task->Compound;
if (compoundTask->StateMeetsPreconditions(taskArguments))
// Decomposition is determined by the task itself
return compoundTask->Decompose(taskList, taskArguments);
if (compoundTask->StateMeetsPreconditions(taskArguments))
// Decomposition is determined by the task itself
return compoundTask->Decompose(taskList, taskArguments);
return false;
return false;
}*/
PlanStepStatus PlanStep(PlanState& planState)
{
// When the stack is empty, find a goal task to push onto the task or add tasks as per usual
// If any Decompositions or Preconditions fail, we must fail the entire plan because we have
// no alternate methods
if (planState.DecompositionStack.empty())
{
if (planState.InitialCallList.empty())
return PlanStepStatus::NoTasks;
else if (planState.WorkingCallList.empty() && planState.FinalCallList.empty())
planState.WorkingCallList = planState.InitialCallList;
else if (planState.WorkingCallList.empty() && !planState.FinalCallList.empty())
return PlanStepStatus::PlanComplete;
TaskCallList compoundDecompositions;
std::cout << "\nPlanStep()\nplanState.WorkingCallList.size() = " << planState.WorkingCallList.size() << "\n";
// When the stack is empty, find a goal task to push onto the task or add tasks as per usual
// If any Decompositions or Preconditions fail, we must fail the entire plan because we have
// no alternate methods
if (planState.DecompositionStack.empty())
{
if (planState.InitialCallList.empty())
return PlanStepStatus::NoTasks;
else if (planState.WorkingCallList.empty() && planState.FinalCallList.empty())
planState.WorkingCallList = planState.InitialCallList;
else if (planState.WorkingCallList.empty() && !planState.FinalCallList.empty())
return PlanStepStatus::PlanComplete;
TaskCallList compoundDecompositions;
std::cout << "\nPlanStep()\nplanState.WorkingCallList.size() = "
<< planState.WorkingCallList.size() << "\n";
printTaskCallList(planState.WorkingCallList);
for (TaskCallListIterator currentTaskCallIter = planState.WorkingCallList.begin();
currentTaskCallIter != planState.WorkingCallList.end();
currentTaskCallIter = planState.WorkingCallList.erase(currentTaskCallIter), std::cout << "Erased " << "planState.WorkingCallList.size() = " << planState.WorkingCallList.size() << "\n")
currentTaskCallIter = planState.WorkingCallList.erase(currentTaskCallIter),
std::cout << "Erased "
<< "planState.WorkingCallList.size() = "
<< planState.WorkingCallList.size() << "\n")
{
TaskCall currentTaskCall = (*currentTaskCallIter);
Task* currentTask = currentTaskCall.TaskToCall;
Task* currentTask = currentTaskCall.TaskToCall;
if (!currentTask)
continue;
TaskType currentTaskType = currentTask->GetType();
// NullEntity = TODO
// NullEntity = TODO
TaskArguments taskArguments = {0, planState.StacklessState, currentTaskCall.Parameters};
std::cout << "TaskType currentTaskType = " << (int)currentTaskType << "\n";
std::cout << "TaskType currentTaskType = " << (int)currentTaskType << "\n";
switch (currentTaskType)
{
case TaskType::Goal:
{
std::cout << "Goal\n";
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
// Well, a method could be restricted to a single Primitive Task or a
// CompoundTask
decomposition.Parameters = currentTaskCall.Parameters;
decomposition.InitialState = planState.StacklessState; // -- CHANGE for stacked
// Perform the decomposition
if (!goalTask->DecomposeMethodAtIndex(decomposition.CallList,
decomposition.MethodIndex,
decomposition.Parameters))
return PlanStepStatus::Failed_NoPossiblePlan;
decomposition.MethodIndex,
decomposition.Parameters))
return PlanStepStatus::Failed_NoPossiblePlan;
planState.DecompositionStack.push_back(decomposition);
std::cout << "planState.DecompositionStack.push_back(decomposition);\n";
std::cout << "planState.DecompositionStack.push_back(decomposition);\n";
if (planState.BreakOnStackPush)
{
@ -86,20 +92,20 @@ PlanStepStatus PlanStep(PlanState& planState)
}
// TODO: IMPORTANT! This code will collapse horribly if I don't now start
// processing the stack
planState.WorkingCallList.erase(currentTaskCallIter);
planState.WorkingCallList.erase(currentTaskCallIter);
return PlanStepStatus::SuccessfulDecomposition;
}
break;
case TaskType::Primitive:
{
std::cout << "Primitive\n";
std::cout << "Primitive\n";
PrimitiveTask* primitiveTask = currentTask->Primitive;
if (!primitiveTask->StateMeetsPreconditions(taskArguments))
return PlanStepStatus::Failed_NoPossiblePlan;
// If at top level, apply primitive tasks immediately to the stackless state
// If at top level, apply primitive tasks immediately to the stackless state
primitiveTask->ApplyStateChange(taskArguments);
planState.StacklessState = taskArguments.worldState;
planState.StacklessState = taskArguments.worldState;
planState.FinalCallList.push_back(currentTaskCall);
@ -113,16 +119,16 @@ PlanStepStatus PlanStep(PlanState& planState)
break;
case TaskType::Compound:
{
std::cout << "Compound\n";
std::cout << "Compound\n";
CompoundTask* compoundTask = currentTask->Compound;
if (!compoundTask->StateMeetsPreconditions(taskArguments))
return PlanStepStatus::Failed_NoPossiblePlan;
if (!compoundTask->Decompose(compoundDecompositions, taskArguments))
return PlanStepStatus::Failed_NoPossiblePlan;
return PlanStepStatus::Failed_NoPossiblePlan;
currentTaskCallIter = planState.WorkingCallList.erase(currentTaskCallIter);
currentTaskCallIter = planState.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
}
@ -133,212 +139,286 @@ PlanStepStatus PlanStep(PlanState& planState)
if (compoundDecompositions.size())
{
std::cout << "compoundDecompositions.size() = " << compoundDecompositions.size() << "\n";
std::cout << "compoundDecompositions.size() = " << compoundDecompositions.size()
<< "\n";
planState.WorkingCallList.insert(planState.WorkingCallList.begin(),
compoundDecompositions.begin(),
compoundDecompositions.end());
// We have to break here because we are adding things to the list we're iterating
// on; We'll process the tasks next Step
//break; // UPDATE: Eventually we'll need a way to start over the loop if the caller wants whole plan in one call
std::cout << "PlanStep Done\n";
return PlanStepStatus::SuccessfulDecomposition;
// break; // UPDATE: Eventually we'll need a way to start over the loop if the
// caller wants whole plan in one call
std::cout << "PlanStep Done\n";
return PlanStepStatus::SuccessfulDecomposition;
}
std::cout << "Loop Done\n";
std::cout << "Loop Done\n";
}
}
else
{
// Remember: If goal fails to decompose and goal is bottom of stack, fail
GoalDecompositionStack::iterator currentStackFrameIter = planState.DecompositionStack.end() - 1;
GoalDecomposition& currentStackFrame = *currentStackFrameIter;
std::cout << "\nPlanStep()\ncurrentStackFrame.CallList.size() = " << currentStackFrame.CallList.size() << "\n";
TaskCallList compoundDecompositions;
bool methodFailed = false;
for (TaskCallListIterator currentTaskCallIter = currentStackFrame.CallList.begin();
currentTaskCallIter != currentStackFrame.CallList.end();
currentTaskCallIter = currentStackFrame.CallList.erase(currentTaskCallIter), std::cout << "Erased " << "currentStackFrame.CallList.size() = " << planState.WorkingCallList.size() << "\n")
{
TaskCall currentTaskCall = (*currentTaskCallIter);
Task* currentTask = currentTaskCall.TaskToCall;
if (!currentTask)
continue;
TaskType currentTaskType = currentTask->GetType();
// NullEntity = TODO
TaskArguments taskArguments = {0, currentStackFrame.WorkingState, currentTaskCall.Parameters};
std::cout << "TaskType currentTaskType = " << (int)currentTaskType << "\n";
switch (currentTaskType)
{
case TaskType::Goal:
{
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 PlanStepStatus::Failed_NoPossiblePlan;
currentStackFrame.CallList.erase(currentTaskCallIter);
planState.DecompositionStack.push_back(decomposition);
std::cout << "planState.DecompositionStack.push_back(decomposition);\n";
// Pushing to the stack invalidates our currentStackFrame reference; update it
// What the actual fuck
std::cout << "Ref updating from " << &currentStackFrame << " to " << &(*currentStackFrameIter) << "\n";
currentStackFrameIter = planState.DecompositionStack.end() - 1;
currentStackFrame = *currentStackFrameIter;
if (planState.BreakOnStackPush)
{
//currentStackFrame.CallList.erase(currentTaskCallIter);
return PlanStepStatus::SuccessfulDecomposition;
}
// TODO: IMPORTANT! This code will collapse horribly if I don't now start
// processing the stack
//currentStackFrame.CallList.erase(currentTaskCallIter);
return PlanStepStatus::SuccessfulDecomposition;
}
break;
case TaskType::Primitive:
{
std::cout << "Primitive\n";
PrimitiveTask* primitiveTask = currentTask->Primitive;
if (!primitiveTask->StateMeetsPreconditions(taskArguments))
{
methodFailed = true;
break;
}
primitiveTask->ApplyStateChange(taskArguments);
currentStackFrame.WorkingState = taskArguments.worldState;
currentStackFrame.FinalCallList.push_back(currentTaskCall);
if (planState.BreakOnPrimitiveApply)
{
currentStackFrame.CallList.erase(currentTaskCallIter);
return PlanStepStatus::SuccessfulPrimitive;
}
// Keep processing tasks otherwise
}
break;
case TaskType::Compound:
{
std::cout << "Compound\n";
CompoundTask* compoundTask = currentTask->Compound;
if (!compoundTask->StateMeetsPreconditions(taskArguments))
{
methodFailed = true;
break;
}
if (!compoundTask->Decompose(compoundDecompositions, taskArguments))
{
methodFailed = true;
break;
}
currentTaskCallIter = currentStackFrame.CallList.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
}
break;
default:
return PlanStepStatus::Failed_BadData;
}
if (methodFailed)
{
std::cout << "Method failed decomposition\n";
// Clear stack frame
currentStackFrame.CallList.clear();
currentStackFrame.FinalCallList.clear();
//currentStackFrame.Parameters.clear(); // Little weird (implicit assumption that GoalTask uses exact parameters)
currentStackFrame.WorkingState = currentStackFrame.InitialState;
// Try the next method
currentStackFrame.MethodIndex++;
if (!currentStackFrame.DecomposedGoalTask->DecomposeMethodAtIndex(currentStackFrame.CallList,
currentStackFrame.MethodIndex,
currentStackFrame.Parameters))
{
planState.DecompositionStack.pop_back();
return PlanStepStatus::FailedGoalDecomposition;
}
// TODO: make this optional return
return PlanStepStatus::FailedMethodDecomposition;
}
if (compoundDecompositions.size())
{
std::cout << "compoundDecompositions.size() = " << compoundDecompositions.size() << "\n";
std::cout << "currentStackFrame.CallList.size() = " << currentStackFrame.CallList.size() << "\n";
std::cout << "First task type: " << (int)(*compoundDecompositions.begin()).TaskToCall->GetType() << "\n";
currentStackFrame.CallList.insert(currentStackFrame.CallList.begin(),
compoundDecompositions.begin(),
compoundDecompositions.end());
// We have to break here because we are adding things to the list we're iterating
// on; We'll process the tasks next Step
//break; // UPDATE: TODO: Eventually we'll need a way to start over the loop if the caller wants whole plan in one call
std::cout << "PlanStep Done\n";
return PlanStepStatus::SuccessfulDecomposition;
}
std::cout << "Loop Done\n";
}
// Finished processing this stack frame
if (currentStackFrame.CallList.empty() && !currentStackFrame.FinalCallList.empty())
{
bool onlyStackFrame = planState.DecompositionStack.size() == 1;
TaskCallList* parentFinalCallList = nullptr;
WorldState* parentWorkingState = nullptr;
// If this is the only frame on the stack, its call list and working state goes to the plan, else, the previous stack
if (onlyStackFrame)
{
parentFinalCallList = &planState.FinalCallList;
parentWorkingState = &planState.StacklessState;
}
else
{
GoalDecomposition& previousStackFrame = *(currentStackFrameIter - 1);
parentFinalCallList = &previousStackFrame.FinalCallList;
parentWorkingState = &previousStackFrame.WorkingState;
}
// Should this be appended?
parentFinalCallList->insert(parentFinalCallList->end(),
currentStackFrame.FinalCallList.begin(),
currentStackFrame.FinalCallList.end());
*parentWorkingState = currentStackFrame.WorkingState;
planState.DecompositionStack.pop_back();
std::cout << "Frame Done\n";
}
GoalDecompositionStack::iterator currentStackFrameIter =
planState.DecompositionStack.end() - 1;
GoalDecomposition& currentStackFrame = *currentStackFrameIter;
std::cout << "\nPlanStep()\ncurrentStackFrame.CallList.size() = "
<< currentStackFrame.CallList.size() << "\n";
printTaskCallList(currentStackFrame.CallList);
std::cout << "Stack Depth: ";
for (int i = 0; i < planState.DecompositionStack.size(); i++)
std::cout << "=";
std::cout << "\n";
{
std::cout << "----Fullstack working lists\n";
std::cout << "[0]\n";
printTaskCallList(planState.WorkingCallList);
int i = 1;
for (GoalDecomposition& stackFrame : planState.DecompositionStack)
{
std::cout << "[" << i++ << "]\n";
printTaskCallList(stackFrame.CallList);
}
std::cout << "----\n";
}
TaskCallList compoundDecompositions;
bool methodFailed = false;
for (TaskCallListIterator currentTaskCallIter = currentStackFrame.CallList.begin();
currentTaskCallIter != currentStackFrame.CallList.end();
currentTaskCallIter = currentStackFrame.CallList.erase(currentTaskCallIter),
std::cout << "Erased "
<< "currentStackFrame.CallList.size() = "
<< planState.WorkingCallList.size() << "\n")
{
TaskCall currentTaskCall = (*currentTaskCallIter);
Task* currentTask = currentTaskCall.TaskToCall;
if (!currentTask)
continue;
TaskType currentTaskType = currentTask->GetType();
// NullEntity = TODO
TaskArguments taskArguments = {0, currentStackFrame.WorkingState,
currentTaskCall.Parameters};
std::cout << "TaskType currentTaskType = " << (int)currentTaskType << "\n";
switch (currentTaskType)
{
case TaskType::Goal:
{
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 PlanStepStatus::Failed_NoPossiblePlan;
// TODO erase ahead of time because fuck
// Strange things are afoot when we push to stack
currentStackFrame.CallList.erase(currentTaskCallIter);
{
std::cout << "----Fullstack working lists [PRE PUSH BACK]\n";
std::cout << "[0]\n";
printTaskCallList(planState.WorkingCallList);
int i = 1;
for (GoalDecomposition& stackFrame : planState.DecompositionStack)
{
std::cout << "[" << i++ << "]\n";
printTaskCallList(stackFrame.CallList);
}
std::cout << "----\n";
}
planState.DecompositionStack.push_back(decomposition);
std::cout << "planState.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)
/*std::cout << "Ref updating from " << &currentStackFrame << " to "
<< &(*(planState.DecompositionStack.end() - 1)) << "\n";
currentStackFrameIter = planState.DecompositionStack.end() - 1;
currentStackFrame = *currentStackFrameIter;*/
{
std::cout << "----Fullstack working lists [POST PUSH BACK]\n";
std::cout << "[0]\n";
printTaskCallList(planState.WorkingCallList);
int i = 1;
for (GoalDecomposition& stackFrame : planState.DecompositionStack)
{
std::cout << "[" << i++ << "]\n";
printTaskCallList(stackFrame.CallList);
}
std::cout << "----\n";
}
if (planState.BreakOnStackPush)
{
// currentStackFrame.CallList.erase(currentTaskCallIter);
return PlanStepStatus::SuccessfulDecomposition;
}
// TODO: IMPORTANT! This code will collapse horribly if I don't now start
// processing the stack
// currentStackFrame.CallList.erase(currentTaskCallIter);
return PlanStepStatus::SuccessfulDecomposition;
}
break;
case TaskType::Primitive:
{
std::cout << "Primitive\n";
PrimitiveTask* primitiveTask = currentTask->Primitive;
if (!primitiveTask->StateMeetsPreconditions(taskArguments))
{
methodFailed = true;
break;
}
primitiveTask->ApplyStateChange(taskArguments);
currentStackFrame.WorkingState = taskArguments.worldState;
currentStackFrame.FinalCallList.push_back(currentTaskCall);
if (planState.BreakOnPrimitiveApply)
{
currentStackFrame.CallList.erase(currentTaskCallIter);
return PlanStepStatus::SuccessfulPrimitive;
}
// Keep processing tasks otherwise
}
break;
case TaskType::Compound:
{
std::cout << "Compound\n";
CompoundTask* compoundTask = currentTask->Compound;
if (!compoundTask->StateMeetsPreconditions(taskArguments))
{
methodFailed = true;
break;
}
if (!compoundTask->Decompose(compoundDecompositions, taskArguments))
{
methodFailed = true;
break;
}
currentTaskCallIter = currentStackFrame.CallList.erase(currentTaskCallIter);
// we need to push our decomposition to our call list, but we're iterating on
// it. By pushing our decomposition to compoundDecompositions, we break out of
// the loop and tack it on there
}
break;
default:
return PlanStepStatus::Failed_BadData;
}
if (methodFailed)
{
std::cout << "Method failed decomposition\n";
// Clear stack frame
currentStackFrame.CallList.clear();
currentStackFrame.FinalCallList.clear();
// currentStackFrame.Parameters.clear(); // Little weird (implicit assumption that
// GoalTask uses exact parameters)
currentStackFrame.WorkingState = currentStackFrame.InitialState;
// Try the next method
currentStackFrame.MethodIndex++;
if (!currentStackFrame.DecomposedGoalTask->DecomposeMethodAtIndex(
currentStackFrame.CallList, currentStackFrame.MethodIndex,
currentStackFrame.Parameters))
{
planState.DecompositionStack.pop_back();
return PlanStepStatus::FailedGoalDecomposition;
}
// TODO: make this optional return
return PlanStepStatus::FailedMethodDecomposition;
}
if (compoundDecompositions.size())
{
std::cout << "compoundDecompositions.size() = " << compoundDecompositions.size()
<< "\n";
std::cout << "currentStackFrame.CallList.size() = "
<< currentStackFrame.CallList.size() << "\n";
std::cout << "Decomposition:\n";
printTaskCallList(compoundDecompositions);
currentStackFrame.CallList.insert(currentStackFrame.CallList.begin(),
compoundDecompositions.begin(),
compoundDecompositions.end());
// We have to break here because we are adding things to the list we're iterating
// on; We'll process the tasks next Step
// break; // UPDATE: TODO: Eventually we'll need a way to start over the loop if the
// caller wants whole plan in one call
std::cout << "PlanStep Done\n";
return PlanStepStatus::SuccessfulDecomposition;
}
std::cout << "Loop Done\n";
}
// Finished processing this stack frame
if (currentStackFrame.CallList.empty() && !currentStackFrame.FinalCallList.empty())
{
bool onlyStackFrame = planState.DecompositionStack.size() == 1;
TaskCallList* parentFinalCallList = nullptr;
WorldState* parentWorkingState = nullptr;
// If this is the only frame on the stack, its call list and working state goes to the
// plan, else, the previous stack
if (onlyStackFrame)
{
parentFinalCallList = &planState.FinalCallList;
parentWorkingState = &planState.StacklessState;
}
else
{
GoalDecomposition& previousStackFrame = *(currentStackFrameIter - 1);
parentFinalCallList = &previousStackFrame.FinalCallList;
parentWorkingState = &previousStackFrame.WorkingState;
}
std::cout << "Collapsing stack frame. Adding List:\n";
printTaskCallList(currentStackFrame.FinalCallList);
std::cout << "To parent:\n";
printTaskCallList(*parentFinalCallList);
// Should this be appended?
parentFinalCallList->insert(parentFinalCallList->end(),
currentStackFrame.FinalCallList.begin(),
currentStackFrame.FinalCallList.end());
*parentWorkingState = currentStackFrame.WorkingState;
planState.DecompositionStack.pop_back();
std::cout << "Frame Done\n";
if (planState.BreakOnStackPop)
return PlanStepStatus::SuccessfulDecomposition;
// TODO: Continue processing the stack
return PlanStepStatus::SuccessfulDecomposition;
}
}
return PlanStepStatus::PlanComplete;
return PlanStepStatus::PlanComplete;
}
}

43
src/ai/htn/HTNTasks.cpp

@ -17,7 +17,8 @@ int GoalTask::GetNumMethods(void)
return Methods ? Methods->size() : 0;
}
bool GoalTask::DecomposeMethodAtIndex(TaskCallList& decomposition, int index, ParameterList& parameters)
bool GoalTask::DecomposeMethodAtIndex(TaskCallList& decomposition, int index,
ParameterList& parameters)
{
Task* methodToDecompose = GetMethodAtIndex(index);
if (!methodToDecompose)
@ -59,7 +60,7 @@ PrimitiveTask::~PrimitiveTask(void)
{
}
TaskType Task::GetType(void)
TaskType Task::GetType(void) const
{
if (Goal)
return TaskType::Goal;
@ -69,4 +70,42 @@ TaskType Task::GetType(void)
return TaskType::Primitive;
return TaskType::None;
}
std::ostream& operator<<(std::ostream& os, const Task& task)
{
switch (task.GetType())
{
case TaskType::None:
os << "Task Type:None " << &task;
break;
case TaskType::Goal:
os << "Task Type:Goal task addr " << &task << " Goal addr " << task.Goal;
break;
case TaskType::Compound:
os << "Task Type:Compound addr " << &task << " Compound addr " << task.Compound;
break;
case TaskType::Primitive:
os << "Task Type:Primitive addr " << &task << " Primitive addr " << task.Primitive;
break;
}
return os;
}
void printTaskList(const TaskList& tasks)
{
std::cout << "TaskList size = " << tasks.size() << " addr " << &tasks << ":\n";
for (unsigned int i = 0; i < tasks.size(); i++)
{
std::cout << "\t[" << i << "] " << *tasks[i] << "\n";
}
}
void printTaskCallList(const TaskCallList& tasks)
{
std::cout << "TaskCallList size = " << tasks.size() << " addr " << &tasks << ":\n";
for (unsigned int i = 0; i < tasks.size(); i++)
{
std::cout << "\t[" << i << "] " << *tasks[i].TaskToCall << "\n";
}
}
}

29
src/ai/htn/HTNTasks.hpp

@ -2,6 +2,8 @@
#include "HTNTypes.hpp"
#include <iostream>
namespace Htn
{
struct Task;
@ -20,13 +22,13 @@ typedef TaskCallList::iterator TaskCallListIterator;
// Different types of tasks
//
class GoalTask //: public Task
class GoalTask //: public Task
{
private:
TaskList* Methods;
public:
GoalTask(void)=default;
GoalTask(void) = default;
int GetNumMethods(void);
Task* GetMethodAtIndex(int index);
@ -36,7 +38,7 @@ public:
void SetMethods(TaskList* newMethods);
};
class CompoundTask //: public Task
class CompoundTask //: public Task
{
public:
CompoundTask(void);
@ -45,7 +47,7 @@ public:
virtual bool Decompose(TaskCallList& taskCallList, const TaskArguments& arguments) = 0;
};
class PrimitiveTask //: public Task
class PrimitiveTask //: public Task
{
public:
PrimitiveTask(void);
@ -68,17 +70,17 @@ enum class TaskType
class Task
{
public:
Task(void);
Task(void);
// Type is used to know what to cast this Task to
TaskType GetType(void);
// Type is used to know what to cast this Task to
TaskType GetType(void);
protected:
// This is set by the constructors of each different Task
TaskType Type;
// This is set by the constructors of each different Task
TaskType Type;
};*/
// Instead of the commented code, just use a simple struct which stores all types of tasks but
// Instead of the commented code, just use a simple struct which stores all types of tasks but
// only hope to allow only one thing to be filled in for it
struct Task
{
@ -86,6 +88,11 @@ struct Task
CompoundTask* Compound = nullptr;
PrimitiveTask* Primitive = nullptr;
TaskType GetType(void);
TaskType GetType(void) const;
};
std::ostream& operator<<(std::ostream& os, const Task& task);
void printTaskList(const TaskList& tasks);
void printTaskCallList(const TaskCallList& tasks);
}

18
src/unitTesting/HTN_test.cpp

@ -96,7 +96,7 @@ int main()
nestedGoalTask.SetMethods(&nestedMethods);
Htn::Task nestedGoalTaskTask;
nestedGoalTaskTask.Goal = &nestedGoalTask;
Htn::TaskCall nestedGoalTaskCall = {&nestedGoalTaskTask, params};
Htn::TaskCall nestedGoalTaskCall = {&nestedGoalTaskTask, params};
// Compounds and primitives, but no goals
{
@ -115,8 +115,8 @@ int main()
break;
}
std::cout << "Final Plan length: " << testPlan.FinalCallList.size()
<< (testPlan.FinalCallList.size() == 3 ? " (Pass)" : " (Fail)") << "\n";
std::cout << "\n\nFinal Plan length: " << testPlan.FinalCallList.size()
<< (testPlan.FinalCallList.size() == 3 ? " (Pass)" : " (Fail)") << "\n\n";
}
// One goal (one stack frame)
@ -132,16 +132,17 @@ int main()
break;
}
std::cout << "Final Plan length: " << testPlan.FinalCallList.size()
<< (testPlan.FinalCallList.size() == 1 ? " (Pass)" : " (Fail)") << "\n";
std::cout << "\n\nFinal Plan length: " << testPlan.FinalCallList.size()
<< (testPlan.FinalCallList.size() == 1 ? " (Pass)" : " (Fail)") << "\n\n";
}
// Nested goal (two stack frames)
{
Htn::PlanState testPlan;
testPlan.InitialCallList.push_back(nestedGoalTaskCall);
testPlan.InitialCallList.push_back(nestedGoalTaskCall);
for (int i = 0; i < 10; i++)
for (int i = 0; i < 12; i++)
{
Htn::PlanStepStatus status = Htn::PlanStep(testPlan);
std::cout << "[" << i << "] Returned Status " << (int)status << "\n";
@ -149,8 +150,9 @@ int main()
break;
}
std::cout << "Final Plan length: " << testPlan.FinalCallList.size()
<< (testPlan.FinalCallList.size() == 1 ? " (Pass)" : " (Fail)") << "\n";
std::cout << "\n\nFinal Plan length: " << testPlan.FinalCallList.size()
<< (testPlan.FinalCallList.size() == 2 ? " (Pass)" : " (Fail)") << "\n\n";
printTaskCallList(testPlan.FinalCallList);
}
return 0;

Loading…
Cancel
Save