diff --git a/.gitignore b/.gitignore index 598ae79..1e69f5f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /.vscode +/.vs diff --git a/source/PlanningAI.Tests/AgentTests.cs b/source/PlanningAI.Tests/AgentTests.cs index 3d7f3d7..6b64fcd 100644 --- a/source/PlanningAI.Tests/AgentTests.cs +++ b/source/PlanningAI.Tests/AgentTests.cs @@ -118,7 +118,33 @@ await Assert.ThrowsAsync( Assert.Equal(goal1, agent.CurrentGoal); } - } + + [Fact] + public async Task ShouldUseStateConsiderationToChooseBestGoal() + { + var planner = PlannerFactory.CreatePlanner(); + + DomainState initialState = DomainState.Empty; + initialState = initialState.Set("testState1", 0.2f); + initialState = initialState.Set("testState2", 0.5f); + + var agent = new Agent(planner, null, initialState); + + var goal1 = new TestGoal { Weight = 1 }; + var goal2 = new TestGoal { Weight = 1 }; + + goal1.AddConsideration(Consideration.FromState("testState1")); + goal2.AddConsideration(Consideration.FromState("testState2")); + + agent.AddGoal(goal1); + agent.AddGoal(goal2); + + await Assert.ThrowsAsync( + async () => await agent.RunActionsAsync()); + + Assert.Equal(goal2, agent.CurrentGoal); + } + } public abstract class GoalAction : AsyncExecutableActionBase { diff --git a/source/PlanningAi/Agents/Agent.cs b/source/PlanningAi/Agents/Agent.cs index d1c506a..f832ae1 100644 --- a/source/PlanningAi/Agents/Agent.cs +++ b/source/PlanningAi/Agents/Agent.cs @@ -22,17 +22,18 @@ public partial class Agent : IAgent private readonly IPlanner _planner; private readonly ILogger _logger; - public DomainState CurrentState { get; private set; } = DomainState.Empty; + public DomainState CurrentState { get; private set; }; public IAgentGoal CurrentGoal { get; private set; } public IDomainAction CurrentAction { get; private set; } public IReadOnlyList Actions => _actions; public IReadOnlyList Goals => _goals; - public Agent(IPlanner planner, ILogger logger = null) + public Agent(IPlanner planner, ILogger logger = null, DomainState initialState = null) { _planner = planner ?? throw new ArgumentNullException(nameof(planner)); _logger = logger ?? new DummyLogger(); + CurrentState = initialState ?? DomainState.Empty; } /// diff --git a/source/PlanningAi/Agents/Consideration.cs b/source/PlanningAi/Agents/Consideration.cs index 926efd1..8003588 100644 --- a/source/PlanningAi/Agents/Consideration.cs +++ b/source/PlanningAi/Agents/Consideration.cs @@ -18,5 +18,11 @@ public static IConsideration FromFunc(Func getValueFunc, str { return new DelegateConsideration(name ?? "Unnamed", getValueFunc); } - } + + [PublicAPI] + public static IConsideration FromState(string value, string name = null) + { + return new StateConsideration(name ?? "Unnamed", value); + } + } } \ No newline at end of file diff --git a/source/PlanningAi/Agents/StateConsideration.cs b/source/PlanningAi/Agents/StateConsideration.cs new file mode 100644 index 0000000..fd54786 --- /dev/null +++ b/source/PlanningAi/Agents/StateConsideration.cs @@ -0,0 +1,23 @@ +using System; +using PlanningAi.Planning; + +namespace PlanningAi.Agents +{ + internal sealed class StateConsideration : IConsideration + { + private readonly string _value; + + public string Name { get; } + + public StateConsideration(string name, string value) + { + Name = name ?? throw new ArgumentNullException(nameof(name)); + _value = string.IsNullOrEmpty(value) ? throw new ArgumentNullException(nameof(value)) : value; + } + + public float GetValue(DomainState currentState) + { + return currentState.GetValueOrDefault(_value); + } + } +}