-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: Add first draft of Goal * chore: Improve the Goal documentation and structure * test: Add tests for Goal * chore: Adhere to the SonarCloud rules * chore: Convert tabs to spaces * chore: configure editorconfig to make files and with newline * feat: process feedback and refactor many aspects * fix: process pr feedback * fix: provide usability support for boolean based heuristics * test: new goal constructor
- Loading branch information
1 parent
3cff3d3
commit f401280
Showing
9 changed files
with
398 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
using System; | ||
|
||
namespace Aplib.Core.Desire | ||
{ | ||
/// <summary> | ||
/// Contains helper methods to generate commonly used heuristic functions. | ||
/// </summary> | ||
public static class CommonHeuristicFunctions | ||
{ | ||
/// <summary> | ||
/// Converts a boolean-based heuristic function to a <see cref="Goal.HeuristicFunction"/>. | ||
/// </summary> | ||
/// <param name="heuristicFunction"> | ||
/// A heuristic function which returns true only when the state is considered completed. | ||
/// </param> | ||
/// <returns>A heuristic function which wraps around the boolean-based heuristic function.</returns> | ||
public static Goal.HeuristicFunction Boolean(Func<bool> heuristicFunction) | ||
=> () => Heuristics.BooleanHeuristic(heuristicFunction.Invoke()); | ||
|
||
/// <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 }; | ||
|
||
/// <summary> | ||
/// Returns a heuristic function which always, at all times, and forever, returns a value indicating the state | ||
/// can be seen as completed. | ||
/// </summary> | ||
/// <returns>Said heuristic function.</returns> | ||
public static Goal.HeuristicFunction Completed() => Constant(0f); | ||
|
||
/// <summary> | ||
/// Returns a heuristic function which always, at all times, and forever, returns a value indicating the state | ||
/// can be seen as NOT completed. | ||
/// </summary> | ||
/// <returns>Said heuristic function.</returns> | ||
public static Goal.HeuristicFunction Uncompleted() => Constant(69_420f); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
using System; | ||
|
||
namespace Aplib.Core.Desire | ||
{ | ||
/// <summary> | ||
/// A goal effectively combines a heuristic function with a tactic, and aims to meet the heuristic function 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 abstract definition of what is means to test the Goal's heuristic function. Returns <see cref="Heuristics"/>, as | ||
/// they represent how close we are to matching the heuristic function, and if the goal is completed. | ||
/// </summary> | ||
/// <seealso cref="Goal.Evaluate"/> | ||
public delegate Heuristics HeuristicFunction(); | ||
|
||
|
||
/// <summary> | ||
/// Gets the <see cref="Heuristics"/> of the current state of the game. | ||
/// </summary> | ||
/// <remarks>If no heuristics have been calculated yet, they will be calculated first.</remarks> | ||
public virtual Heuristics CurrentHeuristics => _currentHeuristics ??= _heuristicFunction.Invoke(); | ||
|
||
/// <summary> | ||
/// The name used to display the current goal during debugging, logging, or general overviews. | ||
/// </summary> | ||
public string Name { get; } | ||
|
||
/// <summary> | ||
/// The description used to describe the current goal during debugging, logging, or general overviews. | ||
/// </summary> | ||
public string Description { get; } | ||
|
||
/// <summary> | ||
/// The goal is considered to be completed, when the distance of the <see cref="CurrentHeuristics"/> is below | ||
/// this value. | ||
/// </summary> | ||
protected double _epsilon { 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> | ||
/// 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 backing field of <see cref="Heuristics"/>. | ||
/// </summary> | ||
private Heuristics? _currentHeuristics; | ||
|
||
/// <summary> | ||
/// Creates a new goal which works with <see cref="Heuristics"/>. | ||
/// </summary> | ||
/// <param name="tactic">The tactic used to approach this goal.</param> | ||
/// <param name="heuristicFunction">The heuristic function 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> | ||
/// <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; | ||
_heuristicFunction = heuristicFunction; | ||
Name = name; | ||
Description = description; | ||
_epsilon = epsilon; | ||
} | ||
|
||
/// <summary> | ||
/// Creates a new goal which works with boolean-based <see cref="Heuristics"/>. | ||
/// </summary> | ||
/// <param name="tactic">The tactic used to approach this goal.</param> | ||
/// <param name="heuristicFunction">The heuristic function 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> | ||
/// <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, Func<bool> heuristicFunction, string name, string description, double epsilon = 0.005d) | ||
{ | ||
_tactic = tactic; | ||
_heuristicFunction = CommonHeuristicFunctions.Boolean(heuristicFunction); | ||
Name = name; | ||
Description = description; | ||
_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. | ||
/// </summary> | ||
public void Iterate() | ||
{ | ||
_tactic.IterateBdiCycle(); | ||
} | ||
|
||
/// <summary> | ||
/// 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 completed.</returns> | ||
/// <seealso cref="_epsilon"/> | ||
public bool Evaluate() | ||
{ | ||
return CurrentHeuristics.Distance < _epsilon; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
namespace Aplib.Core.Desire | ||
{ | ||
/// <summary> | ||
/// Contains all information on how close the associated state is to its goal. | ||
/// Can be used to optimise search algorithms. | ||
/// </summary> | ||
public class Heuristics | ||
{ | ||
/// <summary> | ||
/// The logical distance the current state is to its goal. | ||
/// </summary> | ||
public float Distance { get; set; } | ||
|
||
/// <summary> | ||
/// Creates a heuristic value representing just a boolean. The heuristic value is considered '0' or 'done' when | ||
/// the boolean is true. Non-zero otherwise. | ||
/// </summary> | ||
/// <param name="value">True if completed, False if not completed.</param> | ||
/// <returns></returns> | ||
public static Heuristics BooleanHeuristic(bool value) => new() { Distance = value ? 0f : 1f }; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
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 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"/> | ||
public abstract class Tactic | ||
{ | ||
/// <summary> | ||
/// Execute the next cycle in the Believe Desire Intent Cycle. | ||
/// </summary> | ||
public abstract void IterateBdiCycle(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
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(() => { }); | ||
Goal.HeuristicFunction heuristicFunction = CommonHeuristicFunctions.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 goal = new(tactic, heuristicFunction, name, description); // Does not use helper methods on purpose | ||
|
||
// Assert | ||
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 | ||
int iterations = 0; | ||
Tactic tactic = new TacticStub(() => iterations++); | ||
|
||
// Act | ||
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 | ||
int iterations = 0; | ||
Tactic tactic = new TacticStub(() => iterations++); | ||
|
||
// Act | ||
Goal goal = new TestGoalBuilder().UseTactic(tactic).Build(); | ||
goal.Iterate(); | ||
|
||
// Assert | ||
iterations.Should().BeGreaterThan(0); | ||
} | ||
|
||
/// <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_WhenReached_ReturnsAsCompleted() | ||
{ | ||
// Arrange | ||
Goal.HeuristicFunction heuristicFunction = CommonHeuristicFunctions.Completed(); | ||
|
||
// Act | ||
Goal goal = new TestGoalBuilder().WithHeuristicFunction(heuristicFunction).Build(); | ||
bool isCompleted = goal.Evaluate(); | ||
|
||
// Assert | ||
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_WhenNotReached_DoesNotReturnAsCompleted() | ||
{ | ||
// Arrange | ||
Goal.HeuristicFunction heuristicFunction = CommonHeuristicFunctions.Uncompleted(); | ||
|
||
// Act | ||
Goal goal = new TestGoalBuilder().WithHeuristicFunction(heuristicFunction).Build(); | ||
bool isCompleted = goal.Evaluate(); | ||
|
||
// Assert | ||
isCompleted.Should().Be(false); | ||
} | ||
|
||
/// <summary> | ||
/// Given the Goal's different constructors have been called with semantically equal argumetns | ||
/// when the Evaluate() method of all goals are used, | ||
/// then all returned values should equal. | ||
/// </summary> | ||
/// <param name="goalCompleted"></param> | ||
[Theory] | ||
[InlineData(true)] | ||
[InlineData(false)] | ||
public void GoalConstructor_WhereHeuristicFunctionTypeDiffers_HasEqualBehaviour(bool goalCompleted) | ||
{ | ||
// Arrange | ||
Tactic tactic = new TacticStub(() => { }); | ||
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"; | ||
|
||
Func<bool> heuristicFunctionBoolean = () => goalCompleted; | ||
Goal.HeuristicFunction heuristicFunctionNonBoolean = CommonHeuristicFunctions.Boolean(() => goalCompleted); | ||
|
||
Goal goalBoolean = new(tactic, heuristicFunctionBoolean, name, description); | ||
Goal goalNonBoolean = new(tactic, heuristicFunctionNonBoolean, name, description); | ||
|
||
// Act | ||
bool goalBooleanEvaluation = goalBoolean.Evaluate(); | ||
bool goalNonBooleanEvaluation = goalNonBoolean.Evaluate(); | ||
|
||
// Assert | ||
goalBooleanEvaluation.Should().Be(goalNonBooleanEvaluation); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
using Aplib.Core.Desire; | ||
|
||
namespace Aplib.Tests.Stubs.Desire; | ||
|
||
/// <summary> | ||
/// 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> | ||
internal class TacticStub(Action iteration) : Tactic | ||
{ | ||
/// <inheritdoc /> | ||
public override void IterateBdiCycle() | ||
{ | ||
iteration.Invoke(); | ||
} | ||
} |
Oops, something went wrong.