Planner Reasoner: Quickstart

Defining a simple game

Before we begin implementing AI logic, we have to have something to work on. Let’s create a simple item crafting game!

The game consists of four types of objects:

  1. Items

  2. Recipes - combinations of items that result in other items

  3. Factories, characterized by lists of Recipes they make available

  4. Agents - characters acting in the world by picking up Items and creating new ones using the Recipes in Factories

Classes that have their representation in abstract planning space implement IPlannerObject interface and provide translation logic.

  • C++

  • C#

class Object
{
public:
  Object(const std::string& name) : name(name) {}

  std::string name;
};


class Item : public Object, public grail::planner::IPlannerObject
{
public:
  Item(const std::string& name) : Object(name) {}

  // Inherited via IPlannerObject;
  virtual grail::planner::WorldObject ToWorldObject(grail::planner::Domain& domain, const grail::planner:ObjectIndexBinding&) const override
  {
    grail::planner::WorldObject obj = domain.CreateObjectOfType("item");
    obj.SetParameter("name", name);
    return obj;
  }
};

class Recipe
{
public:
  //[item name, required item count] pairs
  std::vector<std::pair<std::string, int>> components;
  std::string productName;
};

class Factory : public Object, public grail::planner::IPlannerObject
{
public:
  Factory(const std::string& name, std::vector<std::shared_ptr<Item>>& items) : Object(name), worldItems(items) {}

  void MakeRecipe(const Recipe& recipe, Agent& maker) const
  {
    //...item creation logic omitted for brevity
  }

  // Inherited via IPlannerObject
  virtual grail::planner::WorldObject ToWorldObject(grail::planner::Domain& domain, const grail::planner::ObjectIndexBinding&) const override
  {
    return domain.CreateObjectOfType(name);
  }

  std::vector<Recipe*> recipes;
  std::vector<std::shared_ptr<Item>>& worldItems;
};


class Agent
{
public:
  void PickUp(Item* item)
  {
    //..pick up logic omitted for brevity
  }

  std::vector<std::shared_ptr<Item>>* worldItems; //a reference to all items present in the game world
  std::vector<std::shared_ptr<Factory>>* worldFactories; //a reference to all factories present in the game world
  Object* nearbyObject = nullptr;
  std::vector<std::pair<std::string, int>> inventory;
};
public class Object
{
  public Object(string name)
  {
    this.name = name;
  }

  public string name;
};

public class Item : Object, Grail.IPlannerObject
{
  public Item(string name) : base(name) {}

  public Grail.WorldObject ToWorldObject(Grail.Domain domain, Grail.ObjectIndexBinding)
  {
    Grail.WorldObject obj = domain.CreateObjectOfType("item");
    obj.SetParameter("name", name);
    return obj;
  }
};

public class Recipe
{
  //[item name, required item count] pairs
  public Dictionary<string, int> components = Dictionary<string, int>();
  public string productName;
};

public class Factory : Object, Grail.IPlannerObject
{
  public Factory(string name, List<Item> items) : base(name)
  {
    worldItems = items;
  }

  public void MakeRecipe(Recipe recipe, Agent maker)
  {
    //...item creation logic omitted for brevity
  }

  // Inherited via IPlannerObject
  public Grail.WorldObject ToWorldObject(Grail.Domain domain, Grail.ObjectIndexBinding binding)
  {
    return domain.CreateObjectOfType(name);
  }

  private List<Recipe> recipes;
  private List<Item> worldItems;
};


public class Agent
{
  public void PickUp(Item item)
  {
    //..pick up logic omitted for brevity
  }

  public List<Item> worldItems; //a reference to all items present in the game world
  public List<Factory> worldFactories; //a reference to all factories present in the game world
  public Object nearbyObject = null;
  public List<(string name, int count)> inventory;
};

Defining behaviors

Next, we proceed to define behaviors that our agent can perform:

  1. Approaching items and factories in the game world (GoTo behavior)

  2. Crafting items in factories (MakeRecipe behavior)

  3. Picking up existing items (PickUp behavior)

For better readability, Update, IsFinished and IsLegal method implementations are omitted in this example.

  • C++

  • C#

