I'm attempting to implement GOAP in my game. The game is a simulation-type game that would not only benefit from GOAP, but is almost essentially a requirement based on the scope of what an agent can do.
I have the following code. There's no planner in here, just the definitions of Actions, State etc
#include <vector>
#include <string>
#include <unordered_map>
class Condition
{
public:
bool operator()(const int value) const
{
return m_min <= value && value <= m_max;
}
enum class OP : std::uint8_t
{
EE, LT, GT, LE, GE
};
Condition(const int min, const int max, const OP op)
:
m_op(op),
m_min(min),
m_max(max)
{}
static Condition CreateEE(const int goal)
{
return Condition(goal, goal, OP::EE);
}
static Condition CreateGT(const int goal)
{
return Condition(goal+1, std::numeric_limits<int>::max(), OP::GT);
}
static Condition CreateLT(const int goal)
{
return Condition(std::numeric_limits<int>::min(), goal-1, OP::LT);
}
static Condition CreateGE(const int goal)
{
return Condition(goal, std::numeric_limits<int>::max(), OP::GT);
}
static Condition CreateLE(const int goal)
{
return Condition(std::numeric_limits<int>::min(), goal, OP::LT);
}
private:
OP m_op;
int m_min;
int m_max;
};
class Effect
{
public:
enum class OP : std::uint8_t
{
ADD, SUB, MUL, DIV, EQ
};
Effect(OP op, int value)
:
m_op(op),
m_value(value)
{
}
protected:
OP m_op;
int m_value;
};
class Action
{
public:
void AddPrereq(const std::string& name, Condition value)
{
m_prereqs.emplace_back(name,value);
}
void AddEffect(const std::string& name, Effect effect)
{
m_effects.emplace_back(name,effect);
}
void SetCost(const float cost)
{
m_cost = cost;
}
private:
float m_cost;
std::vector<std::pair<std::string,Condition>> m_prereqs;
std::vector<std::pair<std::string,Effect>> m_effects;
};
class WorldState
{
public:
void AddFact(const std::string& name, const int value)
{
m_facts[name] = value;
}
private:
std::unordered_map<std::string,int> m_facts;
};
class GoalState
{
public:
void AddCondition(const std::string& name, const Condition condition)
{
m_conditions.emplace_back(name, condition);
}
private:
std::vector<std::pair<std::string,Condition>> m_conditions;
};
int main()
{
Action goTo;
goTo.AddEffect("isAtPosition", Effect(Effect::OP::EQ, 1));
Action pickUp;
pickUp.AddPrereq("isAtPosition", Condition::CreateEE(1));
pickUp.AddEffect("itemInPossession", Effect(Effect::OP::EQ, 1));
Action drop;
drop.AddPrereq("itemInPossession", Condition::CreateEE(1));
drop.AddEffect("itemAtPosition", Effect(Effect::OP::EQ, 1));
WorldState ws;
ws.AddFact("itemAtPosition", 0);
GoalState gs;
gs.AddCondition("itemAtPosition", Condition::CreateGE(1));
return 0;
}
For now I'm just using boolean values, because for my test-case, it's about the agent moving to a position, picking up an item, and moving it to another position. Later on, a goal might be to move n items or something.
Actions would be:
- Goto (puts agent in position)
- PickUp (puts item in possession)
- Goto (puts agent in position)
- Drop (puts item in location) Goal Met!
But how do I use the Goto
action here twice? It's effect is the same, setting the "isAtPosition" variable. Would I need to create a new state variable for "isAtItemPosition" vs "isAtDestinationPosition"?
Because that seems like I would be effectively crafting specific Actions for each possible Goal, with the effect of me determining all the possible sequences of Actions.
How do I encode the state information in such a way that the same actions from the pool can be applied at different stages, having a different effect? (Goto the item position vs goto the destination position).