Skip to content

Commit

Permalink
feat: process feedback and refactor many aspects
Browse files Browse the repository at this point in the history
  • Loading branch information
SilasPeters committed Mar 6, 2024
1 parent 7cfed63 commit 9e637d7
Show file tree
Hide file tree
Showing 10 changed files with 178 additions and 106 deletions.
100 changes: 63 additions & 37 deletions Aplib.Core/Desire/Goal.cs
Original file line number Diff line number Diff line change
@@ -1,59 +1,86 @@
namespace Aplib.Core.Desire
{
/// <summary>
/// A goal effectively combines a heuristicFunction with a tactic, and aims to meet the heuristicFunction by
/// applying the tactic. Goals are combined in a <see cref="GoalStructure"/>, and are used to prepare tests or do
/// the testing.
/// </summary>
/// <seealso cref="GoalStructure"/>
public class Goal
{
/// <summary>
/// The goal is considered to be completed, when the distance of the
/// <see cref="CurrentHeuristics"/> is below this value.
/// The goal is considered to be completed, when the distance of the <see cref="CurrentHeuristics"/> is below
/// this value.
/// </summary>
private const float Epsilon = 0.005f;
protected double epsilon;

/// <summary>
/// The <see cref="Heuristics"/> of the current state of the game.
/// The abstract definition of what is means to test the Goal's heuristicFunction. Returns <see cref="Heuristics"/>, as
/// they represent how close we are to matching the heuristicFunction, and if the goal is completed.
/// </summary>
public Heuristics CurrentHeuristics { get; private set; }
/// <seealso cref="Goal.Evaluate"/>
/// <remarks>The paper mentions predicates, yet the Java Aplib uses heuristics. We use heuristics as well.</remarks>
public delegate Heuristics HeuristicFunction();


/// <summary>
/// The <see cref="Tactic"/> used to achieve this <see cref="Goal"/>, which is executed during every iteration
/// of the BDI cycle.
/// </summary>
/// <seealso cref="Iterate()"/>
private readonly Tactic _tactic;
/// <summary>
/// The <see cref="IGoalPredicate"/> used to test whether this <see cref="Goal"/> has been completed.
/// Gets the <see cref="Heuristics"/> of the current state of the game.
/// </summary>
/// <seealso cref="IsCompleted()"/>
private readonly IGoalPredicate _goalPredicate;
/// <remarks>If no heuristics have been calculated yet, they will be calculated first.</remarks>
public virtual Heuristics CurrentHeuristics => _currentHeuristics ??= heuristicFunction.Invoke();

// MetaData useful for debugging
/// <summary>
/// The name used to display the current goal during debugging, logging, or general overviews.
/// </summary>
public readonly string Name;
public string Name { get; }

/// <summary>
/// The description used to describe the current goal during debugging, logging, or general overviews.
/// </summary>
public readonly string Description;
public string Description { get; }


/// <summary>
/// The concrete implementation of this Goal's <see cref="HeuristicFunction"/>. Used to test whether this goal is
/// completed.
/// </summary>
/// <seealso cref="Evaluate"/>
protected HeuristicFunction heuristicFunction;

/// <summary>
/// A goal effectively combines a predicate with a tactic, and aims to meet the predicate by applying the tactic.
/// Goals are combined in a <see cref="GoalStructure"/>, and are used to prepare tests or do the testing.
/// The backing field of <see cref="Heuristics"/>.
/// </summary>
private Heuristics? _currentHeuristics;

/// <summary>
/// The <see cref="Tactic"/> used to achieve this <see cref="Goal"/>, which is executed during every iteration
/// of the BDI cycle.
/// </summary>
/// <seealso cref="Iterate()"/>
private readonly Tactic _tactic;

/// <summary>
/// Creates a new goal with specified arguments. Upon creation, the <see cref="CurrentHeuristics"/> will be
/// set to <see cref="Heuristics.Default"/>.
/// </summary>
/// <param name="tactic">The tactic used to approach this goal.</param>
/// <param name="goalPredicate">The predicate which defines whether a goal is reached</param>
/// <param name="heuristicFunction">The heuristicFunction which defines whether a goal is reached</param>
/// <param name="name">The name of this goal, used to quickly display this goal in several contexts.</param>
/// <param name="description">The description of this goal, used to explain this goal in several contexts.</param>
/// <seealso cref="GoalStructure"/>
public Goal(Tactic tactic, IGoalPredicate goalPredicate, string name, string description)
/// <param name="epsilon">
/// The goal is considered to be completed, when the distance of the <see cref="CurrentHeuristics"/> is below
/// this value.
/// </param>
public Goal(Tactic tactic, HeuristicFunction heuristicFunction, string name, string description, double epsilon = 0.005d)
{
_tactic = tactic;
_goalPredicate = goalPredicate;
Name = name;
Description = description;

CurrentHeuristics = _goalPredicate.Test(); // TODO is this the right time?
_tactic = tactic;
this.heuristicFunction = heuristicFunction;
Name = name;
Description = description;
this.epsilon = epsilon;
}


/// <summary>
/// Performs the next steps needed to be taken to approach this goal. Effectively this means that one BDI
/// cycle will be executed.
Expand All @@ -64,16 +91,15 @@ public void Iterate()
}

/// <summary>
/// Tests whether the goal has been achieved, bases on the <see cref="_goalPredicate"/> and the
/// <see cref="CurrentHeuristics"/>. When the distance of the heuristics is smaller than <see cref="Epsilon"/>,
/// the goal is considered to be achieved.
/// Tests whether the goal has been achieved, bases on the <see cref="heuristicFunction"/> and the
/// <see cref="CurrentHeuristics"/>. When the distance of the heuristics is smaller than <see cref="epsilon"/>,
/// the goal is considered to be completed.
/// </summary>
/// <returns>A boolean representing whether the goal is considered to be achieved.</returns>
/// <seealso cref="Epsilon"/>
public bool IsCompleted()
/// <returns>A boolean representing whether the goal is considered to be completed.</returns>
/// <seealso cref="epsilon"/>
public bool Evaluate()
{
CurrentHeuristics = _goalPredicate.Test();
return CurrentHeuristics.Distance < Epsilon;
return CurrentHeuristics.Distance < epsilon;
}
}
}
}
2 changes: 1 addition & 1 deletion Aplib.Core/Desire/Heuristics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ public class Heuristics
/// </summary>
public float Distance { get; set; }
}
}
}
15 changes: 0 additions & 15 deletions Aplib.Core/Desire/IGoalPredicate.cs