class GoTo : public grail::Behavior
{
public:
  GoTo(Object* obj) : target(obj) {}

  // Inherited via Behavior
  virtual void Start(grail::AIEntity& entity) override
  {
    //the agent is stored on the entity's blackboard
    auto agent = entity.GetBlackboard().GetValue<std::shared_ptr<Agent>>("controlled_agent");
    agent->nearbyObject = target;
  }

private:
  Object* target;
};


class MakeRecipe : public grail::Behavior
{
public:
  MakeRecipe(Factory* fac, const Recipe* rec) : target(fac), recipe(rec) {}

  // Inherited via Behavior
  virtual void Start(grail::AIEntity& entity) override
  {
    auto agent = entity.GetBlackboard().GetValue<std::shared_ptr<Agent>>("controlled_agent");
    target->MakeRecipe(*recipe, *agent);
  }

private:
  Factory* target;
  const Recipe* recipe;
};


class PickUp : public grail::Behavior
{
public:
  PickUp() = default;
  PickUp(Item* obj) : target(obj) {}

  // Inherited via Behavior
  virtual void Start(grail::AIEntity& entity, float) override
  {
    auto agent = entity.GetBlackboard().GetValue<std::shared_ptr<Agent>>("controlled_agent");
    agent->PickUp(target);
  }
private:
  Item* target;
};
public class GoTo : Grail.Behavior
{
  public GoTo(Object obj)
  {
    target = obj
  }

  // Inherited via Behavior
  public override void Start(Grail.AIEntity entity)
  {
    //the agent is stored on the entity's blackboard
    var agent = entity.Blackboard.GetValue<Agent>("controlled_agent");
    agent.nearbyObject = target;
  }

  private Object target;
};


public class MakeRecipe : Grail.Behavior
{
  public MakeRecipe(Factory fac, Recipe rec)
  {
    target = fac;
    recipe = rec;
  }

  // Inherited via Behavior
  public override void Start(Grail.AIEntity entity)
  {
    var agent = entity.Blackboard.GetValue<Agent>("controlled_agent");
    target.MakeRecipe(recipe, agent);
  }

  private Factory target;
  private Recipe recipe;
};


public class PickUp : Grail.Behavior
{
  public PickUp(Item obj)
  {
    target = obj;
  }

  // Inherited via Behavior
  public override void Start(Grail.AIEntity entity, float deltaTime)
  {
    var agent = entity.Blackboard.GetValue<Agent>("controlled_agent");
    agent.PickUp(target);
  }

  private Item target;
};

Planner domain definition

In this section, an example of domain definition is presented. We start by defining WorldObject types and then proceed to action template creation.

For these tasks, DomainTranslator class is used. A basic outline of our translator looks like this:

  • C++

  • C#

class Translator : public grail::planner::DomainTranslator
{
public:
  Translator(grail::MemoryPool& memory, const std::vector<Factory>& allFactories, const std::map<const Recipe*, std::string>& recipeNames)
    : grail::planner::DomainTranslator{ memory }, factoryDatabase(allFactories), recipeNames(recipeNames)
  {
    for (const auto& pair : recipeNames)
    {
      nameRecipes.insert({ pair.second, pair.first });
    }
  }

  // Inherited via DomainTranslator; the methods below will be implemented in the following sections
  virtual std::vector<grail::planner::WorldObjectType> CreateObjectTypes(const grail::AIEntity&, const grail::planner::Goal&) override
  {
  }

  virtual void AddActionTemplates(const grail::AIEntity&, const std::shared_ptr<grail::planner::Domain>& inDomain, const grail::planner::Goal&) override
  {
  }


  //the implementation of these three methods will be described in the
  //'State Translation' section
  virtual grail::Plan TranslatePlan(const grail::AIEntity&, const grail::planner::Planner::AbstractPlan& plan, const grail::planner::Goal&) const override
  {
  }

  virtual std::vector<grail::planner::IPlannerObject*> GeneratePlannerObjects(grail::AIEntity& entity, const grail::planner::Goal&) override
  {
  }

