Skip to content

Generic GOAP library based on λ-expressions processing

License

Notifications You must be signed in to change notification settings

ArBom/SharpPDDL

Repository files navigation

thumbnail

This is the class library based on PDDL intellection and in effect it's a implementation of GOAP (Goal Oriented Action Planning) algorithm. It uses only C# 7.2 standard library. Values inside classes using to find solution have to be ValueType only (most numeric, like: int, short etc., char, bool).

Warning

Library has several bugs, works unstable so is not to use, still.

One can to use previously defined classes which are using in other part of one's programm. At this version library can return the plan of doing to realize the goal. Examples of problems possible to solution by this algorithm:

Tower of Hanoi

Treatment the puzzle: wiki

In this problem, you will use two types of objects (brick and table). Both have some common features (HanoiObj).

public class HanoiObj //It cannot be abstract
{
    public int HanoiObjSizeUpSide = 0;
    public bool IsEmptyUpSide;
}

public class HanoiBrick : HanoiObj
{
    readonly public int Size;
}

public class HanoiTable : HanoiObj
{
    public readonly int no;
}
classDiagram

namespace Legend {

    class Class{
        Its a block representant some class
    }

    class Object {
        Its a block representant some object / class instance
    }

}

    style Object fill:#391, stroke-style:..
    style Class fill:#139, stroke-style:..

namespace HanoiTower {

    class HanoiObj{
        +int HanoiObjSizeUpSide
        +bool IsEmptyUpSide
    }

    class HanoiBrick{
        +int Size
    }

    class HanoiTable {
        +int no
    }
}
    HanoiObj <|-- HanoiBrick
    HanoiObj <|-- HanoiTable

    style HanoiObj fill:#139, stroke-style:..
    style HanoiBrick fill:#139, stroke-style:..
    style HanoiTable fill:#139, stroke-style:..

namespace SharpPDDL {

    class Root_TreeNode{
        ~SingleTypeOfDomein Content
        ~List~TreeNode~ Children 
    }

    class HanoiObj_SingleTypeOfDomein {
        ~Type Type : BaseShapes.HanoiObj
        ~List~ValueOfThumbnail~ CumulativeValues 
    }

    class 0_TreeNode{
        ~SingleTypeOfDomein Content
        ~List~TreeNode~ Children 
    }

    class HanoiBrick_SingleTypeOfDomein {
        ~Type Type : BaseShapes.HanoiObj
        ~List~ValueOfThumbnail~ CumulativeValues 
    }

    class 1_TreeNode{
        ~SingleTypeOfDomein Content
        ~List~TreeNode~ Children 
    }

    class HanoiTable_SingleTypeOfDomein {
        ~Type Type : BaseShapes.HanoiObj
        ~List~ValueOfThumbnail~ CumulativeValues 
    }
}
    style Root_TreeNode fill:#391, stroke-style:..
    style 0_TreeNode fill:#391, stroke-style:..
    style 1_TreeNode fill:#391, stroke-style:..
    style HanoiObj_SingleTypeOfDomein fill:#391, stroke-style:..
    style HanoiBrick_SingleTypeOfDomein fill:#391, stroke-style:..
    style HanoiTable_SingleTypeOfDomein fill:#391, stroke-style:..
    
    Root_TreeNode --> "Children[0]" 0_TreeNode
    Root_TreeNode --> "Children[1]" 1_TreeNode
    0_TreeNode --> "Content" HanoiBrick_SingleTypeOfDomein
    1_TreeNode --> "Content" HanoiTable_SingleTypeOfDomein
    Root_TreeNode --> "Content" HanoiObj_SingleTypeOfDomein
    HanoiObj_SingleTypeOfDomein ..> "≙" HanoiObj
    HanoiBrick_SingleTypeOfDomein ..> "≙" HanoiBrick
    HanoiTable_SingleTypeOfDomein ..> "≙" HanoiTable

    note for HanoiObj_SingleTypeOfDomein "CumulativeValues:<br> 1: HanoiObSizeUpSide<br> 2: IsEmptyUpSide"
    note for HanoiTable_SingleTypeOfDomein "CumulativeValues:<br> 1: HanoiObSizeUpSide<br> 2: IsEmptyUpSide<br> // int:no is not use in any action"
    note for HanoiBrick_SingleTypeOfDomein "CumulativeValues:<br> 1: HanoiObSizeUpSide<br> 2: IsEmptyUpSide<br> 3: Size"