This file was deleted.

6 changes: 3 additions & 3 deletions Aplib.Core/Desire/Tactic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ namespace Aplib.Core.Desire
{
/// <summary>
/// Tactics are the real meat of <see cref="Goal"/>s, as they define how the agent can approach the goal in hopes
/// of finding a solution which meets the Goal's predicate. A tactic represents a smart combination of
/// <see cref="Action"/>s, which are executed in a Believe Desire Intent Cycle.
/// of finding a solution which makes the Goal's heuristic function evaluate to being completed. A tactic represents
/// a smart combination of <see cref="Action"/>s, which are executed in a Believe Desire Intent Cycle.
/// </summary>
/// <seealso cref="Goal"/>
/// <seealso cref="Action"/>
Expand All @@ -14,4 +14,4 @@ public abstract class Tactic
/// </summary>
public abstract void IterateBdiCycle();
}
}
}
4 changes: 0 additions & 4 deletions Aplib.Tests/Aplib.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,4 @@
<ProjectReference Include="..\Aplib.Core\Aplib.Core.csproj" />
</ItemGroup>

<ItemGroup>
<Folder Include="Tools\" />
</ItemGroup>

</Project>
89 changes: 58 additions & 31 deletions Aplib.Tests/Desire/GoalTests.cs
Original file line number Diff line number Diff line change
@@ -1,84 +1,111 @@
using Aplib.Core.Desire;
using Aplib.Tests.Stubs.Desire;
using Aplib.Tests.Tools;
using FluentAssertions;

namespace Aplib.Tests.Desire;