  virtual void SetWorldStateParameters(const grail::AIEntity& entity, grail::planner::WorldState& state, const grail::planner::Goal& goal) const
  {
  }

private:
  const std::vector<Factory>& factoryDatabase; //a list of all possible factory types
  std::map<const Recipe*, std::string> recipeNames; //recipe to name mapping
  std::map<std::string, const Recipe*> nameRecipes; //name to recipe mapping
};
public class Translator : Grail.Planning.DomainTranslator
{
  public Translator(List<Factory> allFactories, Dictionary<Recipe, string> recipeNames)
  {
    factoryDatabase = allFactories;
    this.recipeNames = recipeNames;
    for (var recipe : recipeNames)
    {
      nameRecipes.Add(recipe.Value, recipe.Key)
    }
  }

  // Inherited via DomainTranslator; the methods below will be implemented in the following sections
  protected override List<Grail.Planning.WorldObjectType> CreateObjectTypes(Grail.AIEntity, Grail.Planning.Goal goal)
  {
  }

  protected override void AddActionTemplates(Grail.AIEntity, Grail.Planning.Domain inDomain, Grail.Planning.Goal goal)
  {
  }

  //the implementation of these three methods will be described in the
  //'State Translation' section
  public override Grail.Planning.Plan TranslatePlan(Grail.AIEntity, Grail.Planning.Planner.AbstractPlan plan, Grail.Planning.Goal goal)
  {
  }

  protected override List<Grail.Planning.IPlannerObject> GeneratePlannerObjects(Grail.AIEntity entity,  Grail.Planning.Goal)
  {
  }

  protected override void SetWorldStateParameters(Grail.AIEntity entity, Grail.Planning.WorldState state,  Grail.Planning.Goal goal)
  {
  }

  private List<Factory> factoryDatabase; //a list of all possible factory types
  private Dictionary<Recipe, string> recipeNames; //recipe to name mapping
  private Dictionary<string, Recipe> nameRecipes = new Dictionary<string, Recipe>(); //name to recipe mapping
};

Object type definition

Planner WorldObject types are declared inside the CreateObjectTypes method.

  • C++

  • C#

virtual std::vector<grail::planner::WorldObjectType> CreateObjectTypes(const grail::AIEntity&, const grail::planner::Goal&) override
{
  //the object types created inside this method will be automatically added
  //to the planning domain

  std::vector<grail::planner::WorldObjectType> result;
  result.push_back(grail::planner::WorldObjectType(memory, "item")); //here, we create an object type for items

  //each factory is a separate object type in the plan space
  for (const auto& factory : factoryDatabase)
  {
    result.push_back(grail::planner::WorldObjectType(memory, factory.name));
  }
  return result;
}
protected override List<Grail.Planning.WorldObjectType> CreateObjectTypes(Grail.AIEntity, Grail.Planning.Goal goal)
{

  //the object types created inside this method will be automatically added
  //to the planning domain

  List<WorldObjectType> result = new List<WorldObjectType>
  {
    new WorldObjectType("item") //here, we create an object type for items
  };

  //each factory is a separate object type in the plan space
  foreach (var factory in factoryDatabase)
  {
    result.Add(new WorldObjectType(factory.name));
  }

  return result;
}

Planner action template definition

Action templates are defined inside AddActionTemplate method.

  • C++

  • C#