Loading

Instances of class used to define action shouldn't be use in other part of program. In time of create actions library create class instance excluding use the class constructor.

For these classes one can define rules in library like "Move brick onto another brick" or "Move brick on table". Preconditions, effect etc. are phrased by library's user as Expressions (System.Linq.Expressions):

HanoiBrick MovedBrick = null; //you can take brick...
HanoiObj ObjBelowMoved = null; //...from table or another brick... 
HanoiBrick NewStandB = null; //...and put it into bigger brick...
HanoiTable NewStandT = null; //...or empty table spot.

Expression<Predicate<HanoiObj>> ObjectIsNoUp = (HO => HO.IsEmptyUpSide); //Moved brick have to be empty up side
Expression<Predicate<HanoiBrick, HanoiBrick>> PutSmallBrickAtBigger = ((MB, NSB) => (MB.Size < NSB.Size)); //you can put smaller brick onto bigger one
Expression<Predicate<HanoiBrick, HanoiObj>> FindObjBelongMovd = ((MB, OBM) => (MB.Size == OBM.HanoiObjSizeUpSide));

ActionPDDL moveBrickOnBrick = new ActionPDDL("Move brick onto another brick"); //1st action with 3 parameters: MovedBrick, ObjBelowMoved, NewStandB

moveBrickOnBrick.AddPartOfActionSententia(ref MovedBrick, "Place the {0}-size brick ", MB => MB.Size);
moveBrickOnBrick.AddPartOfActionSententia(ref NewStandB, "onto {0}-size brick.", MB => MB.Size);

moveBrickOnBrick.AddPrecondiction("Moved brick is no up", ref MovedBrick, ObjectIsNoUp); //MovedBrick.IsEmptyUpSide == true
moveBrickOnBrick.AddPrecondiction("New stand is empty", ref NewStandB, ObjectIsNoUp); //NewStandB.IsEmptyUpSide == true
moveBrickOnBrick.AddPrecondiction("Small brick on bigger one", ref MovedBrick, ref NewStandB, PutSmallBrickAtBigger); //MovedBrick.Size < NewStandB.Size
moveBrickOnBrick.AddPrecondiction("Find brick bottom moved one", ref MovedBrick, ref ObjBelowMoved, FindObjBelongMovd); //MovedBrick.Size == ObjBelowMoved.HanoiObjSizeUpSide

moveBrickOnBrick.AddEffect("New stand is full", ref NewStandB, NS => NS.IsEmptyUpSide, false); //NewStandB.IsEmptyUpSide = false
moveBrickOnBrick.AddEffect("Old stand is empty", ref ObjBelowMoved, NS => NS.IsEmptyUpSide, true); //ObjBelowMoved.IsEmptyUpSide = true
moveBrickOnBrick.AddEffect("UnConsociate Objs", ref ObjBelowMoved, OS => OS.HanoiObjSizeUpSide, 0); //ObjBelowMoved.HanoiObjSizeUpSide = 0
moveBrickOnBrick.AddEffect("Consociate Bricks", ref NewStandB, NSB => NSB.HanoiObjSizeUpSide, ref MovedBrick, MB => MB.Size); //NewStandB.HanoiObjSizeUpSide = MovedBrick.Size

newDomein.AddAction(moveBrickOnBrick); //Putting empty brick onto bigger one