public class GoalTests
{
/// <summary>
/// Given valid parameters and metadata,
/// When the goal is constructed,
/// Then the goal should correctly store the metadata.
/// </summary>
[Fact]
public void Goal_WhenConstructed_ContainsCorrectMetaData()
{
// Arrange
Tactic tactic = new TacticStub(() => { });
IGoalPredicate predicate = new ConstantGoalPredicate(0f);
var name = "Such a good goal name";
var description = "\"A lie is just a good story that someone ruined with the truth.\" - Barney Stinson";
Goal.HeuristicFunction heuristicFunction = CommonGoalHeuristicFunctions.Constant(0f);
const string name = "Such a good goal name";
const string description = "\"A lie is just a good story that someone ruined with the truth.\" - Barney Stinson";

// Act
Goal g = new(tactic, predicate, name, description); // Does not use helper method DefaultGoal on purpose
Goal goal = new(tactic, heuristicFunction, name, description); // Does not use helper method DefaultGoal on purpose

// Assert
g.Should().NotBeNull();
g.Name.Should().Be(name);
g.Description.Should().Be(description);
goal.Should().NotBeNull();
goal.Name.Should().Be(name);
goal.Description.Should().Be(description);
}

/// <summary>
/// Given the Goal is created properly using its constructor,
/// When the goal has been constructed,
/// Then the given tactic has not been applied yet
/// </summary>
[Fact]
public void Goal_WhenConstructed_DidNotIterateYet()
{
// Arrange
var iterations = 0;
int iterations = 0;
Tactic tactic = new TacticStub(() => iterations++);

// Act
Goal _ = DefaultGoal(tactic: tactic);
Goal _ = new TestGoalBuilder().UseTactic(tactic).Build();

// Assert
iterations.Should().Be(0);
}

/// <summary>
/// Given the Goal is created properly using its constructor,
/// When the goal is being iterated over,
/// Then the given tactic has has been applied at least once
/// </summary>
[Fact]
public void Goal_WhenIterating_DoesIterate()
{
// Arrange
var iterations = 0;
int iterations = 0;
Tactic tactic = new TacticStub(() => iterations++);

// Act
Goal g = DefaultGoal(tactic: tactic);
g.Iterate();
Goal goal = new TestGoalBuilder().UseTactic(tactic).Build();
goal.Iterate();

// Assert
iterations.Should().BeGreaterThan(0);
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void Goal_WhenReached_ReturnsAsCompleted(bool completed)
/// <summary>
/// Given the Goal's heuristic function is configured to have reached its goal
/// when the Evaluate() method of a goal is used,
/// then the method should return true.
/// </summary>
[Fact]
public void Goal_WhenNotReached_DoesNotReturnAsCompleted()
{
// Arrange
float distance = completed ? 0 : 69420;
IGoalPredicate predicate = new ConstantGoalPredicate(distance);
const float distance = 0;
Goal.HeuristicFunction heuristicFunction = CommonGoalHeuristicFunctions.Constant(distance);

// Act
bool isCompleted = DefaultGoal(predicate: predicate).IsCompleted();
Goal goal = new TestGoalBuilder().WithHeuristicFunction(heuristicFunction).Build();
bool isCompleted = goal.Evaluate();

// Assert
isCompleted.Should().Be(completed);
isCompleted.Should().Be(true);
}

/// <summary>
/// Given the Goal's heuristic function is configured to *not* have reached its goal,
/// when the Evaluate() method of a goal is used,
/// then the method should return false.
/// </summary>
[Fact]
public void Goal_WhenReached_ReturnsAsCompleted()
{
// Arrange
const float distance = 69_420;
Goal.HeuristicFunction heuristicFunction = CommonGoalHeuristicFunctions.Constant(distance);

#region HelperMethods

private static Goal DefaultGoal(Tactic? tactic = null, IGoalPredicate? predicate = null, string? name = null,
string? description = null) => new(
tactic: tactic ?? new TacticStub(() => { }),
goalPredicate: predicate ?? new ConstantGoalPredicate(0),
name: name ?? "Such a good goal name",
description: description ??
"\"A lie is just a good story that someone ruined with the truth.\" - Barney Stinson");
// Act
Goal goal = new TestGoalBuilder().WithHeuristicFunction(heuristicFunction).Build();
bool isCompleted = goal.Evaluate();

#endregion
}
// Assert
isCompleted.Should().Be(false);
}
}
15 changes: 15 additions & 0 deletions Aplib.Tests/Stubs/Desire/CommonGoalHeuristicFunctions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Aplib.Core.Desire;

namespace Aplib.Tests.Stubs.Desire;

/// <summary>
/// Defines several commonly used implementations of the <see cref="Goal.HeuristicFunction"/>, to clean up tests.
/// </summary>
internal static class CommonGoalHeuristicFunctions
{
/// <summary>
/// A <see cref="Goal.HeuristicFunction"/> which always returns <see cref="Heuristics"/> with the same distance.
/// </summary>
/// <param name="distance">The distance which the heuristic function must always return.</param>
public static Goal.HeuristicFunction Constant(float distance) => () => new Heuristics { Distance = distance };
}
13 changes: 0 additions & 13 deletions Aplib.Tests/Stubs/Desire/ConstantGoalPredicate.cs

This file was deleted.

4 changes: 2 additions & 2 deletions Aplib.Tests/Stubs/Desire/TacticStub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ namespace Aplib.Tests.Stubs.Desire;
/// A fake tactic, which is just a wrapper around the <see cref="Action"/> you define as argument.
/// </summary>
/// <param name="iteration">The method to be executed during iteration.</param>
public class TacticStub(Action iteration) : Tactic
internal class TacticStub(Action iteration) : Tactic
{
/// <inheritdoc />
public override void IterateBdiCycle()
{
iteration.Invoke();
}
}
}
Loading

0 comments on commit 9e637d7

Please sign in to comment.