virtual void AddActionTemplates(const grail::AIEntity&, const std::shared_ptr<grail::planner::Domain>& inDomain, const grail::planner::Goal&) override
{
  //foreach factory type
  for (const auto& factory : factoryDatabase)
  {
    //generate a new plan space action for each recipe
    for (const auto recipe : factory.recipes)
    {
      //to create the action and add it to the domain, pass its name and its parameter type names
      //in this case, there's only one parameter of a type corresponding to the factory name
      auto& action = inDomain->AddActionTemplate(recipeNames.at(recipe), { factory.name });

      //set action preconditions as a lambda function
      action.SetPreconditions([=](const std::vector<const grail::planner::WorldObject*>&, const grail::planner::WorldState& state)
      {
        //return false if any of the recipe components is missing
        for(const auto& compPair : recipe->components)
        {
          if (state.GetParameterValue<int>(compPair.first) < compPair.second)
          {
            return false;
          }
        }
        return true;
      });

      //analogously, set action effect function
      action.SetEffects([=](const std::vector<grail::planner::WorldObject*>&, grail::planner::WorldState& state)
      {
        //inventory items are stored as world state parameters
        //when this action is executed, the corresponding item counter gets incremented
        int itemCount = state.GetParameterValue<int>(recipe->productName);
        state.SetParameter(recipe->productName, itemCount + 1);

        //the recipe ingredients get consumed during the production process
        for (const auto& compPair : recipe->components)
        {
          itemCount = state.GetParameterValue<int>(compPair.first);
          state.SetParameter(compPair.first, itemCount - compPair.second);
        }
      });
    }
  }

  //pick up action definition, accepting objects of type 'item' as parameters
  auto& action = inDomain->AddActionTemplate("pick_up", { "item" });

  action.SetPreconditions([](const std::vector<const grail::planner::WorldObject*>& args, const grail::planner::WorldState&)
  {
    //the action is legal only if the item is not picked
    return args[0]->GetParameterValue<bool>("is_picked") == false;
  });

  action.SetEffects([](const std::vector<grail::planner::WorldObject*>& args, grail::planner::WorldState& state)
  {
    //increment the item counter, mark the object as picked
    std::string itemName = args[0]->GetParameterValue<std::string>("name");
    int itemCount = state.GetParameterValue<int>(itemName);
    state.SetParameter(itemName, itemCount + 1);
    args[0]->SetParameter("is_picked", true);
  });
}
protected override void AddActionTemplates(AIEntity entity, Domain domain, Goal goal)
{
  //foreach factory type
  foreach (var factory in factoryDatabase)
  {
    //generate a new plan space action for each recipe
    foreach (var recipe in factory.recipes)
    {
      //to create the action and add it to the domain, pass its name and its parameter type names
      //in this case, there's only one parameter of a type corresponding to the factory name
      var action = domain.AddActionTemplate(recipeNames[recipe], new List<string>() {factory.name});

      //set action preconditions as a lambda function
      action.Preconditions = (args, state) =>
      {
        //return false if any of the recipe components is missing
        foreach (var componentPair in recipe.components)
        {
          if (state.GetParameterValue<int>(componentPair.Key) < componentPair.Value)
          {
            return false;
          }
        }

        return true;
      };

      //analogously, set action effect function
      action.Effects = (args, state) =>
      {
        //inventory items are stored as world state parameters
        //when this action is executed, the corresponding item counter gets incremented
        int itemCount = state.GetParameterValue<int>(recipe.productName);
        state.SetParameter(recipe.productName, itemCount + 1);

        //the recipe ingredients get consumed during the production process
        foreach (var componentPair in recipe.components)
        {
          if (componentPair.Key != "axe" && componentPair.Key != "pickaxe")
          {
            itemCount = state.GetParameterValue<int>(componentPair.Key);
            state.SetParameter(componentPair.Key, itemCount - componentPair.Value);
          }
        }
      };
    }
  }

  //pick up action definition, accepting objects of type 'item' as parameters
  var pickUpAction = domain.AddActionTemplate("pick_up", new List<string>() {"item"});
  pickUpAction.Preconditions = (args, state) => !args[0].GetParameterValue<bool>("is_picked");
  pickUpAction.Effects = (args, state) =>
  {
    //increment the item counter, mark the object as picked
    string itemName = args[0].GetParameterValue<string>("name");
    int itemCount = state.GetParameterValue<int>(itemName);
    state.SetParameter(itemName, itemCount + 1);
    args[0].SetParameter("is_picked", true);
  };
}

State translation

In this section, we describe the implementation of DomainTranslator methods responsible for producing abstract state representations from 'real' game states and translating abstract action sequences to Behaviors.

Creating World Objects

GeneratePlannerObjects method should return all objects implementing IPlannerObject interface that we want to be taken into account in a particular planning domain. In our case, it means all items and factories.

  • C++

  • C#