ActionPDDL moveBrickOnTable = new ActionPDDL("Move brick on table"); //2st action with 3 parameters: MovedBrick, ObjBelowMoved, NewStandT

moveBrickOnTable.AddPartOfActionSententia(ref MovedBrick, "Place the {0}-size brick ", MB => MB.Size);
moveBrickOnTable.AddPartOfActionSententia(ref NewStandT, "onto table no {0}.", NS => NS.no);

moveBrickOnTable.AddPrecondiction("Moved brick is no up", ref MovedBrick, ObjectIsNoUp); //MovedBrick.IsEmptyUpSide == true
moveBrickOnTable.AddPrecondiction("New table is empty", ref NewStandT, ObjectIsNoUp); //NewStandT.IsEmptyUpSide == true
moveBrickOnTable.AddPrecondiction("Find brick bottom moved one", ref MovedBrick, ref ObjBelowMoved, FindObjBelongMovd); //MovedBrick.Size == ObjBelowMoved.HanoiObjSizeUpSide

moveBrickOnTable.AddEffect("New stand is full", ref NewStandT, NS => NS.IsEmptyUpSide, false); //NewStandT.IsEmptyUpSide = false
moveBrickOnTable.AddEffect("Old stand is empty", ref ObjBelowMoved, NS => NS.IsEmptyUpSide, true); //ObjBelowMoved.IsEmptyUpSide = true
moveBrickOnTable.AddEffect("UnConsociate Objs", ref ObjBelowMoved, OS => OS.HanoiObjSizeUpSide, 0); //ObjBelowMoved.HanoiObjSizeUpSide = 0
moveBrickOnTable.AddEffect("Consociate Bricks", ref NewStandT, NST => NST.HanoiObjSizeUpSide, ref MovedBrick, MB => MB.Size); //NewStandT.HanoiObjSizeUpSide = MovedBrick.Size

newDomein.AddAction(moveBrickOnTable); //Putting empty brick onto empty table spot

Solution output for 3-bricks-hanoi-tower problem:

Transfer bricks onto table no. 3 determined!!! Total Cost: 7
Move brick on table: Place the 1-size brick onto table no 2.
Move brick on table: Place the 2-size brick onto table no 1.
Move brick onto another brick: Place the 1-size brick onto 2-size brick.
Move brick on table: Place the 3-size brick onto table no 2.
Move brick on table: Place the 1-size brick onto table no 0.
Move brick onto another brick: Place the 2-size brick onto 3-size brick.
Move brick onto another brick: Place the 1-size brick onto 2-size brick.
Water pouring puzzle

Treatment the puzzle: wiki

public class WaterJug
{
  public readonly float Capacity;
  public float flood;}
DomeinPDDL DecantingDomein = new DomeinPDDL("Decanting problems"); //In this problem...

ActionPDDL DecantWater = new ActionPDDL("Decant water"); //...you need one action with 2 arguments:
WaterJug SourceJug = null; //The jug from which you pour,
WaterJug DestinationJug = null; // and the jug you pour into.

DecantWater.AddPartOfActionSententia(ref SourceJug, "from {0}-liter jug ", SJ => SJ.Capacity);
DecantWater.AddPartOfActionSententia(ref DestinationJug, "to the {0}-liter jug.", DJ => DJ.Capacity);

//In the effect of decanting the level in the jug from which you pour is maked smaller after that,...
DecantWater.AddEffect( //SourceJug.flood = DestinationJug.flood + SourceJug.flood >= DestinationJug.Capacity ? SourceJug.flood - DestinationJug.Capacity + DestinationJug.flood : 0
    "Reduce source jug flood",
    ref SourceJug,
    Source_Jug => Source_Jug.flood,
    ref DestinationJug,
    (Source_Jug, Destination_Jug) => Destination_Jug.flood + Source_Jug.flood >= Destination_Jug.Capacity ? Source_Jug.flood - Destination_Jug.Capacity + Destination_Jug.flood : 0);

