0

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:

  1. Goto (puts agent in position)
  2. PickUp (puts item in possession)
  3. Goto (puts agent in position)
  4. 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).

NeomerArcana
  • 1,978
  • 3
  • 23
  • 50

1 Answers1

0

Yes, if you only have boolean values, then you need separate action for each location.

If you can, I would consider using a slightly more powerful representation, such as STRIPS, or PDDL. That will make it a lot easier to describe somewhat more complex scenarios, and it will have the added benefit that you can use off-the-shelf solvers to develop plans for your agents.

In PDDL you could have an action (move ?agent ?from ?to), with the precondition that the agent is at location ?from and the effect that they will be at location ?to after the action has been performed. For picking up an object you could have (pick-up ?agent ?object) with the precodition that both the agent and the object are at the same location. This is far more generic than fixed boolean actions, and will allow later extensions more easily.

Oliver Mason
  • 2,240
  • 2
  • 15
  • 23
  • thanks for your answer, but it doesn't really explain how to utilise the variable information in the planner. PDDL just appears to be a formalised gramme, there's nothing I see there about implementation – NeomerArcana May 30 '20 at 01:05
  • @NeomerArcana For the implementation look at any modern planner. One option that I'm currently working with is GraphPlan. – Oliver Mason May 30 '20 at 19:21