virtual std::vector<grail::planner::IPlannerObject*> GeneratePlannerObjects(grail::AIEntity& entity, const grail::planner::Goal&) override
{
  std::vector<grail::planner::IPlannerObject*> result;

  auto agent = entity.GetBlackboard().GetValue<std::shared_ptr<Agent>>("controlled_agent");
  for (auto& item : *agent->worldItems)
  {
    result.push_back(item.get());
  }

  for (auto& factory : *agent->worldFactories)
  {
    result.push_back(factory.get());
  }
  return result;
}
protected override List<IPlannerObject> GeneratePlannerObject(AIEntity entity, Goal goal)
{
  List<IPlannerObject> result = new List<IPlannerObject>();

  var agent = entity.Blackboard.GetValue<Agent>("controlled_agent");
  foreach (var item in agent.worldItems)
  {
    result.Add(item);
  }

  foreach (var factory in agent.worldFactories)
  {
    result.Add(factory);
  }

  return result;
}

Setting World State parameters

  • C++

  • C#

virtual void SetWorldStateParameters(const grail::AIEntity& entity, grail::planner::WorldState& state, const grail::planner::Goal& goal) const
{
  auto agent = entity.GetBlackboard().GetValue<std::shared_ptr<Agent>>("controlled_agent");
  for (const auto& inventoryEntry : agent->inventory)
  {
    state.SetParameter(inventoryEntry.first, inventoryEntry.second); //item name and count
  }
}
protected override void SetWorldStateParameters(Grail.AIEntity entity, Grail.Planning.WorldState state,  Grail.Planning.Goal goal)
{
  var agent = entity.Blackboard.GetValue<Agent>("controlled_agent");
  for (var (itemName, count) : agent.inventory)
  {
    state.SetParameter(itemName, count);
  }
}

Translation from abstract actions to behaviors

After a plan is found, it has to be converted to a behavior sequence. To do this, we implement TranslatePlan method. Inside the method we use the binding field (defined in DomainTranslator base class) to retrieve object references, then we construct behaviors to be executed based

  • C++

  • C#

virtual grail::Plan TranslatePlan(const grail::AIEntity&, const grail::planner::Planner::AbstractPlan& plan, const grail::planner::Goal&) const override
{
  grail::Plan result;
  for (const auto& action : plan.actions)
  {
    if (action.GetName() == "pick_up")
    {
      auto item = binding.GetObjectByIndex<Item>(action.GetArgumentIndices()[0]);
      result.PushBehavior(std::make_unique<GoTo>(item));
      result.PushBehavior(std::make_unique<PickUp>(item));
    }
    else
    {
      auto factory = binding.GetObjectByIndex<Factory>(action.GetArgumentIndices()[0]);
      result.PushBehavior(std::make_unique<GoTo>(factory));
      result.PushBehavior(std::make_unique<MakeRecipe>(factory, nameRecipes.at(action.GetName())));
    }
  }
  return result;
}
public override Plan TranslatePlan(AIEntity entity, Planner.AbstractPlan plan, Goal goal)
{
  Plan result = new Plan();
  foreach (var action in plan.actions)
  {
    if (action.Name == "pick_up")
    {
      var item = binding.GetObjectByIndex<Item>(action.ArgumentIndices[0]);
      result.PushBehavior(new GoTo(item));
      result.PushBehavior(new PickUp(item));
    }
    else
    {
      var factory = binding.GetObjectByIndex<Factory>(action.ArgumentIndices[0]);
      result.PushBehavior(new GoTo(factory));
      result.PushBehavior(new MakeRecipe(factory, nameRecipes[action.Name]));
    }
  }

  return result;
}

Defining goals

Grail provides the Goal class to handle goal logic and its interaction with the reasoner. For more details on this class, see Goal page.

In this tutorial, we focus on a concrete implementation of a goal: creating an item.

  • C++

  • C#

class ItemCreated : public grail::planner::Goal
{
public:
  ItemCreated() = default;
  ItemCreated(const std::string& name) : itemName(name) {} //the class is initialized with the requested item name

  // Inherited via Goal
  virtual bool IsReached(const grail::AIEntity& entity) const override
  {
    auto agent = entity.GetBlackboard().GetValue<std::shared_ptr<Agent>>("controlled_agent");

    //return true if there is one or more specified items in the game world
    auto itemIter = std::find_if(agent->inventory.begin(), agent->inventory.end(), [&](const auto& itemPair)
    {
      return itemPair.first == itemName;
    });
    return itemIter != agent.inventory.end() && itemIter->second > 0;
  }