//...the level in the jug you pour into is maked bigger.
DecantWater.AddEffect( //DestinationJug.flood = DestinationJug.flood + SourceJug.flood >= DestinationJug.Capacity ? DestinationJug.Capacity : DestinationJug.flood + SourceJug.flood
    "Increase destination jug flood",
    ref DestinationJug,
    Destination_Jug => Destination_Jug.flood,
    ref SourceJug,
    (Destination_Jug, Source_Jug) => Destination_Jug.flood + Source_Jug.flood >= Destination_Jug.Capacity ? Destination_Jug.Capacity : Destination_Jug.flood + Source_Jug.flood);

//One need to do as fast as possible
DecantWater.DefineActionCost(ref SourceJug, ref DestinationJug, (S, D) => WaterJug.DecantedWater(S.flood, D.Capacity, D.flood));

DecantingDomein.AddAction(DecantWater);
SharpPDDL : Divide in half determined!!! Total Cost: 22
Decant water: from 8-liter jug to the 5-liter jug. Action cost: 5
Decant water: from 5-liter jug to the 3-liter jug. Action cost: 3
Decant water: from 3-liter jug to the 8-liter jug. Action cost: 3
Decant water: from 5-liter jug to the 3-liter jug. Action cost: 2
Decant water: from 8-liter jug to the 5-liter jug. Action cost: 5
Decant water: from 5-liter jug to the 3-liter jug. Action cost: 1
Decant water: from 3-liter jug to the 8-liter jug. Action cost: 3
all states generated
Travelling salesman problem

Treatment the problem: wiki

Define the action:

ActionPDDL Travel = new ActionPDDL("Travel");
City From = null; //Salesman leaves "From" city,
City To = null; //and goes to "To" city.

Travel.AddPartOfActionSententia(ref To, "Go to {0}.", T => T.Name);

Travel.AddPrecondiction( // From.SalesmanHere == true
    "Salesnam is in FROM city now",
    ref From,
    F => F.SalesmanHere);

//Salesman visit city only one time
Travel.AddPrecondiction( // To.Visiting == false
    "Salesnam havent been in TO city",
    ref To,
    F => !F.Visited);

Travel.AddEffect( // From.SalesmanHere = false
    "Salesman leaves city",
    ref From,
    F => F.SalesmanHere,
    false);

Travel.AddEffect( // To.SalesmanHere = true
    "Salesman arrives new city",
    ref To,
    T => T.SalesmanHere,
    true);

Travel.AddEffect( // To.Visited = true
    "Salesman visit new city",
    ref To,
    T => T.Visited,
    true);

Travel.DefineActionCost(ref From, ref To, (F, T) => CitiesAPI.DistanceAPI(F.PostalCode, T.PostalCode));

Some DistanceMatrix / Travel action cost:

Distance Koszalin Gniezno Kraków Płock Poznań Warszawa Lublin
Koszalin 0 245 700 372 250 520 687
Gniezno 245 0 456 165 48 293 448
Kraków 700 456 0 364 458 290 304
Płock 372 165 364 0 227 109 295
Poznań 250 48 458 227 0 311 478
Warszawa 520 293 290 109 311 0 173
Lublin 687 448 304 295 478 173 0
SharpPDDL : Visit all cities determined!!! Total Cost: 1806
Travel: Go to Gniezno. Action cost: 245
Travel: Go to Poznan. Action cost: 48
Travel: Go to Plock. Action cost: 227
Travel: Go to Warszawa. Action cost: 109
Travel: Go to Lublin. Action cost: 173
Travel: Go to Kraków. Action cost: 304
Travel: Go to Koszalin. Action cost: 700

Make you sure about the solution with another program: AtoZmath.com

Important

If you wanna wait for finish some execution, and than do the next one, you need to block thread. Don't use Tasks inside ExpressionExecution Funct. Program will not wait for it.


License: Creative Commons Attribution-NonCommercial-ShareAlike4.0