  //this method specifies goal conditions in the abstract plan space
  virtual void SetPlanSpaceConditions(grail::planner::Planner& planner, const grail::planner::ObjectIndexBinding&) const override
  {
    std::string name = itemName;
    planner.PushCondition([name](const grail::planner::WorldState& state)
    {
      return state.GetParameterValue<int>(name) > 0;
    });
  }

private:
  std::string itemName;
};
public class ItemCreated : Goal
{
  private string itemName;

  public ItemCreated(string name)
  {
    itemName = name;
  }

  public override bool IsReached(AIEntity entity)
  {
    var agent = entity.Blackboard.GetValue<Agent>("controlled_agent");
    return agent.inventory.ContainsKey(itemName) && agent.inventory[itemName] > 0;
  }

  public override void SetPlanSpaceConditions(Planner planner, ObjectIndexBinding binding)
  {
    string name = itemName;
    planner.PushCondition(state => state.GetParameterValue<int>(name) > 0);
  }
}

Defining a goal selector

To give our Reasoner a proper goal, we need to implement a Goal Selector. In this example, we’ll limit ourselves to the simplest possible selection logic: setting a single, predefined goal.

  • C++

  • C#

class DummyGoalSelector : public grail::planner::GoalSelector
{
public:

  DummyGoalSelector(const std::string& itemName) : itemToCraft{ itemName } {}

  virtual void UpdateGoal(grail::planner::IGoalAcceptor& goalAcceptor, grail::AIEntity& entity) override
  {
    //if no goal is currently chosen, set one
    if (goalAcceptor.GetCurrentGoal() == nullptr)
    {
      goalAcceptor.SetNewGoal(std::make_unique<ItemCreated>(itemToCraft), entity);
    }
  }

private:
  std::string itemToCraft;
};
class DummyGoalSelector : IGoalSelector
{
  public DummyGoalSelector(string itemName)
  {
    this.itemToCraft = itemName;
  }

  public void UpdateGoal(IGoalAcceptor goalAcceptor, AIEntity entity)
  {
    //if no goal is currently chosen, set one
    if (goalAcceptor.CurrentGoal  == null)
    {
      goalAcceptor.SetNewGoal(new ItemCreated(itemToCraft), entity);
    }
  }

  private readonly string itemToCraft;
};

Reasoner creation and assignment

In this section, we show how to create and use a Planner Reasoner that uses the Domain Translator that we defined above.

  • C++

  • C#

//create a shared memory pool for world state data
//this pointer should NOT be released before the reasoner
auto pool = std::make_unique<grail::planner::MemoryPool>();

//create domain translator. 'allFactories' and 'recipeNames' arguments will be made clear in the next section
auto translator = std::make_unique<Translator>(pool, allFactories, recipeNames);
auto entity = std::make_shared<grail::AIEntity>();

//set planner reasoner configuration and create a reasoner instance
grail::planner::PlannerReasoner::Config config;
config.iterationsPerFrame = 20;
config.maxIterations = 50000;
config.maxPlanLength = 20;
config.usePartialPlans = true;
auto plannerReasoner = std::make_unique<grail::planner::PlannerReasoner>(pool, std::make_shared<DummyGoalSelector>("grail"), std::move(translator), config);

//assign the reasoner
entity->SetReasoner(std::move(plannerReasoner));
//create domain translator. 'allFactories' and 'recipeNames' arguments will be made clear in the next section
var translator = new Translator(pool, allFactories, recipeNames);
var entity = new Grail.AIEntity();

//set planner reasoner configuration and create a reasoner instance
Grail.PlannerReasoner.Config config
{
  MaxIterations = 500000,
  MaxPlanLength = 20,
  IterationPerUpdate = 20,
  UsePartialPlans = true
}
var plannerReasoner = new Grail.PlannerReasoner(new DummyGoalSelector("grail"), translator, config);

//assign the reasoner
entity.Reasoner = plannerReasoner;

Putting it all together

In this section, we show a complete implementation of our simple crafting game, along with AIEntity data setup.

  • C++

  • C#

//GAME SETUP START
//definition of starting game world items
std::vector<std::shared_ptr<Item>> worldItems;
worldItems.push_back(std::make_shared<Item>("wood"));
worldItems.push_back(std::make_shared<Item>("ore"));
worldItems.push_back(std::make_shared<Item>("ore"));

//definition of all available recipes

//to produce an axe, you need 1 wood and 1 metal
Recipe axeRecipe;
axeRecipe.components = { { "wood", 1} , { "metal", 1} };
axeRecipe.productName = "axe";

Recipe pickaxeRecipe;
pickaxeRecipe.components = { { "wood", 1 },{ "metal", 1 } };
pickaxeRecipe.productName = "pickaxe";

Recipe woodRecipe;
woodRecipe.components = { { "axe", 1} };
woodRecipe.productName = "wood";

Recipe oreRecipe;
oreRecipe.components = { { "pickaxe", 1 }};
oreRecipe.productName = "ore";

Recipe metalRecipe;
metalRecipe.components = { { "ore", 1 } };
metalRecipe.productName = "metal";

Recipe grailRecipe;
grailRecipe.components = { { "wood", 3 }, { "metal", 3} };
grailRecipe.productName = "grail";

//factory types and their corresponding recipes
Factory workshop("workshop", worldItems);
workshop.recipes = { &axeRecipe };

Factory forest("forest", worldItems);
forest.recipes = { &woodRecipe };

Factory forge("forge", worldItems);
forge.recipes = { &pickaxeRecipe, &metalRecipe, &grailRecipe };

Factory mine("mine", worldItems);
mine.recipes = { &oreRecipe };

//definition of game world factories
std::vector<std::shared_ptr<Factory>> worldFactories =
{
  std::make_shared<Factory>(workshop),
  std::make_shared<Factory>(forest),
  std::make_shared<Factory>(forge),
  std::make_shared<Factory>(mine)
};

//create the agent to be controlled by our AI and initialize it with game world info
auto agent = std::make_shared<Agent>();
agent->worldItems = &worldItems;
agent->worldFactories = &worldFactories;
//GAME SETUP END

//AI SETUP START
grail::AIManager manager; //create an AI Manager responsible for updating AI Entities

//create a shared memory pool for world state data
//this pointer should NOT be released before the reasoner
auto pool = std::make_unique<grail::planner::MemoryPool>();

//define a mapping from recipes to action names and factory type list
//use them to create a domain translator
std::map<const Recipe*, std::string> recipeNames =
{
  {&axeRecipe, "make_axe"},
  {&pickaxeRecipe, "make_pickaxe"},
  {&woodRecipe, "make_wood"},
  {&oreRecipe, "make_ore"},
  {&metalRecipe, "make_metal"},
  {&grailRecipe, "make_grail"}
};
std::vector<Factory> allFactories = { workshop, forest, forge, mine };
auto translator = std::make_unique<Translator>(pool, allFactories, recipeNames);

//create an AI Entity to control our agent
auto entity = std::make_shared<grail::AIEntity>();

//set planner reasoner configuration and create a reasoner instance
grail::planner::PlannerReasoner::Config config;
config.iterationsPerFrame = 20;
config.maxIterations = 50000;
config.maxPlanLength = 20;
config.usePartialPlans = true;
auto plannerReasoner = std::make_unique<grail::planner::PlannerReasoner>(pool, std::make_shared<DummyGoalSelector>("grail"), std::move(translator), config);

//assign the reasoner
entity->SetReasoner(std::move(plannerReasoner));

//set up entity data and register the entity in the AI Manager
entity->GetBlackboard().SetValue("controlled_agent", agent);
manager.RegisterAIEntity(agent, 0);

//AI SETUP END

//a simple game loop
float deltaTime = 0.05f;
for (int i = 0; i < 1000; ++i)
{
  manager.UpdateReasoners();
  manager.UpdateEntities(deltaTime);
}

//if everything went as expected, this iterator should point to a valid object
auto itemIter = std::find_if(agent->inventory.begin(), agent->inventory.end(), [&](const auto& itemPair)
{
  return itemPair.first == "grail";
});
//GAME SETUP START
//definition of starting game world items
List<Item> worldItems = new List<Item>();
worldItems.Add(new Item("wood"));
worldItems.Add(new Item("ore"));
worldItems.Add(new Item("ore"));


//definition of all available recipes

//to produce an axe, you need 1 wood and 1 metal
Recipe axeRecipe = new Recipe();
axeRecipe.components = new Dictionary<string, int>();
axeRecipe.components["wood"] = 1;
axeRecipe.components["metal"] = 1;
axeRecipe.productName = "axe";

Recipe pickaxeRecipe = new Recipe();
pickaxeRecipe.components = new Dictionary<string, int>();
pickaxeRecipe.components["wood"] = 1;
pickaxeRecipe.components["metal"] = 1;
pickaxeRecipe.productName = "pickaxe";

Recipe woodRecipe = new Recipe();
woodRecipe.components = new Dictionary<string, int>();
woodRecipe.components["axe"] = 1;
woodRecipe.productName = "wood";

Recipe oreRecipe = new Recipe();
oreRecipe.components = new Dictionary<string, int>();
oreRecipe.components["pickaxe"] = 1;
oreRecipe.productName = "ore";

Recipe metalRecipe = new Recipe();
metalRecipe.components = new Dictionary<string, int>();
metalRecipe.components["ore"] = 1;
metalRecipe.productName = "metal";

Recipe grailRecipe = new Recipe();
grailRecipe.components = new Dictionary<string, int>();
grailRecipe.components["wood"] = 3;
grailRecipe.components["metal"] = 3;
grailRecipe.productName = "grail";

//factory types and their corresponding recipes
Factory workshop = new Factory("workshop", worldItems);
workshop.recipes = new List<Recipe>() {axeRecipe};

Factory forest = new Factory("forest", worldItems);
forest.recipes = new List<Recipe>() {woodRecipe};

Factory forge = new Factory("forge", worldItems);
forge.recipes = new List<Recipe>() { pickaxeRecipe, metalRecipe, grailRecipe};

Factory mine = new Factory("mine", worldItems);
mine.recipes = new List<Recipe>() {oreRecipe};

List<Factory> factories = new List<Factory>() {workshop, forest, forge, mine};

//definition of game world factories
//this is a just copy of the list above, but we keep them separated, because they represent different things
List<Factory> worldFactories = new List<Factory>(factories);

//create the agent to be controlled by our AI and initialize it with game world info
var agent = new Agent();
agent.worldItems = worldItems;
agent.worldFactories = worldFactories;
//GAME SETUP END

//AI SETUP START
Grail.AIManager manager; //create an AI Manager responsible for updating AI Entities

//define a mapping from recipes to action names and factory type list
//use them to create a domain translator
var recipeNames = new Dictionary<Recipe, string>();
recipeNames[axeRecipe] = "make_axe";
recipeNames[pickaxeRecipe] = "make_pickaxe";
recipeNames[woodRecipe] = "make_wood";
recipeNames[oreRecipe] = "make_ore";
recipeNames[metalRecipe] = "make_metal";
recipeNames[grailRecipe] = "make_grail";

List<Factory> allFactories = new List<Factory>() {workshop, forest, forge, mine};
var translator = new Translator(allFactories, recipeNames);

//create an AI Entity to control our agent
var entity = new Grail.AIEntity();

//set planner reasoner configuration and create a reasoner instance
Grail.PlannerReasoner.Config config
{
  MaxIterations = 500000,
  MaxPlanLength = 20,
  IterationPerUpdate = 20,
  UsePartialPlans = true
};
var plannerReasoner = new PlannerReasoner(new DummyGoalSelector("grail"), translator, config);

//assign the reasoner
entity.Reasoner = plannerReasoner;

//set up entity data and register the entity in the AI Manager
entity.Blackboard.SetValue("controlled_agent", agent);
manager.RegisterAIEntity(agent, 0);

//AI SETUP END

//a simple game loop
float deltaTime = 0.05f;
for (int i = 0; i < 1000; ++i)
{
  manager.UpdateReasoners();
  manager.UpdateEntities(deltaTime);
}

//if everything went as expected, this flag should be true after the loop
bool grailCreated = false;
foreach (var item in agent.inventory)
{
  if (item.Key == "grail" && item.Value > 0)
  {
    grailCreated = true;
    break;
  }
}